Compare commits

...

162 Commits

Author SHA1 Message Date
nyagami 5262567d61 Minor: hide reload button for custom page novels 2024-07-07 07:30:11 +07:00
nyagami b45a30f0cc Fix: avoid re-render in novel screen 2024-07-07 07:20:47 +07:00
nyagami 867b63af22 Feat: Plugin Custom JS/CSS 2024-07-06 11:42:18 +07:00
nyagami b3a5e237f4 added default cover 2024-07-06 11:41:29 +07:00
nyagami 7aae3a81c3 Fix: inlibrary badge 2024-07-06 11:32:25 +07:00
nyagami 078796784b Feat: Service Manager (#1124)
* feat: ServiceManager & import multiple epub files

* Fix: merge chapter based on index of toc in file list

* notifcation when jobs get done

* Fix epub: only read table of content in navmap

* add update library to service manager

* add drive backup/restore to service manager

* add self host backup/restore to service manager

* add migrate novel to service manager

* add download chapter to service manager

* clean up
2024-07-05 23:32:10 +07:00
Patrick Loser 15aed61aa6 Feat: tailing skeletons (#1064)
* tailing loading covers

* fix type issues

* fixed compatibility issues

* show skeleton even if row complete

* cleanup

* replaced !! with Boolean()

* Update type names

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* updated type occurances

* fixed key issue

* deleted unused vars and comments

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
2024-07-05 23:31:53 +07:00
Justin COLLON b67d6f8088 fix: update defaultCover URL (#1122) 2024-06-30 02:14:04 +05:30
nyagami 9b4bdfea89 Merge epub chapters (#1117)
* Fix: ignore non existed files

* merge chapters & scan cover

* remove unused tocMap

* clean code
2024-06-27 16:04:50 +07:00
r1di 70220a5a87 Fix: Volume scrolling (#1114)
* Volume scrolling fix

the scrolling by volume button right now scrolls 100% of the screen, which hides text when scrolled so, the commit is just changing the the scrolling to 50%.

* typo

* 50% to 75% scroll

---------

Co-authored-by: Ridi <ridi.culous.progger@gmail.com>
2024-06-25 20:48:10 +07:00
Patrick Loser aa95743d1c Fix: hide progressbar on opening drawer (#1102) 2024-06-25 20:20:47 +07:00
nyagami eb818ef55b add website to about page 2024-06-23 17:37:35 +07:00
nyagami 0bda5fd055 No default repo 2024-06-23 17:37:29 +07:00
Patrick Loser ea336902f2 Fix: Scroll to current Chapter (#1101)
* cleanup + fixed scroll to current Chapter

* removed unused memoization

* removed unused import

* ChapterDrawer.tsx => index.tsx

* added correct types

* applied suggestion

* changed chapter.position to scrollIndex + 2

* refactored index.ts
2024-06-14 23:17:22 +07:00
nyagami 58a633e5b9 [skip ci] ignore prettier for android folder 2024-06-13 00:39:19 +07:00
nyagami 74ed5a446d Fix: background color for plugin icon 2024-06-13 00:17:41 +07:00
nyagami a5d073a1ec Fix: Adding Category/Repo buttons position 2024-06-12 23:45:51 +07:00
nyagami f4d3d9d4eb Fix: navigation bar content color after closing reader 2024-06-12 23:40:51 +07:00
nyagami 772a9ef3a2 Feat: Plugin lazy init(#1104)
* move plugins to single folder

* lazy initial

* clean up

* fix: restore novels
2024-06-12 16:26:55 +07:00
Patrick Loser 6f3278f6ff Fix: Allow id and class attributes (#1066)
added exceptions to sanitizeHtml

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
2024-06-10 20:26:34 +07:00
Adam Kemish f11a10ba22 Feat: Add open random entry button (#1098)
* Add open random entry button

* Move menu logic to searchbarv2
2024-06-10 20:20:54 +07:00
Justin COLLON 02304fd394 [skip ci] version increase for default plugins repo(#1074) 2024-06-09 12:18:05 +07:00
nyagami 94d11a12ff Fix: detect gzip images 2024-06-09 10:42:08 +07:00
nyagami fca37868f1 Feat: NOVEL and CHAPTER reader context 2024-06-08 23:01:07 +07:00
nyagami 574f54a9bc Fix: update novel logic for custom page 2024-06-08 22:12:38 +07:00
nyagami fb0649f07f Minor fix: update novel toast 2024-06-08 22:06:12 +07:00
nyagami e3def3821e Fix: Repository add button position 2024-06-08 21:44:44 +07:00
nyagami 334c981013 Fix: add imageRequestInit for Reader instead of fetching base64 2024-06-08 21:42:07 +07:00
nyagami b62c8e3aea Download file with cookie persisted from RN (#1100)
* download file

* clean up
2024-06-08 20:15:41 +07:00
nyagami 09a482e4b7 Fix: do not modify available plugins 2024-06-08 01:57:51 +07:00
nyagami b7bbbbe962 Fix: revert to app storage 2024-06-08 01:42:47 +07:00
Elfing 30265471b5 [skip ci] Adding support for French localization (#1092)
synchro Text translate
2024-06-04 21:35:02 +07:00
nyagami b4b038dd42 fix: use back button instead of previous 2024-06-04 19:37:45 +07:00
nyagami 6517028cd4 minor fix: more header & error on first time launching 2024-06-04 19:28:21 +07:00
nyagami 3f62f6f225 Feat: Permissions requesting (#1096)
fix: app permissions & remove restore errors
2024-06-04 18:52:51 +07:00
nyagami 822256229f Refactor: convert Java to Kotlin (#1095)
* kotlin migrating

* fix: kotlin generations

* fix: unzip & cleanup
2024-06-04 14:14:03 +07:00
nyagami 4f23e1814e Feat: Onboarding screen & FileManager (#1093)
* base onboard screen

* pick theme step

* cleanup

* refactor: TextFile -> FileManager

* pick storage folder

* default root storage

* refactor: fixed storage -> custom storage

* replace rnfs by native module FileManager

* clean up

* refactor: corresponding theme list
2024-06-03 21:21:46 +07:00
Justin COLLON aec6c34b66 Update: Version utils & Github actions (#1072)
* github action:
 - custom apk:
   - packet name with git commit id
   - app name with git commit id
 - cached `npm install`
 - update actions versions

* About page:
 - Version based on package.json
 - added git hash to the version if not Stable
 - RELEASE_DATE, GIT_HASH, BUILD_TYPE created at build time from setEnvFile.cjs
 - special BUILD_TYPE for github actions
 - Version modal copy on click
 - removed versionUtils

* expo-clipboard instead of react-native clipboard

* buildRelease Release instead of Debug ^^'
Gihtub Actions => Github Action
2024-05-31 00:00:07 +07:00
Rider21 e50b6c4a47 Feat: Plugin storages (#986)
* test

* fix

* Update pluginManager.ts

* test

* fix lint

* test

* test webview

* sync

* /

* Update WebviewScreen.tsx

* CookieManager

* Update storage.ts

* Update сookie.ts

* fix title

* MarqueeText...

* Slow

* Even slower

* Squashed commit of the following:

commit da4d7e878aced6567b98fc802c34715b27dfbe00
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Fri Mar 8 13:21:19 2024 +0300

    /

commit 592c4dbfca2d04931ffffba19d1eec20db3ac183
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Fri Mar 8 13:20:34 2024 +0300

    test

commit 63240502d67cda6e851d602728b873e0b6d2aed0
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Fri Mar 8 12:38:13 2024 +0300

    /

commit 591d2592a7f64e65da4f2c61216b564f3eafa2e6
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Fri Mar 8 11:42:03 2024 +0300

    fix progressbar

commit 3d34cfb72827fdcf61267eafbb128b9e893ef40f
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 21:13:49 2024 +0300

    forgot

commit c36d090a60eadfcc4bee1f0cae04bf9213a694a4
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 20:51:31 2024 +0300

    /

commit 3c4e43b7bb6f845d94737aa8cba96b0dc1714b4b
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 20:15:38 2024 +0300

    /

commit 0a20a483968fa42d24996a1d74f4c8e91f86d49a
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 19:46:31 2024 +0300

    add zIndex

commit 7d4490110457f1ae189890268731fc1726a87825
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 19:27:32 2024 +0300

    test

commit b585d5ee0a6d36707ddc323547fdd1976451ee5a
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 18:28:49 2024 +0300

    /

commit cb7241c33d6cea3c1493f41b46fdd67f70455731
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 18:06:30 2024 +0300

    Update Appbar.tsx

commit 5fab766e17708a1be6a9f53029918cad122110bb
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 17:55:24 2024 +0300

    Update WebviewScreen.tsx

commit aec9b92d033156936b97a8455f6198fa20570906
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 17:15:16 2024 +0300

    xd

commit 1fa385b4bd85bea4da34ff9c23021fb92bc622d9
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 17:11:23 2024 +0300

    2

commit 589870de27cee2fcf29c894bd055a936bb0e7a5d
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 16:28:24 2024 +0300

    1

commit 343d2dd5b64404a3ea54d8e0366d8c856c9ea429
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 16:11:08 2024 +0300

    test

commit f2dbf34a7962b04cee46fdb37a9d0ea83ca541fe
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Thu Mar 7 14:36:48 2024 +0300

    test

commit 04c04d97a9dd87815c36145b3e0bd63e1bb3d118
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Wed Mar 6 22:37:45 2024 +0300

    test zoom

commit 6b80559e053a0d1a31f4c4762570b894bb5f70e5
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Wed Mar 6 22:33:55 2024 +0300

    fix

commit 287ce94efc18036ff9cca3054e9ca182137ffe8c
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Wed Mar 6 19:00:01 2024 +0300

    test

commit 67bdddc4d10920b559122f3d7df0225cea65497b
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Wed Mar 6 11:10:22 2024 +0300

    Test newArch

commit 762353788716341474832815f332ad6ff9ebe5be
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Tue Mar 5 20:49:29 2024 +0300

    Update Appbar.tsx

commit 2a876cb942847fb4c67085a6736b2d9e6e9a3012
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Tue Mar 5 20:42:28 2024 +0300

    modal

commit ba4cadec3f87d565fe045e3c977f604444a06571
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Tue Mar 5 16:50:38 2024 +0300

    1

commit 6c14a18b7083fc3292f796408e2defdad2728b10
Author: Rider21 <58046032+Rider21@users.noreply.github.com>
Date:   Tue Mar 5 14:54:27 2024 +0300

    test

* fix

* fix

* Update pluginManager.ts

* test

* fix

* fix lint

* -marquee

* fix defaultCover

* test

* Update src/plugins/helpers/storage.ts

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* test

* Typescript fixes

* private pluginID

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
2024-05-28 15:50:24 +07:00
nyagami 1ac6d116a2 Fix: Reader footer position & system bar (#1076)
* replace native Navigation bar by expo-navigation-bar

* fix fullscreen logic

* fix: navigation button color

* fix: navigation color when theme changed

* fix: status bar color on full screen mode

* 40 is too large

* no 40 is acceptable :)

* refactor setBarColor

* refactor: rename setStatusBarColor

* no padding for landscape mode
2024-05-28 15:13:46 +07:00
K1ngfish3r 6864d07dd9 Remove html entity replace function when sanitizing text (#1049)
* add spacing between <>

* remove check entirely

* fix ol tag
2024-05-28 11:21:36 +05:30
Lukas Richter 671e4f4bfd Fix: updateNovelTotalPages query (#1069) 2024-05-16 23:29:06 +07:00
nyagami e2d22a5b05 enhance TTS (#1054)
* fix: read by paragraph and empty string

* add voice picker modal

* add tts voice picker for preview

* fix: tts for bionic reading

* stop speak when get out of reader

* search voice

* temporary fix: original font

* fix: start & resume

* fix: close context menu on copy
2024-05-01 00:29:53 +07:00
Rajarshee Chatterjee 3cb2eb6b11 Make Reader Appbar and Footer Animation Smoother 2024-04-30 21:09:03 +05:30
Rajarshee Chatterjee 4597a95e9c Prevent Crash on Promise Rejection 2024-04-30 20:19:28 +05:30
Rajarshee Chatterjee cf4d97e7f9 Use Promise.allSettled and Update Plugin Repository Regex 2024-04-30 20:13:57 +05:30
Rajarshee Chatterjee 09f7319f2f feat: Repositories (#1051)
* feat: Repositories

* feat: Repositories

* Cleanup

* Cleanup

* Cleanup
2024-04-28 18:38:52 +05:30
Rajarshee Chatterjee c77dc52f29 Tweak UX (#1052)
* Tweak UX

* Prevent Crash in Migrate Screen

* Cleanup
2024-04-28 18:38:30 +05:30
Rajarshee Chatterjee 3cd5b40c25 Show Chapter Titles By Default 2024-04-28 09:37:49 +05:30
Rajarshee Chatterjee 42570d29f3 Move Update Icon Outside of Swipe Actions 2024-04-28 09:29:42 +05:30
nyagami 7e7a8866ca Fix: update novel pages (#1048)
* refreshPage button

* no latestChapter

* no hasUpdate in novel page
2024-04-27 09:44:54 +07:00
K1ngfish3r ed5e599edb add table style (#1020) 2024-04-26 00:16:46 +07:00
Patrick Loser addcadeba2 Feature: dynamic novel appbar (#998)
* update depd

* dynamic novel appbar

* fixed merge problems

* changed color from background to surface2

* added gitignore
2024-04-26 00:15:06 +07:00
Palloxin 484c2b7e33 Fix arabic alignment and language list order (#1024)
* Fix arabic alignment and language list order

- Left-to-Right for arabic language with u200e
- Fix alpabethic order

* Cleanup

---------

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2024-04-13 10:59:23 +05:30
nyagami b6fea9111c Fix: insert/update novel/chapter logic (#1019)
* fix: always create novelDir

* fix: download new chapters

* update novel totalPages if refreshNovelMetadata is off

* fix: updateChapter logic

* fix: insertId could be -1
2024-03-30 23:49:48 +07:00
K1ngfish3r 9ef8cab475 fix chapter.page when containing special character (#1015)
* fix page when containing special character

* page always defaults to 1

* AND page = ?
2024-03-26 22:29:19 +07:00
nyagami 7ea8ebacd1 Update plugin sanbox (#1014)
* add htmlparser2

* fix typo

* flatten plugins
2024-03-26 17:46:38 +07:00
nyagami e171dfebc1 Update lastest chapter logic (#1011)
update lastest chapter logic
2024-03-24 13:55:23 +07:00
nyagami 9f6ffc23bf Fix bugs & Reader improvement (#1009)
* fix: wrong chapter order

* fix: reader footer styles

* make swipe getures less sensitive

* fix: update only ongoing novels option

* synchonize update library

* add horizontal scrollbar

* update swipe gesture sensitive

* no swipe when image modal is visible

* add bionic reading

* fix: import untitled epub
2024-03-23 11:50:35 +07:00
nyagami 8906253527 Feat: protobuf support (#1003)
* feat: protobuf support

* fix: spead operator is slower

* fix: redundant JSON encoding

* add fetchProto to plugin sandbox
2024-03-21 16:37:07 +07:00
nyagami 817e4dc9d1 Cheaper epub parsing (#1007)
* cheaper epub parsing

* cleaner process

* no empty page list

* more epub detail

* fix nullable
2024-03-20 13:37:55 +07:00
nyagami 762945497b Fixes & Features (#1002)
* stop loading even no chapters

* edit artist

* more detail reader settings
2024-03-16 20:42:04 +07:00
nyagami a7aa203c30 refactor: safe ternary operator rendering 2024-03-14 14:24:11 +07:00
nyagami aeaecda0cd Fix app logics (#1001)
* fix: DocumentPicker apis

* fix: retry on browse source screen

* fix: set categories logic

* fix: dont fire noti when pausing downloading
2024-03-14 00:28:23 +07:00
nyagami a54ed6de0e Fix: Novel Drawer & Navigation color (#999)
* fix: replace DrawerLayoutAndroid with React Navigation DrawerLayout
slower

* fix: bottom navigation color

* fix: drawer background

* fix: theme dependency
2024-03-11 19:30:40 +07:00
nyagami dde33a0576 Fix themes & Added Reader Image Modal (#997)
* fix: annoying scroll on context menu

* fix app theme

* fix: android textselecthandle color

* fix plugin render callback dependencies

* add image modal for reader
2024-03-10 23:04:50 +07:00
nyagami 40b7542a78 Adjust UI for chilling (#995)
* more screen

* browse screen: reduce re-renders

* fix description styles

* browse screen tabs and swipe styles
2024-03-10 17:34:54 +07:00
Patrick Loser 88059fd6b9 Fix: Midnight Dusk Theme (#983)
* fix midnight Dusk

* added string
2024-03-10 17:34:35 +07:00
nyagami 0d5d7ad084 New Switch look (#994)
* new Switch look

* refactor: merge SwitchSetting to SwitchItem

* fix: remove margin
2024-03-10 14:00:59 +07:00
nyagami 202feb1f41 Fix chapters logic & Rework Plugins management (UI) (#993)
* fix: not download/remove local chapters

* fix: deleteChapters callback

* remove plugin id: no sense fot user

* rework plugin management
2024-03-10 00:23:34 +07:00
nyagami db478130e6 Fix Filter Sheet color & Reader Context menu (#991)
* fix: initialize once

* fix: filter sheet color

* fix: jump to chapter modal

* move volume button to context menu

* rework TTS

* fix: cleanup

* minor fix
2024-03-08 18:51:39 +07:00
Justin COLLON 607941ea1b Language: Add Thai (#989)
* Add Thai

* Thai in languagesMapping
2024-03-06 18:33:31 +07:00
nyagami da1631ba38 Upgrade RN 0.72, Prettier FilterSheet (#985)
* upgrade RN to 0.72, expo to 49

* prettier filter sheet

* fix: use react-native activity indicator

* uninstall @react-navigation/material-bottom-tabs

* fix: plugin card loading size
2024-03-05 13:22:11 +07:00
nyagami 2eb6f96b8e Fix: remove redundant install-time permissions 2024-03-03 00:25:07 +07:00
Patrick Loser d244001a18 Fix: replaced animation (#982)
* replaced animation

* lint issue
2024-03-02 23:39:30 +07:00
nyagami a25e8cb358 Add Polish 2024-03-01 19:57:31 +07:00
Patrick Loser 03b19479c9 Feat: new icons and splashscreen (#974)
* new icons and splashscreen

* removed playstore image

* changed ic_stat_name to notification_icon

* smaller splashscreen animation
2024-02-28 20:36:26 +07:00
Rider21 4f5451495f Fix: Webview theme (#977)
* fast fix

* hotfix

* fixed browser opening

* rewritten

* Update Appbar.tsx

* Update Appbar.tsx

* added BackHandler

* Update Appbar.tsx

* Update src/screens/WebviewScreen/components/Appbar.tsx

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* Update src/screens/WebviewScreen/components/Appbar.tsx

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
2024-02-28 20:09:27 +07:00
Rider21 6510270037 Fix: webview improved (#975)
* Update pluginManager.ts

* test

* fix

* fix Share

* add qs

* test webview

* Update WebviewScreen.tsx

* Update WebviewScreen.tsx

* webview

* test

* fix crash

* react-native-cookies/cookies

* Update SettingsAdvancedScreen.tsx

* yes

* Compact

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

* expandURL -> resolveUrl

* delete button

* Update fetch.ts

* test

* fix crash

* Update src/services/plugin/fetch.ts

Co-authored-by: nyagami <hoangquan05112002@gmail.com>

---------

Co-authored-by: nyagami <hoangquan05112002@gmail.com>
2024-02-26 20:33:50 +07:00
nyagami 0208b4265f Fix: backup/restore covers | clean build configs (#976)
fix: backup/restore covers | clean build configs
2024-02-26 11:35:18 +07:00
Rajarshee Chatterjee 334475431c Sort Plugins by Name & Center Plugin's Spinner 2024-02-26 09:19:44 +05:30
nyagami a2220cf3b8 Beta (#973)
* revert: plugin host

* fix: totalPages for legacy backup
2024-02-25 16:47:45 +07:00
nyagami 9c05266c3c Feat: Add features from main and fixes (#972)
* fix: download chapters filter

* fix: deletechapters missing

* fix: tweak chapter

* optimise: chapters preprocess

* fix: bookmark chapter

* fix: not showToast when no network

* fix: preprocess updates|history|download

* fix: ScrollView for morescreen

* feat: exclude download chapter filter

* fix: show labels in nav by default
2024-02-25 13:30:59 +07:00
nyagami 21e46d5493 Fix: Bug fixes (#969)
* fix: Range selection text

* fix: backup novel transaction

* fix: not show fetch plugins error

* fix: Filter button color

* adjust: plugin detail weight

* minor: plugin tab bar ripple color

* fix: category card buttons
2024-02-24 20:58:23 +07:00
nyagami ca688bb294 Upgrade TTS (#968)
* fix: plugins in/uninstall problem

* fix: remove useCallback for plugin item

* feat: implement new UI for TTS

* magic TTS

* fix: infinite loop

* fix: safe epub path

* magic: chose start point for TTS
2024-02-24 11:43:36 +07:00
nyagami 93f5871f1a Optimizations and bug fixes (#964)
* upgrade react-native-webview

* optimise: no render reader content when change reader settings

* optimise: no render reader content when change general settings

* fix: missing page when open reader

* fix: library only downloaded filter

* fix: center reader footer

* fix: reader footer padding

* fix: stop loading after fetch chapters

* showToast if cancel epub

* optimise: plugin list

* fix: remove LNReader path

* optimise: replace RNFS utf-8 by native

* fix: add description as epub  summary

* feat: hot reload for assets files (reader statics files)
2024-02-22 18:51:16 +07:00
nyagami 1bcc0e6664 LNReader beta version (#961)
* beta host

* Refactor: url -> path (#944)

* refactor: url -> pathr

* fix: pluginId

* page support (#947)

* add new fields

* minor fix

* pages for novel

* render novel pages with drawer

* support real paginated novels

* descending novel chapter list

* update when has new latest chapter

* set hasUpdate for all pages after update

* fix: insert pageIndex

* fix: refreshChapters

* minor refactor: pageIndex

* fix: update page logic

* fix: order by pageIndex

* refactor: Chapter.pageindex -> index (position in page)

* Revert "refactor: Chapter.pageindex -> index (position in page)"

This reverts commit 109298e0ccfa4a0d3d369087e05cb6f307a786b4.

* refactor: pageindex -> position

* fix: reader drawer

* temporary fix migration

* fix: Serialize plugins into one file (#949)

* serialize plugins into one file

* fix typo

* Fix: insertChapters (#950)

* Fix: re-renders in novel screen(#951)

* remove unnecessary wrap

* fix: add local novel back to local category

* fix: use variables for default states

* fix: do not re-render when no FAB

* close drawer if pages length = 1

* add Portal.Host

* Cheaper useNovel hook (#952)

* fix: using pure state for novel and chapters

* fix: remove latestChapter mmkv hook

* shorter key

* fix: bookmark

* fix: save chapter progress in db

* minor refactor: chapterProgressElement

* Fix: Error/Empty actions (#956)

* fix: reader error handler

* fix: opacity for novel summary

* fix: library empty view

* fix: upsert and pages logic

* fix: update novel logic

* fix: import epub

* fix: plugin site in global search

* fix: scrollbar font size

* minor fix
2024-02-19 12:48:42 +07:00
nyagami 1b5e345236 more default headers (#942)
more default headers
2024-02-07 18:53:52 +07:00
nyagami e5a1753e52 Fix tabiew (#939)
* fix: tabview look

* fix: error when library is not fetched fully

* fix: legacy backup

* fix: handle error for plugin methods
2024-02-03 10:19:09 +07:00
uy/sun 4bf615477e Fix update only when chapter info changed (#935)
fix: update only when info changed
2024-02-01 12:04:09 +07:00
uy/sun e0dbf87ca7 Fix chapters status lost problem (#934)
* ci: rename workflows

* chore: update translations

* fix: use upsert
2024-02-01 09:44:57 +07:00
nyagami ad2412eb62 Zip backup (#933)
* add remote zip/unzip

* reimplement: remote backup

* fix: close backup request

* get response for remote zip

* add zip drive backup

* add zip drive restore

* fix: categories are not restored

* change scope to drive.file

* add description for backup action

* refactor: Backup types
2024-01-31 19:19:30 +07:00
nyagami 6c20d1855d Pre alpha patch 5 (#928)
* fix: `default` category is added after id = 1 was deleted

* add new fields for Novel and Chapter table
2024-01-31 14:30:27 +07:00
nyagami 3f59bcf751 fix: display native lang in plugin card 2024-01-29 18:15:46 +07:00
nyagami aeafe9d1ca minor: add NativeLanguage type 2024-01-29 18:06:48 +07:00
nyagami 2501e5a20e Merge remote-tracking branch 'origin/update-drive-auth' 2024-01-29 17:16:57 +07:00
nyagami bd3ccdbc07 Pre alpha patch 4 (#919)
* update google-services.json

* fix: wrong package name - ZipArchive
2024-01-28 19:55:23 +07:00
nyagami 8c70e39492 refactor: EpubParser -> ZipArchive 2024-01-28 18:45:52 +07:00
nyagami 58f6475749 update: use cheerio to parse local novel 2024-01-28 18:43:20 +07:00
nyagami de9763e83e update google-services 2024-01-27 12:22:00 +07:00
nyagami fdc15a0936 Fix: pre-alpha-patch-3 (#914)
* fix: not allow to delete local category

* fix: throw github cache issue

* refactor: use english key

* minor: native language details

* add translatable strings
2024-01-27 09:26:37 +07:00
uy/sun 0e633b42ba chore: add more translatable strings (#908)
* chore: strings in browseScreen

* chore: update translations in strings.json

* chore: translate MoreScreen and downloader.serviceRunning

* chore: translate all things in moreScreen

* feat: support passing options

* chore: update more translations in settings

* chore: translation in LibraryScreen

* chore: add missing en translations

* chore: rearrange strings path

* chore: translate backupScreen

* chore: translate SettingsReaderScreen

* chore: translate statsScreen

* chore: translate HistoryScreen

* chore: translate NovelScreen

* chore: translate remaining strings

* chore: missing translations

* chore: add missing and improve Chinese translation

* chore: improve naming

* chore: remove toast section and add more translations related to notifications

* revert: status translations

* chore: translate NovelStatus

* chore: translate error messages I have encountered

* chore: add more translations used in updateNotification

* chore: update missing translations

* chore: translate backup modal

* chore: translate all taskTitle

* chore: improve translations

* chore: add common.example

* chore: improve emptyChapterMessage

* chore: rearrange backup strings

* chore: flatten keys

* chore: remove unused strings

* chore: translate more stuff

* chore: flatten aboutScreen

* chore: flatten generalSettingsScreen

* chore: flatten readerSettings

* chore: flatten browseSettingsScreen

* chore: flatten settingsScreen

* chore: updated based on review

* chore: translate DefaultCategory

* chore: fix sources translation
2024-01-26 18:47:39 +07:00
nyagami bf4d761c05 fix: not allow to delete local category 2024-01-26 16:18:39 +07:00
nyagami 09d51b26c6 update plugins host (#910)
* update plugins host

* fix: missing branch
2024-01-25 20:04:17 +07:00
nyagami 8f6c390dc2 Fix issues reported by pre-alpha users - patch 2 (#906)
* delete background_action key before action done

* force to delete BACKGROUND_ACTION.
in case app is shutdowned while running bg actions
2024-01-24 09:02:53 +07:00
nyagami 0cf75fd6f9 Fix: pre-alpha-patch-1 (#905)
* fix: wrong version checker, update lastused

* fix: only show update dialog if newer version

* fix: remove chapter id in chapter item

* fix: date in chapter drawer

* fix: decode uri for local epub

* fix: simpler SetCategoriesModal

* fix: not allow local as default

* fix: no delay on selfhost backup/restore
2024-01-22 23:08:27 +07:00
nyagami b823c913b5 Last step to Alpha (#904)
* update CONTRIBUTING.md

* fix: change host branch

* fix: remove showToast from plugin sandbox

* update actions

* update version
2024-01-22 08:55:30 +07:00
nyagami 01e6e9873e More fetch, add urlencode (#903)
* feat: add fetchText

* minor: add urlencode
2024-01-21 15:35:01 +07:00
nyagami d299e91a93 Merge pull request #897 from nyagami/main-feats
Add some features from main, improve UI
2024-01-21 13:05:32 +07:00
nyagami 430e5712fa fix: add chapter number, fix novel screen ui 2024-01-20 20:49:16 +07:00
nyagami 551425c018 feat: artist name,
fix: chapter release as local time
2024-01-20 18:26:45 +07:00
nyagami 665bca00ea fix: regex freeze app 2024-01-18 23:16:09 +07:00
nyagami 2ec6032673 chores: add translations 2024-01-18 15:40:37 +07:00
nyagami d52f630775 fix: circle requires in deleteCachedNovels 2024-01-18 15:04:22 +07:00
nyagami 3e7f51e9ae feat: custom UserAgent 2024-01-18 14:56:31 +07:00
nyagami bc977fd0cb feat: useUserAgent 2024-01-18 14:23:32 +07:00
nyagami deef93172b fix: clear novel settings when deleting 2024-01-18 14:12:26 +07:00
nyagami fa8b69a630 fix: chapter drawer layout 2024-01-18 14:07:04 +07:00
nyagami abd8d65914 fix: cleanup 2024-01-18 12:50:36 +07:00
Nyagami c71dda77d4 feat: save novel cover to download folder (if possible)
remove clear cached covers
2024-01-18 00:18:57 +07:00
Nyagami c2caee6ef9 replace FastImage by regular Image
- Glide -> Fresco (image loader)
2024-01-17 18:41:22 +07:00
Nyagami e2588908c1 minor: page number required in searchNovels 2024-01-16 17:39:40 +07:00
Nyagami 9b36043a00 feat: search next page 2024-01-15 23:24:19 +07:00
Nyagami 72fe3bb0f3 remove Flipper (which may cause internet stuck) 2024-01-15 21:01:13 +07:00
Nyagami 9f75945cf6 fix: skip chapter when error downloading 2024-01-15 20:47:36 +07:00
Nyagami 82f2b4e3b5 fix: filter picker value text color 2024-01-15 20:22:15 +07:00
Nyagami bdb7eb4521 fix: expo notification icon (not app icon) 2024-01-15 19:59:37 +07:00
Nyagami c42f0a0bb0 fix: background actions notification icon 2024-01-15 19:57:37 +07:00
Nyagami a33db07583 fix: expo notification icon 2024-01-15 19:45:23 +07:00
Nyagami 2b99c3d17d fix: android user-agent 2024-01-14 23:22:48 +07:00
Nyagami 6ea1ecb43d fix: handle fetchApi for 2 kind of headers 2024-01-14 23:22:18 +07:00
Nyagami bbe5ccea81 remove cookie stuffs 2024-01-14 23:19:05 +07:00
Nyagami da3a589c88 refactor MMKV backup data to util 2024-01-14 14:56:47 +07:00
Nyagami 26b312372e add tracker client variables for build action 2024-01-14 14:22:13 +07:00
Nyagami 39dc3ec8d6 fix: center label when no badge in library 2024-01-14 11:34:04 +07:00
Nyagami 560848f5d5 fix: reader bottom tab border 2024-01-14 11:32:55 +07:00
Nyagami 0bbd0da68c fix: ui for tracker 2024-01-14 10:24:39 +07:00
Nyagami bbdd569522 fix: jump to chapter modal keyboard 2024-01-14 10:02:08 +07:00
Nyagami 937e789b67 fix: exclude some MMKV keys when backup 2024-01-14 09:47:58 +07:00
Nyagami f8647a8e49 upgrade react-native-paper to fix modal ondismis 2024-01-14 02:12:12 +07:00
Nyagami f93bceb136 remove play button from history 2024-01-13 22:46:58 +07:00
Nyagami e8d343893f add animated fab 2024-01-13 22:38:48 +07:00
Nyagami 769ff36352 custom js, css modal 2024-01-13 22:02:30 +07:00
Nyagami 1253f44413 update ui for installed plugined 2024-01-13 17:52:02 +07:00
Nyagami 1c802c5e3b fix: update plugin logic 2024-01-13 17:50:55 +07:00
Nyagami a19b374cb4 fix: small plugin card, loading when installing 2024-01-13 16:25:53 +07:00
nyagami 420603f911 Replace Redux thunk by react native MMKV (#893)
* refactor: useBoolean hook

* refactor: useGithubUpdateChecker hook

* refactor: move showToast to utils

* refactor: useBackHandler hook

* refactor: useTextToSpeech hook

* refactor: useDeviceOrientation hook

* refactor: usePreviousRouteName hook

* refactor: useFullscreenMode hook

* refactor: useSearch hook

* refactor: useTheme hook

* fix: remove updates reducer

* refactor: useCategories hook

* refactor: useHistory hook

* refactor: useLibrarySettings hook
remove unused useCategories hook

* fix: reanimated version for expo

* refactor: library, chatper hooks

* refactor: app, browse settings hooks

* refactor: usePlugins hook

* fix: max depth in useEffect, plugins state logic

* feat: add Anilist tracker

* refactor: useTracker hook

* useTrackedNovel hook

* minor fixs

* refactor: useNovel hook

* minor fix: book mark button

* refactor: useDownload hook

* fix: migrate function

* remove redux + cleanup

* update node version for build

* fix: download Queue, migrate action

* fix: increaes hit slop for thumb, add reader playground

* fix: plugins not updated when installing while searching

* fix: safe plugin management

* Squashed commit of the following:

commit 51968f66bdd69f28f1863b5668f9511ba53c3fff
Author: Nyagami <hoangquan05112002@gmail.com>
Date:   Sat Jan 13 13:20:33 2024 +0700

    better logic for download progress bar

commit 5069de11644e7aece39efc73dfd80d093982b032
Author: Nyagami <hoangquan05112002@gmail.com>
Date:   Sat Jan 13 13:14:33 2024 +0700

    fix: delete BACKGROUND_ACTION in finally clause

commit 181103e994831da6744d122faaa60c48da529a63
Author: Nyagami <hoangquan05112002@gmail.com>
Date:   Sat Jan 13 13:10:56 2024 +0700

    update react-native-background-actions
2024-01-13 14:28:24 +05:30
nyagami 7a6710dc0b feat: Google Drive Backup (#872)
* setup google signin

* minify build scripts, add google signin modal

* feat: Drive backup

* feat: drive restore

* fix: remote backup

* upgrade react native to stable version
+ less build configs

* fix: path separator.
feat: sync backup between 2 methods

* legacy backup

* fix: expo speech
2024-01-07 16:58:05 +05:30
Marcin Majewski 7262f1d249 Filters Redesign (#856)
New filters system adding intelisense

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2023-12-27 19:36:45 +05:30
nyagami 5c12540071 refactor: Convert Javascript files into Typescript (#845)
* cheaper debug build

* index.ts, App.tsx

* TS for navigators

* TS NovelScreen

* TS Reader Screen

* TS More Screen

* TS Source Screen

* TS Browse Settings Screen

* TS Global Search Screen

* fix: useNavigation for more stack

* fix: Composite Types for More Screen

* TS Browse + Migrate Screen

* TS Settings Screen

* TS util

* TS services

* fix: Library query

* TS Components

* refactor: settingsSliceV1

* refactor: Preference reducer

* TS for MAL

* refactor: Native modules

* Not TS the entry file

* fix: zip folder entry, special file name

* fix: plugin is undefined if 'local'

* fix: add param for morestack navigation

* fix: Component is bool

* fix: novel reducer

* fix: not updating local novels
2023-12-27 19:35:05 +05:30
nyagami 7fbdd92117 feat: Import EPUB (#833)
* implement classes for plugins

* feat: plugin manages UA, cookie itself

* fix: repo path

* safer regexString

* feat: EpubParser native module

* add reject to parser

* local category, better insert category trigger

* add isLocal field to Novel

* category update logic for local novels

* feat: import epub

* delete downloaded folder when delete novel

* local novel screen

* update build.yml

* fix: novel without cover
2023-11-10 18:31:09 +05:30
nyagami 5c818be5ab refactor: Classes for Plugins (#824)
* implement classes for plugins

* feat: plugin manages UA, cookie itself

* fix: repo path

* safer regexString
2023-11-04 15:46:18 +05:30
nyagami 1b25acba26 refactor: Using classes for reader UI (#775)
using classes
2023-09-15 07:46:28 +05:30
nyagami 434d03615c fix: Remove Description and Protected (#753) 2023-08-22 18:07:20 +05:30
nyagami 9bde9528df feat: FileSystem, Highlight TTS (#741)
* fix restoreCategory

* feat: using file for js, css
- cheaper seekbar (maybe)
- cheaper showScrollPercentage
- autoSaveInterval
- cheaper loading image
- cheaper error images handler (no need protected attribute anymore)

* feat: store downloads in file

* move plugins to app download folder

* Feat: Hightlight TTS
- optimise render chapter text
- scroll fix
2023-08-18 21:44:05 +05:30
nyagami 191975f0f9 Tssupport, backup AppVersion (#680)
* add exports variable

* fix: remove pinned and lasteUsed after uninstall

* fix: language filter for installed plugin

* create index for Chapter in novelId

* reorder headers parame in fetchApi

* remove Language from plugin libs

* fix: not backup empty cover

* feat: backup app version

* fix: chapter index

* feat: checkAppVersion when restoring

* update: using normal exports instead of default
move fetchFile into fetch.ts

* defaultCoverUri -> defaultCover
2023-08-17 08:49:28 +05:30
nyagami f8895b80b4 fix: wrong mincroll in Update, History. TS Reader, Navigation (Main) (#665)
* fetch prev&next chapter and text at the same time.

* typescript for Main and Reader (and some related screens)

* fix: update time order

* feat: three dots button for browse

* fix: reader got wrong minscroll

* remove unused package drawer

* fix: seekbar bottom position

* add data-set in script (Reader)
2023-08-17 08:49:14 +05:30
nyagami e5c73615f1 feat: Remote Backup (#659)
* feat: remote backup (novel, category)

* feat: full backup

* fix: throw when downloading without installed plugin

* update: using websocket to tranfer data

* remote restore

* error handler for restore

* normalize code by recursion

* Not backuping tracker.
Because users can share their backup.
And tracker setting cotains their own token.

* fix: not create .nomedia in download dir

* fix:  2 children have same key in filter input

* fix: 2 children have same key in Picker Item
2023-06-30 09:41:10 +05:30
K1ngfish3r 800eb5ee30 Update pluginManager.ts (#661)
add showToast
2023-06-30 09:39:16 +05:30
nyagami 721b7edf1c fix: new github path for plugins json files (#636)
* change plugins host

* remove suffix

* update: new github path
2023-05-16 21:10:36 +05:30
nyagami 27501b223d fix: Update Plugins Host (#627)
* change plugins host

* remove suffix
2023-05-11 21:08:21 +05:30
nyagami 0e5c4b8243 feat: Plugins & Rework Database (#579)
* remove AsyncStorage, disable hermes

* init

* update logic

* update libs for plugins

* move interface to types

* refactor, add language type

* small refactor of languages

* getPlugin

* update alias

* collect plugin on launch

* update interface Plugin

* move lang to PluginItem

* implement install plugins

* fix plugins reference

* fix fetching logic

* fix empty list

* refactor Browscreen

* implement searchNovels

* refactor plugin reducer

* try to remove old sources

* try to refactor

* tabview with theme, remove rn-fetch-blob

* update lib for plugin, temp fix for browse

* try to replace source by plugin in db

* many updates

* update db

* fix queries error

* update db

* update db

* update History table

* update db types

* format table name

* rename service

* rename field

* refactor interfaces

* update constrait

* update novel queries

* update constrait

* update novel and category queries

* more flexible category

* update library queris

* create category trigger, add constraints

* sync variables name with js rule :(

* setup database 90%

* updates queries

* queries to add novel to lib

* remove delete updates => delete download

* minor

* app work 20%

* query default one time

* createdb first

* remove 90% sources

* fix library queries

* temporary fix

* remove all source

* remove old languages constant

* update trigger

* update types

* refactor types, update trigger, refactor queries

* at least can go to novel screen

* update novel redux

* can go to novel screen

* remove generator

* change sourceId to pluginId

* can read chapter :(

* minor fix

* can use history screen

* unique key in historyscreen

* remove never use hook

* at least can use reader

* minor

* fix download queries

* fix history and chapterDrawer doenst need setNovel

* fix last read

* update click heart to follow novel

* insertNovel

* long press novels in library,

* change default category by move up

* update edit category, fix foreign constrait

* loading before get novel done

* fix categories were added.
Dont know why trigger didnt work.
So I decide to use normal queries

* fix errors when edit infos

* shorter init plugin

* fix stats screen

* minor fix for seekbar

* set re-request, fix download chapter

* hard fix:), maybe refactor it later

* must use sum to count :D

* update query

* fix migrate screen

* minor fix

* fix migrate screen and global search display

* minor

* quick fix for update screen

* update plugin props

* lol :D

* Backup work now.
But need to install plugins

* restore error backup

* If get novel without chapters. Fetch it again

* if all error novels are restored, remove error file

* minor fix

* remove never used packages.

* Complete plugin feature. Bump to RN 0.69.
I will write a  changelog soon

* fetchFile for app side

* Fix: transparent bottem sheet, undefined index.
hideNavigationBar causes transparent bottem sheet (still opening but invisible.
Hope to find an actual fix later

* newUpdateDialog without markdown

* fix: Browse plugins behaviors

* fix: reader screen crashs when plugin is not installed

* Minor fix: undefined variables when updating

* minor fix: fetchFile return base64

* only update newversion

* fix: buildReleas succesfully but app crashes when opening.
from: https://github.com/facebook/react-native/issues/34114

* fix: webview send correct body height

* Refactor: remove HistoryTable.
remove PluginStatus

* Refactor: Migrate Update and History into Chapter.
- remove nerver used hook: useChapter

* fix: edit categories modal in novel screen

* minor: LL date format

* minor: remove html2text lib

* minor: migrate inlibrary novel

* Migrate novel and clean up

* small fix for migrate function

* Database Diagram

* optimise: O(1) to determine if lastread is read

* fix: position could be undefined

* refactor: change prop name. postion.position -> postion.offsetY

* fix filter

* fix: missing param in parseChapterNumber

* not a good fix, but it works xD

* fix: Text trunct almost title, should find a better fix later.

* enable feature: migrate from browse screen

* add data-plugin-id tag

* no `ghost` novel (which has no category)

* minor fix: detele all novel categories when removing novel form lib

* minor: deleteDownloadsSnackbar

* edit warning

* add chapter tag on preview

* add id for plugin, novel, chapter. smaller padding

* Use BottomSheetModal

* implement cookies bypass

* default language filter

* change default user agent

* shorter langue native name

* missing NOVEL_ERROR

* add filterInputs

* correct spelling :)

* add dayjs

* minor: remove console.log

* update site for pluginItem

* update icon,

* minor: remove unnecessary things
2023-05-08 07:09:20 +05:30
557 changed files with 39714 additions and 61517 deletions
-3
View File
@@ -1,3 +0,0 @@
# Windows files
[*.bat]
end_of_line = crlf
+1 -1
View File
@@ -1,6 +1,6 @@
module.exports = {
root: true,
extends: '@react-native-community',
extends: '@react-native',
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
overrides: [
-3
View File
@@ -1,3 +0,0 @@
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
+2 -2
View File
@@ -45,7 +45,7 @@ body:
label: LNReader version
description: You can find your LNReader version in **More → About**.
placeholder: |
Example: "1.1.14"
Example: "2.0.0"
validations:
required: true
@@ -88,7 +88,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[1.1.14](https://github.com/lnreader/lnreader/releases/latest)**.
- label: I have updated the app to version **[2.0.0](https://github.com/lnreader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+1 -1
View File
@@ -34,7 +34,7 @@ body:
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/LNReader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[1.1.14](https://github.com/LNReader/lnreader/releases/latest)**.
- label: I have updated the app to version **[2.0.0](https://github.com/LNReader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+66 -10
View File
@@ -1,49 +1,105 @@
name: CI
name: Build
on:
push:
branches:
- main
- master
jobs:
build-android:
name: Build App
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- name: Clone Repository (Latest)
uses: actions/checkout@v4
with:
node-version: '16'
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Cache npm & node modules
uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-cache-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-cache-npm-
- name: Install Dependencies
run: |
npm install --legacy-peer-deps
npm install
- name: Create .env
run: |
cat > .env << EOF
MYANIMELIST_CLIENT_ID=${{ vars.MYANIMELIST_CLIENT_ID }}
ANILIST_CLIENT_ID=${{ vars.ANILIST_CLIENT_ID }}
GIT_HASH=$(git rev-parse --short HEAD)
RELEASE_DATE=$(date --utc +'%d/%m/%y %I:%M %p %Z')
BUILD_TYPE=Github Action
EOF
- name: Cache Gradle Wrapper
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle Dependencies
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-caches-
- name: Cache build intermediates
uses: actions/cache@v4
with:
path: |
android/app/build/generated
android/app/build/intermediates
android/app/build/tmp
android/.gradle
key: ${{ runner.os }}-build-${{ hashFiles('package-lock.json') }}
- name: Make Gradlew Executable
run: cd android && chmod +x ./gradlew
- name: Build Android Release
run: |
cd android && ./gradlew assembleRelease --no-daemon
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/LNReader.apk
- name: Set Env
run: |
set -x
echo "COMMIT_COUNT=$(git rev-list --count HEAD)" >> $GITHUB_ENV
echo "COMMIT_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v1
env:
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
uses: actions/upload-artifact@v4
with:
name: LNReader-r${{ env.COMMIT_COUNT }}
path: android/app/build/outputs/apk/release/app-release.apk
path: android/app/build/outputs/apk/release/LNReader.apk
- name: Build Custom APK
env:
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
COMMIT_ID: ${{ env.COMMIT_ID }}
run: |
sed -i 's/LNReader/LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})/g' android/app/src/main/res/values/strings.xml
cd android && ./gradlew assembleRelease --no-daemon -PcustomAppId=com.rajarsheechatterjee.LNReader.commit_${{ env.COMMIT_ID }}
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}\(${{ env.COMMIT_ID }}\)-customID.apk
- name: Upload Custom APK
env:
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
COMMIT_ID: ${{ env.COMMIT_ID }}
uses: actions/upload-artifact@v4
with:
name: LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})-customID
path: android/app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})-customID.apk
+4 -4
View File
@@ -1,11 +1,11 @@
name: CI
name: Lint
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
build:
@@ -14,6 +14,6 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm i --legacy-peer-deps
run: npm install
- name: Run ESLint
run: npm run lint
+5 -2
View File
@@ -8,7 +8,8 @@ npm-debug.*
*.mobileprovision
*.orig.*
web-build/
.env
reader_playground/index.html
# macOS
.DS_Store
@@ -73,4 +74,6 @@ web-build/
# @end expo-cli
.vscode/
src/sources/en/generators/
src/sources/en/generators/
*.lockb
.tool-versions
+2 -1
View File
@@ -1 +1,2 @@
**/node_modules
**/node_modules
android
+14 -20
View File
@@ -3,21 +3,19 @@ import { enableFreeze } from 'react-native-screens';
enableFreeze(true);
import React, { useEffect } from 'react';
import React 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 { createTables } from '@database/db';
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
import Main from './src/navigators/Main';
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
Notifications.setNotificationHandler({
handleNotification: async () => {
@@ -29,25 +27,21 @@ Notifications.setNotificationHandler({
},
});
const App = () => {
useEffect(() => {
LottieSplashScreen.hide();
createDatabase();
}, []);
createTables();
LottieSplashScreen.hide();
const App = () => {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<AppErrorBoundary>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
<SafeAreaProvider>
<PaperProvider>
<BottomSheetModalProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</BottomSheetModalProvider>
</PaperProvider>
</SafeAreaProvider>
</AppErrorBoundary>
</GestureHandlerRootView>
);
+44 -1
View File
@@ -19,7 +19,7 @@ git clone https://github.com/<your-account-name>/lnreader.git
cd lnreader
# install dependencies
npm install --legacy-peer-deps
npm install
# build the apk (the built apk will be found in ~/lnreader/android/app/build/outputs/apk/release/)
npm run buildRelease
@@ -57,3 +57,46 @@ codebase, however you can always check to see if the source code is compliant by
```bash
npm run lint
```
# Database
```mermaid
erDiagram
Category ||--|{ NovelCategory : contains
Category{
INTEGER id PK
TEXT name
INTEGER sort
}
NovelCategory{
INTEGER id PK
INTEGER novelId FK
INTEGER categoryId FK "default: whose sort = 1"
}
Novel ||--|{ NovelCategory : contains
Novel{
INTEGER id PK
TEXT url
TEXT pluginId
TEXT name
TEXT cover
TEXT summary
TEXT author
TEXT artist
TEXT status
TEXT genres
INTEGER inLibrary "default: 0"
}
Novel ||--o| Chapter : has
Chapter{
INTEGER id PK
INTEGER novelId FK
text url
TEXT name
TEXT releaseTime
INTEGER bookmark "default: 0"
INTEGER unread "default: 1"
TEXT readTime
INTEGER isDownloaded "default: 0"
TEXT updatedTime
TEXT chapterNumber
}
```
+86 -166
View File
@@ -1,157 +1,92 @@
apply plugin: "com.android.application"
import com.android.build.OutputFile
apply plugin: "com.facebook.react"
/**
* 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: []
* ]
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
// reactNativeDir = file("../node_modules/react-native")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
project.ext.react = [
enableHermes: true
]
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/index.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* 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.
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore.
* The preferred build flavor of JavaScriptCore (JSC).
*
* 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
* 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);
ext {
versionMajor = 1
versionMinor = 1
versionPatch = 14
versionMajor = 2
versionMinor = 0
versionPatch = 0
}
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
namespace "com.rajarsheechatterjee.LNReader"
defaultConfig {
applicationId 'com.rajarsheechatterjee.LNReader'
applicationId project.hasProperty('customAppId') ? project.getProperty('customAppId') : '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"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
@@ -172,58 +107,43 @@ android {
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 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
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'
}
addUnimodulesDependencies()
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation 'com.facebook.soloader:soloader:0.10.5+'
implementation 'androidx.core:core-ktx:1.9.0'
if (enableHermes) {
def hermesPath = "../../node_modules/hermes-engine/android/";
debugImplementation files(hermesPath + "hermes-debug.aar")
releaseImplementation files(hermesPath + "hermes-release.aar")
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'
}
}
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} 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.compile
into 'libs'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
apply plugin: 'org.jetbrains.kotlin.android'; applyNativeModulesAppBuildGradle(project)
-19
View File
@@ -1,19 +0,0 @@
"""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,
)
+47
View File
@@ -0,0 +1,47 @@
{
"project_info": {
"project_number": "523872485654",
"project_id": "lnreader-backup",
"storage_bucket": "lnreader-backup.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:523872485654:android:169cd062458db962ce1037",
"android_client_info": {
"package_name": "com.rajarsheechatterjee.LNReader"
}
},
"oauth_client": [
{
"client_id": "523872485654-14jtut8orr7dbrk2chea279d7k8889sr.apps.googleusercontent.com",
"client_type": 1,
"android_info": {
"package_name": "com.rajarsheechatterjee.LNReader",
"certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625"
}
},
{
"client_id": "523872485654-liarmq8nl0g5an2cki3bpg9jc0d8a21j.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyBH_j-0Jyo9aFJ_KV8Nr3te2hs_L_ZYhrE"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "523872485654-liarmq8nl0g5an2cki3bpg9jc0d8a21j.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}
-8
View File
@@ -12,11 +12,3 @@
-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 *;
}
+6 -1
View File
@@ -3,5 +3,10 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" />
<application
android:usesCleartextTraffic="true"
tools:targetApi="28"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
</application>
</manifest>
@@ -1,69 +0,0 @@
/**
* 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.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 ReactInstanceManager.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());
}
}
}
}
+4 -11
View File
@@ -1,5 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET" />
@@ -15,13 +14,7 @@
<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" />
@@ -33,6 +26,7 @@
android:largeHeap="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
@@ -47,10 +41,10 @@
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
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">
@@ -64,7 +58,6 @@
<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>
+440
View File
@@ -0,0 +1,440 @@
html {
scroll-behavior: smooth;
overflow-x: hidden;
padding-top: var(--StatusBar-currentHeight);
word-wrap: break-word;
}
::selection {
color: var(--theme-onSecondary);
background-color: var(--theme-secondary);
}
::-moz-selection {
color: var(--theme-onSecondary);
background-color: var(--theme-secondary);
}
body {
padding-left: var(--readerSettings-padding);
padding-right: var(--readerSettings-padding);
padding-bottom: 40px;
font-size: var(--readerSettings-textSize);
background-color: var(--readerSettings-theme);
color: var(--readerSettings-textColor);
text-align: var(--readerSettings-textAlign);
line-height: var(--readerSettings-lineHeight);
font-family: var(--readerSettings-fontFamily);
}
chapter {
display: block;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
}
a {
color: var(--theme-primary);
}
img {
display: block;
width: auto;
height: auto;
max-width: 100%;
}
table {
background-color: var(--theme-onPrimary);
border-collapse: collapse;
color: var(--theme-primary);
}
th {
font-weight: bold;
}
td {
padding: 10px;
text-align: center;
}
table,
th,
td {
border: 1px solid var(--theme-outline);
}
.hidden {
visibility: hidden;
}
.nextButton,
.infoText {
width: 100%;
border-radius: 50px;
border-width: 1;
color: var(--theme-onPrimary);
background-color: var(--theme-primary);
font-family: var(--readerSettings-fontFamily);
font-size: 16px;
border-width: 0;
user-select: none;
}
.nextButton {
min-height: 40px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
padding: 0 16px;
}
.infoText {
background-color: transparent;
text-align: center;
border: none;
margin: 0px;
color: inherit;
padding-top: 16px;
padding-bottom: 16px;
}
.chapterCtn {
min-height: var(--chapterCtn-height);
margin-bottom: auto;
}
.d-none {
display: none;
}
#ToolWrapper {
position: fixed;
top: 50%;
transform: translateY(-50%);
right: 5vw;
}
#ToolWrapper.horizontal {
top: unset;
right: unset;
left: 50%;
transform: translateX(-50%);
bottom: 120px;
display: flex;
flex-direction: row-reverse;
align-items: center;
}
@media only screen and (min-width: 500px) {
#ToolWrapper.horizontal {
bottom: 80px;
}
}
#ToolWrapper.hidden {
opacity: 0;
right: 0;
}
#ToolWrapper.hidden.horizontal {
opacity: 0;
bottom: 0;
right: unset;
}
#TTS-Controller {
display: block;
}
#ToolWrapper.horizontal > #TTS-Controller {
margin-left: 4px;
}
#TTS-Controller button {
background: var(--theme-surface-0-9);
border: 0;
}
#TTS-Controller svg {
fill: var(--theme-onSurface);
}
#ToolWrapper > #ScrollBar {
margin-top: 8px;
width: 2.4rem;
height: 45vh;
min-height: 200px;
border-radius: 1.2rem;
background-color: var(--theme-surface-0-9);
touch-action: none;
font-size: 16px;
user-select: none;
}
#ScrollBar > .scrollbar-item {
display: flex;
justify-content: center;
align-items: center;
color: var(--theme-onSurface);
}
#ScrollBar > .scrollbar-item.scrollbar-text {
height: 12.5%;
}
#ScrollBar > .scrollbar-item#scrollbar-slider {
height: 75%;
align-items: unset;
}
#ScrollBar #scrollbar-track {
width: 0.1rem;
height: 100%;
background-color: var(--theme-outline);
}
#ScrollBar #scrollbar-progress {
width: 100%;
height: 0;
background-color: var(--theme-primary);
}
#ScrollBar #scrollbar-thumb-wrapper {
position: relative;
top: 100%;
height: 2rem;
width: 2rem;
display: flex;
align-items: center;
justify-content: center;
transform: translate(-0.95rem, -1rem);
}
#ScrollBar #scrollbar-thumb {
top: 100%;
height: 1rem;
width: 1rem;
border-radius: 100%;
background-color: var(--theme-primary);
}
#ToolWrapper.horizontal > #ScrollBar {
display: flex;
width: 80vw;
height: 2.4rem;
min-height: unset;
align-items: center;
margin-top: unset;
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item {
display: flex;
justify-content: center;
color: var(--theme-onSurface);
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item.scrollbar-text {
width: 12.5%;
}
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item#scrollbar-slider {
width: 75%;
align-items: center;
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-track {
height: 0.1rem;
width: 100%;
background-color: var(--theme-outline);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-progress {
height: 100%;
width: 0;
background-color: var(--theme-primary);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb-wrapper {
position: relative;
left: 100%;
height: 2rem;
width: 2rem;
display: flex;
align-items: center;
justify-content: center;
transform: translate(-0.95rem, -1rem);
}
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb {
top: 100%;
height: 1rem;
width: 1rem;
border-radius: 100%;
background-color: var(--theme-primary);
}
#reader-footer-wrapper {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
}
#reader-footer {
display: flex;
flex: 3;
padding: 0.2rem 2rem;
background-color: var(--readerSettings-theme);
color: var(--readerSettings-textColor);
font-size: 1rem;
text-align: center;
user-select: none;
}
.reader-footer-item {
flex: 1;
}
.reader-footer-item:first-child {
text-align: left;
}
.reader-footer-item:last-child {
text-align: right;
}
.highlight {
color: var(--theme-onSecondary);
background-color: var(--theme-secondary);
}
.contextMenu {
position: absolute;
height: 0;
overflow: hidden;
background: var(--theme-surface-0-9);
-webkit-backdrop-filter: blur(1px);
backdrop-filter: blur(1px);
position: fixed;
top: var(--top);
left: var(--left);
-webkit-animation: menuAnimation 0.4s 0s both;
animation: menuAnimation 0.4s 0s both;
transform-origin: left;
list-style: none;
margin: 4px;
padding: 0;
display: flex;
flex-direction: column;
z-index: 999999999;
}
.contextMenu-item {
padding: 4px;
}
.contextMenu-button {
color: var(--theme-onSurface);
background: 0;
border: 0;
white-space: nowrap;
width: 100%;
border-radius: 4px;
padding: 6px 24px 6px 7px;
text-align: left;
display: flex;
align-items: center;
font-size: 14px;
width: 100%;
-webkit-animation: menuItemAnimation 0.2s 0s both;
animation: menuItemAnimation 0.2s 0s both;
font-family: var(--readerSettings-fontFamily);
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: var(--theme-rippleColor);
}
.contextMenu-button svg {
fill: var(--theme-onSurface);
}
.contextMenu-button span {
margin-left: 4px;
}
@-webkit-keyframes menuAnimation {
0% {
opacity: 0;
transform: scale(0.5);
}
100% {
height: var(--height);
opacity: 1;
border-radius: 8px;
transform: scale(1);
}
}
@keyframes menuAnimation {
0% {
opacity: 0;
transform: scale(0.5);
}
100% {
height: var(--height);
opacity: 1;
border-radius: 8px;
transform: scale(1);
}
}
@-webkit-keyframes menuItemAnimation {
0% {
opacity: 0;
transform: translateX(-10px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
@keyframes menuItemAnimation {
0% {
opacity: 0;
transform: translateX(-10px);
}
100% {
opacity: 1;
transform: translateX(0);
}
}
#Image-Modal {
position: fixed;
left: 0;
top: 0;
user-select: none;
}
#Image-Modal.show {
display: flex;
height: 100%;
width: 100%;
background-color: var(--theme-surface-0-9);
align-items: center;
justify-content: center;
overflow: scroll;
}
#Image img {
display: block;
width: 100%;
}
File diff suppressed because one or more lines are too long
+643
View File
@@ -0,0 +1,643 @@
/**
* https://fonts.google.com/icons
* Filled
* other settings are default
*/
const copyIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M360-240q-33 0-56.5-23.5T280-320v-480q0-33 23.5-56.5T360-880h360q33 0 56.5 23.5T800-800v480q0 33-23.5 56.5T720-240H360ZM200-80q-33 0-56.5-23.5T120-160v-560h80v560h440v80H200Z"/></svg>';
const volumeIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320Z"/></svg>';
const selectAllIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M280-280v-400h400v400H280Zm80-80h240v-240H360v240ZM200-200v80q-33 0-56.5-23.5T120-200h80Zm-80-80v-80h80v80h-80Zm0-160v-80h80v80h-80Zm0-160v-80h80v80h-80Zm80-160h-80q0-33 23.5-56.5T200-840v80Zm80 640v-80h80v80h-80Zm0-640v-80h80v80h-80Zm160 640v-80h80v80h-80Zm0-640v-80h80v80h-80Zm160 640v-80h80v80h-80Zm0-640v-80h80v80h-80Zm160 640v-80h80q0 33-23.5 56.5T760-120Zm0-160v-80h80v80h-80Zm0-160v-80h80v80h-80Zm0-160v-80h80v80h-80Zm0-160v-80q33 0 56.5 23.5T840-760h-80Z"/></svg>';
const translateIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="m476-80 182-480h84L924-80h-84l-43-122H603L560-80h-84ZM160-200l-56-56 202-202q-35-35-63.5-80T190-640h84q20 39 40 68t48 58q33-33 68.5-92.5T484-720H40v-80h280v-80h80v80h280v80H564q-21 72-63 148t-83 116l96 98-30 82-122-125-202 201Zm468-72h144l-72-204-72 204Z"/></svg>';
const resumeIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M240-240v-480h80v480h-80Zm160 0 400-240-400-240v480Z"/></svg>';
const pauseIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M560-200v-560h160v560H560Zm-320 0v-560h160v560H240Z"/></svg>';
const stopIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M320-320h320v-320H320v320ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>';
const selectVolumeIcon =
'<svg height="24" viewBox="0 -960 960 960" width="24"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131Zm-80-29L280-360H120v-240h160l200-200v640Zm80-160v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320ZM40-680v-240h240v80H120v160H40ZM680-40v-80h160v-160h80v240H680Z"/></svg>';
/**
* @type {import("./type").Reader}
*/
class Reader {
constructor() {
this.selection = window.getSelection();
this.viewport = document.querySelector('meta[name=viewport]');
this.footerWrapper = document.getElementById('reader-footer-wrapper');
this.percentage = document.getElementById('reader-percentage');
this.battery = document.getElementById('reader-battery');
this.time = document.getElementById('reader-time');
this.paddingTop = parseInt(
getComputedStyle(document.querySelector('html')).getPropertyValue(
'padding-top',
),
);
this.chapter = document.querySelector('chapter');
this.rawHTML = this.chapter.innerHTML;
this.chapterHeight = this.chapter.scrollHeight + this.paddingTop;
this.layoutHeight = window.innerHeight;
this.pluginId = this.chapter.getAttribute('data-plugin-id');
this.novelId = this.chapter.getAttribute('data-novel-id');
this.chapterId = this.chapter.getAttribute('data-chapter-id');
this.saveProgressInterval = setInterval(
() =>
this.post({
type: 'save',
data: parseInt(
((window.scrollY + this.layoutHeight) / this.chapterHeight) * 100,
),
}),
autoSaveInterval,
);
this.time.innerText = new Date().toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
this.timeInterval = setInterval(() => {
this.time.innerText = new Date().toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
hour12: false,
});
}, 60000);
this.updateBatteryLevel(batteryLevel);
this.updateGeneralSettings(initSettings);
}
refresh = () => {
this.chapterHeight = this.chapter.scrollHeight + this.paddingTop;
};
post = obj => window.ReactNativeWebView.postMessage(JSON.stringify(obj));
updateReaderSettings = settings => {
document.documentElement.style.setProperty(
'--readerSettings-theme',
settings.theme,
);
document.documentElement.style.setProperty(
'--readerSettings-padding',
settings.padding + '%',
);
document.documentElement.style.setProperty(
'--readerSettings-textSize',
settings.textSize + 'px',
);
document.documentElement.style.setProperty(
'--readerSettings-textColor',
settings.textColor,
);
document.documentElement.style.setProperty(
'--readerSettings-textAlign',
settings.textAlign,
);
document.documentElement.style.setProperty(
'--readerSettings-lineHeight',
settings.lineHeight,
);
document.documentElement.style.setProperty(
'--readerSettings-fontFamily',
settings.fontFamily,
);
if (settings.fontFamily) {
new FontFace(
settings.fontFamily,
'url("file:///android_asset/fonts/' + settings.fontFamily + '.ttf")',
)
.load()
.then(function (loadedFont) {
document.fonts.add(loadedFont);
});
} else {
// have no affect with a font declared in head
document.fonts.forEach(fontFace => document.fonts.delete(fontFace));
}
};
updateGeneralSettings = settings => {
this.showScrollPercentage = settings.showScrollPercentage;
this.showBatteryAndTime = settings.showBatteryAndTime;
this.verticalSeekbar = settings.verticalSeekbar;
this.bionicReading = settings.bionicReading;
if (this.bionicReading) {
this.chapter.innerHTML = textVide.textVide(this.rawHTML);
} else {
this.chapter.innerHTML = this.rawHTML;
}
if (settings.swipeGestures) {
swipeHandler.enable();
} else {
swipeHandler.disable();
}
if (!this.showScrollPercentage && !this.showBatteryAndTime) {
this.footerWrapper.classList.add('d-none');
} else {
this.footerWrapper.classList.remove('d-none');
if (this.showScrollPercentage) {
this.percentage.classList.remove('hidden');
} else {
this.percentage.classList.add('hidden');
}
if (this.showBatteryAndTime) {
this.battery.classList.remove('hidden');
this.time.classList.remove('hidden');
} else {
this.battery.classList.add('hidden');
this.time.classList.add('hidden');
}
}
if (!this.verticalSeekbar) {
toolWrapper.$.classList.add('horizontal');
} else {
toolWrapper.$.classList.remove('horizontal');
}
if (scrollHandler) {
scrollHandler.refresh();
scrollHandler.update();
}
};
updateBatteryLevel = level => {
this.battery.innerText = Math.ceil(level * 100) + '%';
};
}
class ToolWrapper {
constructor() {
this.$ = document.getElementById('ToolWrapper');
this.$.onclick = e => {
e.stopPropagation();
};
this.tools = [];
}
hide = () => {
this.$.classList.add('hidden');
this.visible = false;
};
show = () => {
this.$.classList.remove('hidden');
this.visible = true;
for (const tool of this.tools) {
tool.onShow?.();
}
};
}
class ScrollHandler {
constructor(reader, toolWrapper) {
this.reader = reader;
this.toolWrapper = toolWrapper;
this.$ = document.getElementById('ScrollBar');
this.$.innerHTML = `
<div class="scrollbar-item scrollbar-text" id="scrollbar-percentage">
0
</div>
<div class="scrollbar-item" id="scrollbar-slider">
<div id="scrollbar-track">
<div id="scrollbar-progress">
<div id="scrollbar-thumb-wrapper">
<div id="scrollbar-thumb"></div>
</div>
</div>
</div>
</div>
<div class="scrollbar-item scrollbar-text">
100
</div>
`;
this.percentage = this.$.querySelector('#scrollbar-percentage');
this.progress = this.$.querySelector('#scrollbar-progress');
this.thumb = this.$.querySelector('#scrollbar-thumb-wrapper');
this.slider = this.$.querySelector('#scrollbar-slider');
this.sliderHeight = this.slider.clientHeight;
this.sliderOffsetY = this.slider.getBoundingClientRect().top;
this.lock = false;
window.onscroll = () => !this.lock && this.update();
this.thumb.ontouchstart = () => (this.lock = true);
this.thumb.ontouchend = () => (this.lock = false);
this.thumb.ontouchmove = e => {
const ratio =
((this.reader.verticalSeekbar
? e.changedTouches[0].clientY
: e.changedTouches[0].clientX) -
this.sliderOffsetY) /
this.sliderHeight;
this.update(ratio < 0 ? 0 : ratio);
};
}
update = ratio => {
if (ratio === undefined) {
ratio =
(window.scrollY + this.reader.layoutHeight) / this.reader.chapterHeight;
}
if (ratio > 1) {
ratio = 1;
}
const percentage = parseInt(ratio * 100);
if (this.toolWrapper.visible) {
if (this.reader.verticalSeekbar) {
this.progress.style.height = percentage + '%';
this.progress.style.width = '100%';
} else {
this.progress.style.height = '100%';
this.progress.style.width = percentage + '%';
}
this.percentage.innerText = percentage;
}
if (this.lock) {
window.scrollTo({
top: this.reader.chapterHeight * ratio - this.reader.layoutHeight,
behavior: 'instant',
});
}
if (this.reader.percentage) {
this.reader.percentage.innerText = percentage + '%';
}
};
refresh = () => {
if (this.reader.verticalSeekbar) {
this.sliderHeight = this.slider.clientHeight;
this.sliderOffsetY = this.slider.getBoundingClientRect().top;
} else {
this.sliderHeight = this.slider.clientWidth;
this.sliderOffsetY = this.slider.getBoundingClientRect().left;
}
};
onShow = () => {
this.reader.refresh();
this.refresh();
this.update();
};
}
class SwipeHandler {
constructor() {
this.initialX = null;
this.initialY = null;
}
touchStartHandler = e => {
this.initialX = e.changedTouches[0].screenX;
this.initialY = e.changedTouches[0].screenY;
};
/**
* @param {TouchEvent} e
*/
touchEndHandler = e => {
let diffX = e.changedTouches[0].screenX - this.initialX;
let diffY = e.changedTouches[0].screenY - this.initialY;
if (
e.target.id?.startsWith('scrollbar') ||
e.target.id === 'Image-Modal-img'
) {
return;
}
if (Math.abs(diffX) > Math.abs(diffY) * 2 && Math.abs(diffX) > 180) {
if (diffX < 0 && this.initialX >= window.innerWidth / 2) {
e.preventDefault();
reader.post({ type: 'next' });
} else if (diffX > 0 && this.initialX <= window.innerWidth / 2) {
e.preventDefault();
reader.post({ type: 'prev' });
}
}
};
enable = () => {
document.addEventListener('touchstart', this.touchStartHandler);
document.addEventListener('touchend', this.touchEndHandler);
};
disable = () => {
document.removeEventListener('touchstart', this.touchStartHandler);
document.removeEventListener('touchend', this.touchEndHandler);
};
}
/**
* @type {import('./type').TextToSpeech}
*/
// Walk through all leave -> convert each leaf to list of tts elements -> read through all tts elements
class TextToSpeech {
constructor(reader) {
this.reader = reader;
this.readableNodeNames = ['#text', 'B', 'I', 'SPAN', 'EM', 'BR', 'STRONG'];
this.leaf = this.reader.chapter;
this.reading = false;
this.started = false;
this.$ = document.getElementById('TTS-Controller');
this.$.classList.add('d-none');
this.$.onclick = () => {
if (this.reading) {
this.pause();
} else {
this.resume();
}
};
}
start = () => {
this.started = false;
this.$.innerHTML = `<button>${pauseIcon}</button>`;
this.$.classList.remove('d-none');
this.stop();
this.leaf = this.reader.chapter;
this.next();
};
startHere = () => {
this.$.innerHTML = `<button>${pauseIcon}</button>`;
this.$.classList.remove('d-none');
this.stop();
if (this.reader.selection.type === 'Range') {
this.leaf = this.reader.selection.anchorNode;
while (this.readableNodeNames.includes(this.leaf.nodeName)) {
this.leaf = this.leaf.parentNode;
}
this.reader.selection.removeAllRanges();
this.speak();
} else {
this.next();
}
};
resume = () => {
this.$.innerHTML = `<button>${pauseIcon}</button>`;
this.next();
};
readable() {
if (
this.leaf.nodeName !== 'SPAN' &&
this.readableNodeNames.includes(this.leaf.nodeName)
) {
return false;
}
if (!this.leaf.hasChildNodes()) {
return false;
}
const a = [];
for (let i = 0; i < this.leaf.childNodes.length; i++) {
if (
!this.readableNodeNames.includes(this.leaf.childNodes.item(i).nodeName)
) {
return false;
}
}
return true;
}
findNextTextNode() {
do {
if (this.reader.chapter.isSameNode(this.leaf)) {
this.leaf = this.leaf.firstElementChild;
} else {
if (this.leaf.firstElementChild) {
this.leaf = this.leaf.firstElementChild;
} else if (this.leaf.nextElementSibling) {
this.leaf = this.leaf.nextElementSibling;
} else {
this.leaf = this.leaf.parentElement;
}
}
} while (!this.readable());
if (this.reader.chapter.isSameNode(this.leaf)) {
return;
}
}
next = () => {
try {
this.leaf?.classList?.remove('highlight');
if (this.reading) {
if (this.leaf.nextElementSibling) {
this.leaf = this.leaf.nextElementSibling;
} else {
this.findNextTextNode();
}
} else if (!this.started) {
this.findNextTextNode();
this.started = true;
}
this.speak();
} catch (e) {
alert(e);
}
};
stop = () => {
this.reader.post({ type: 'stop-speak' });
this.$.classList.add('d-none');
this.leaf?.classList?.remove('highlight');
this.reading = false;
};
pause = () => {
this.$.innerHTML = `<button>${resumeIcon}</button>`;
this.reading = false;
this.reader.post({ type: 'stop-speak' });
};
speak = () => {
this.reading = true;
if (this.reader.chapter.isSameNode(this.leaf)) {
return;
}
this.leaf.classList.add('highlight');
this.reader.post({ type: 'speak', data: this.leaf?.innerText });
};
}
/**
* @type {import("./type").ContextMenu}
*/
class ContextMenu {
/**
* @param {import("./type").Reader} reader
*/
constructor(reader) {
this.reader = reader;
this.items = {
START_READING: this.renderItem({
name: 'Start Reading',
icon: volumeIcon,
action: () => {
this.closeMenu();
tts.start();
},
}),
START_HERE: this.renderItem({
name: 'Start Here',
icon: selectVolumeIcon,
action: () => {
this.closeMenu();
tts.startHere();
},
}),
COPY: this.renderItem({
name: 'Copy',
icon: copyIcon,
action: () => {
this.closeMenu();
this.reader.post({
type: 'copy',
data: this.reader.selection.toString(),
});
},
}),
SELECT_ALL: this.renderItem({
name: 'Select All',
icon: selectAllIcon,
action: () => {
const range = document.createRange();
range.selectNodeContents(this.reader.chapter);
this.reader.selection.removeAllRanges();
this.reader.selection.addRange(range);
},
}),
};
this.commonItems = [this.items.COPY, this.items.SELECT_ALL];
this.contextMenu = document.createElement('ul');
this.contextMenu.classList.add('contextMenu');
this.isOpened = false;
}
/**
* @param {{name: string, icon: string, action: () => void}} data
* @returns
*/
renderItem(data) {
const item = document.createElement('li');
const button = document.createElement('button');
button.innerHTML = `${data.icon} <span>${data.name}</span>`;
button.classList.add('contextMenu-button');
item.classList.add('contextMenu-item');
item.appendChild(button);
button.addEventListener('click', event => {
event.stopPropagation();
data.action();
});
return item;
}
renderMenu(items) {
this.contextMenu.innerHTML = '';
items.concat(this.commonItems).forEach((item, index) => {
item.firstChild.setAttribute('style', `animation-delay: ${index * 0.1}s`);
this.contextMenu.appendChild(item);
});
}
closeMenu() {
if (this.isOpened) {
this.isOpened = false;
this.contextMenu.remove();
}
}
init() {
this.reader.chapter.addEventListener('click', () => {
this.closeMenu(this.contextMenu);
});
document.addEventListener('contextmenu', e => {
e.preventDefault();
if (e.target instanceof HTMLImageElement) {
if (imageModal.showing) {
imageModal.hide();
} else {
imageModal.show(e.target);
}
return;
}
this.isOpened = true;
if (this.reader.selection.type === 'Range') {
this.renderMenu([this.items.START_HERE, this.items.START_READING]);
} else {
this.renderMenu([this.items.START_READING]);
}
const { clientX, clientY } = e;
e.target.appendChild(this.contextMenu);
const positionY =
clientY + this.contextMenu.scrollHeight >= window.innerHeight
? window.innerHeight - this.contextMenu.scrollHeight - 20
: clientY;
const positionX =
clientX + this.contextMenu.scrollWidth >= window.innerWidth
? window.innerWidth - this.contextMenu.scrollWidth - 20
: clientX;
this.contextMenu.setAttribute(
'style',
`--width: ${this.contextMenu.scrollWidth}px;
--height: ${this.contextMenu.scrollHeight}px;
--top: ${positionY}px;
--left: ${positionX}px;`,
);
});
}
}
/**
* @type {import("./type").ImageModal}
*/
class ImageModal {
/**
* @param {import('./type').Reader} reader
*/
constructor(reader) {
this.reader = reader;
this.$ = document.getElementById('Image-Modal');
this.img = this.$.querySelector('img');
this.showing = false;
this.$.onclick = e => {
if (e.target.id !== 'Image-Modal-img') {
e.stopPropagation();
this.hide();
}
};
this.$.ontouchmove = e => {
e.stopImmediatePropagation();
};
}
/**
*
* @param {HTMLImageElement} image
*/
show(image) {
this.img.src = image.src;
this.img.alt = `Can not render image from ${image.src}`;
this.reader.viewport.setAttribute(
'content',
'width=device-width, initial-scale=1.0, maximum-scale=10',
);
this.$.classList.add('show');
this.showing = true;
}
hide() {
this.$.classList.remove('show');
this.showing = false;
this.img.src = '';
this.img.alt = '';
this.reader.viewport.setAttribute(
'content',
'width=device-width, initial-scale=1.0, maximum-scale=1.0',
);
}
}
try {
var swipeHandler = new SwipeHandler();
var toolWrapper = new ToolWrapper();
var reader = new Reader();
var scrollHandler = new ScrollHandler(reader, toolWrapper);
var tts = new TextToSpeech(reader);
toolWrapper.tools = [scrollHandler, tts];
imageModal = new ImageModal(reader);
const contextMenu = new ContextMenu(reader);
contextMenu.init();
} catch (e) {
alert(e);
}
@@ -0,0 +1 @@
var textVide = function(L){'use strict'; const f = t=>t == null || t === '',h = (t,n)=>Object.keys(t).reduce((e,s)=>(n(e[s]) && delete e[s],e),t),I = (t,n)=>({...n,...h(t,f)}),_ = ['<b>','</b>'],y = t=>I(t,{sep:_,fixationPoint:1,ignoreHtmlTag:!0,ignoreHtmlEntity:!0}),u = [[0,4,12,17,24,29,35,42,48],[1,2,7,10,13,14,19,22,25,28,31,34,37,40,43,46,49],[1,2,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49],[0,2,4,5,6,8,9,11,14,15,17,18,20,0,21,23,24,26,27,29,30,32,33,35,36,38,39,41,42,44,45,47,48],[0,2,3,5,6,7,8,10,11,12,14,15,17,19,20,21,23,24,25,26,28,29,30,32,33,34,35,37,38,39,41,42,43,44,46,47,48]],A = (t,n)=>{const {length:i} = t,e = u[n - 1] ?? u[0],s = e.findIndex(c=>i <= c); let o = i - s; return s === -1 && (o = i - e.length),Math.max(o,0);},R = (t,n)=>typeof n === 'string' ? `${n}${t}${n}` : `${n[0]}${t}${n[1]}`,m = t=>Array.from(t).map(n=>{const i = n.index,[e] = n,{length:s} = e; return [i,i + s - 1];}),H = /(<!--[\s\S]*?-->)|(<[^>]*>)/g,N = t=>{const n = t.matchAll(H),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},O = /(&[\w#]+;)/g,M = t=>{const n = t.matchAll(O),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},x = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu,F = (t,n = {})=>{if (!(t != null && t.length)) {return '';} const {fixationPoint:i,sep:e,ignoreHtmlTag:s,ignoreHtmlEntity:o} = y(n),c = Array.from(t.matchAll(x)); let a = '',l = 0,r; s && (r = N(t)); let d; o && (d = M(t)); for (const g of c){if ((r == null ? void 0 : r(g)) || (d == null ? void 0 : d(g))) {continue;} const [D] = g,T = g.index,E = T + A(D,i),U = t.slice(l,T); a += U,T !== E && (a += R(t.slice(T,E),e)),l = E;} const G = t.slice(l); return a + G;}; return L.textVide = F,Object.defineProperty(L,Symbol.toStringTag,{value:'Module'}),L;}({});
+69
View File
@@ -0,0 +1,69 @@
import {
ChapterGeneralSettings,
ChapterReaderSettings,
} from '@hooks/persisted/useSettings';
import 'typescript';
import 'typescript/lib/lib.dom';
interface Reader {
selection: Selection;
viewport: HTMLMetaElement;
footerWrapper: HTMLDivElement;
percentage: HTMLDivElement;
battery: HTMLDivElement;
time: HTMLDivElement;
paddingTop: number;
chapter: HTMLElement;
chapterHeight: number;
layoutHeight: number;
pluginId: string;
novelId: string;
chapterId: string;
saveProgressInterval: NodeJS.Timeout;
timeInterval: NodeJS.Timeout;
refresh: () => void;
post: (message: { type: string; data?: any }) => void;
updateReaderSettings: (settings: ChapterReaderSettings) => void;
updateGeneralSettings: (settings: ChapterGeneralSettings) => void;
updateBatteryLevel: (level: number) => void;
}
interface TextToSpeech {
reader: Reader;
leaf: HTMLUnknownElement;
reading: boolean;
readableTags: string[];
start: () => void;
startHere: () => void;
resume: () => void;
pause: () => void;
stop: () => void;
started: () => boolean;
}
export enum ContextMenuItem {
START_READING = 'START_READING',
START_HERE = 'START_HERE',
COPY = 'COPY',
SELECT_ALL = 'SELECT_ALL',
}
interface ContextMenu {
reader: Reader;
contextMenu: HTMLUListElement;
items: Record<ContextMenuItem, HTMLLIElement>;
renderItem: (data: {
name: string;
icon: string;
action: () => void;
}) => HTMLLIElement;
renderMenu: (items: HTMLLIElement[]) => void;
closeMenu: (menu: HTMLUListElement) => void;
init: () => void;
}
interface ImageModal {
reader: Reader;
showing: boolean;
show: (image: HTMLImageElement) => void;
hide: () => void;
}
@@ -0,0 +1,3 @@
package com.rajarsheechatterjee.EpubUtil
class ChapterEntry (val name: String, val href: String)
@@ -0,0 +1,204 @@
package com.rajarsheechatterjee.EpubUtil
import android.util.Xml
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.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.FileInputStream
class EpubUtil(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
override fun getName(): String {
return "EpubUtil"
}
private fun initParse(file: File): XmlPullParser {
val parser = Xml.newPullParser()
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
parser.setInput(FileInputStream(file), null)
parser.nextTag()
return parser
}
private fun readText(parser: XmlPullParser): String {
var result = ""
if (parser.next() == XmlPullParser.TEXT) {
result = parser.text
parser.nextTag()
}
return result
}
private fun cleanUrl(url: String): String {
return url.replaceFirst("#[^.]+?$".toRegex(), "")
}
@ReactMethod
fun parseNovelAndChapters(epubDirPath: String, promise: Promise) {
try {
val containerFile = File(epubDirPath, "META-INF/container.xml")
val contentMetaFile = File(epubDirPath, getContentMetaFilePath(containerFile))
val contentDir = contentMetaFile.parent
val novel = contentDir?.let { getNovelMetadata(contentMetaFile, it) }
promise.resolve(novel)
} catch (e: Exception) {
promise.reject(e)
}
}
private fun getContentMetaFilePath(file: File): String {
val parser = initParse(file)
while (parser.next() != XmlPullParser.END_TAG) {
val tag = parser.name
if (tag != null && tag == "rootfile") {
return parser.getAttributeValue(null, "full-path")
}
}
return "OEBPS/content.opf" // default
}
private fun mergeChapters(
entryList: List<ChapterEntry>,
fileList: List<String>,
contentDir: String
): ReadableArray {
val foundIndexes = entryList.mapIndexed { index, entry ->
val foundIndex = fileList.indexOf(entry.href)
if (foundIndex < index) -1
else foundIndex
}
val linkedIndex = foundIndexes.mapIndexed { arrayIndex, foundIndex ->
if (foundIndex == -1) Pair(-1, -1)
else {
var nextIndex = -1
for (j in arrayIndex + 1 until foundIndexes.size) {
if (foundIndexes[j] != -1) {
nextIndex = foundIndexes[j]
break
}
}
if (nextIndex == -1) nextIndex = fileList.size
Pair(foundIndex, nextIndex)
}
}
val chapters: WritableArray = WritableNativeArray()
linkedIndex.forEachIndexed { arrayIndex, (firstIndex, lastIndex) ->
val chapter = WritableNativeMap()
val entryPath = "${contentDir}/${entryList[arrayIndex].href}"
chapter.putString("name", entryList[arrayIndex].name)
if (firstIndex == -1 || firstIndex == lastIndex) {
chapter.putString("path", entryPath)
} else {
val mergedChapterPath = "${entryPath}-copy-$arrayIndex.html"
chapter.putString("path", mergedChapterPath)
val entryFile = File(entryPath)
val mergedFile = File(mergedChapterPath)
mergedFile.createNewFile()
mergedFile.appendBytes(entryFile.readBytes())
for (i in firstIndex + 1 until lastIndex) {
val file = File("${contentDir}/${fileList[i]}")
mergedFile.appendBytes(file.readBytes())
}
}
chapters.pushMap(chapter)
}
return chapters
}
private fun getNovelMetadata(file: File, contentDir: String): ReadableMap {
val novel: WritableMap = WritableNativeMap()
val parser = initParse(file)
val refMap = HashMap<String, String>()
val entryList = mutableListOf<ChapterEntry>()
val fileList = mutableListOf<String>()
val tocFile = File(contentDir, "toc.ncx")
if (tocFile.exists()) {
val tocParser = initParse(tocFile)
var label = ""
while(tocParser.next() != XmlPullParser.END_DOCUMENT){
if(tocParser.name == "navMap") break;
}
while (tocParser.next() != XmlPullParser.END_DOCUMENT) {
val tag = tocParser.name
if(tag == "navMap") break;
if (tag != null) {
if (tag == "text") {
label = readText(tocParser)
} else if (tag == "content") {
val href = cleanUrl(tocParser.getAttributeValue(null, "src"))
if (label.isNotBlank() && File("${contentDir}/$href").exists()) {
entryList.add(ChapterEntry(name = label, href = href))
}
label = ""
}
}
}
} else {
throw Error("Table of content doesn't exist!")
}
var cover: String? = null
while (parser.next() != XmlPullParser.END_DOCUMENT) {
val tag = parser.name
if (tag != null) {
when (tag) {
"item" -> {
val id = parser.getAttributeValue(null, "id")
val href = parser.getAttributeValue(null, "href")
if (id != null) {
refMap[id] = href
}
}
"itemref" -> {
val idRef = parser.getAttributeValue(null, "idref")
val href = refMap[idRef]
if (href != null && File("$contentDir/$href").exists()) {
fileList.add(href)
}
}
"title" -> novel.putString("name", readText(parser))
"creator" -> novel.putString("author", readText(parser))
"contributor" -> novel.putString("artist", readText(parser))
"description" -> novel.putString("summary", readText(parser))
"meta" -> {
val metaName = parser.getAttributeValue(null, "name")
if (metaName != null && metaName == "cover") {
cover = parser.getAttributeValue(null, "content")
}
}
}
parser.next()
}
}
val chapters = mergeChapters(entryList, fileList, contentDir)
novel.putArray("chapters", chapters)
if (cover != null) {
val coverPath = contentDir + "/" + refMap[cover]
novel.putString("cover", coverPath)
} else {
// try scanning Images dir if exists
val imageDir = File("$contentDir/Images")
if (imageDir.exists() && imageDir.isDirectory) {
imageDir.listFiles()?.forEach { img ->
if (img.isFile && img.name.lowercase().contains("cover|illus".toRegex())) {
cover = img.path
}
}
}
if (cover != null) {
novel.putString("cover", cover)
}
}
return novel
}
}
@@ -0,0 +1,17 @@
package com.rajarsheechatterjee.EpubUtil
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class EpubUtilPackage : ReactPackage {
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
listOf(EpubUtil(context)).toMutableList()
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
}
@@ -0,0 +1,344 @@
package com.rajarsheechatterjee.FileManager
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import com.facebook.react.bridge.BaseActivityEventListener
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.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.modules.network.CookieJarContainer
import com.facebook.react.modules.network.ForwardingCookieHandler
import com.facebook.react.modules.network.OkHttpClientProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Headers
import okhttp3.JavaNetCookieJar
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.io.BufferedReader
import java.io.File
import java.io.FileOutputStream
import java.io.FileReader
import java.io.FileWriter
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.io.PushbackInputStream
import java.util.zip.GZIPInputStream
class FileManager(context: ReactApplicationContext) :
ReactContextBaseJavaModule(context) {
override fun getName(): String {
return "FileManager"
}
private val BUFFER_SIZE = 4096
private val okHttpClient = OkHttpClientProvider.createClient()
private var _promise: Promise? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO)
private val activityEventListener = object : BaseActivityEventListener() {
override fun onActivityResult(
activity: Activity?,
requestCode: Int,
resultCode: Int,
intent: Intent?
) {
if (requestCode == FOLDER_PICKER_REQUEST) {
when (resultCode) {
Activity.RESULT_CANCELED -> resolveUri()
Activity.RESULT_OK -> {
if (intent != null) {
intent.data?.let { uri ->
val flags =
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
context.contentResolver.takePersistableUriPermission(uri, flags)
resolveUri(uri)
}
}
}
}
}
}
}
init {
context.addActivityEventListener(activityEventListener)
val cookieContainer = okHttpClient.cookieJar as CookieJarContainer
val cookieHandler = ForwardingCookieHandler(reactApplicationContext)
cookieContainer.setCookieJar(JavaNetCookieJar(cookieHandler))
}
private fun getFileUri(filepath: String): Uri {
var uri = Uri.parse(filepath)
if (uri.scheme == null) {
// No prefix, assuming that provided path is absolute path to file
val file = File(filepath)
if (file.isDirectory) {
throw Exception("Invalid file, folder found!")
}
uri = Uri.parse("file://$filepath")
}
return uri
}
private fun getInputStream(filepath: String): InputStream {
val uri = getFileUri(filepath)
return reactApplicationContext.contentResolver.openInputStream(uri)
?: throw Exception("ENOENT: could not open an input stream for '$filepath'")
}
private val writeAccessByAPILevel: String
get() = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) "w" else "rwt"
private fun getOutputStream(filepath: String): OutputStream {
val uri = getFileUri(filepath)
return reactApplicationContext.contentResolver.openOutputStream(uri, writeAccessByAPILevel)
?: throw Exception("ENOENT: could not open an output stream for '$filepath'")
}
@ReactMethod
fun writeFile(path: String, content: String, promise: Promise) {
try {
val fw = FileWriter(path)
fw.write(content)
fw.close()
promise.resolve(null)
} catch (e: Exception) {
promise.reject(e)
}
}
@ReactMethod(isBlockingSynchronousMethod = true)
fun readFile(path: String): String {
return File(path).bufferedReader().readText()
}
@ReactMethod
fun copyFile(filepath: String, destPath: String, promise: Promise) {
try {
copyFileContent(filepath, destPath, null, promise)
} catch (e: Exception) {
promise.reject(e)
}
}
@ReactMethod
fun moveFile(filepath: String, destPath: String, promise: Promise) {
try {
val inFile = File(filepath)
copyFileContent(filepath, destPath, { inFile.delete() }, promise)
} catch (e: Exception) {
promise.reject(e)
}
}
private fun copyFileContent(
filepath: String,
destPath: String,
onDone: (() -> Unit)? = null,
promise: Promise? = null
) {
coroutineScope.launch(Dispatchers.IO) {
val inputStream = getInputStream(filepath)
val outputStream = getOutputStream(destPath)
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } > 0) {
outputStream.write(buffer, 0, length)
Thread.yield()
}
inputStream.close()
outputStream.close()
if (onDone != null) {
onDone()
}
promise?.resolve(null)
}
}
@ReactMethod
fun exists(filepath: String, promise: Promise) {
try {
val file = File(filepath)
promise.resolve(file.exists())
} catch (e: Exception) {
promise.reject(e)
}
}
@ReactMethod
fun mkdir(filepath: String, promise: Promise) {
try {
val file = File(filepath)
if (!file.exists()) {
val created = file.mkdirs()
if (!created) throw Exception("Directory could not be created")
}
promise.resolve(null)
} catch (e: Exception) {
promise.reject(e)
}
}
private fun deleteRecursive(fileOrDirectory: File) {
if (fileOrDirectory.isDirectory) {
for (child in fileOrDirectory.listFiles()!!) {
deleteRecursive(child)
}
}
fileOrDirectory.delete()
}
@ReactMethod
fun unlink(filepath: String, promise: Promise) {
try {
val file = File(filepath)
if (!file.exists()) throw Exception("File does not exist")
deleteRecursive(file)
promise.resolve(null)
} catch (e: Exception) {
promise.reject(e)
}
}
@ReactMethod
fun readDir(directory: String, promise: Promise) {
try {
val file = File(directory)
if (!file.exists()) throw Exception("Folder does not exist")
val files = file.listFiles()
val fileMaps: WritableArray = WritableNativeArray()
for (childFile in files!!) {
val fileMap: WritableMap = WritableNativeMap()
fileMap.putString("name", childFile.name)
fileMap.putString("path", childFile.absolutePath)
fileMap.putBoolean("isDirectory", childFile.isDirectory)
fileMaps.pushMap(fileMap)
}
promise.resolve(fileMaps)
} catch (e: Exception) {
promise.reject(e)
}
}
override fun getConstants(): Map<String, Any> {
val constants: MutableMap<String, Any> = HashMap()
val externalDirectory = this.reactApplicationContext.getExternalFilesDir(null)
if (externalDirectory != null) {
constants["ExternalDirectoryPath"] = externalDirectory.absolutePath
}
val externalCachesDirectory = this.reactApplicationContext.externalCacheDir
if (externalCachesDirectory != null) {
constants["ExternalCachesDirectoryPath"] = externalCachesDirectory.absolutePath
}
return constants
}
private fun resolveUri(uri: Uri? = null) {
_promise?.let { promise ->
try {
if (uri == null) promise.resolve(null)
else {
val docId = DocumentsContract.getTreeDocumentId(uri)
val split =
docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
promise.resolve(
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
)
}
} catch (e: Exception) {
promise.reject(e)
} finally {
_promise = null
}
}
}
@ReactMethod
fun pickFolder(promise: Promise) {
if (_promise != null) promise.reject(Exception("Can not perform action"))
_promise = promise
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
currentActivity?.startActivityForResult(intent, FOLDER_PICKER_REQUEST)
}
private fun decompressStream(input: InputStream?): InputStream {
val pb = PushbackInputStream(input, 2)
val signature = ByteArray(2)
val len = pb.read(signature)
pb.unread(signature, 0, len)
return if (signature[0] == 0x1f.toByte() && signature[1] == 0x8b.toByte())
GZIPInputStream(pb) else pb
}
@ReactMethod
fun downloadFile(
url: String,
destPath: String,
method: String,
headers: ReadableMap,
body: String?,
promise: Promise
) {
coroutineScope.launch {
try {
val headersBuilder = Headers.Builder()
headers.entryIterator.forEach { entry ->
headersBuilder.add(entry.key, entry.value.toString())
}
val requestBuilder = Request.Builder()
.url(url)
.headers(headersBuilder.build())
if (method.lowercase() == "get") {
requestBuilder.get()
} else if (body != null) {
requestBuilder.post(body.toRequestBody())
}
okHttpClient.newCall(requestBuilder.build())
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
promise.reject(e)
}
override fun onResponse(call: Call, response: Response) {
if (!response.isSuccessful || response.body == null) {
promise.reject(Exception("Failed to download load: ${response.code}"))
return
}
try {
val inputStream = decompressStream(response.body!!.byteStream())
FileOutputStream(destPath).use { fos ->
inputStream.copyTo(fos, BUFFER_SIZE)
}
promise.resolve(null)
} catch (e: Exception) {
promise.reject(e)
}
}
})
} catch (e: Exception) {
promise.reject(e)
}
}
}
companion object {
const val FOLDER_PICKER_REQUEST = 1
}
}
@@ -0,0 +1,17 @@
package com.rajarsheechatterjee.FileManager
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class FileManagerPackage : ReactPackage {
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
listOf(FileManager(context)).toMutableList()
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
}
@@ -1,102 +0,0 @@
package com.rajarsheechatterjee.LNReader;
import android.content.Intent;
import android.content.res.Configuration;
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 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 {
// Added automatically by Expo Config
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
sendBroadcast(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's
// discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
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);
SplashScreen.show(this,R.style.SplashScreenTheme, R.id.lottie);
SplashScreen.setAnimationFinished(true);
super.onCreate(null);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (VolumeButtonListener.isActive()) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (action == KeyEvent.ACTION_DOWN) {
// volup
VolumeButtonListener.up();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
// voldown
VolumeButtonListener.down();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
default:
return super.dispatchKeyEvent(event);
}
}
return super.dispatchKeyEvent(event);
}
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "main";
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Override
protected ReactRootView createRootView() {
return new RNGestureHandlerEnabledRootView(MainActivity.this);
}
};
}
}
@@ -0,0 +1,80 @@
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.WindowManager
import com.facebook.react.ReactActivity
import com.facebook.react.ReactActivityDelegate
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener
import expo.modules.ReactActivityDelegateWrapper
import org.devio.rn.splashscreen.SplashScreen
class MainActivity : ReactActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val layoutParams = WindowManager.LayoutParams()
layoutParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
window.attributes = layoutParams
}
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
@Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
super.onCreate(null)
SplashScreen.show(this, R.style.SplashScreenTheme, R.id.lottie)
SplashScreen.setAnimationFinished(true)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (VolumeButtonListener.isActive) {
val action = event.action
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
if (action == KeyEvent.ACTION_DOWN) {
// volup
VolumeButtonListener.up()
if (!VolumeButtonListener.prevent) return super.dispatchKeyEvent(event)
}
true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (action == KeyEvent.ACTION_DOWN) {
// voldown
VolumeButtonListener.down()
if (!VolumeButtonListener.prevent) return super.dispatchKeyEvent(event)
}
true
}
else -> 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 fun getMainComponentName(): String {
return "main"
}
override fun createReactActivityDelegate(): ReactActivityDelegate {
return ReactActivityDelegateWrapper(
this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, DefaultReactActivityDelegate(
this,
mainComponentName, // If you opted-in for the New Architecture, we enable the Fabric Renderer.
fabricEnabled
)
)
}
}
@@ -1,100 +0,0 @@
package com.rajarsheechatterjee.LNReader;
import android.app.Application;
import android.content.Context;
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.LNReader.generated.BasePackageList;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
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;
public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList());
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
packages.add(new com.rajarsheechatterjee.LNReader.NavigationBarColorPackage());
packages.add(new VolumeButtonListenerPackage());
return packages;
}
@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());
}
/**
* 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();
}
}
}
}
@@ -0,0 +1,64 @@
package com.rajarsheechatterjee.LNReader
import android.app.Application
import android.content.res.Configuration
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import com.rajarsheechatterjee.EpubUtil.EpubUtilPackage
import com.rajarsheechatterjee.FileManager.FileManagerPackage
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage
import com.rajarsheechatterjee.ZipArchive.ZipArchivePackage
import expo.modules.ApplicationLifecycleDispatcher.onApplicationCreate
import expo.modules.ApplicationLifecycleDispatcher.onConfigurationChanged
import expo.modules.ReactNativeHostWrapper
class MainApplication : Application(), ReactApplication {
private val mReactNativeHost: ReactNativeHost =
ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
override fun getUseDeveloperSupport(): Boolean {
return BuildConfig.DEBUG
}
override fun getPackages(): List<ReactPackage> {
val packages: MutableList<ReactPackage> = PackageList(this).packages
packages.add(VolumeButtonListenerPackage())
packages.add(ZipArchivePackage())
packages.add(FileManagerPackage())
packages.add(EpubUtilPackage())
return packages
}
override fun getJSMainModuleName(): String {
return "index"
}
override val isNewArchEnabled: Boolean
get() = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean
get() = BuildConfig.IS_HERMES_ENABLED
})
override fun getReactNativeHost(): ReactNativeHost {
return mReactNativeHost
}
override fun onCreate() {
super.onCreate()
SoLoader.init(this, /* native exopackage */false)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
onApplicationCreate(this)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
onConfigurationChanged(this, newConfig)
}
}
@@ -1,27 +0,0 @@
package com.rajarsheechatterjee.LNReader.generated;
import java.util.Arrays;
import java.util.List;
import org.unimodules.core.interfaces.Package;
public class BasePackageList {
public List<Package> getPackageList() {
return Arrays.<Package>asList(
new expo.modules.application.ApplicationPackage(),
new expo.modules.constants.ConstantsPackage(),
new expo.modules.documentpicker.DocumentPickerPackage(),
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.haptics.HapticsPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.lineargradient.LinearGradientPackage(),
new expo.modules.localization.LocalizationPackage(),
new expo.modules.notifications.NotificationsPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.sqlite.SQLitePackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
}
@@ -1,170 +0,0 @@
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);
}
}
}
@@ -1,25 +0,0 @@
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;
}
}
@@ -1,93 +0,0 @@
package com.rajarsheechatterjee.VolumeButtonListener;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class VolumeButtonListener extends ReactContextBaseJavaModule {
static ReactApplicationContext appContext;
public VolumeButtonListener(ReactApplicationContext context) {
super(context);
appContext = context;
}
private static boolean active = false;
public static boolean isActive() {
return active;
}
public static boolean prevent = false;
public static void sendEvent(boolean up) {
if (active) {
WritableMap args = Arguments.createMap();
if (up)
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeUp", args);
else
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeDown", args);
}
}
public static void down() {
sendEvent(false);
}
public static void up() {
sendEvent(true);
}
@ReactMethod
public void connect() {
active = true;
}
@ReactMethod
void disconnect() {
active = false;
}
@ReactMethod
public void pause() {
active = false;
}
@ReactMethod
public void unpause() {
active = true;
}
@ReactMethod
public void preventDefault() {
prevent = true;
}
@ReactMethod
public void noPreventDefault() {
prevent = false;
}
@NonNull
@Override
public String getName() {
return "VolumeButtonListener";
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
}
@@ -0,0 +1,86 @@
package com.rajarsheechatterjee.VolumeButtonListener
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.modules.core.DeviceEventManagerModule
class VolumeButtonListener(appContext: ReactApplicationContext) :
ReactContextBaseJavaModule(appContext) {
init {
VolumeButtonListener.appContext = appContext
}
@ReactMethod
fun connect() {
isActive = true
}
@ReactMethod
fun disconnect() {
isActive = false
}
@ReactMethod
fun pause() {
isActive = false
}
@ReactMethod
fun unpause() {
isActive = true
}
@ReactMethod
fun preventDefault() {
prevent = true
}
@ReactMethod
fun noPreventDefault() {
prevent = false
}
override fun getName(): String {
return "VolumeButtonListener"
}
@ReactMethod
fun addListener(eventName: String?) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
fun removeListeners(count: Int?) {
// Keep: Required for RN built in Event Emitter Calls.
}
companion object {
lateinit var appContext: ReactApplicationContext
var isActive = false
private set
var prevent = false
fun sendEvent(up: Boolean) {
if (isActive) {
val args = Arguments.createMap()
if (up) appContext.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
).emit(
"VolumeUp",
args
) else appContext.getJSModule(
DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
).emit("VolumeDown", args)
}
}
fun down() {
sendEvent(false)
}
fun up() {
sendEvent(true)
}
}
}
@@ -1,30 +0,0 @@
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();
}
}
@@ -0,0 +1,16 @@
package com.rajarsheechatterjee.VolumeButtonListener
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class VolumeButtonListenerPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> = listOf(VolumeButtonListener(reactContext)).toMutableList()
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
}
@@ -0,0 +1,121 @@
package com.rajarsheechatterjee.ZipArchive
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.ReadableMap
import java.io.File
import java.io.FileOutputStream
import java.net.HttpURLConnection
import java.net.URL
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
override fun getName(): String {
return "ZipArchive"
}
@ReactMethod
fun unzip(sourceFilePath: String, distDirPath: String, promise: Promise) {
Thread {
try {
ZipFile(sourceFilePath).use { zis ->
zis.entries().asSequence().filterNot { it.isDirectory }.forEach { zipEntry ->
val newFile = File(distDirPath, zipEntry.name)
newFile.parentFile?.mkdirs()
zis.getInputStream(zipEntry).use { inputStream ->
FileOutputStream(newFile).use { fos -> inputStream.copyTo(fos, 4096) }
}
}
}
promise.resolve(null)
} catch (e: Exception) {
promise.reject(e)
}
}.start()
}
@ReactMethod
fun remoteUnzip(
distDirPath: String,
urlString: String,
headers: ReadableMap,
promise: Promise
) {
val connection = URL(urlString).openConnection() as HttpURLConnection
Thread {
try {
connection.requestMethod = "GET"
val it = headers.entryIterator
while (it.hasNext()) {
val (key, value) = it.next()
connection.setRequestProperty(key, value.toString())
}
ZipInputStream(connection.inputStream).use { zis ->
generateSequence { zis.nextEntry }
.filterNot { it.isDirectory }
.forEach { zipEntry ->
val newFile = File(distDirPath, zipEntry.name)
newFile.parentFile?.mkdirs()
FileOutputStream(newFile).use { fos -> zis.copyTo(fos, 4096) }
}
}
if (connection.responseCode == 200) {
promise.resolve(null)
} else {
throw Exception("Network request failed")
}
} catch (e: Exception) {
promise.reject(e)
} finally {
connection.disconnect()
}
}.start()
}
private fun zipProcess(sourceDirPath: String, zos: ZipOutputStream) {
val sourceDir = File(sourceDirPath)
sourceDir.walkBottomUp().filter { it.isFile }.forEach { file ->
val zipFileName =
file.absolutePath.removePrefix(sourceDir.absolutePath).removePrefix("/")
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
zos.putNextEntry(entry)
file.inputStream().use { fis -> fis.copyTo(zos, 4096) }
}
}
@ReactMethod
fun remoteZip(
sourceDirPath: String,
urlString: String,
headers: ReadableMap,
promise: Promise
) {
Thread {
val connection = URL(urlString).openConnection() as HttpURLConnection
try {
connection.requestMethod = "POST"
val it = headers.entryIterator
while (it.hasNext()) {
val (key, value) = it.next()
connection.setRequestProperty(key, value.toString())
}
ZipOutputStream(connection.outputStream).use { zipProcess(sourceDirPath, it) }
if (connection.responseCode == 200) {
promise.resolve(
connection.inputStream.bufferedReader().use { it.readText() })
} else {
throw Exception("Network request failed")
}
} catch (e: Exception) {
promise.reject(e)
} finally {
connection.disconnect()
}
}.start()
}
}
@@ -0,0 +1,17 @@
package com.rajarsheechatterjee.ZipArchive
import android.view.View
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ReactShadowNode
import com.facebook.react.uimanager.ViewManager
class ZipArchivePackage : ReactPackage {
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
listOf(ZipArchive(context)).toMutableList()
override fun createViewManagers(
reactContext: ReactApplicationContext
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 617 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 837 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,93 @@
<vector android:alpha="0" android:height="1dp"
android:viewportHeight="1024" android:viewportWidth="1024"
android:width="1dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillAlpha="0.994" android:fillColor="#132c33"
android:pathData="M479.5,-0.5C500.8,-0.5 522.2,-0.5 543.5,-0.5C704.4,13.5 832.9,84.8 929,213.5C985.1,293.3 1016.5,382 1023.5,479.5C1023.5,500.8 1023.5,522.2 1023.5,543.5C1009.2,709.4 934.5,840.2 799.5,936C722,987.8 636.6,1017 543.5,1023.5C522.2,1023.5 500.8,1023.5 479.5,1023.5C333.8,1011.4 213.3,950.4 118,840.5C47.3,754.3 7.8,655.3 -0.5,543.5C-0.5,522.2 -0.5,500.8 -0.5,479.5C13.5,318.6 84.8,190.1 213.5,94C293.3,37.9 382,6.4 479.5,-0.5Z" android:strokeAlpha="0.994"/>
<path android:fillColor="#106476" android:pathData="M870.5,325.5C876.8,340.6 882.7,355.9 888,371.5C889.7,373.2 891.1,375.2 892,377.5C895.1,386.3 897.4,395.3 899,404.5C900,406.7 901.5,408.4 903.5,409.5C903.5,410.2 903.5,410.8 903.5,411.5C907.2,436.2 910.8,460.8 914.5,485.5C915.4,500.5 915.7,515.5 915.5,530.5C911.7,612.8 885.4,686.8 836.5,752.5C836.5,750.7 837,749 838,747.5C894.4,667.2 918.8,577.8 911,479.5C899,365 848.5,271.8 759.5,200C652.9,120.8 534.9,97.5 405.5,130C299,161.6 218.1,225.4 163,321.5C108.7,424.4 99.1,531.4 134,642.5C147,680.2 165,715.2 188,747.5C189,749 189.5,750.7 189.5,752.5C141.4,688 115.1,615.3 110.5,534.5C110.3,518.5 110.5,502.5 111,486.5C112.8,477.3 113.8,467.9 114,458.5C119,435.8 124.4,413.1 130,390.5C140,359.6 153.3,330.6 170,303.5C184.2,283.1 199.4,263.5 215.5,244.5C231.1,229.1 246.7,213.9 262.5,199C273.8,191.7 285.1,184.4 296.5,177C297.3,175.5 298.3,174.2 299.5,173C303.2,172.6 306.5,171.3 309.5,169C313,166 316.3,162.9 319.5,159.5C330.1,155.2 340.1,149.9 349.5,143.5C360.1,142.3 370.1,139.2 379.5,134C385.7,132.1 392.1,130.5 398.5,129C400.5,128 402.5,127 404.5,126C452.1,113.9 500.4,109.7 549.5,113.5C567,115.2 584.3,117.7 601.5,121C620.6,126.7 639.8,132.6 659,138.5C668,141.9 676.8,145.8 685.5,150C695.4,156.1 705.8,161.5 716.5,166C717.2,167 717.8,168 718.5,169C733.7,175.2 747.1,184.2 758.5,196C764.2,199.9 769.5,204.2 774.5,209C776.6,210.8 778.9,212.1 781.5,213C785.3,218.2 789.8,222.7 795,226.5C797.1,228.3 798.4,230.7 799,233.5C807,241.6 815,249.6 823,257.5C823.3,258.8 823.7,260.2 824,261.5C828,266.5 832.4,271.2 837,275.5C841.8,284.4 847.1,293.1 853,301.5C858.3,307.4 862.6,314.1 866,321.5C867,323.4 868.5,324.7 870.5,325.5Z"/>
<path android:fillColor="#126e81" android:pathData="M836.5,752.5C791,813.4 733,858.3 662.5,887C545.5,931 431.5,924.3 320.5,867C268.3,838.7 224.7,800.5 189.5,752.5C189.5,750.7 189,749 188,747.5C165,715.2 147,680.2 134,642.5C99.1,531.4 108.7,424.4 163,321.5C218.1,225.4 299,161.6 405.5,130C534.9,97.5 652.9,120.8 759.5,200C848.5,271.8 899,365 911,479.5C918.8,577.8 894.4,667.2 838,747.5C837,749 836.5,750.7 836.5,752.5Z"/>
<path android:fillColor="#a1abaf" android:pathData="M660.5,203.5C657.7,203.4 655,202.8 652.5,201.5C641.6,195.4 629.9,191.5 617.5,190C609.7,188 602.4,185 595.5,181C580.9,179.2 566.6,176.6 552.5,173C521.2,169 490.2,169.9 459.5,175.5C447.6,178.3 435.6,180.5 423.5,182C418.2,183.8 412.9,185.3 407.5,186.5C388.5,192.1 370.1,199.6 352.5,209C351.2,209.5 349.9,209.7 348.5,209.5C423.4,170 502.4,159.1 585.5,177C611.8,182.6 636.8,191.4 660.5,203.5Z"/>
<path android:fillColor="#d7e2e6" android:pathData="M688.5,223.5C690.9,224.3 692.9,225.7 694.5,227.5C695.4,229.8 696.7,232 698.5,234C734,257.8 763.8,287.3 788,322.5C789.3,323.9 790.8,324.9 792.5,325.5C793.2,325.8 793.8,326.2 794.5,326.5C794.3,327.9 794.5,329.2 795,330.5C851.6,424.2 862.9,523.2 829,627.5C824.9,638.7 820.3,649.7 815,660.5C812.6,667.1 810.4,673.7 808.5,680.5C806.4,682.7 803.9,684.7 801,686.5C790.6,704.3 778.8,720.7 765.5,735.5C765.5,737.2 765.5,738.8 765.5,740.5C721,790.4 666.4,824.1 601.5,841.5C586.8,843.9 572.2,846.4 557.5,849C523.2,853.2 488.9,853.4 454.5,849.5C364.7,834.1 292.3,790.4 237.5,718.5C236.2,715.7 235.4,712.7 235,709.5C188.1,642.5 167.8,567.8 174,485.5C176.7,456.8 182.5,428.8 191.5,401.5C194.1,391.7 197.3,382 201,372.5C201.9,372 202.8,371.3 203.5,370.5C208.1,361.7 212.3,352.7 216,343.5C220.2,338.6 223.9,333.3 227,327.5C242.1,304 260,282.5 280.5,263C290.7,256.1 300.3,248.4 309.5,240C314.3,237.6 318.9,235.1 323.5,232.5C408.4,179.1 499.7,164.2 597.5,188C611.5,191.7 625.2,196.3 638.5,202C641.5,202.3 644.5,202.7 647.5,203C661.2,209.8 674.9,216.7 688.5,223.5Z"/>
<path android:fillColor="#cfdade" android:pathData="M556.5,177.5C577.5,179.3 597.9,184.2 617.5,192C621.3,192.3 624.9,192.9 628.5,194C636.6,198 645,201.2 653.5,203.5C663.6,208.4 673.6,213.6 683.5,219C685.6,220 687.3,221.5 688.5,223.5C674.9,216.7 661.2,209.8 647.5,203C644.5,202.7 641.5,202.3 638.5,202C625.2,196.3 611.5,191.7 597.5,188C499.7,164.2 408.4,179.1 323.5,232.5C330.8,226.2 338.8,220.9 347.5,216.5C348.5,215.1 349.5,213.8 350.5,212.5C363.9,207 377.2,201.1 390.5,195C402.2,191.3 413.8,187.7 425.5,184C448.7,180 472.1,177 495.5,175C505.5,174.3 515.5,174.3 525.5,175C535.8,176.1 546.2,176.9 556.5,177.5Z"/>
<path android:fillColor="#c4ced2" android:pathData="M556.5,177.5C546.2,176.9 535.8,176.1 525.5,175C515.5,174.3 505.5,174.3 495.5,175C472.1,177 448.7,180 425.5,184C413.8,187.7 402.2,191.3 390.5,195C377.2,201.1 363.9,207 350.5,212.5C368.9,202.5 387.9,193.8 407.5,186.5C412.9,185.3 418.2,183.8 423.5,182C435.6,180.5 447.6,178.3 459.5,175.5C491.2,171.4 522.9,171.5 554.5,176C555.4,176.3 556.1,176.8 556.5,177.5Z"/>
<path android:fillColor="#bac3c7" android:pathData="M652.5,201.5C652.8,202.2 653.2,202.8 653.5,203.5C645,201.2 636.6,198 628.5,194C624.9,192.9 621.3,192.3 617.5,192C597.9,184.2 577.5,179.3 556.5,177.5C556.1,176.8 555.4,176.3 554.5,176C522.9,171.5 491.2,171.4 459.5,175.5C490.2,169.9 521.2,169 552.5,173C566.6,176.6 580.9,179.2 595.5,181C602.4,185 609.7,188 617.5,190C629.9,191.5 641.6,195.4 652.5,201.5Z"/>
<path android:fillColor="#0e5565" android:pathData="M870.5,325.5C868.5,324.7 867,323.4 866,321.5C862.6,314.1 858.3,307.4 853,301.5C847.1,293.1 841.8,284.4 837,275.5C832.4,271.2 828,266.5 824,261.5C823.7,260.2 823.3,258.8 823,257.5C815,249.6 807,241.6 799,233.5C798.4,230.7 797.1,228.3 795,226.5C789.8,222.7 785.3,218.2 781.5,213C778.9,212.1 776.6,210.8 774.5,209C769.5,204.2 764.2,199.9 758.5,196C747.1,184.2 733.7,175.2 718.5,169C717.8,168 717.2,167 716.5,166C705.8,161.5 695.4,156.1 685.5,150C676.8,145.8 668,141.9 659,138.5C639.8,132.6 620.6,126.7 601.5,121C584.3,117.7 567,115.2 549.5,113.5C500.4,109.7 452.1,113.9 404.5,126C402.5,127 400.5,128 398.5,129C392.1,130.5 385.7,132.1 379.5,134C370.1,139.2 360.1,142.3 349.5,143.5C420.7,111.5 495,101.3 572.5,113C678.8,130.2 765.3,180.7 832,264.5C846.7,283.7 859.5,304 870.5,325.5Z"/>
<path android:fillColor="#a9b3b7" android:pathData="M652.5,201.5C655,202.8 657.7,203.4 660.5,203.5C704.9,224.6 742.6,254.2 773.5,292.5C765,283.5 756.4,274.7 747.5,266C744,263.6 741,260.8 738.5,257.5C733.8,254.1 729.1,250.8 724.5,247.5C722.4,245.3 720.1,243.3 717.5,241.5C709.8,236.7 702.1,232 694.5,227.5C692.9,225.7 690.9,224.3 688.5,223.5C687.3,221.5 685.6,220 683.5,219C673.6,213.6 663.6,208.4 653.5,203.5C653.2,202.8 652.8,202.2 652.5,201.5Z"/>
<path android:fillColor="#cfdadd" android:pathData="M694.5,227.5C702.1,232 709.8,236.7 717.5,241.5C720.1,243.3 722.4,245.3 724.5,247.5C740.5,264 756.6,280.3 773,296.5C779.5,304.9 785.8,313.3 792,321.5C792.5,322.8 792.7,324.1 792.5,325.5C790.8,324.9 789.3,323.9 788,322.5C763.8,287.3 734,257.8 698.5,234C696.7,232 695.4,229.8 694.5,227.5Z"/>
<path android:fillColor="#a8b3b6" android:pathData="M407.5,186.5C387.9,193.8 368.9,202.5 350.5,212.5C348.1,212.5 345.7,213 343.5,214C280.9,251.6 233.3,303.1 200.5,368.5C200,369.3 199.4,369.6 198.5,369.5C231,299.7 281,246.4 348.5,209.5C349.9,209.7 351.2,209.5 352.5,209C370.1,199.6 388.5,192.1 407.5,186.5Z"/>
<path android:fillColor="#c7d1d5" android:pathData="M350.5,212.5C349.5,213.8 348.5,215.1 347.5,216.5C338.8,220.9 330.8,226.2 323.5,232.5C318.9,235.1 314.3,237.6 309.5,240C300.3,248.4 290.7,256.1 280.5,263C260,282.5 242.1,304 227,327.5C223.9,333.3 220.2,338.6 216,343.5C212.3,352.7 208.1,361.7 203.5,370.5C202.8,371.3 201.9,372 201,372.5C197.3,382 194.1,391.7 191.5,401.5C187.7,410.3 184.4,419.3 181.5,428.5C183.2,412.5 188.1,397.5 196,383.5C196.7,378.7 197.5,374.1 198.5,369.5C199.4,369.6 200,369.3 200.5,368.5C233.3,303.1 280.9,251.6 343.5,214C345.7,213 348.1,212.5 350.5,212.5Z"/>
<path android:fillColor="#0f5969" android:pathData="M349.5,143.5C340.1,149.9 330.1,155.2 319.5,159.5C316.3,162.9 313,166 309.5,169C306.5,171.3 303.2,172.6 299.5,173C298.3,174.2 297.3,175.5 296.5,177C285.1,184.4 273.8,191.7 262.5,199C246.7,213.9 231.1,229.1 215.5,244.5C199.4,263.5 184.2,283.1 170,303.5C153.3,330.6 140,359.6 130,390.5C124.4,413.1 119,435.8 114,458.5C113.8,467.9 112.8,477.3 111,486.5C110.5,502.5 110.3,518.5 110.5,534.5C105.6,408 149,301.1 240.5,214C273,184.7 309.3,161.2 349.5,143.5Z"/>
<path android:fillColor="#b5bfc3" android:pathData="M724.5,247.5C729.1,250.8 733.8,254.1 738.5,257.5C741,260.8 744,263.6 747.5,266C756.4,274.7 765,283.5 773.5,292.5C804,327.4 825.6,367.4 838.5,412.5C837.5,412 836.7,411.4 836,410.5C833.5,403.3 831,396.2 828.5,389C820.3,372.7 812.4,356.2 805,339.5C803.9,338.7 802.7,338 801.5,337.5C799.9,333.3 797.6,329.6 794.5,326.5C793.8,326.2 793.2,325.8 792.5,325.5C792.7,324.1 792.5,322.8 792,321.5C785.8,313.3 779.5,304.9 773,296.5C756.6,280.3 740.5,264 724.5,247.5Z"/>
<path android:fillColor="#d0dade" android:pathData="M558.5,314.5C570.2,314.3 581.8,314.5 593.5,315C603.6,318.9 605.1,325 598,333.5C597.2,342.4 597.3,351.2 598.5,360C629.8,360.3 661.2,360.7 692.5,361C694.7,361.8 696.2,363.3 697,365.5C697.7,376.5 697.7,387.5 697,398.5C696.2,400 695,401.2 693.5,402C661.6,403.5 629.6,404 597.5,403.5C597.2,411.8 597.5,419.9 598.5,428C623.3,428 647.9,428.7 672.5,430C673.7,430.9 674.5,432.1 675,433.5C675.7,444.2 675.7,454.8 675,465.5C674.2,468.8 672,470.7 668.5,471C638.7,471.2 608.8,471.3 579,471.5C547.8,471.7 516.6,471.2 485.5,470C483.5,468.7 482.3,466.8 482,464.5C481.3,454.5 481.3,444.5 482,434.5C482.3,432.7 483.1,431.2 484.5,430C507.1,428.5 529.7,428 552.5,428.5C552.5,420.2 552.5,411.8 552.5,403.5C525.2,403.7 497.8,403.5 470.5,403C467.2,403.1 465.1,401.6 464,398.5C462,387.2 462,375.8 464,364.5C465.2,363 466.7,361.8 468.5,361C496.5,360.5 524.5,360.3 552.5,360.5C552.1,346.5 552.6,332.5 554,318.5C555.4,316.9 556.9,315.6 558.5,314.5Z"/>
<path android:fillColor="#849094" android:pathData="M591.5,317.5C580.5,317.5 569.5,317.5 558.5,317.5C558.8,332.7 558.5,347.7 557.5,362.5C557.5,347.2 557.5,331.8 557.5,316.5C569,316.2 580.3,316.5 591.5,317.5Z"/>
<path android:fillColor="#132c33" android:pathData="M591.5,317.5C599,318.8 599.5,321.8 593,326.5C592.5,338.5 592.3,350.5 592.5,362.5C625.8,362.5 659.2,362.5 692.5,362.5C692.5,373.2 692.5,383.8 692.5,394.5C658.8,394.5 625.2,394.5 591.5,394.5C591.2,406.7 591.5,418.7 592.5,430.5C618,431.5 643.7,431.8 669.5,431.5C669.5,441.8 669.5,452.2 669.5,462.5C608.8,462.5 548.2,462.5 487.5,462.5C487.5,451.8 487.5,441.2 487.5,430.5C510.2,430.5 532.8,430.5 555.5,430.5C556.2,430.5 556.8,430.5 557.5,430.5C558.5,418.7 558.8,406.7 558.5,394.5C528.5,394.5 498.5,394.5 468.5,394.5C468.5,383.8 468.5,373.2 468.5,362.5C498.2,362.5 527.8,362.5 557.5,362.5C558.5,347.7 558.8,332.7 558.5,317.5C569.5,317.5 580.5,317.5 591.5,317.5Z"/>
<path android:fillColor="#cfdade" android:pathData="M331.5,324.5C366.9,324 402.2,324.5 437.5,326C439.5,327.3 440.7,329.2 441,331.5C441.9,342.2 441.6,352.9 440,363.5C439,365.3 437.5,366.4 435.5,367C399.8,368 364.1,367.6 328.5,366C327.3,365.1 326.5,363.9 326,362.5C325.3,351.5 325.3,340.5 326,329.5C327.4,327.3 329.2,325.6 331.5,324.5Z"/>
<path android:fillColor="#b8c1c4" android:pathData="M330.5,331.5C330.2,341 330.5,350.3 331.5,359.5C331.6,360.1 331.9,360.6 332.5,361C349.3,361.2 366.2,361.3 383,361.5C400.2,361.5 417,360.8 433.5,359.5C434.2,359.5 434.8,359.5 435.5,359.5C435.8,359.5 436.2,359.5 436.5,359.5C436.4,361.4 435.4,362.6 433.5,363C399.2,363.7 364.8,363.7 330.5,363C329.9,362.3 329.4,361.4 329,360.5C328.3,351.2 328.3,341.8 329,332.5C329.4,331.9 329.9,331.6 330.5,331.5Z"/>
<path android:fillColor="#a3abaf" android:pathData="M331.5,359.5C365.5,359.5 399.5,359.5 433.5,359.5C417,360.8 400.2,361.5 383,361.5C366.2,361.3 349.3,361.2 332.5,361C331.9,360.6 331.6,360.1 331.5,359.5Z"/>
<path android:fillColor="#142d34" android:pathData="M330.5,327.5C365.5,327.5 400.5,327.5 435.5,327.5C435.5,338.2 435.5,348.8 435.5,359.5C434.8,359.5 434.2,359.5 433.5,359.5C399.5,359.5 365.5,359.5 331.5,359.5C330.5,350.3 330.2,341 330.5,331.5C330.5,330.2 330.5,328.8 330.5,327.5Z"/>
<path android:fillColor="#79868a" android:pathData="M330.5,327.5C365.7,326.5 401,326.2 436.5,326.5C436.5,337.5 436.5,348.5 436.5,359.5C436.2,359.5 435.8,359.5 435.5,359.5C435.5,348.8 435.5,338.2 435.5,327.5C400.5,327.5 365.5,327.5 330.5,327.5Z"/>
<path android:fillColor="#0f5a6b" android:pathData="M870.5,325.5C884.8,352.1 895.8,380.1 903.5,409.5C901.5,408.4 900,406.7 899,404.5C897.4,395.3 895.1,386.3 892,377.5C891.1,375.2 889.7,373.2 888,371.5C882.7,355.9 876.8,340.6 870.5,325.5Z"/>
<path android:fillColor="#8e979a" android:pathData="M468.5,394.5C498.5,394.5 528.5,394.5 558.5,394.5C558.8,406.7 558.5,418.7 557.5,430.5C558.1,419.1 557.5,407.9 555.5,397C528.2,396.7 500.8,396.3 473.5,396C471.6,395.8 470,395.3 468.5,394.5Z"/>
<path android:fillColor="#a6aeb1" android:pathData="M690.5,395.5C690.4,396.1 690.1,396.6 689.5,397C658.2,397.3 626.8,397.7 595.5,398C594.6,399 593.6,399.9 592.5,400.5C592.5,398.8 592.5,397.2 592.5,395.5C625.2,395.5 657.8,395.5 690.5,395.5Z"/>
<path android:fillColor="#b4bec1" android:pathData="M468.5,362.5C468.5,373.2 468.5,383.8 468.5,394.5C470,395.3 471.6,395.8 473.5,396C500.8,396.3 528.2,396.7 555.5,397C557.5,407.9 558.1,419.1 557.5,430.5C556.8,430.5 556.2,430.5 555.5,430.5C556.6,420.9 556.8,411.2 556,401.5C555.5,401 555,400.5 554.5,400C526.5,399.7 498.5,399.3 470.5,399C469.7,398.2 468.8,397.3 468,396.5C467.2,385 467.3,373.7 468.5,362.5Z"/>
<path android:fillColor="#b9c3c6" android:pathData="M690.5,395.5C691.2,395.5 691.8,395.5 692.5,395.5C691.9,396.5 691.2,397.5 690.5,398.5C658.8,399.3 627.2,399.8 595.5,400C595,400.5 594.5,401 594,401.5C593.2,411.2 593.4,420.9 594.5,430.5C593.8,430.5 593.2,430.5 592.5,430.5C592.5,420.5 592.5,410.5 592.5,400.5C593.6,399.9 594.6,399 595.5,398C626.8,397.7 658.2,397.3 689.5,397C690.1,396.6 690.4,396.1 690.5,395.5Z"/>
<path android:fillColor="#515f63" android:pathData="M692.5,394.5C692.5,394.8 692.5,395.2 692.5,395.5C691.8,395.5 691.2,395.5 690.5,395.5C657.8,395.5 625.2,395.5 592.5,395.5C592.5,397.2 592.5,398.8 592.5,400.5C592.5,410.5 592.5,420.5 592.5,430.5C591.5,418.7 591.2,406.7 591.5,394.5C625.2,394.5 658.8,394.5 692.5,394.5Z"/>
<path android:fillColor="#ced9dd" android:pathData="M191.5,401.5C182.5,428.8 176.7,456.8 174,485.5C167.8,567.8 188.1,642.5 235,709.5C235.4,712.7 236.2,715.7 237.5,718.5C214.2,688.9 196.9,655.9 185.5,619.5C182.6,606.1 179.5,592.8 176,579.5C174,566 171.8,552.6 169.5,539.5C167.9,501.8 171.9,464.8 181.5,428.5C184.4,419.3 187.7,410.3 191.5,401.5Z"/>
<path android:fillColor="#ced8dc" android:pathData="M313.5,384.5C359.2,384.3 404.8,384.5 450.5,385C452,386.2 453.2,387.7 454,389.5C454.7,400.5 454.7,411.5 454,422.5C451.8,425.8 448.6,427.3 444.5,427C400.5,428 356.5,427.6 312.5,426C311,424.8 309.8,423.3 309,421.5C308.3,410.8 308.3,400.2 309,389.5C310,387.3 311.5,385.6 313.5,384.5Z"/>
<path android:fillColor="#bbc4c8" android:pathData="M313.5,389.5C313.5,399.2 313.5,408.8 313.5,418.5C314.4,419.3 315.4,420.1 316.5,421C360.2,421.7 403.8,421.7 447.5,421C448.4,420.3 449,419.5 449.5,418.5C449.5,408.8 449.5,399.2 449.5,389.5C450.7,399.7 450.8,410 450,420.5C449.1,421.7 447.9,422.5 446.5,423C402.5,423.7 358.5,423.7 314.5,423C313.8,421.7 313,420.6 312,419.5C311.3,409.8 311.3,400.2 312,390.5C312.4,389.9 312.9,389.6 313.5,389.5Z"/>
<path android:fillColor="#9da6a9" android:pathData="M313.5,418.5C358.8,418.5 404.2,418.5 449.5,418.5C449,419.5 448.4,420.3 447.5,421C403.8,421.7 360.2,421.7 316.5,421C315.4,420.1 314.4,419.3 313.5,418.5Z"/>
<path android:fillColor="#132c33" android:pathData="M449.5,389.5C449.5,399.2 449.5,408.8 449.5,418.5C404.2,418.5 358.8,418.5 313.5,418.5C313.5,408.8 313.5,399.2 313.5,389.5C313.5,388.5 313.5,387.5 313.5,386.5C358.8,386.5 404.2,386.5 449.5,386.5C449.5,387.5 449.5,388.5 449.5,389.5Z"/>
<path android:fillColor="#273b41" android:pathData="M592.5,430.5C593.2,430.5 593.8,430.5 594.5,430.5C620.2,429.8 645.5,430.5 670.5,432.5C670.5,432.8 670.5,433.2 670.5,433.5C670.5,443.2 670.5,452.8 670.5,462.5C670.2,462.5 669.8,462.5 669.5,462.5C669.5,452.2 669.5,441.8 669.5,431.5C643.7,431.8 618,431.5 592.5,430.5Z"/>
<path android:fillColor="#b8c4c8" android:pathData="M198.5,369.5C197.5,374.1 196.7,378.7 196,383.5C188.1,397.5 183.2,412.5 181.5,428.5C171.9,464.8 167.9,501.8 169.5,539.5C163.8,480.3 173.5,423.6 198.5,369.5Z"/>
<path android:fillColor="#cfdadd" android:pathData="M330.5,445.5C365.8,445.3 401.2,445.5 436.5,446C439.1,447.3 440.6,449.5 441,452.5C441.9,463.2 441.6,473.9 440,484.5C439.2,485.3 438.3,486.2 437.5,487C401.5,487.7 365.5,487.7 329.5,487C327.3,486.3 326.1,484.8 326,482.5C325.3,471.8 325.3,461.2 326,450.5C327,448.3 328.5,446.6 330.5,445.5Z"/>
<path android:fillColor="#132c32" android:pathData="M435.5,448.5C435.5,458.8 435.5,469.2 435.5,479.5C400.8,479.5 366.2,479.5 331.5,479.5C331.5,469.2 331.5,458.8 331.5,448.5C366.2,448.5 400.8,448.5 435.5,448.5Z"/>
<path android:fillColor="#273b41" android:pathData="M435.5,447.5C435.5,447.8 435.5,448.2 435.5,448.5C400.8,448.5 366.2,448.5 331.5,448.5C331.5,458.8 331.5,469.2 331.5,479.5C330.5,470.7 330.2,461.7 330.5,452.5C330.5,450.8 330.5,449.2 330.5,447.5C365.5,447.5 400.5,447.5 435.5,447.5Z"/>
<path android:fillColor="#6b7679" android:pathData="M487.5,430.5C487.5,441.2 487.5,451.8 487.5,462.5C548.2,462.5 608.8,462.5 669.5,462.5C669.8,462.5 670.2,462.5 670.5,462.5C670.2,463.3 669.5,463.7 668.5,463.5C608.5,463.5 548.5,463.5 488.5,463.5C488.2,463.5 487.8,463.5 487.5,463.5C486.5,453 486.2,442.3 486.5,431.5C486.5,430.8 486.8,430.5 487.5,430.5Z"/>
<path android:fillColor="#c8d3d6" android:pathData="M486.5,431.5C486.2,442.3 486.5,453 487.5,463.5C487.8,465.8 489.1,466.9 491.5,467C550.8,467.7 610.2,467.7 669.5,467C670.9,465.8 671.7,464.3 672,462.5C672.7,453.2 672.7,443.8 672,434.5C671.6,433.9 671.1,433.6 670.5,433.5C670.5,433.2 670.5,432.8 670.5,432.5C672.4,432.6 673.6,433.6 674,435.5C674.7,445.2 674.7,454.8 674,464.5C672.6,466.1 671.1,467.6 669.5,469C608.8,469.7 548.2,469.7 487.5,469C486.2,467.6 485,466.1 484,464.5C483.3,454.2 483.3,443.8 484,433.5C484.7,432.6 485.5,432 486.5,431.5Z"/>
<path android:fillColor="#bac3c7" android:pathData="M670.5,433.5C671.1,433.6 671.6,433.9 672,434.5C672.7,443.8 672.7,453.2 672,462.5C671.7,464.3 670.9,465.8 669.5,467C610.2,467.7 550.8,467.7 491.5,467C489.1,466.9 487.8,465.8 487.5,463.5C487.8,463.5 488.2,463.5 488.5,463.5C488.6,464.1 488.9,464.6 489.5,465C548.8,465.7 608.2,465.7 667.5,465C668.1,464.6 668.4,464.1 668.5,463.5C669.5,463.7 670.2,463.3 670.5,462.5C670.5,452.8 670.5,443.2 670.5,433.5Z"/>
<path android:fillColor="#a6aeb1" android:pathData="M488.5,463.5C548.5,463.5 608.5,463.5 668.5,463.5C668.4,464.1 668.1,464.6 667.5,465C608.2,465.7 548.8,465.7 489.5,465C488.9,464.6 488.6,464.1 488.5,463.5Z"/>
<path android:fillColor="#0f5b6c" android:pathData="M903.5,411.5C914.1,450.5 918.1,490.2 915.5,530.5C915.7,515.5 915.4,500.5 914.5,485.5C910.8,460.8 907.2,436.2 903.5,411.5Z"/>
<path android:fillColor="#9fa8ab" android:pathData="M435.5,447.5C436.4,448.6 436.8,449.9 436.5,451.5C436.8,461.5 436.5,471.5 435.5,481.5C434.7,481.8 434,482.3 433.5,483C399.5,483.7 365.5,483.7 331.5,483C331,482.3 330.3,481.8 329.5,481.5C329.2,471.7 329.5,462 330.5,452.5C330.2,461.7 330.5,470.7 331.5,479.5C366.2,479.5 400.8,479.5 435.5,479.5C435.5,469.2 435.5,458.8 435.5,448.5C435.5,448.2 435.5,447.8 435.5,447.5Z"/>
<path android:fillColor="#c2ccd0" android:pathData="M436.5,451.5C437.1,451.6 437.6,451.9 438,452.5C438.7,462.2 438.7,471.8 438,481.5C437.6,482.4 437.1,483.3 436.5,484C418.2,485.1 399.9,485.6 381.5,485.5C364.5,485.2 347.5,484.8 330.5,484.5C329.9,483.6 329.5,482.6 329.5,481.5C330.3,481.8 331,482.3 331.5,483C365.5,483.7 399.5,483.7 433.5,483C434,482.3 434.7,481.8 435.5,481.5C436.5,471.5 436.8,461.5 436.5,451.5Z"/>
<path android:fillColor="#142c33" android:pathData="M459.5,485.5C538.8,485.5 618.2,485.5 697.5,485.5C697.5,509.5 697.5,533.5 697.5,557.5C686.2,557.5 674.8,557.5 663.5,557.5C663.5,545.8 663.5,534.2 663.5,522.5C663.5,521.8 663.5,521.2 663.5,520.5C663.5,519.5 663.5,518.5 663.5,517.5C606.8,517.5 550.2,517.5 493.5,517.5C493.5,531.5 493.5,545.5 493.5,559.5C482.2,559.5 470.8,559.5 459.5,559.5C459.5,536.5 459.5,513.5 459.5,490.5C459.5,489.2 459.5,487.8 459.5,486.5C459.5,486.2 459.5,485.8 459.5,485.5Z"/>
<path android:fillColor="#8f9a9e" android:pathData="M459.5,485.5C499.2,484.8 539,484.5 579,484.5C619.2,484.5 659,485.2 698.5,486.5C698.8,510.7 698.5,534.7 697.5,558.5C686,558.8 674.7,558.5 663.5,557.5C674.8,557.5 686.2,557.5 697.5,557.5C697.5,533.5 697.5,509.5 697.5,485.5C618.2,485.5 538.8,485.5 459.5,485.5Z"/>
<path android:fillColor="#bac6ca" android:pathData="M838.5,412.5C841.3,419.5 843.4,426.9 845,434.5C854.7,478.4 855.8,522.4 848.5,566.5C851.3,530.5 851.1,494.5 848,458.5C846.2,447.9 843.5,437.6 840,427.5C839.5,422.5 839,417.5 838.5,412.5Z"/>
<path android:fillColor="#132c32" android:pathData="M331.5,507.5C366.2,507.5 400.8,507.5 435.5,507.5C435.5,517.8 435.5,528.2 435.5,538.5C400.8,538.5 366.2,538.5 331.5,538.5C331.5,528.2 331.5,517.8 331.5,507.5Z"/>
<path android:fillColor="#cbd6d9" android:pathData="M459.5,486.5C459.5,487.8 459.5,489.2 459.5,490.5C458.3,514.3 458.2,538.3 459,562.5C459.5,563 460,563.5 460.5,564C471.2,564.7 481.8,564.7 492.5,564C493.3,563.7 494,563.2 494.5,562.5C495.3,549.9 495.8,537.2 496,524.5C496.9,523.3 498.1,522.5 499.5,522C553.2,521.3 606.8,521.3 660.5,522C661,522.5 661.5,523 662,523.5C662.3,535.5 662.7,547.5 663,559.5C663.8,560.3 664.7,561.2 665.5,562C675.2,562.7 684.8,562.7 694.5,562C695.7,561 696.7,559.8 697.5,558.5C698.5,534.7 698.8,510.7 698.5,486.5C699.5,487 700.3,487.6 701,488.5C701.7,511.8 701.7,535.2 701,558.5C700.3,561.3 698.4,562.8 695.5,563C684.5,563.7 673.5,563.7 662.5,563C662,562.5 661.5,562 661,561.5C659.9,549.4 659,537.2 658.5,525C605.2,524.5 551.8,524.3 498.5,524.5C498.7,537.5 498.5,550.5 498,563.5C496.2,566.3 493.7,567.8 490.5,568C479.8,568.9 469.1,568.5 458.5,567C456.3,565.4 454.9,563.2 454.5,560.5C455.3,537.2 455.8,513.9 456,490.5C456.7,488.7 457.8,487.3 459.5,486.5Z"/>
<path android:fillColor="#aab3b7" android:pathData="M663.5,520.5C663.5,521.2 663.5,521.8 663.5,522.5C662.7,522.2 662,521.7 661.5,521C605.8,520.2 550.1,520.3 494.5,521.5C494.9,520.1 495.9,519.2 497.5,519C552.2,518.3 606.8,518.3 661.5,519C662,519.7 662.7,520.2 663.5,520.5Z"/>
<path android:fillColor="#9ca4a7" android:pathData="M663.5,520.5C662.7,520.2 662,519.7 661.5,519C606.8,518.3 552.2,518.3 497.5,519C495.9,519.2 494.9,520.1 494.5,521.5C494.8,534.3 494.5,547 493.5,559.5C493.5,545.5 493.5,531.5 493.5,517.5C550.2,517.5 606.8,517.5 663.5,517.5C663.5,518.5 663.5,519.5 663.5,520.5Z"/>
<path android:fillColor="#132c33" android:pathData="M629.5,538.5C629.9,541.8 628.5,544.1 625.5,545.5C624.2,582 624.2,618.3 625.5,654.5C625.7,658.6 627.7,661.4 631.5,663C641.5,663.7 651.5,663.7 661.5,663C665.1,661.1 667.6,658.3 669,654.5C671.2,646.6 673,638.6 674.5,630.5C675,629.1 675.6,627.8 676.5,626.5C686.4,629.9 696.4,632.9 706.5,635.5C707.1,636.4 707.4,637.4 707.5,638.5C704.9,648.7 702.4,659 700,669.5C698.8,673.6 697.3,677.6 695.5,681.5C691.1,688.5 684.8,692.9 676.5,694.5C655.5,695.5 634.5,695.8 613.5,695.5C600.6,694.3 593.1,687.3 591,674.5C590.5,630.5 590.3,586.5 590.5,542.5C590.5,541.2 590.5,539.8 590.5,538.5C596.1,537.2 602.1,536.5 608.5,536.5C614.5,536.7 620.5,536.8 626.5,537C627.7,537.2 628.7,537.7 629.5,538.5Z"/>
<path android:fillColor="#2b3e44" android:pathData="M331.5,507.5C331.5,517.8 331.5,528.2 331.5,538.5C366.2,538.5 400.8,538.5 435.5,538.5C435.2,539.3 434.5,539.7 433.5,539.5C399.8,539.5 366.2,539.5 332.5,539.5C330.5,531.2 329.9,522.6 330.5,513.5C330.5,512.2 330.5,510.8 330.5,509.5C330.3,508.5 330.7,507.8 331.5,507.5Z"/>
<path android:fillColor="#b3bcbf" android:pathData="M435.5,507.5C436.2,508.8 437.1,510.1 438,511.5C438.7,521.2 438.7,530.8 438,540.5C437.6,542.4 436.4,543.4 434.5,543.5C400.5,543.7 366.5,543.5 332.5,543C331.7,542.2 330.8,541.3 330,540.5C329.2,531.3 329.3,522.3 330.5,513.5C329.9,522.6 330.5,531.2 332.5,539.5C332.8,540.2 333.1,540.8 333.5,541.5C366.5,542.7 399.5,542.8 432.5,542C433.3,541.3 433.6,540.5 433.5,539.5C434.5,539.7 435.2,539.3 435.5,538.5C435.5,528.2 435.5,517.8 435.5,507.5Z"/>
<path android:fillColor="#a5aeb1" android:pathData="M332.5,539.5C366.2,539.5 399.8,539.5 433.5,539.5C433.6,540.5 433.3,541.3 432.5,542C399.5,542.8 366.5,542.7 333.5,541.5C333.1,540.8 332.8,540.2 332.5,539.5Z"/>
<path android:fillColor="#c7d1d5" android:pathData="M330.5,509.5C330.5,510.8 330.5,512.2 330.5,513.5C329.3,522.3 329.2,531.3 330,540.5C330.8,541.3 331.7,542.2 332.5,543C366.5,543.5 400.5,543.7 434.5,543.5C417.5,544.8 400.2,545.5 382.5,545.5C365.2,545.3 347.8,545.2 330.5,545C330,544.1 329.3,543.2 328.5,542.5C327.3,532.2 327.2,521.8 328,511.5C328.7,510.6 329.5,510 330.5,509.5Z"/>
<path android:fillColor="#cdd8dc" android:pathData="M794.5,326.5C797.6,329.6 799.9,333.3 801.5,337.5C802.7,338 803.9,338.7 805,339.5C812.4,356.2 820.3,372.7 828.5,389C831,396.2 833.5,403.3 836,410.5C836.7,411.4 837.5,412 838.5,412.5C839,417.5 839.5,422.5 840,427.5C843.5,437.6 846.2,447.9 848,458.5C851.1,494.5 851.3,530.5 848.5,566.5C842.5,607.2 829.2,645.2 808.5,680.5C810.4,673.7 812.6,667.1 815,660.5C820.3,649.7 824.9,638.7 829,627.5C862.9,523.2 851.6,424.2 795,330.5C794.5,329.2 794.3,327.9 794.5,326.5Z"/>
<path android:fillColor="#b4bdc0" android:pathData="M459.5,490.5C459.5,513.5 459.5,536.5 459.5,559.5C470.8,559.5 482.2,559.5 493.5,559.5C494.5,547 494.8,534.3 494.5,521.5C550.1,520.3 605.8,520.2 661.5,521C662,521.7 662.7,522.2 663.5,522.5C663.5,534.2 663.5,545.8 663.5,557.5C674.7,558.5 686,558.8 697.5,558.5C696.7,559.8 695.7,561 694.5,562C684.8,562.7 675.2,562.7 665.5,562C664.7,561.2 663.8,560.3 663,559.5C662.7,547.5 662.3,535.5 662,523.5C661.5,523 661,522.5 660.5,522C606.8,521.3 553.2,521.3 499.5,522C498.1,522.5 496.9,523.3 496,524.5C495.8,537.2 495.3,549.9 494.5,562.5C494,563.2 493.3,563.7 492.5,564C481.8,564.7 471.2,564.7 460.5,564C460,563.5 459.5,563 459,562.5C458.2,538.3 458.3,514.3 459.5,490.5Z"/>
<path android:fillColor="#becacd" android:pathData="M169.5,539.5C171.8,552.6 174,566 176,579.5C179.5,592.8 182.6,606.1 185.5,619.5C176.5,593.7 171.1,567 169.5,539.5Z"/>
<path android:fillColor="#ccd7db" android:pathData="M674.5,630.5C673,638.6 671.2,646.6 669,654.5C667.6,658.3 665.1,661.1 661.5,663C651.5,663.7 641.5,663.7 631.5,663C627.7,661.4 625.7,658.6 625.5,654.5C625.3,619.5 625.5,584.5 626,549.5C630.1,546.6 631.3,542.9 629.5,538.5C628.7,537.7 627.7,537.2 626.5,537C620.5,536.8 614.5,536.7 608.5,536.5C602.1,536.5 596.1,537.2 590.5,538.5C589.8,538.8 589.2,539.1 588.5,539.5C587.3,586.2 587.2,632.8 588,679.5C588.6,683.6 590.1,687.2 592.5,690.5C593.3,691.4 594.3,691.7 595.5,691.5C599.4,695.2 604.1,697.7 609.5,699C631.2,699.7 652.8,699.7 674.5,699C678.4,697.5 682.4,696.4 686.5,695.5C691,693.2 694.3,689.7 696.5,685C697.8,684.7 698.9,684.2 700,683.5C702.3,679.1 703.9,674.4 705,669.5C705.5,659.1 706.3,648.7 707.5,638.5C707.4,637.4 707.1,636.4 706.5,635.5C708,636.2 708.9,637.6 709,639.5C710.4,656.8 707.1,673.1 699,688.5C693.4,696.1 685.9,701 676.5,703C653.8,703.7 631.2,703.7 608.5,703C595.4,699.9 587.9,691.8 586,678.5C585.3,631.8 585.3,585.2 586,538.5C587.1,536.2 588.9,534.7 591.5,534C602.5,533.3 613.5,533.3 624.5,534C630.5,534.3 633.8,537.3 634.5,543C633.9,546.9 632.4,550.4 630,553.5C629,588.5 629.4,623.5 631,658.5C632.5,660 634.3,660.8 636.5,661C644.6,661.9 652.6,661.6 660.5,660C667.7,652.6 671.7,643.8 672.5,633.5C672.6,632 673.3,631 674.5,630.5Z"/>
<path android:fillColor="#aeb7ba" android:pathData="M590.5,542.5C590.3,586.5 590.5,630.5 591,674.5C593.1,687.3 600.6,694.3 613.5,695.5C633.7,696.7 654,696.8 674.5,696C675.4,695.7 676.1,695.2 676.5,694.5C684.8,692.9 691.1,688.5 695.5,681.5C694,687.1 691,691.8 686.5,695.5C682.4,696.4 678.4,697.5 674.5,699C652.8,699.7 631.2,699.7 609.5,699C604.1,697.7 599.4,695.2 595.5,691.5C594.5,687.4 592.7,683.8 590,680.5C589.2,634.3 589.3,588.3 590.5,542.5Z"/>
<path android:fillColor="#838d90" android:pathData="M676.5,694.5C676.1,695.2 675.4,695.7 674.5,696C654,696.8 633.7,696.7 613.5,695.5C634.5,695.8 655.5,695.5 676.5,694.5Z"/>
<path android:fillColor="#bdc7ca" android:pathData="M707.5,638.5C706.3,648.7 705.5,659.1 705,669.5C703.9,674.4 702.3,679.1 700,683.5C698.9,684.2 697.8,684.7 696.5,685C694.3,689.7 691,693.2 686.5,695.5C691,691.8 694,687.1 695.5,681.5C697.3,677.6 698.8,673.6 700,669.5C702.4,659 704.9,648.7 707.5,638.5Z"/>
<path android:fillColor="#aeb7bb" android:pathData="M629.5,538.5C631.3,542.9 630.1,546.6 626,549.5C625.5,584.5 625.3,619.5 625.5,654.5C624.2,618.3 624.2,582 625.5,545.5C628.5,544.1 629.9,541.8 629.5,538.5Z"/>
<path android:fillColor="#c3cdd1" android:pathData="M590.5,538.5C590.5,539.8 590.5,541.2 590.5,542.5C589.3,588.3 589.2,634.3 590,680.5C592.7,683.8 594.5,687.4 595.5,691.5C594.3,691.7 593.3,691.4 592.5,690.5C590.1,687.2 588.6,683.6 588,679.5C587.2,632.8 587.3,586.2 588.5,539.5C589.2,539.1 589.8,538.8 590.5,538.5Z"/>
<path android:fillColor="#cad6db" android:pathData="M808.5,680.5C796.6,702.3 782.3,722.3 765.5,740.5C765.5,738.8 765.5,737.2 765.5,735.5C778.8,720.7 790.6,704.3 801,686.5C803.9,684.7 806.4,682.7 808.5,680.5Z"/>
<path android:fillColor="#d0dbdf" android:pathData="M474.5,666.5C466.2,669.7 457.6,671.4 448.5,671.5C447.8,671.9 447.3,672.6 447,673.5C446.7,673.2 446.3,672.8 446,672.5C445.7,640.8 445.3,609.2 445,577.5C444.8,576.3 444.3,575.3 443.5,574.5C443.5,572.8 443.5,571.2 443.5,569.5C403.3,569.2 363.3,569.5 323.5,570.5C323.5,571.5 323.5,572.5 323.5,573.5C322.9,573.6 322.4,573.9 322,574.5C321.3,619.5 321.3,664.5 322,709.5C322.8,710.3 323.7,711.2 324.5,712C333.8,714 343.2,714 352.5,712C353.9,710.8 354.7,709.3 355,707.5C355.3,703.8 355.7,700.2 356,696.5C356.4,695.6 356.9,694.8 357.5,694C374.2,693.3 390.8,693.3 407.5,694C408.4,694.4 409.3,694.9 410,695.5C409.7,700.1 411.2,703.9 414.5,707C423.2,707.7 431.8,707.7 440.5,707C442.3,706.2 443.8,705 445,703.5C446,697.9 446.9,692.2 447.5,686.5C448.7,687.3 449.7,688.3 450.5,689.5C452.8,693.9 454.6,698.5 456,703.5C459.1,710.8 464.6,713.3 472.5,711C481.2,708.8 489.2,705.3 496.5,700.5C502.5,698 507.8,694.5 512.5,690C513.8,689.5 515.1,689.3 516.5,689.5C518.3,688.9 519.9,688.1 521.5,687C527.7,679.8 534.2,673 541,666.5C552.5,646.5 558.9,624.8 560,601.5C560.3,584.5 560.7,567.5 561,550.5C563.7,548.3 564.8,545.5 564.5,542C564.4,540 563.8,538.5 562.5,537.5C560.9,537 559.2,536.6 557.5,536.5C546.3,535.5 535,535.2 523.5,535.5C523.5,536.5 523.5,537.5 523.5,538.5C522.2,539.1 521.4,540.1 521,541.5C520.8,565.5 520.2,589.5 519,613.5C517.2,619.1 515.7,624.8 514.5,630.5C508.7,644 499.4,654.3 486.5,661.5C486.6,660.5 486.3,659.7 485.5,659C499.8,650.4 509.3,637.9 514,621.5C515.8,614.9 517.2,608.3 518,601.5C518.3,582.2 518.7,562.8 519,543.5C518.8,538.6 521,535.5 525.5,534C536.5,533.3 547.5,533.3 558.5,534C569.5,537.3 571.3,543.5 564,552.5C563.9,572.5 563.2,592.5 562,612.5C554.2,670.8 521.4,705.1 463.5,715.5C461,715.2 458.8,714.2 457,712.5C454.1,706.2 451.5,699.9 449,693.5C448.2,697.1 447.6,700.8 447,704.5C445.2,707.3 442.7,708.8 439.5,709C430.4,709.8 421.4,709.5 412.5,708C411.7,707.2 410.8,706.3 410,705.5C409.8,702.2 409.3,699.1 408.5,696C391.8,695.3 375.2,695.3 358.5,696C357.7,701.1 357.2,706.3 357,711.5C356.5,713.3 355.3,714.5 353.5,715C343.5,715.7 333.5,715.7 323.5,715C322.1,714.5 320.9,713.7 320,712.5C318.3,666.3 318,619.9 319,573.5C319.9,570.2 322.1,568.4 325.5,568C365.2,567 404.9,567.4 444.5,569C445.7,569.9 446.5,571.1 447,572.5C447.5,605.2 447.7,637.8 447.5,670.5C456.5,669.2 465.5,667.8 474.5,666.5Z"/>
<path android:fillColor="#c5d0d3" android:pathData="M323.5,573.5C323.5,574.2 323.5,574.8 323.5,575.5C322.5,619.7 322.2,664 322.5,708.5C331.6,710.5 341,711 350.5,710C351.4,709.3 352,708.5 352.5,707.5C353.5,703 353.8,698.4 353.5,693.5C354.3,693.2 355,692.7 355.5,692C373.5,691.3 391.5,691.3 409.5,692C410,692.7 410.7,693.2 411.5,693.5C412.2,696.3 412.8,699 413.5,701.5C413.8,701.5 414.2,701.5 414.5,701.5C414.4,702.5 414.7,703.3 415.5,704C424.9,704.9 434.2,704.6 443.5,703C444.1,693.9 444.9,684.7 446,675.5C446.9,677.7 448,679.7 449.5,681.5C450.5,684.1 450.8,686.8 450.5,689.5C449.7,688.3 448.7,687.3 447.5,686.5C446.9,692.2 446,697.9 445,703.5C443.8,705 442.3,706.2 440.5,707C431.8,707.7 423.2,707.7 414.5,707C411.2,703.9 409.7,700.1 410,695.5C409.3,694.9 408.4,694.4 407.5,694C390.8,693.3 374.2,693.3 357.5,694C356.9,694.8 356.4,695.6 356,696.5C355.7,700.2 355.3,703.8 355,707.5C354.7,709.3 353.9,710.8 352.5,712C343.2,714 333.8,714 324.5,712C323.7,711.2 322.8,710.3 322,709.5C321.3,664.5 321.3,619.5 322,574.5C322.4,573.9 322.9,573.6 323.5,573.5Z"/>
<path android:fillColor="#737e82" android:pathData="M413.5,687.5C414.5,691.6 414.8,696 414.5,700.5C423.8,700.5 433.2,700.5 442.5,700.5C433.3,701.5 424,701.8 414.5,701.5C414.2,701.5 413.8,701.5 413.5,701.5C413.5,698.5 413.5,695.5 413.5,692.5C413.5,690.8 413.5,689.2 413.5,687.5Z"/>
<path android:fillColor="#b6bfc2" android:pathData="M413.5,692.5C413.5,695.5 413.5,698.5 413.5,701.5C412.8,699 412.2,696.3 411.5,693.5C410.7,693.2 410,692.7 409.5,692C391.5,691.3 373.5,691.3 355.5,692C355,692.7 354.3,693.2 353.5,693.5C353.5,692.2 353.5,690.8 353.5,689.5C372.2,689.3 390.8,689.5 409.5,690C411,690.7 412.3,691.5 413.5,692.5Z"/>
<path android:fillColor="#a2aaae" android:pathData="M413.5,687.5C413.5,689.2 413.5,690.8 413.5,692.5C412.3,691.5 411,690.7 409.5,690C390.8,689.5 372.2,689.3 353.5,689.5C353.5,690.8 353.5,692.2 353.5,693.5C353.8,698.4 353.5,703 352.5,707.5C352.5,700.8 352.5,694.2 352.5,687.5C372.8,687.5 393.2,687.5 413.5,687.5Z"/>
<path android:fillColor="#a5aeb1" android:pathData="M547.5,645.5C546.6,651.6 544.5,657.3 541,662.5C533.3,672 525.1,681 516.5,689.5C515.1,689.3 513.8,689.5 512.5,690C507.8,694.5 502.5,698 496.5,700.5C486.3,703.7 475.9,706.7 465.5,709.5C461.2,709.8 459.2,707.8 459.5,703.5C460.6,704.6 461.4,705.9 462,707.5C490.8,703.1 514.8,690.1 534,668.5C539.2,661.2 543.7,653.5 547.5,645.5Z"/>
<path android:fillColor="#b8c2c6" android:pathData="M562.5,537.5C563.8,538.5 564.4,540 564.5,542C564.8,545.5 563.7,548.3 561,550.5C560.7,567.5 560.3,584.5 560,601.5C558.9,624.8 552.5,646.5 541,666.5C534.2,673 527.7,679.8 521.5,687C519.9,688.1 518.3,688.9 516.5,689.5C525.1,681 533.3,672 541,662.5C544.5,657.3 546.6,651.6 547.5,645.5C553,630.3 556.5,614.6 558,598.5C558.3,580.8 558.7,563.2 559,545.5C562.5,543.6 563.7,540.9 562.5,537.5Z"/>
<path android:fillColor="#b4bdc0" android:pathData="M323.5,575.5C323.5,619.5 323.5,663.5 323.5,707.5C333.2,707.5 342.8,707.5 352.5,707.5C352,708.5 351.4,709.3 350.5,710C341,711 331.6,710.5 322.5,708.5C322.2,664 322.5,619.7 323.5,575.5Z"/>
<path android:fillColor="#bcc5c9" android:pathData="M443.5,574.5C444.3,575.3 444.8,576.3 445,577.5C445.3,609.2 445.7,640.8 446,672.5C446.3,672.8 446.7,673.2 447,673.5C447.3,672.6 447.8,671.9 448.5,671.5C452.2,682.2 455.8,692.8 459.5,703.5C459.2,707.8 461.2,709.8 465.5,709.5C475.9,706.7 486.3,703.7 496.5,700.5C489.2,705.3 481.2,708.8 472.5,711C464.6,713.3 459.1,710.8 456,703.5C454.6,698.5 452.8,693.9 450.5,689.5C450.8,686.8 450.5,684.1 449.5,681.5C448,679.7 446.9,677.7 446,675.5C444.9,684.7 444.1,693.9 443.5,703C434.2,704.6 424.9,704.9 415.5,704C414.7,703.3 414.4,702.5 414.5,701.5C424,701.8 433.3,701.5 442.5,700.5C443.5,658.7 443.8,616.7 443.5,574.5Z"/>
<path android:fillColor="#8e999d" android:pathData="M323.5,570.5C363.3,569.5 403.3,569.2 443.5,569.5C443.5,571.2 443.5,572.8 443.5,574.5C443.8,616.7 443.5,658.7 442.5,700.5C442.5,657.2 442.5,613.8 442.5,570.5C402.8,570.5 363.2,570.5 323.5,570.5Z"/>
<path android:fillColor="#142c33" android:pathData="M323.5,570.5C363.2,570.5 402.8,570.5 442.5,570.5C442.5,613.8 442.5,657.2 442.5,700.5C433.2,700.5 423.8,700.5 414.5,700.5C414.8,696 414.5,691.6 413.5,687.5C393.2,687.5 372.8,687.5 352.5,687.5C352.5,694.2 352.5,700.8 352.5,707.5C342.8,707.5 333.2,707.5 323.5,707.5C323.5,663.5 323.5,619.5 323.5,575.5C323.5,574.8 323.5,574.2 323.5,573.5C323.5,572.5 323.5,571.5 323.5,570.5Z"/>
<path android:fillColor="#728084" android:pathData="M413.5,597.5C414.5,618.3 414.8,639.3 414.5,660.5C393.7,660.8 373,660.5 352.5,659.5C352.8,659.5 353.2,659.5 353.5,659.5C354.2,659.5 354.8,659.5 355.5,659.5C374.5,659.5 393.5,659.5 412.5,659.5C413.5,640 413.8,620.3 413.5,600.5C413.5,599.5 413.5,598.5 413.5,597.5Z"/>
<path android:fillColor="#bcc6ca" android:pathData="M413.5,600.5C413.8,620.3 413.5,640 412.5,659.5C412.8,640.8 412.5,622.2 411.5,603.5C393.2,602.3 374.9,602.2 356.5,603C356,603.5 355.5,604 355,604.5C354.2,622.9 354.3,641.2 355.5,659.5C354.8,659.5 354.2,659.5 353.5,659.5C353.2,639.9 353.5,620.4 354.5,601C364.4,599.9 374.4,599.4 384.5,599.5C394.2,599.5 403.9,599.9 413.5,600.5Z"/>
<path android:fillColor="#cfdade" android:pathData="M412.5,659.5C393.5,659.5 374.5,659.5 355.5,659.5C354.3,641.2 354.2,622.9 355,604.5C355.5,604 356,603.5 356.5,603C374.9,602.2 393.2,602.3 411.5,603.5C412.5,622.2 412.8,640.8 412.5,659.5Z"/>
<path android:fillColor="#d7e2e6" android:pathData="M358.5,605.5C375.2,605.5 391.8,605.5 408.5,605.5C408.5,622.8 408.5,640.2 408.5,657.5C391.6,657.8 374.8,657.5 358,656.5C357.2,639.4 357.3,622.4 358.5,605.5Z"/>
<path android:fillColor="#a0a8ab" android:pathData="M413.5,597.5C413.5,598.5 413.5,599.5 413.5,600.5C403.9,599.9 394.2,599.5 384.5,599.5C374.4,599.4 364.4,599.9 354.5,601C353.5,620.4 353.2,639.9 353.5,659.5C353.2,659.5 352.8,659.5 352.5,659.5C352.5,638.8 352.5,618.2 352.5,597.5C372.8,597.5 393.2,597.5 413.5,597.5Z"/>
<path android:fillColor="#8e999d" android:pathData="M557.5,536.5C546.5,536.5 535.5,536.5 524.5,536.5C524.8,555 524.5,573.3 523.5,591.5C523.5,573.8 523.5,556.2 523.5,538.5C523.5,537.5 523.5,536.5 523.5,535.5C535,535.2 546.3,535.5 557.5,536.5Z"/>
<path android:fillColor="#c4ced2" android:pathData="M523.5,538.5C523.5,556.2 523.5,573.8 523.5,591.5C523.6,605.4 520.6,618.4 514.5,630.5C515.7,624.8 517.2,619.1 519,613.5C520.2,589.5 520.8,565.5 521,541.5C521.4,540.1 522.2,539.1 523.5,538.5Z"/>
<path android:fillColor="#142c33" android:pathData="M557.5,536.5C559.2,536.6 560.9,537 562.5,537.5C563.7,540.9 562.5,543.6 559,545.5C558.7,563.2 558.3,580.8 558,598.5C556.5,614.6 553,630.3 547.5,645.5C543.7,653.5 539.2,661.2 534,668.5C514.8,690.1 490.8,703.1 462,707.5C461.4,705.9 460.6,704.6 459.5,703.5C455.8,692.8 452.2,682.2 448.5,671.5C457.6,671.4 466.2,669.7 474.5,666.5C478.9,665.6 482.9,663.9 486.5,661.5C499.4,654.3 508.7,644 514.5,630.5C520.6,618.4 523.6,605.4 523.5,591.5C524.5,573.3 524.8,555 524.5,536.5C535.5,536.5 546.5,536.5 557.5,536.5Z"/>
<path android:fillColor="#cad8dc" android:pathData="M601.5,841.5C553.1,855.4 504.1,858.1 454.5,849.5C488.9,853.4 523.2,853.2 557.5,849C572.2,846.4 586.8,843.9 601.5,841.5Z"/>
</vector>
Binary file not shown.

Before

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>
@@ -5,21 +5,19 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/colorPrimaryDark"
android:background="@color/splashscreen_background"
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"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="130dp"
app:lottie_rawRes="@raw/loading"
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" />
<background android:drawable="@mipmap/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,5 @@
<?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"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 874 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long
+1 -2
View File
@@ -1,10 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<color name="iconBackground">#FFFFFF</color>
<color name="splashscreen_background">#202125</color>
<color name="splashscreen_background">#1F2024</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"?>
<resources>
<color name="ic_launcher_background">#132C33</color>
</resources>
+8 -2
View File
@@ -3,14 +3,17 @@
encoding="UTF-8"
standalone="yes"
?>
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:textColor">#000000</item>
<!-- 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>
<item name="colorControlActivated">#2596be</item>
</style>
<style name="SplashScreenTheme" parent="SplashScreen_SplashTheme">
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
<item name="android:windowActionBar">false</item>
@@ -21,6 +24,9 @@
<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="android:windowSplashScreenBackground" tools:targetApi="s">@color/colorPrimaryDark</item>
<item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s" >@drawable/invisible</item>
<item name="android:windowSplashScreenAnimationDuration" tools:targetApi="s">1000</item>
<!-- <item name="icon">@drawable/no_icon</item> -->
<item name="android:windowBackground">@drawable/splashscreen</item>
</style>
@@ -0,0 +1,20 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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.rajarsheechatterjee.LNReader;
import android.content.Context;
import com.facebook.react.ReactInstanceManager;
/**
* Class responsible of loading Flipper inside your React Native application. This is the release
* flavor of it so it's empty as we don't want to load Flipper.
*/
public class ReactNativeFlipper {
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
// Do nothing as we don't want to initialize Flipper on Release.
}
}
+12 -41
View File
@@ -1,53 +1,24 @@
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 = "29.0.3"
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "21.1.6352462"
compileSdkVersion = 33
targetSdkVersion = 33
kotlinVersion = "1.8.10"
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
ndkVersion = "23.1.7779620"
}
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:4.1.0")
// 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"
}
}
}
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
google()
jcenter()
maven { url 'https://www.jitpack.io' }
classpath("com.android.tools.build:gradle")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

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