Compare commits

..

58 Commits

Author SHA1 Message Date
Rajarshee Chatterjee 5b8cfebe3d Revert "Export to EPUB" and Release v1.1.17 (#703)
* Revert "feat: EPUB Export (#646)"

This reverts commit c0b4610910.

* Revert "Export to EPUB"

* Release v1.1.17

* Revert React Native to 0.69.9

* Fix Update Checker
2023-08-03 07:43:26 +05:30
alistair3149 e760bd1a9b fix(source): add all pages in the same chapter for Linovelib (#722) 2023-08-02 23:31:14 +05:30
danisty d260eb1b5e Performance fixes, general fixes and addition of features (#723)
* fix: Fixed images proxied through Cloudflare not loading

* feat: Add utility to render images from Cloudflare proxied sites

* feat(source): Add NOVA (Spanish source)

* fix(source): NovelasLigera display of images and improved display of chapters text

* fix(source): NovelasLigera properly parse metadata

* fix(source): YuukiTls display of novels

* fix(source): TuNovelaLigera missing Cloudflare bypass and not properly getting covers

* fix(source): Other significant changes related to the request of popular novels from sources with no more than 1 page

* fix(source): NOVA not added to source manager

* fix: Remove loading Novel shimmer effect item when nothing is loading

* fix: Properly update chapters and metadata of novels that aren't saved in the local library

* fix: sanitizer doing unnecessary parsing which may add noticable delay to chapter loading

* refac: Add <center> tag to the sanitizer allowed tags

* fix: sanitizer being called each re-render of ReaderScreen plus unnecessary re-render of the WebView

* fix: memoized chapter html conversion to text for TTS, operation can be expensive

* refactor: Moved cloudflareImagesBypass to sources/helpers and fixed NOVA novels summary format
2023-08-02 23:30:44 +05:30
alistair3149 d59f95f13a fix(source): bypass image CDN in Linovelib (#716)
* fix(source): bypass image CDN in Linovelib

* fix(source): also check for src attribute in img in Linovelib
2023-07-31 09:07:32 +05:30
alistair3149 0d9da26eff fix(source): fix various lazyload issue with Linovelib (#714)
* fix(source): load lazyloaded images in Linovelib

* fix(source): recover original text in Linovelib
2023-07-29 20:53:51 +05:30
alistair3149 a769110c70 fix(source): fix incorrect property name (#713) 2023-07-28 10:35:47 +05:30
alistair3149 71ea318503 refactor(source): Use fetchHtml for Linovelib (#710)
* refactor(source): Use fetchHtml for Linovelib

* Update src/sources/ch/linovelib.js

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>

---------

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2023-07-28 09:46:12 +05:30
alistair3149 59d5f08c83 feat(source): Add Linovelib (#709)
* feat(source): Add Linovelib

* fix(source): use fetchHtml for searchNovels in Linovelib
2023-07-27 09:34:00 +05:30
Rajarshee Chatterjee 36230cebea fix(source): Update Ranobes URL 2023-07-27 08:34:14 +05:30
Rajarshee Chatterjee cbee29ea85 fix: Highlight on Novel Selection 2023-07-26 07:53:20 +05:30
Rhino 9b5a44fe59 fix(source): Update Ranobes URL (#690)
Update Ranobes URL
2023-07-16 17:29:21 +05:30
Rajarshee Chatterjee 9f87edaa7c build: Fix Artifact Name 2023-07-15 14:36:33 +05:30
Rajarshee Chatterjee e976c9793c Release v1.1.16 2023-07-15 13:21:46 +05:30
Rajarshee Chatterjee a9a0a5ad83 chore: Update translations (#625)
* New translations strings.json (Romanian)
[ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (French)
[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 (Chinese Traditional)
[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]
2023-07-15 13:21:29 +05:30
K1ngfish3r 4c00b2bc23 fix(source): Arnovel (#671)
* fix arnovel

* update 1stkissnovel url

* fix images 1stkissnovel

* QoL fixes for Agitoon and Pawread

* Partial fix for sakuranovel/indowebnovel chapnames

* Experimental lnp and pawread array

* I know how commas work

* fix inoveltranslation actual

* startCase

* fix bug
2023-07-15 13:15:32 +05:30
Patrick Loser 414ca51660 fix: Export to EPUB Improvements (#670)
* Test config

* update react-native-epub-creator

* updated deps

* option for export to epub

* added prettier to dev deps since I couldn't push

* removed white background when pulling up bottomsheet

* added options to the export to epub feature

* support for customCSS -JS and inchapter images

* package update

* fixed weird typing

* fixed weird KeyboardAvoidingView

* remove test config

* logic fix

* removed styles2

* fixed package problems

* fixed package problems

* notification icons

* package reinstallation

* package reinstallation

* customFileModal fix

* fixed ChapterItem alignment

* added github suggestions
2023-07-15 13:14:11 +05:30
Rajarshee Chatterjee 0d4e16509c fix: Env Variables 2023-07-03 22:50:04 +05:30
Rajarshee Chatterjee 0e131c039d fix: Crash in Reader (Closes #673) 2023-07-02 13:09:30 +05:30
Rajarshee Chatterjee 79639e6d29 feat: Add Bionic Reading (Closes #637) 2023-07-02 12:33:49 +05:30
Rajarshee Chatterjee 14ed97b2c3 fix: Shifting Text in CSS/JS Editor (Closes #654) 2023-06-30 08:51:03 +05:30
Rajarshee Chatterjee 0b5a012bfa fix: Persists Bookmark State (Closes #676) 2023-06-30 08:48:20 +05:30
Rajarshee Chatterjee 3074853fd6 fix: Update Patch Version to 15 (Closes #648) 2023-06-30 08:30:06 +05:30
Rajarshee Chatterjee 710cca66f3 fix: Remove Play Button from History 2023-06-30 08:24:48 +05:30
K1ngfish3r e66ee5f8ec fix: Update Sources (#657)
Closes LNReader/lnreader-sources#470
Closes LNReader/lnreader-sources#518
Closes LNReader/lnreader-sources#508
Closes LNReader/lnreader-sources#504
Closes LNReader/lnreader-sources#494
Closes LNReader/lnreader-sources#483
Closes LNReader/lnreader-sources#519
Closes LNReader/lnreader-sources#360
2023-06-16 09:36:21 +05:30
Patrick Loser c0b4610910 feat: EPUB Export (#646)
* Export to Epub

* removed uneccessary css

* updated dependency
2023-06-10 22:07:47 +05:30
Justin COLLON dd0ab5253e fix(source): NovelDeGlace (FR) (#658)
Closes LNReader/lnreader-sources#434
Closes LNReader/lnreader-sources#416
2023-06-10 22:05:45 +05:30
Patrick Loser 3adc655f7a feat: Add Trailing Skeleton Novel Covers (#629)
* TS NovelCover

* added some changes to the loading animation
2023-06-07 21:13:58 +05:30
Rider21 2d6251bddd feat(source): Add Bookriver (#641)
* add BookRiver

* Update bookriver.js

* updated tags, genres
2023-05-29 09:38:49 +05:30
Memexurer bbd134753b fix(source): NovelOnline (#635) 2023-05-15 10:05:55 +05:30
Rajarshee Chatterjee 61bf6794fa chore: Update Dependencies 2023-05-14 12:23:39 +05:30
Rajarshee Chatterjee 69d8a0012a fix: Don't Replace Characters in <> (Closes #602) 2023-05-14 10:59:09 +05:30
Rojikku ea82742190 fix(fetch): Add capacity to change UA string (#634)
* fix(fetch): Add capacity to change UA string

* fix(fetch): Set to string of false instead of empty

* fix(NovelUpdates): Enable Cloudflare Bypass

* fix(NovelUpdates): Add proper error throwing for bypass

* fix(NovelUpdates): Add dual output option to fetchHtml for NU

* fix(fetch+NU): Adjust fetch to not require a specific parameter

* fix(fetch+NU): Adjust away from raw to separate cloudflare check function
2023-05-14 10:49:58 +05:30
Rajarshee Chatterjee d161c393e4 fix: Add Chapter Name in Reader (Closes #601)
Memoize Webview Reader
2023-05-14 10:46:27 +05:30
Rajarshee Chatterjee a9c44ced4f Minor Tracker Fixes 2023-05-14 09:42:12 +05:30
Nick Ignoffo 135712c491 Normalized trackers refactor (+AniList) (#631)
* Begin AniList support

Mostly functional. Many large, sweeping changes to the data model used, so it may be worth looking into migrating the existing data structures. Otherwise it's a bad time for people already using MAL to track.

Much of the data model is documented with JSDoc, mostly for my sanity. I've never used JSDoc before so idk how correct these defs are 🤷

MAL has been mostly migrated over to the new format. There's a few straggling bits that seem to reference the old code that need to be cleared out.

Things to do:

* Status selector needs to respect AniList values/options when AL is in use
* Rating selector should respect the user's chosen rating format
* AniList option in the Discover section

* Use new MAL revalidator

* Clean up tracker creation/getting

* Generalize name for tracker cards file

* Normalize user list status

* Initialize trackers at app start

Fixes issues where things are initialized in the wrong order

* Tracker specific score implementations

MAL is effectively unchanged
AL supports all available formats. The UI leaves a bit to be desired, but it's functional.

* AniList browsing in discover

* Move AniList on top in settings for abc consistency

* Update tracker notifier when finishing chapter

* Migrate new tracker changes to TS

Additional cleanup pass

* Tracker data migration

* Fix AniList top novels info display
2023-05-14 09:37:23 +05:30
Nick Ignoffo daf40325d1 Fix scroll to progress on downloaded chapters (#632)
Fix scroll to progress
2023-05-14 08:27:05 +05:30
Rojikku c2438223c9 ScribbleHub: Convert to TS; Implement Latest, Genres, and Sorting (#626)
* ScribbleHub: Convert to TS

* ScribbleHub: Implement sorting; Debug TS

* ScribbleHub: Implement genres; Final TS debug
2023-05-11 21:10:06 +05:30
Patrick Loser 0ec250cff2 fix: Directly Add Novel to Library Category (#623)
Closes #611
2023-05-11 21:07:39 +05:30
Rojikku 55255ec64f fix(pre-commit): Make pre-commit executable for linux; gitignore jenv (#624) 2023-05-11 21:06:14 +05:30
Rojikku 3a8135ccd7 fix(ReadWN): Implemented better chapter assumption (#622)
fix(ReadWM): Implemented better chapter assumption
2023-05-08 07:06:56 +05:30
Patrick Loser 77bfc46d2a feat: Allow Accessing Variables in CustomJS (#619)
Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2023-05-08 07:03:33 +05:30
Rajarshee Chatterjee 1afe254bf3 fix(source): ScribbleHub
Closes LNReader/lnreader-sources#463
Closes LNReader/lnreader-sources#491
2023-05-08 06:49:21 +05:30
Rajarshee Chatterjee 4199789683 chore: Update translations (#591)
* New translations strings.json (Italian)
[ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (Indonesian)
[ci skip]
2023-05-06 10:50:55 +05:30
K1ngfish3r f0d0696a8f fix: Source Updates (#615)
Closes LNReader/lnreader-sources#476
Closes LNReader/lnreader-sources#478
Closes LNReader/lnreader-sources#485
Closes LNReader/lnreader-sources#487
Closes LNReader/lnreader-sources#486
2023-05-06 10:41:38 +05:30
Patrick Loser 7221070963 fix: App Crash on Chapter End (#598)
* fixed midnightdusk

* fixed missing key

* fixed navigationbar visible after closing drawer

* removed insets

* Novelscreen warning removal

* removed remove Prefix

* fixed App crash at the end of chapter

* added Tooltip
2023-05-06 10:39:56 +05:30
Rajarshee Chatterjee ec5df867c3 fix: Downgrade React Native 2023-04-11 07:36:07 +05:30
Rajarshee Chatterjee 2dc226bbaf feat(source): Add Novels Online 2023-04-10 21:04:14 +05:30
Rajarshee Chatterjee 920b33ce17 fix: Revert to AsyncStorage for redux-persist Storage 2023-04-10 08:39:06 +05:30
Patrick Loser 5dcfc0c69c feat: Source Specific CSS (#597)
* copied settingsreaderscreen

* copied plusminusfield

* copied SettingsReaderScreen

* copied strings

* fix

* PlusMinusField -> Counter

* sourcecard change

* WebViewReaderChange

* Fixed ReaderLineHeight and ReaderTextSize

* showToast for CustomFileModal
2023-04-10 08:12:00 +05:30
Rajarshee Chatterjee 2e73cd6fd4 refactor: Use BottomSheetModal 2023-04-09 16:28:29 +05:30
Rajarshee Chatterjee da87f3a393 chore(deps): Update React Native 2023-04-09 12:50:53 +05:30
Rajarshee Chatterjee 4de8fab76a chore: Update Workflows 2023-04-08 19:22:29 +05:30
Rajarshee Chatterjee 9b7eb06027 Release v1.1.15 2023-04-06 21:55:10 +05:30
Rajarshee Chatterjee c210ddddc5 fix: Only Show Update Modal if New Version is Higher 2023-04-06 21:55:10 +05:30
Rajarshee Chatterjee ca163d83fb Minor Fixes 2023-04-06 21:55:10 +05:30
Rajarshee Chatterjee 49e4d78c92 chore: Update translations (#569)
* New translations strings.json (Romanian)
[ci skip]

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

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

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

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

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

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

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

* New translations strings.json (Russian)
[ci skip]
2023-04-06 21:54:50 +05:30
r1di c88b2e5d2a feat: extending Volume button feature, by scroll amount (#587)
Closes #447 

* Main
Added volume button scroll amount

* Volume button scroll amount

---------

Co-authored-by: Ridi <Ridi>
2023-04-06 20:42:36 +05:30
Rider21 be2dab5b5d feat: Add New Sources (#583)
* fix

* Update ranobelib.js

* fix

* test

* fix

* fix path

* test

* added sources

* added ALL sources on novel.tl

* add tags

* Update NovelTlScraper.js

* fix
2023-03-30 13:19:00 +05:30
1393 changed files with 72952 additions and 156280 deletions
+19
View File
@@ -0,0 +1,19 @@
module.exports = {
root: true,
extends: '@react-native-community',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
overrides: [
{
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
rules: {
'@typescript-eslint/no-shadow': ['warn'],
'no-shadow': 'off',
'no-undef': 'off',
'react-native/no-inline-styles': 'warn',
'no-console': 'warn',
'react-hooks/exhaustive-deps': 'warn',
},
},
],
};
-1
View File
@@ -1 +0,0 @@
blank_issues_enabled: false
+4 -4
View File
@@ -2,7 +2,7 @@
name: Issue report
description: Report an issue in LNReader
labels: [bug]
labels: [Bug]
body:
- type: textarea
id: reproduce-steps
@@ -45,7 +45,7 @@ body:
label: LNReader version
description: You can find your LNReader version in **More → About**.
placeholder: |
Example: "2.1.4"
Example: "1.1.17"
validations:
required: true
@@ -86,9 +86,9 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with a plugin, I should be opening an issue in the [plugins repository](https://github.com/lnreader/lnreader-plugins/issues/new/choose).
- 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 **[2.1.4](https://github.com/lnreader/lnreader/releases/latest)**.
- label: I have updated the app to version **[1.1.17](https://github.com/lnreader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+3 -3
View File
@@ -2,7 +2,7 @@
name: Feature request
description: Suggest a feature to improve LNReader
labels: [feature]
labels: [Feature Request]
body:
- type: textarea
id: feature-description
@@ -32,9 +32,9 @@ body:
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).
- 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 **[2.1.4](https://github.com/lnreader/lnreader/releases/latest)**.
- 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

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

+38 -194
View File
@@ -1,208 +1,52 @@
name: Build Preview
name: CI
on:
schedule:
- cron: '30 0 * * *'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
actions: read
contents: read
env:
CI: 'true'
EXPO_NO_TELEMETRY: '1'
NODE_ENV: 'production'
push:
branches:
- main
jobs:
check-preview:
name: Check Preview
runs-on: ubuntu-24.04
outputs:
should-build: ${{ steps.preview.outputs.should-build }}
steps:
- name: Check For Existing Preview
id: preview
uses: actions/github-script@v8
with:
script: |
if (context.eventName === 'workflow_dispatch') {
core.notice('Manual dispatch requested; building a new preview.')
core.setOutput('should-build', 'true')
return
}
const shortSha = context.sha.slice(0, 8)
const artifactName = `LNReader-${shortSha}`
const { data: { artifacts } } = await github.rest.actions.listArtifactsForRepo(
{
owner: context.repo.owner,
repo: context.repo.repo,
name: artifactName,
per_page: 100,
},
)
const previewExists = artifacts.some(
artifact =>
!artifact.expired &&
artifact.workflow_run?.head_sha === context.sha,
)
core.notice(
previewExists
? `A preview artifact already exists for ${shortSha}.`
: `No preview artifact exists for ${shortSha}; starting a build.`,
)
core.setOutput('should-build', String(!previewExists))
build-android:
name: APK
needs: check-preview
if: needs.check-preview.outputs.should-build == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 60
name: Build App
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Clone Repository (Latest)
uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Set Up pnpm
uses: pnpm/action-setup@v6
- name: Set Up Node.js
uses: actions/setup-node@v7
- uses: actions/setup-node@v3
with:
node-version: '22'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Set Up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
node-version: '16'
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Configure Release Environment
env:
MYANIMELIST_CLIENT_ID: ${{ vars.MYANIMELIST_CLIENT_ID }}
ANILIST_CLIENT_ID: ${{ vars.ANILIST_CLIENT_ID }}
run: |
pnpm generate:env:release \
--build-type "GitHub Action" \
--node-env "production"
npm install
- name: Generate Native Android Project
run: |
pnpm exec expo prebuild \
--platform android \
--clean \
--no-install \
--non-interactive
- name: Configure Gradle
uses: gradle/actions/setup-gradle@v6
- name: Cache Gradle Wrapper
uses: actions/cache@v3
with:
cache-provider: enhanced
cache-read-only: >-
${{ github.ref_name != github.event.repository.default_branch }}
cache-cleanup: on-success
add-job-summary: always
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Generate Preview Metadata
run: |
short_sha="${GITHUB_SHA::8}"
base_version_name="$(node --print "require('./app.json').expo.version")"
base_version_code="$(node --print "require('./app.json').expo.android.versionCode")"
echo "PRERELEASE_VERSION_CODE=$((base_version_code + GITHUB_RUN_NUMBER))" >> "$GITHUB_ENV"
echo "PRERELEASE_VERSION_NAME=${base_version_name}-pre.r${GITHUB_RUN_NUMBER}.${short_sha}" >> "$GITHUB_ENV"
echo "PRERELEASE_ARTIFACT_NAME=LNReader-${short_sha}" >> "$GITHUB_ENV"
echo "PRERELEASE_APK_NAME=LNReader-r${GITHUB_RUN_NUMBER}-${short_sha}.apk" >> "$GITHUB_ENV"
- name: Build Preview APK
working-directory: android
run: |
mkdir -p app/build/intermediates/sourcemaps/react/preRelease
./gradlew :app:assemblePreRelease \
--build-cache \
--parallel \
-PpreReleaseVersionCode="$PRERELEASE_VERSION_CODE" \
-PpreReleaseVersionName="$PRERELEASE_VERSION_NAME" \
--stacktrace
mv \
app/build/outputs/apk/preRelease/app-preRelease.apk \
"app/build/outputs/apk/preRelease/$PRERELEASE_APK_NAME"
- name: Upload Preview Sourcemap
uses: actions/upload-artifact@v4
- name: Cache Gradle Dependencies
uses: actions/cache@v3
with:
name: ${{ env.PRERELEASE_ARTIFACT_NAME }}-sourcemap
path: android/app/build/generated/sourcemaps/react/preRelease/index.android.bundle.map
if-no-files-found: error
retention-days: 14
compression-level: 0
- name: Upload Preview APK
id: upload-nightly
uses: actions/upload-artifact@v4
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-caches-
- name: Make Gradlew Executable
run: cd android && chmod +x ./gradlew
- name: 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: ${{ env.PRERELEASE_ARTIFACT_NAME }}
path: android/app/build/outputs/apk/preRelease/${{ env.PRERELEASE_APK_NAME }}
if-no-files-found: error
retention-days: 14
compression-level: 0
- name: Check Discord Notification
id: discord
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_NIGHTLY_WEBHOOK }}
run: |
if [ -n "$DISCORD_WEBHOOK_URL" ]; then
echo "configured=true" >> "$GITHUB_OUTPUT"
else
echo "configured=false" >> "$GITHUB_OUTPUT"
fi
- name: Notify Discord
if: steps.discord.outputs.configured == 'true'
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_NIGHTLY_WEBHOOK }}
ARTIFACT_URL: ${{ steps.upload-nightly.outputs.artifact-url }}
run: |
short_sha="${GITHUB_SHA::8}"
message="[${GITHUB_REPOSITORY}] New nightly build published: \`${short_sha}\`"
payload=$(jq -n \
--arg title "$message" \
--arg url "$ARTIFACT_URL" \
'{
username: "GitHub",
avatar_url: "https://github.com/github.png",
embeds: [{
author: {
name: "github-actions[bot]",
icon_url: "https://github.com/github.png"
},
title: $title,
url: $url,
color: 3092790
}]
}')
curl --fail-with-body \
--request POST \
--header "Content-Type: application/json" \
--data "$payload" \
"$DISCORD_WEBHOOK_URL"
name: LNReader-r${{ env.COMMIT_COUNT }}
path: android/app/build/outputs/apk/release/app-release.apk
+4 -1
View File
@@ -13,9 +13,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1
uses: tachiyomiorg/issue-moderator-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
duplicate-label: Duplicate
auto-close-rules: |
[
{
@@ -29,3 +31,4 @@ jobs:
"message": "Requested information in the template was not filled out."
}
]
auto-close-ignore-label: do-not-autoclose
+7 -39
View File
@@ -1,51 +1,19 @@
name: Lint
name: CI
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
- main
jobs:
build:
name: ESLint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Get pnpm Store Directory
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm Cache
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate Environment Variables
run: pnpm generate:env:debug
- uses: actions/checkout@v2
- name: Install modules
run: npm i
- name: Run ESLint
run: pnpm run lint
run: npm run lint
+19
View File
@@ -0,0 +1,19 @@
name: Lock threads
on:
# Daily
schedule:
- cron: '0 0 * * *'
# Manual trigger
workflow_dispatch:
inputs:
jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v4
with:
github-token: ${{ github.token }}
issue-inactive-days: '2'
pr-inactive-days: '2'
-232
View File
@@ -1,232 +0,0 @@
name: Release Android
run-name: Draft ${{ inputs.bump }} Android release
on:
workflow_dispatch:
inputs:
bump:
description: Version component to increment
required: true
default: Patch
type: choice
options:
- Patch
- Minor
- Major
concurrency:
group: release-android
cancel-in-progress: false
permissions:
contents: write
env:
CI: 'true'
EXPO_NO_TELEMETRY: '1'
NODE_ENV: 'production'
RELEASE_BUMP: ${{ inputs.bump }}
jobs:
release-android:
name: Build Draft Release
if: >-
github.ref == 'refs/heads/master' &&
github.actor_id == vars.RELEASE_ACTOR_ID &&
github.triggering_actor == vars.RELEASE_ACTOR_LOGIN
environment: release
runs-on: ubuntu-24.04
timeout-minutes: 90
steps:
- name: Checkout Repository
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: true
- name: Set Up pnpm
uses: pnpm/action-setup@v6
- name: Set Up Node.js
uses: actions/setup-node@v7
with:
node-version: '22'
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
- name: Set Up Java
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
- name: Install Dependencies
run: pnpm install --frozen-lockfile --prefer-offline
- name: Resolve Release Version
id: version
run: |
RELEASE_VERSION="$(
node scripts/resolve-release-version.cjs "$RELEASE_BUMP"
)"
echo "Releasing $RELEASE_BUMP version: $RELEASE_VERSION"
echo "RELEASE_VERSION=$RELEASE_VERSION" >> "$GITHUB_ENV"
echo "version=$RELEASE_VERSION" >> "$GITHUB_OUTPUT"
- name: Validate Release
env:
GH_TOKEN: ${{ github.token }}
run: |
if ! [[ "$RELEASE_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Version must use stable semver, for example 2.0.4"
exit 1
fi
if git rev-parse --verify --quiet "refs/tags/v$RELEASE_VERSION"; then
echo "Tag v$RELEASE_VERSION already exists"
exit 1
fi
if gh release view "v$RELEASE_VERSION" >/dev/null 2>&1; then
echo "Release v$RELEASE_VERSION already exists"
exit 1
fi
- name: Prepare Release Commit
run: |
pnpm release:prepare "$RELEASE_VERSION"
if git diff --quiet -- \
package.json \
app.json \
.github/ISSUE_TEMPLATE/report_issue.yml \
.github/ISSUE_TEMPLATE/request_feature.yml; then
CURRENT_VERSION="$(node -p "require('./package.json').version")"
if [ "$CURRENT_VERSION" != "$RELEASE_VERSION" ]; then
echo "No release changes were generated for $RELEASE_VERSION"
exit 1
fi
echo "Release version $RELEASE_VERSION is already committed; resuming"
else
git diff --check
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add \
package.json \
app.json \
.github/ISSUE_TEMPLATE/report_issue.yml \
.github/ISSUE_TEMPLATE/request_feature.yml
git commit -m "chore: Release v$RELEASE_VERSION"
fi
git fetch origin master
REMOTE_VERSION="$(
node -e "
const { execFileSync } = require('child_process');
const contents = execFileSync(
'git',
['show', 'origin/master:package.json'],
{ encoding: 'utf8' },
);
process.stdout.write(JSON.parse(contents).version);
"
)"
if [ "$REMOTE_VERSION" = "$RELEASE_VERSION" ]; then
echo "Release commit is already on master; resuming from it"
git checkout --detach origin/master
echo "RELEASE_ALREADY_PUSHED=true" >> "$GITHUB_ENV"
else
echo "RELEASE_ALREADY_PUSHED=false" >> "$GITHUB_ENV"
fi
echo "RELEASE_SHA=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
- name: Configure Release Environment
env:
MYANIMELIST_CLIENT_ID: ${{ vars.MYANIMELIST_CLIENT_ID }}
ANILIST_CLIENT_ID: ${{ vars.ANILIST_CLIENT_ID }}
run: |
pnpm generate:env:release \
--build-type "Release" \
--node-env "production" \
--myanimelist-client-id "$MYANIMELIST_CLIENT_ID" \
--anilist-client-id "$ANILIST_CLIENT_ID"
- name: Generate Native Android Project
run: |
pnpm exec expo prebuild \
--platform android \
--clean \
--no-install
- name: Configure Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-provider: enhanced
cache-cleanup: on-success
add-job-summary: always
- name: Build Release APKs
working-directory: android
run: |
mkdir -p app/build/intermediates/sourcemaps/react/release
./gradlew :app:assembleRelease \
--build-cache \
--parallel \
-PreleaseAbiSplits=true \
--stacktrace
- name: Package Release APKs
run: pnpm release:package "$RELEASE_VERSION"
- name: Upload Sourcemaps
run: |
cp android/app/build/generated/sourcemaps/react/release/index.android.bundle.map \
"release-artifacts/LNReader-v${RELEASE_VERSION}-sourcemap.map"
- name: Verify APK Signatures
run: |
APKSIGNER="$(find "$ANDROID_HOME/build-tools" -type f -name apksigner |
sort -V |
tail -1)"
if [ -z "$APKSIGNER" ]; then
echo "Android apksigner was not found"
exit 1
fi
for APK in release-artifacts/*.apk; do
"$APKSIGNER" verify "$APK"
done
- name: Upload Release APKs
uses: actions/upload-artifact@v4
with:
name: LNReader-v${{ steps.version.outputs.version }}
path: release-artifacts/
if-no-files-found: error
retention-days: 30
compression-level: 0
- name: Push Release Commit
run: |
if [ "$RELEASE_ALREADY_PUSHED" = "true" ]; then
echo "Release commit is already on master"
else
git push origin HEAD:master
fi
- name: Create Draft GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "v$RELEASE_VERSION" \
release-artifacts/* \
--target "$RELEASE_SHA" \
--title "LNReader v$RELEASE_VERSION" \
--generate-notes \
--draft
-51
View File
@@ -1,51 +0,0 @@
name: Tests
on:
push:
branches:
- master
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
name: Jest
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Get pnpm Store Path
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Cache pnpm
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate Environment
run: pnpm generate:env:debug
- name: Run Tests
run: pnpm run test
-51
View File
@@ -1,51 +0,0 @@
name: Type Check
on:
push:
branches:
- master
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
type-check:
name: TypeScript
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
- name: Setup pnpm
uses: pnpm/action-setup@v4
- name: Get pnpm Store Path
shell: bash
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Cache pnpm
uses: actions/cache@v4
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install Dependencies
run: pnpm install --frozen-lockfile
- name: Generate Environment
run: pnpm generate:env:debug
- name: Check Types
run: pnpm run type-check
+5 -41
View File
@@ -8,9 +8,8 @@ npm-debug.*
*.mobileprovision
*.orig.*
web-build/
.*env
.env.local
reader_playground/index.html
.env
# macOS
.DS_Store
@@ -39,7 +38,7 @@ DerivedData
*.hmap
*.ipa
*.xcuserstate
**/.xcode.env.local
project.xcworkspace
# Android/IntelliJ
#
@@ -49,10 +48,6 @@ build/
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.kotlin/
# node.js
#
@@ -71,7 +66,6 @@ buck-out/
# CocoaPods
/ios/Pods/
Gemfile.lock
# Expo
.expo/
@@ -81,36 +75,6 @@ web-build/
.vscode/
src/sources/en/generators/
*.lockb
*.lock
!pnpm-lock.yaml
.tool-versions
flake.lock
# pnpm
.pnpm-store
src/generated/**/*
.cursor/
.agents/
.claude/
.jj/
.sisyphus/
# Rock
.rock/
# TypeScript
*.tsbuildinfo
*/**/org.eclipse.buildship.core.prefs
*/**/org.eclipse.jdt.core.prefs
*/**/.classpath
*/**/.project
android/
release-artifacts/
!modules/**/android/
!plugins/android/
ios/
!modules/**/ios/
# jenv
.java-version
Regular → Executable
View File
-4
View File
@@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm run type-check
+1 -3
View File
@@ -1,3 +1 @@
**/node_modules
android/**/js/van.js
android/**/js/text-vibe.js
**/node_modules
-1
View File
@@ -7,5 +7,4 @@ module.exports = {
trailingComma: 'all',
arrowParens: 'avoid',
quoteProps: 'preserve',
endOfLine: 'auto', // stop prettier from getting mad on windows
};
-132
View File
@@ -1,132 +0,0 @@
# AGENTS.md
Guidance for AI coding agents working in this repository.
## Project overview
LNReader is a free, open-source light novel reader for Android, built with
React Native (Expo) and TypeScript. Content is fetched at runtime by
JavaScript "plugins" (per-source scrapers) rather than bundled with the app.
Plugin _requests_ live in a separate repo, [lnreader-plugins](https://github.com/lnreader/lnreader-plugins);
this repo is the reader app itself.
## Setup
```bash
pnpm install
```
- Node >= 22.11.0, pnpm (see `packageManager` in `package.json`, currently pnpm@11.20.0).
- This is a React Native/Expo app targeting Android; there is no web or iOS
build target maintained here. Building/running the app requires an Android
SDK and a device/emulator, which is generally not available in an agent
sandbox — prefer running lint, type-check, and tests to verify changes.
- See `CONTRIBUTING.md` (and `CONTRIBUTING-NIX.md` for a Nix-based dev shell)
for full local Android setup instructions.
## Commands
Run these from the repo root:
```bash
pnpm run lint # eslint ./src
pnpm run lint:fix # eslint --fix
pnpm run format:check # prettier --check
pnpm run format # prettier --write
pnpm run type-check # tsc --noEmit
pnpm run check # format:check + lint + type-check (run before finishing any change)
pnpm test # jest (all projects)
pnpm run test:rn # jest --selectProjects rn (React Native/component/hook tests)
pnpm run test:db # jest --selectProjects db (database/query tests, node env)
pnpm run test:watch
pnpm run test:coverage
```
Always run `pnpm run check` (or at minimum lint + type-check + the relevant
test project) before considering a change complete. A pre-commit hook
(`husky` + `lint-staged`) runs prettier/eslint on staged files, and a
pre-push hook runs `type-check`, so failures here will also block commits/pushes.
## Code style
- TypeScript, functional React components, hooks-based state.
- Formatting is enforced by Prettier (`.prettierrc.js`) — don't hand-format,
run `pnpm run format` instead.
- ESLint config is `eslint.config.js` (flat config, based on `eslint-config-expo`).
Notable rules: `no-console` is an error (use the project's logging/toast
utilities, not `console.*`), `prefer-const`, `no-var`, and several
`react-hooks/*` correctness rules are enforced.
- Use the `@alias/*` path aliases defined in `tsconfig.json` (and mirrored in
`jest.config.js`) instead of deep relative imports, e.g. `@components`,
`@hooks`, `@database/*`, `@screens/*`, `@services/*`, `@utils/*`,
`@api/*`, `@theme/*`, `@plugins/*`, `@navigators/*`, `@i18n/*`, `@type/*`.
- Don't add comments that just restate what the code does; only comment
non-obvious "why" (workarounds, invariants, native/platform quirks).
## Localization (i18n)
- `src/i18n/languages/en/strings.json` is the source-of-truth string file —
it's the only locale you should hand-edit. Every other folder under
`src/i18n/languages/*` is a translation synced automatically from Crowdin
(see `crowdin.yml`); don't edit or "fix" translated strings in those files.
- After adding/changing keys in `en/strings.json`, run
`pnpm run generate:string-types` to regenerate the associated TypeScript types.
## Repository layout
- `src/api` – external API clients (drive/cloud backup providers, etc.)
- `src/components` – shared/reusable UI components
- `src/database` – SQLite (op-sqlite/Drizzle) schema, queries, and migrations
helpers; `src/database/queries/*Queries.ts` are the main data-access layer
- `src/hooks` – shared and "persisted" (MMKV-backed) React hooks
- `src/i18n` – translated strings (see Crowdin integration in `crowdin.yml`)
- `src/navigators` – React Navigation stack/tab setup
- `src/plugins` – plugin manager and plugin type definitions (not the plugin
source scrapers themselves, which live in the separate lnreader-plugins repo)
- `src/screens` – feature screens (library, browse, novel, reader, settings, etc.)
- `src/services` – background tasks, downloads, backups, trackers, EPUB export, updates
- `src/theme` – Material3 theming
- `src/utils` – general utilities
- `drizzle/` – generated SQL migrations (see `pnpm run generate:db-migration`)
- `modules/` – native modules (Nitro modules: epub, tts, file, zip-archive, etc.)
- `test/` – Jest global mocks and the shared `test-utils.tsx` render helpers
- `scripts/` – Node scripts for env file generation, release packaging, etc.
## Testing notes
- Don't add tests for the sake of coverage. Write tests for user-facing
interactions (screens, components a user directly touches) and for core
complex logic (parsing, database queries, sync/backup, chapter/novel
matching, etc.), not for trivial passthroughs, simple type/prop plumbing,
or generated/boilerplate code.
- Tests live in `__tests__` directories next to the code under test and use
Jest + `@testing-library/react-native`.
- Jest is split into two projects (see `jest.config.js`):
- `db` — pure logic/query tests under `src/database`, run in a Node environment.
- `rn` — component/hook/integration tests under `src/`, run with the `jest-expo` preset.
- Import the shared render helpers from `@test-utils` (`render`, `renderNovel`,
`AllTheProviders`) rather than using `@testing-library/react-native`'s
`render` directly, so providers (theme, gesture handler, safe area, novel
context) are set up consistently.
- Mock at module level with `jest.mock(...)`, not inside `beforeEach` — see
`TESTING.md` for the reasoning and common pitfalls (ESM transform errors,
`mockReturnValue` failures, test isolation).
- Global mocks for native modules and database queries live in `test/mocks/`;
hook-specific mocks live in `src/hooks/__mocks__` and `src/hooks/__tests__/mocks.ts`.
## Database changes
- Schema lives under `src/database/schema`; the ORM is Drizzle
(`drizzle-orm`, config in `drizzle.config.ts`).
- After changing the schema, generate a migration rather than hand-writing
SQL: `pnpm run generate:db-migration`. Do not edit files under `drizzle/`
by hand.
## PR / commit conventions
- Keep changes scoped and consistent with existing patterns in the touched
directory; prefer editing existing files over introducing new abstractions.
- Match existing code style; don't reformat unrelated code.
- If you are an AI agent opening a PR or making a commit, attribute it as
such (e.g. a "Generated with <agent/tool name>" note in the commit
message/PR description, and a co-author trailer if your tool supports one).
+64
View File
@@ -0,0 +1,64 @@
import 'react-native-gesture-handler';
import { enableFreeze } from 'react-native-screens';
enableFreeze(true);
import React, { useEffect } from 'react';
import { StatusBar } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import LottieSplashScreen from 'react-native-lottie-splash-screen';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { Provider as PaperProvider } from 'react-native-paper';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/lib/integration/react';
import * as Notifications from 'expo-notifications';
import { createDatabase } from '@database/db';
import { persistor, store } from '@redux/store';
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
import Main from './src/navigators/Main';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
// Initialize trackers
import './src/services/Trackers/index';
import './src/services/Trackers/aniList';
import './src/services/Trackers/myAnimeList';
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
};
},
});
const App = () => {
useEffect(() => {
LottieSplashScreen.hide();
createDatabase();
}, []);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<AppErrorBoundary>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<BottomSheetModalProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</BottomSheetModalProvider>
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
</AppErrorBoundary>
</GestureHandlerRootView>
);
};
export default App;
-126
View File
@@ -1,126 +0,0 @@
import 'react-native-url-polyfill/auto';
import { enableFreeze } from 'react-native-screens';
import { PropsWithChildren, Suspense, useEffect, useMemo } from 'react';
import { StatusBar, StyleSheet } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import * as SplashScreen from 'expo-splash-screen';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import {
MD3DarkTheme,
MD3LightTheme,
Provider as PaperProvider,
} from 'react-native-paper';
import AppErrorBoundary, {
ErrorFallback,
} from '@components/AppErrorBoundary/AppErrorBoundary';
import CrashRecoveryGate from '@components/AppErrorBoundary/CrashRecoveryGate';
import Main from './src/navigators/Main';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
import { useInitDatabase } from '@database/db';
import { useInitializeAppServices } from '@hooks/common/useInitializeAppServices';
import { opSqliteAdapter } from './src/rozenite/opSqliteAdapter';
import { useRozeniteSqlitePlugin } from '@rozenite/sqlite-plugin';
import { ThemeProvider, useTheme } from '@hooks/persisted/useTheme';
import { KeyboardProvider } from 'react-native-keyboard-controller';
enableFreeze(true);
const sqliteAdapters = __DEV__ && opSqliteAdapter ? [opSqliteAdapter] : [];
/**
* The Android window background is resolved from the system light/dark setting,
* so it is bright white whenever the system is light – regardless of the theme
* picked inside the app. Nothing between it and the navigators paints a
* background of its own, so every frame in which no screen is drawn (mounting a
* nested navigator, freezing the screen being left) shows through as a flash.
* Painting the root view keeps the window covered at all times.
*/
const ThemedRootView = ({ children }: PropsWithChildren) => {
const theme = useTheme();
return (
<GestureHandlerRootView
style={[styles.flex, { backgroundColor: theme.background }]}
>
{children}
</GestureHandlerRootView>
);
};
const ThemedPaperProvider = ({ children }: PropsWithChildren) => {
const theme = useTheme();
const paperTheme = useMemo(() => {
const baseTheme = theme.isDark ? MD3DarkTheme : MD3LightTheme;
return {
...baseTheme,
colors: {
...baseTheme.colors,
...theme,
},
};
}, [theme]);
return <PaperProvider theme={paperTheme}>{children}</PaperProvider>;
};
const Application = () => {
useRozeniteSqlitePlugin({ adapters: sqliteAdapters });
const { success: databaseReady, error: databaseError } = useInitDatabase();
const { ready: servicesReady, error: servicesError } =
useInitializeAppServices(Boolean(databaseReady));
useEffect(() => {
if ((databaseReady && servicesReady) || databaseError || servicesError) {
SplashScreen.hideAsync();
}
}, [databaseReady, databaseError, servicesReady, servicesError]);
const initializationError = databaseError || servicesError;
if (initializationError) {
return (
<ErrorFallback error={initializationError} resetError={() => null} />
);
}
if (!databaseReady || !servicesReady) {
return null;
}
return (
<ThemedRootView>
<KeyboardProvider>
<AppErrorBoundary>
<SafeAreaProvider>
<ThemedPaperProvider>
<BottomSheetModalProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</BottomSheetModalProvider>
</ThemedPaperProvider>
</SafeAreaProvider>
</AppErrorBoundary>
</KeyboardProvider>
</ThemedRootView>
);
};
const App = () => (
<Suspense fallback={null}>
<ThemeProvider>
<CrashRecoveryGate>
<Application />
</CrashRecoveryGate>
</ThemeProvider>
</Suspense>
);
export default App;
const styles = StyleSheet.create({
flex: {
flex: 1,
},
});
-108
View File
@@ -1,108 +0,0 @@
# React Native Development Nix Environment Setup
This document outlines how to set up your React Native development environment using Nix flakes for reproducibility and isolation.
## Disclaimer
This was tested on an Arch Linux system with AMD CPU and GPU. There are likely problems with NVIDIA GPUs.
Compatibility with Microsoft WSL2 is not guaranteed, but should work without major issues, except for the emulator.
## Prerequisites
- **Nix installed**: [https://nixos.org/download.html](https://nixos.org/download.html)
- **Git**: Required for cloning the repository.
- **KVM (for emulator, if enabled)**: Ensure KVM is enabled and your user is in the `kvm` group for optimal emulator performance:
```bash
sudo usermod -aG kvm $USER && newgrp kvm # (or log out/in)
kvm-ok # Verify KVM acceleration
```
- **GPU Drivers (for emulator, if enabled)**: For hardware-accelerated emulator (AMD/Intel), ensure host Vulkan drivers are working:
```bash
vulkaninfo | grep -i "device name" # Should show your GPU
```
If not, install `vulkan-radeon` (AMD) or `vulkan-intel` (Intel) on your host system and reboot.
## Getting Started
1. **Clone the Project:**
```bash
git clone <your-fork-url>
cd <your-fork-name>
```
2. **Enter the Development Shell:**
If you have `pnpm` installed, use the defined scripts:
- **For Physical Device Only (default):**
```bash
pnpm run nix:shell
```
- **For Emulator Development (explicitly enable):**
```bash
pnpm run nix:emulator
```
If you do not have `pnpm` installed, execute the commands manually:
- **For Physical Device Only (default):**
```bash
nix develop --extra-experimental-features nix-command --extra-experimental-features flakes
```
- **For Emulator Development (explicitly enable):**
```
nix develop .#emulator --extra-experimental-features nix-command --extra-experimental-features flakes
```
The first time, this will download and set up all required tools, which can take considerable time. Subsequent entries will be much faster. You will also see a message about "Setting up writable Android SDK..." which only happens once.
## Development Workflow
Once inside the `nix develop` shell, all tools are available.
### 1. Start Metro Bundler
Open a new terminal or run in the background:
```bash
pnpm run dev:start
```
### 2. Physical Device Development
1. **Connect Device**: Enable USB debugging on your Android device and connect it via USB.
2. **Verify Device**:
```bash
adb devices
```
3. **Mirror Screen (Optional)**:
```bash
scrcpy # Mirrors screen, provides control
scrcpy -s <device_serial> # If multiple devices connected
```
4. **Run App**:
```bash
npx run android
```
### 3. Android Emulator Development (if enabled)
1. **Launch Emulator**:
```bash
emulator -avd ReactNative_API35 -gpu host -no-metrics -no-audio
```
- Use `-gpu swiftshader_indirect` if hardware acceleration (`-gpu host`) fails.
2. **Verify Emulator**:
```bash
adb devices
```
3. **Run App**:
Launches the the emulater automatically, if it is not already running.
```bash
npx run android
```
+14 -19
View File
@@ -2,19 +2,14 @@
Contributions are welcome and are greatly appreciated!
## Setup Your Environment with Nix
## Setting up your environment
If you are on a Linux system, you can install the nix package manager and use the nix flakes to set up your development environment.
See [CONTRIBUTING-NIX.md](CONTRIBUTING-NIX.md).
## Setting Up Your Environment
After forking to your own GitHub organization or account, take the following steps to get started:
After forking to your own github org or account, do the following steps to get started:
```bash
# prerequisites
node --version >= 20 (for version management, get nvm [recommended])
java sdk --version >= 17 (for version management, get jenv [optional])
node --version <= 16.13.1 (for version management, get nvm [recommended])
java sdk --version <= 11 (for version management, get jenv [optional])
android sdk (https://developer.android.com/studio)
# clone your fork to your local machine
@@ -23,19 +18,16 @@ git clone https://github.com/<your-account-name>/lnreader.git
# step into local repo
cd lnreader
# install pnpm (if not already installed)
npm install -g pnpm
# install dependencies
pnpm install
npm install
# build the apk (the built apk will be found in ~/lnreader/android/app/build/outputs/apk/release/)
pnpm run build:release:android
npm run buildRelease
```
### Developing on Android
You will need an Android device or emulator connected to your computer as well as an IDE of your choice (e.g., VS Code).
You will need an Android device or emulator connected to your computer as well as an IDE of your choice. (eg: vscode)
```bash
# prerequisites
@@ -46,19 +38,22 @@ IDE
adb devices
# run metro for development
pnpm run dev:start
npm start
# then to view on your android device (new terminal)
pnpm run dev:android
npm run android
```
To view any changes to the app with new code, save your code and press "r" on the metro terminal to
reload it. The app on the android device/emulator will reload shortly.
### Style & Linting
This codebase's linting rules are enforced using [ESLint](http://eslint.org/).
It is recommended that you install an eslint plugin for your editor of choice when working on this
codebase, however, you can always check to see if the source code is compliant by running:
codebase, however you can always check to see if the source code is compliant by running:
```bash
pnpm run lint
npm run lint
```
-73
View File
@@ -1,73 +0,0 @@
# Debugging Production Builds
Symbolicate production stack traces from LNReader using uploaded sourcemaps.
## Prerequisites
- **Node.js** (v18 or later)
- The **sourcemap** matching the crashing APK version
- A **stack trace** from either `adb logcat` (device attached) or pasted from a Discord / GitHub issue report
## Getting the Sourcemap
### Release builds
Sourcemaps are uploaded as GitHub release assets. Download the one matching the crashing version:
```
LNReader-v{version}-sourcemap.map
```
Example for `v2.5.0`:
1. Go to the [releases page](https://github.com/lnreader/lnreader/releases)
2. Find release `v2.5.0`
3. Under **Assets**, download `LNReader-v2.5.0-sourcemap.map`
### Preview / nightly builds
Sourcemaps are uploaded as a separate artifact alongside the preview APK, named `LNReader-{sha}-sourcemap`.
1. Go to the [Actions tab](https://github.com/lnreader/lnreader/actions) → **Build Preview**
2. Find the workflow run matching the build hash
3. Under **Artifacts**, download the one with the `-sourcemap` suffix
## Symbolicating a Stack Trace
Save the raw stack trace to a file (from `adb logcat`, or paste from Discord/GitHub into `trace.txt`), then run:
```bash
npx metro-symbolicate <sourcemap.map> < trace.txt
```
Or pipe it directly:
```bash
adb logcat -d | npx metro-symbolicate <sourcemap.map>
```
## Example
Before (Hermes-obfuscated):
```
com.lnreader E Error: Unexpected token
at 93795 (address at index.js:1:729193)
at 28471 (address at index.js:1:219482)
at 10384 (address at index.js:1:81729)
```
After symbolication:
```
com.lnreader E Error: Unexpected token
at handleResponse (src/api/plugins.ts:120:12)
at fetchPlugin (src/hooks/usePlugins.ts:45:8)
at getPluginList (src/screens/browse/BrowseScreen.tsx:89:20)
```
## Troubleshooting
- **Version mismatch**: the sourcemap must match the exact APK build. A mismatched version produces wrong file names and line numbers.
- **No sourcemap artifact**: verify the workflow that built the APK ran successfully and that the "Upload Sourcemaps" / "Upload Preview Sourcemap" step completed without errors.
- **Hermes bytecode**: Hermes compiles JS to bytecode. The sourcemap is generated during this step and maps bytecode offsets back to source. `metro-symbolicate` handles this transparently.
-17
View File
@@ -1,17 +0,0 @@
source 'https://rubygems.org'
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
ruby ">= 2.6.10"
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
gem 'xcodeproj', '< 1.26.0'
gem 'concurrent-ruby', '< 1.3.4'
# Ruby 3.4.0 has removed some libraries from the standard library.
gem 'bigdecimal'
gem 'logger'
gem 'benchmark'
gem 'mutex_m'
gem 'nkf'
+28 -49
View File
@@ -1,68 +1,47 @@
<p align="center">
<a href="https://lnreader.app">
<img src="./.github/readme-images/icon_new.png" align="center" width="128" />
</a>
</p>
<h1 align="center">LNReader</h1>
<img src="./.github/readme-images/icon_alt.png" align="center" width="128" />
<p>
<h1 align="center">
LNReader
</h1>
<p align="center">
LNReader is a free and open source light novel reader for Android, inspired by Tachiyomi.
LNReader is a Tachiyomi-like, free and open source light novel reader for Android.
</p>
<div align="center">
<a href="https://github.com/LNReader/lnreader/actions/workflows/build.yml">
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LNReader/lnreader/build.yml?branch=main">
</a>
<a href="https://discord.gg/QdcWN4MD63">
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=5865F2&color=4752C4&label=discord&style=flat">
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=7289DA&label=Discord&color=17cf48">
</a>
<a href="https://github.com/lnreader/lnreader/releases">
<img alt="GitHub Downloads" src="https://img.shields.io/github/downloads/lnreader/lnreader/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat">
<a href="https://github.com/LNReader/lnreader/releases/latest">
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader">
</a>
<a href="https://github.com/LNReader/lnreader-preview/releases/latest">
<img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/LNReader/lnreader-preview?color=red&label=preview">
</a>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/lnreader"><img src="https://badges.crowdin.net/lnreader/localized.svg"></a>
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader">
</a>
</div>
<div align="center">
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/lnreader/lnreader/build.yml?labelColor=27303D&style=flat">
<a href="https://github.com/lnreader/lnreader/blob/main/LICENSE">
<img alt="GitHub" src="https://img.shields.io/github/license/lnreader/lnreader?labelColor=27303D&color=1a73e8&style=flat">
</a>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/lnreader">
<img src="https://badges.crowdin.net/lnreader/localized.svg">
</a>
</div>
<h2 align="center">Download</h2>
<p align="center">
<a href="https://github.com/lnreader/lnreader/releases/latest">
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/lnreader/lnreader?label=Stable&labelColor=0d7377&color=084c4e&style=flat">
</a>
<a href="https://github.com/lnreader/lnreader/actions/workflows/build.yml">
<img alt="Build Preview" src="https://github.com/lnreader/lnreader/actions/workflows/build.yml/badge.svg">
</a>
</p>
<p align="center">
Get the app from our <a href="https://github.com/lnreader/lnreader/releases">releases page</a>.
</p>
<p align="center">
<em>Android 7.0 or higher.</em>
</p>
<h2 align="center">Screenshots</h2>
<br>
<br>
<p align="center">
<img src="./.github/readme-images/screenshots.png" align="center" />
</p>
## Plugins
## Download
Get the app from our [releases page](https://github.com/rajarsheechatterjee/LNReader/releases).
## Sources
LNReader does not have any affiliation with the content providers available.
Plugin requests should be created at [lnreader-plugins](https://github.com/lnreader/lnreader-plugins).
## Translation
Help translate LNReader into your language on [Crowdin](https://crowdin.com/project/lnreader).
Source requests should be created at [LNReader/lnreader-sources](https://github.com/LNReader/lnreader-sources).
## Building & Contributing
@@ -70,4 +49,4 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md)
## License
[MIT](https://github.com/lnreader/lnreader/blob/main/LICENSE)
[MIT](https://github.com/rajarsheechatterjee/lnreader/blob/main/LICENSE)
-80
View File
@@ -1,80 +0,0 @@
# Testing Guide for LNReader
This guide explains how to write tests in this React Native project using Jest and React Testing Library.
## Existing Mocks
### Global Mocks
The project has global mocks configured in Jest. These are automatically applied:
- `test/mocks/` - Global mocks for native modules (react-native-mmkv, react-navigation, all database queries, etc.)
- `src/hooks/__mocks__/index.ts` - Hook-specific mocks (showToast, getString, parseChapterNumber, etc.)
- `src/hooks/__tests__/mocks.ts` - Extended mocks for persisted hooks
### Using @test-utils
There is a custom render wrapper at `test/test-utils.tsx` with:
- `render` - wraps with GestureHandlerRootView, SafeAreaProvider, PaperProvider, etc.
- `renderNovel` - includes NovelContextProvider
- `AllTheProviders` - the full provider wrapper
Usage:
```typescript
import { render, renderNovel } from '@test-utils';
```
## Common Issues
### 1. ESM Modules Not Transforming
If you see `Cannot use import statement outside a module`, you need to add mocks for the module:
```typescript
jest.mock('@hooks/persisted/usePlugins');
// Add more specific mocks as needed
```
### 2. Mock Functions Not Working
If `mockReturnValue` throws "not a function", create mock functions at the module level:
```typescript
// CORRECT: Module-level mock functions
const mockUseTheme = jest.fn();
jest.mock('@hooks/persisted', () => ({
useTheme: () => mockUseTheme(),
}));
// INCORRECT: Trying to use jest.Mock type casting
// (useTheme as jest.Mock).mockReturnValue(...) // This fails!
```
### 3. Test Isolation
Tests must mock at module level, not in `beforeEach`:
```typescript
// CORRECT
const mockFn = jest.fn();
jest.mock('module', () => ({ useHook: () => mockFn() }));
// INCORRECT - mocks get reset between tests
jest.mock('module');
beforeEach(() => {
// This doesn't work properly
});
```
## Running Tests
```bash
pnpm test # Run all tests
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage
pnpm test:rn # React Native only
pnpm test:db # Database only
```
+55
View File
@@ -0,0 +1,55 @@
# To learn about Buck see [Docs](https://buckbuild.com/).
# To run your application with Buck:
# - install Buck
# - `npm start` - to start the packager
# - `cd android`
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
# - `buck install -r android/app` - compile, install and run application
#
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
lib_deps = []
create_aar_targets(glob(["libs/*.aar"]))
create_jar_targets(glob(["libs/*.jar"]))
android_library(
name = "all-libs",
exported_deps = lib_deps,
)
android_library(
name = "app-code",
srcs = glob([
"src/main/java/**/*.java",
]),
deps = [
":all-libs",
":build_config",
":res",
],
)
android_build_config(
name = "build_config",
package = "com.rajarsheechatterjee.LNReader",
)
android_resource(
name = "res",
package = "com.rajarsheechatterjee.LNReader",
res = "src/main/res",
)
android_binary(
name = "app",
keystore = "//android/keystores:debug",
manifest = "src/main/AndroidManifest.xml",
package_type = "debug",
deps = [
":app-code",
],
)
+271
View File
@@ -0,0 +1,271 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
/**
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
* and bundleReleaseJsAndAssets).
* These basically call `react-native bundle` with the correct arguments during the Android build
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
* bundle directly from the development server. Below you can see all the possible configurations
* and their defaults. If you decide to add a configuration block, make sure to add it before the
* `apply from: "../../node_modules/react-native/react.gradle"` line.
*
* project.ext.react = [
* // the name of the generated asset file containing your JS bundle
* bundleAssetName: "index.android.bundle",
*
* // the entry file for bundle generation. If none specified and
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
* // default. Can be overridden with ENTRY_FILE environment variable.
* entryFile: "index.android.js",
*
* // https://reactnative.dev/docs/performance#enable-the-ram-format
* bundleCommand: "ram-bundle",
*
* // whether to bundle JS and assets in debug mode
* bundleInDebug: false,
*
* // whether to bundle JS and assets in release mode
* bundleInRelease: true,
*
* // whether to bundle JS and assets in another build variant (if configured).
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
* // The configuration property can be in the following formats
* // 'bundleIn${productFlavor}${buildType}'
* // 'bundleIn${buildType}'
* // bundleInFreeDebug: true,
* // bundleInPaidRelease: true,
* // bundleInBeta: true,
*
* // whether to disable dev mode in custom build variants (by default only disabled in release)
* // for example: to disable dev mode in the staging build type (if configured)
* devDisabledInStaging: true,
* // The configuration property can be in the following formats
* // 'devDisabledIn${productFlavor}${buildType}'
* // 'devDisabledIn${buildType}'
*
* // the root of your project, i.e. where "package.json" lives
* root: "../../",
*
* // where to put the JS bundle asset in debug mode
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
*
* // where to put the JS bundle asset in release mode
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in debug mode
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
*
* // where to put drawable resources / React Native assets, e.g. the ones you use via
* // require('./image.png')), in release mode
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
*
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
* // for example, you might want to remove it from here.
* inputExcludes: ["android/**", "ios/**"],
*
* // override which node gets called and with what additional arguments
* nodeExecutableAndArgs: ["node"],
*
* // supply additional arguments to the packager
* extraPackagerArgs: []
* ]
*/
project.ext.react = [
entryFile: "index.js",
enableHermes: true, // clean and rebuild if changing
hermesCommand: '../../node_modules/react-native/sdks/hermesc/%OS-BIN%/hermesc',
]
apply from: "../../node_modules/react-native/react.gradle"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
/**
* Whether to enable the Hermes VM.
*
* This should be set on project.ext.react and mirrored here. If it is not set
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
* and the benefits of using Hermes will therefore be sharply reduced.
*/
def enableHermes = project.ext.react.get("enableHermes", false);
/**
* Architectures to build native code for.
*/
def reactNativeArchitectures() {
def value = project.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}
ext {
versionMajor = 1
versionMinor = 1
versionPatch = 17
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
defaultConfig {
applicationId 'com.rajarsheechatterjee.LNReader'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
// Generated version code. Supports versions up to 1024.1024.2048
versionCode ((((versionMajor << 10) | versionMinor) << 11) | versionPatch)
versionName "$versionMajor.$versionMinor.$versionPatch"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include (*reactNativeArchitectures())
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
// applicationVariants are e.g. debug, release
applicationVariants.all { variant ->
variant.outputs.each { output ->
// For each separate APK per architecture, set a unique version code as described here:
// https://developer.android.com/studio/build/configure-apk-splits.html
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
def abi = output.getFilter(OutputFile.ABI)
if (abi != null) { // null for the universal-debug, universal-release variants
output.versionCodeOverride =
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
}
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
//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 project(':react-native-fs')
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
if (isGifEnabled || isWebpEnabled) {
implementation 'com.facebook.fresco:fresco:2.0.0'
implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.0.0'
}
if(isGifEnabled){
// For animated gif support
implementation 'com.facebook.fresco:animated-gif:2.0.0'
}
if(isWebpEnabled){
// For webp support
implementation 'com.facebook.fresco:webpsupport:2.0.0'
if(isWebpAnimatedEnabled){
// Animated webp support
implementation 'com.facebook.fresco:animated-webp:2.0.0'
}
}
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
exclude group:'com.facebook.flipper'
}
if (enableHermes) {
//noinspection GradleDynamicVersion
implementation("com.facebook.react:hermes-engine:+") { // From node_modules
exclude group:'com.facebook.fbjni'
}
} else {
implementation jscFlavor
}
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.implementation
into 'libs'
}
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesAppBuildGradle(project)
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
+19
View File
@@ -0,0 +1,19 @@
"""Helper definitions to glob .aar and .jar targets"""
def create_aar_targets(aarfiles):
for aarfile in aarfiles:
name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")]
lib_deps.append(":" + name)
android_prebuilt_aar(
name = name,
aar = aarfile,
)
def create_jar_targets(jarfiles):
for jarfile in jarfiles:
name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")]
lib_deps.append(":" + name)
prebuilt_jar(
name = name,
binary_jar = jarfile,
)
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# 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.* {*;}
-keep public class com.dylanvann.fastimage.** {*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
+12
View File
@@ -0,0 +1,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application>
</manifest>
@@ -0,0 +1,70 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
* directory of this source tree.
*/
package com.lnreader;
import android.content.Context;
import com.facebook.flipper.android.AndroidFlipperClient;
import com.facebook.flipper.android.utils.FlipperUtils;
import com.facebook.flipper.core.FlipperClient;
import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
import com.facebook.flipper.plugins.inspector.DescriptorMapping;
import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
import com.facebook.react.ReactInstanceEventListener;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.network.NetworkingModule;
import okhttp3.OkHttpClient;
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
if (FlipperUtils.shouldEnableFlipper(context)) {
final FlipperClient client = AndroidFlipperClient.getInstance(context);
client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
client.addPlugin(new ReactFlipperPlugin());
client.addPlugin(new DatabasesFlipperPlugin(context));
client.addPlugin(new SharedPreferencesFlipperPlugin(context));
client.addPlugin(CrashReporterPlugin.getInstance());
NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
NetworkingModule.setCustomClientBuilder(
new NetworkingModule.CustomClientBuilder() {
@Override
public void apply(OkHttpClient.Builder builder) {
builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
}
});
client.addPlugin(networkFlipperPlugin);
client.start();
// Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
// Hence we run if after all native modules have been initialized
ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
if (reactContext == null) {
reactInstanceManager.addReactInstanceEventListener(
new ReactInstanceEventListener() {
@Override
public void onReactContextInitialized(ReactContext reactContext) {
reactInstanceManager.removeReactInstanceEventListener(this);
reactContext.runOnNativeModulesQueueThread(
new Runnable() {
@Override
public void run() {
client.addPlugin(new FrescoFlipperPlugin());
}
});
}
});
} else {
client.addPlugin(new FrescoFlipperPlugin());
}
}
}
}
+77
View File
@@ -0,0 +1,77 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<!-- Storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE" />
</intent>
</queries>
<application
android:name=".MainApplication"
android:largeHeap="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<meta-data
android:name="expo.modules.notifications.default_notification_icon"
android:resource="@drawable/notification_icon" />
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true"
android:theme="@style/Theme.App.SplashScreen"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lnreader" />
<data android:scheme="com.rajarsheechatterjee.LNReader" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
</manifest>
File diff suppressed because one or more lines are too long
@@ -0,0 +1,105 @@
package com.rajarsheechatterjee.LNReader;
import android.graphics.Color;
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 expo.modules.ReactActivityDelegateWrapper;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import org.devio.rn.splashscreen.SplashScreen;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
Window w = getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
super.onCreate(null);
SplashScreen.show(this,R.style.SplashScreenTheme, R.id.lottie);
SplashScreen.setAnimationFinished(true);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (VolumeButtonListener.isActive()) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (action == KeyEvent.ACTION_DOWN) {
// volup
VolumeButtonListener.up(VolumeButtonListener.getScrollAmount());
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
// voldown
VolumeButtonListener.down(VolumeButtonListener.getScrollAmount());
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
default:
return super.dispatchKeyEvent(event);
}
}
return super.dispatchKeyEvent(event);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "main";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED,
new MainActivityDelegate(this, getMainComponentName()
));
}
public static class MainActivityDelegate extends ReactActivityDelegate {
public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
super(activity, mainComponentName);
}
@Override
protected ReactRootView createRootView() {
ReactRootView reactRootView = new ReactRootView(getContext());
// If you opted-in for the New Architecture, we enable the Fabric Renderer.
reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
return reactRootView;
}
@Override
protected boolean isConcurrentRootEnabled() {
// If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
// More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
}
}
}
@@ -0,0 +1,109 @@
package com.rajarsheechatterjee.LNReader;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import androidx.annotation.NonNull;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.devio.rn.splashscreen.SplashScreenReactPackage;
import com.rnfs.RNFSPackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHostWrapper(this, new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new com.rajarsheechatterjee.LNReader.NavigationBarColorPackage());
packages.add(new VolumeButtonListenerPackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Override
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
}
);
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method
* with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
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
*/
Class<?> aClass = Class.forName("com.rajarsheechatterjee.LNReader.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig);
}
}
@@ -0,0 +1,170 @@
package com.rajarsheechatterjee.LNReader;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.os.Build;
import android.app.Activity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.UiThread;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
import com.facebook.react.uimanager.IllegalViewOperationException;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
public class NavigationBarColorModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBarColor";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity";
private static final String ERROR_API_LEVEL = "API_LEVEl";
private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported";
private static ReactApplicationContext reactContext = null;
private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
public NavigationBarColorModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
public void setNavigationBarTheme(Activity activity, Boolean light) {
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Window window = activity.getWindow();
int flags = window.getDecorView().getSystemUiVisibility();
if (light) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
window.getDecorView().setSystemUiVisibility(flags);
}
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("EXAMPLE_CONSTANT", "example");
return constants;
}
@ReactMethod
public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) {
final WritableMap map = Arguments.createMap();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(String.valueOf(color));
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
} else {
window.setNavigationBarColor(Color.parseColor(String.valueOf(color)));
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}
} else {
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));
}
} else {
promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE));
}
}
@ReactMethod
public void hideNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
View decorView = getCurrentActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
@ReactMethod
public void showNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
Window w = getCurrentActivity().getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
View decorView = w.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
}
@@ -0,0 +1,25 @@
package com.rajarsheechatterjee.LNReader;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NavigationBarColorPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NavigationBarColorModule(reactContext));
return modules;
}
}
@@ -0,0 +1,114 @@
package com.rajarsheechatterjee.VolumeButtonListener;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class VolumeButtonListener extends ReactContextBaseJavaModule {
static ReactApplicationContext appContext;
public VolumeButtonListener(ReactApplicationContext context) {
super(context);
appContext = context;
}
private static boolean active = false;
public static boolean isActive() {
return active;
}
public static boolean prevent = false;
public static void sendEvent(boolean up, int amount) {
if (active) {
WritableMap args = Arguments.createMap();
args.putInt("scrollAmount", amount); // add scroll amount to event data
if (up)
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeUp", args);
else
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeDown", args);
}
}
public static void down(int amount) {
sendEvent(false, amount);
}
public static void up(int amount) {
sendEvent(true, amount);
}
private static int scrollAmount = 100;
public static void setScrollAmount(int amount) {
scrollAmount = amount;
}
public static int getScrollAmount() {
return scrollAmount;
}
@ReactMethod
public void connect() {
active = true;
}
@ReactMethod
void disconnect() {
active = false;
}
@ReactMethod
public void pause() {
active = false;
}
@ReactMethod
public void unpause() {
active = true;
}
@ReactMethod
public void preventDefault() {
prevent = true;
}
@ReactMethod
public void noPreventDefault() {
prevent = false;
}
@NonNull
@Override
public String getName() {
return "VolumeButtonListener";
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void setVolumeButtonScrollAmount(int amount) {
setScrollAmount(amount);
}
@ReactMethod
public void getVolumeButtonScrollAmount(Promise promise) {
promise.resolve(getScrollAmount());
}
}
@@ -0,0 +1,30 @@
package com.rajarsheechatterjee.VolumeButtonListener;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class VolumeButtonListenerPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<NativeModule>();
modules.add(new VolumeButtonListener(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 936 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2014 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
android:insetTop="@dimen/abc_edit_text_inset_top_material"
android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
<selector>
<!--
This file is a copy of abc_edit_text_material (https://bit.ly/3k8fX7I).
The item below with state_pressed="false" and state_focused="false" causes a NullPointerException.
NullPointerException:tempt to invoke virtual method 'android.graphics.drawable.Drawable android.graphics.drawable.Drawable$ConstantState.newDrawable(android.content.res.Resources)'
<item android:state_pressed="false" android:state_focused="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
For more info, see https://bit.ly/3CdLStv (react-native/pull/29452) and https://bit.ly/3nxOMoR.
-->
<item android:state_enabled="false" android:drawable="@drawable/abc_textfield_default_mtrl_alpha"/>
<item android:drawable="@drawable/abc_textfield_activated_mtrl_alpha"/>
</selector>
</inset>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file was created by '@expo/configure-splash-screen' and some of it's content shouldn't be modified by hand
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/splashscreen_background"/>
</layer-list>
@@ -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,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
</adaptive-icon>
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<color name="iconBackground">#FFFFFF</color>
<color name="splashscreen_background">#202125</color>
<color name="colorPrimary">#023c69</color>
<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>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<string name="app_name">LNReader</string>
</resources>
@@ -0,0 +1,28 @@
<?xml
version="1.0"
encoding="UTF-8"
standalone="yes"
?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<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>
</style>
</resources>
+69
View File
@@ -0,0 +1,69 @@
import org.apache.tools.ant.taskdefs.condition.Os
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "31.0.0"
minSdkVersion = 21
compileSdkVersion = 31
targetSdkVersion = 31
if (System.properties['os.arch'] == "aarch64") {
// For M1 Users we need to use the NDK 24 which added support for aarch64
ndkVersion = "24.0.8215888"
} else {
// Otherwise we default to the side-by-side NDK version from AGP.
ndkVersion = "21.4.7075529"
}
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.2")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("de.undercouch:gradle-download-task:5.0.1")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
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"
}
}
}
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../android"))
}
maven {
// Android JSC is installed from npm
url(new File(["node", "--print", "require.resolve('jsc-android/package.json')"].execute(null, rootDir).text.trim(), "../dist"))
}
mavenCentral {
// We don't want to fetch react-native from Maven Central as there are
// older versions over there.
content {
excludeGroup "com.facebook.react"
}
}
google()
maven { url 'https://www.jitpack.io' }
}
}
+49
View File
@@ -0,0 +1,49 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Version of flipper SDK to use with React Native
FLIPPER_VERSION=0.125.0
# Use this property to specify which architecture you want to build.
# You can also override it from the CLI using
# ./gradlew <task> -PreactNativeArchitectures=x86_64
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=false
# Enable GIF support in React Native images (~200 B increase)
expo.gif.enabled=false
# Enable webp support in React Native images (~85 KB increase)
expo.webp.enabled=false
# Enable animated webp support (~3.4 MB increase)
# Disabled by default because iOS doesn't support animated webp
expo.webp.animated=false
Binary file not shown.
+5
View File
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+234
View File
@@ -0,0 +1,234 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+89
View File
@@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+16
View File
@@ -0,0 +1,16 @@
rootProject.name = 'LNReader'
apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json')"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle")
useExpoModules()
include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')
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 ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android')
+3 -86
View File
@@ -1,94 +1,11 @@
{
"expo": {
"version": "2.1.4",
"name": "LNReader",
"slug": "LNReader",
"scheme": "lnreader",
"icon": "./assets/native/ic_launcher.png",
"plugins": [
"./plugins/withAndroidCustomizations",
[
"expo-splash-screen",
{
"image": "./assets/native/ic_launcher_round.png",
"backgroundColor": "#1D1B20",
"resizeMode": "contain",
"dark": {
"image": "./assets/native/ic_launcher_round.png",
"backgroundColor": "#000000"
},
"android": {
"image": "./assets/native/splash_icon.png",
"backgroundColor": "#132C33",
"imageWidth": 288,
"resizeMode": "native",
"dark": {
"image": "./assets/native/splash_icon.png",
"backgroundColor": "#132C33"
}
}
}
],
"expo-localization",
[
"react-native-edge-to-edge",
{
"android": {
"enforceNavigationBarContrast": false
}
}
],
"expo-web-browser",
"./plugins/withReaderAssets",
[
"expo-build-properties",
{
"android": {
"usesCleartextTraffic": true,
"manifestApplicationAttributes": {
"android:largeHeap": "true",
"android:allowBackup": "true"
},
"enableMinifyInReleaseBuilds": true,
"enableShrinkResourcesInReleaseBuilds": true
}
}
]
],
"android": {
"versionCode": 4196356,
"package": "com.rajarsheechatterjee.LNReader",
"adaptiveIcon": {
"foregroundImage": "./assets/native/ic_launcher_foreground.png",
"backgroundImage": "./assets/native/ic_launcher_background.png",
"monochromeImage": "./assets/native/ic_launcher_monochrome.png"
},
"permissions": [
"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION",
"android.permission.WAKE_LOCK",
"android.permission.FOREGROUND_SERVICE",
"android.permission.FOREGROUND_SERVICE_DATA_SYNC",
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
"android.permission.POST_NOTIFICATIONS"
],
"intentFilters": [
{
"action": "SEND",
"category": ["DEFAULT"],
"data": [{ "mimeType": "text/plain" }]
}
]
},
"extra": {
"eas": {
"projectId": "3a63596f-e80c-4342-96b7-a8ac48910489"
}
},
"ios": {
"bundleIdentifier": "com.rajarsheechatterjee.LNReader",
"infoPlist": {
"UIBackgroundModes": ["audio"]
}
"notification": {
"icon": "./assets/notification.png",
"color": "#00adb5"
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

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