Compare commits

...

55 Commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* implemented new bottomsheets

* set containerHeight

* NovelBottomSheet fix

* fixed BottomInsets

* edited FilterBottomSheet + cleanup

* TrackSheet + cleanup

* removal of rn-sliding-up-panel

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

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

* getLoadingColors

* UpdatesSkeletonLoading

* Loading for Migrate

* HistorySkeletonLoading

* HistoryScreen fix

* HistoryScreen fix

* GlobalSearchLoading name change

* SourceScreenSkeletonLoading + implementation in Library + set unread fix

* set SkeletonLoading for LoadingMoreIndicator

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

* started with skeleton loading

* added new package

* added way to adebt to reader styles

* MAL loading

* type fix

* SourceScreenLoading

* NovelScreenLoading

* ESLint fix

* cleanup

* resolved conflict

* GlobalSearchLoading

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

* animated splashscreen

* added lotti animation

* splashscreen centered

* upgrade to android sdk 31

* reverted sdk changes

* fixed unstyled bars

* fixed animation

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

* added FAB to Library screen + some insets

* Added Settings butten

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

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

* started to change jumpToChapterModal

* finished JumpToChapterModal redesign

* delete of results and no result if no text

* edited listElement width

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

* design changes & bugfixes

* added translations

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* started with the implementation of chapter_Drawer

* changed data transer via route and the navigation between chapters

* changed ReadButton and butten at the end of chapter

* changed ReadButton and butten at the end of chapter

* changed ReadButton and butten at the end of chapter

* worked on behaviour, added buttons and styled

* fixed Buttons

* highlighted current chapter

* fixed bug caused of fast scrolling

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

* fixed gradients in chapter list

* changed GestureRecognizer back

* shorted given props at chapter change

* fixed and changed scroll behaviour

* added Translation

* Changed drawer chapter naming

* review

* added chapterDrawer setting

* unessecary

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

* changed setting name "chapterDrawer" to "useChapterDrawerSwipeNavigation"

* put useNovel Hook in chapterDrawer and reverted navigate functions

* reverted changes from original ReaderScreen line 356-359

* removed Chapter transformation

* fixed switch between chapters and mistake in switch

* unified nacigation Params + changed name and location of handleNavigateChapterParams

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

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

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

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

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

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

* fix(delete_chapter): always-coccurent deletion

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

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

* fix image

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

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

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

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

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

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

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

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

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

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

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

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

* fix

* prettier

* test

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

* added time stamp to backup file

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

* Fix comment
2022-09-10 17:18:27 +05:30
Rajarshee Chatterjee 78165b072e Release v1.1.13 2022-09-02 09:30:49 +05:30
191 changed files with 5963 additions and 2790 deletions
+3 -3
View File
@@ -45,7 +45,7 @@ body:
label: LNReader version
description: You can find your LNReader version in **More → About**.
placeholder: |
Example: "1.1.12"
Example: "1.1.14"
validations:
required: true
@@ -86,9 +86,9 @@ body:
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with an source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[1.1.12](https://github.com/lnreader/lnreader/releases/latest)**.
- label: I have updated the app to version **[1.1.14](https://github.com/lnreader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+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.12](https://github.com/LNReader/lnreader/releases/latest)**.
- label: I have updated the app to version **[1.1.14](https://github.com/LNReader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+11
View File
@@ -33,3 +33,14 @@ jobs:
- name: Build Android Release
run: |
cd android && ./gradlew assembleRelease --no-daemon
- name: Set Env
run: |
set -x
echo "COMMIT_COUNT=$(git rev-list --count HEAD)" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: LNReader-r${{ env.COMMIT_COUNT }}
path: android/app/build/outputs/apk/release/app-release.apk
+19 -12
View File
@@ -1,3 +1,4 @@
import 'react-native-gesture-handler';
import { enableFreeze } from 'react-native-screens';
enableFreeze(true);
@@ -9,30 +10,36 @@ import { Provider as PaperProvider } from 'react-native-paper';
import { Provider } from 'react-redux';
import { persistor, store } from './src/redux/store';
import { PersistGate } from 'redux-persist/lib/integration/react';
import * as SplashScreen from 'expo-splash-screen';
import Main from './src/navigators/Main';
import { createDatabase } from './src/database/db';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import LottieSplashScreen from 'react-native-lottie-splash-screen';
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const App = () => {
useEffect(() => {
SplashScreen.preventAutoHideAsync();
LottieSplashScreen.hide();
createDatabase();
}, []);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AppErrorBoundary>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
</AppErrorBoundary>
</GestureHandlerRootView>
);
};
+10 -3
View File
@@ -122,6 +122,12 @@ def jscFlavor = 'org.webkit:android-jsc:+'
*/
def enableHermes = project.ext.react.get("enableHermes", false);
ext {
versionMajor = 1
versionMinor = 1
versionPatch = 14
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
@@ -134,8 +140,9 @@ android {
applicationId 'com.rajarsheechatterjee.LNReader'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.12"
// Generated version code. Supports versions up to 1024.1024.2048
versionCode ((((versionMajor << 10) | versionMinor) << 11) | versionPatch)
versionName "$versionMajor.$versionMinor.$versionPatch"
}
splits {
abi {
@@ -187,7 +194,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation project(':react-native-lottie-splash-screen')
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
+2
View File
@@ -8,6 +8,8 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }
-keep public class com.dylanvann.fastimage.* {*;}
+38 -39
View File
@@ -1,26 +1,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<!-- Storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<queries>
<intent>
@@ -28,24 +28,23 @@
</intent>
</queries>
<application
android:name=".MainApplication"
<application
android:name=".MainApplication"
android:largeHeap="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<meta-data
android:name="expo.modules.notifications.default_notification_icon"
android:resource="@drawable/notification_icon"/>
android:resource="@drawable/notification_icon" />
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color"/>
android:resource="@color/notification_icon_color" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
@@ -54,24 +53,24 @@
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.App.SplashScreen"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="lnreader"/>
<data android:scheme="com.rajarsheechatterjee.LNReader"/>
</intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="lnreader" />
<data android:scheme="com.rajarsheechatterjee.LNReader" />
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
</manifest>
</application>
</manifest>
@@ -1,15 +1,14 @@
package com.rajarsheechatterjee.LNReader;
import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle;
import android.content.res.Configuration;
import android.graphics.Color;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
@@ -17,9 +16,7 @@ import com.facebook.react.ReactRootView;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import expo.modules.splashscreen.singletons.SplashScreen;
import expo.modules.splashscreen.SplashScreenImageResizeMode;
import org.devio.rn.splashscreen.SplashScreen;
public class MainActivity extends ReactActivity {
@@ -35,7 +32,8 @@ public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
// Below line is handled by '@expo/configure-splash-screen' command and it's
// discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
@@ -45,11 +43,11 @@ public class MainActivity extends ReactActivity {
Window w = getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
w.getDecorView()
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
SplashScreen.show(this,R.style.SplashScreenTheme, R.id.lottie);
SplashScreen.setAnimationFinished(true);
super.onCreate(null);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
}
@Override
@@ -69,7 +67,7 @@ public class MainActivity extends ReactActivity {
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
//voldown
// voldown
VolumeButtonListener.down();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
@@ -21,10 +21,11 @@ import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.devio.rn.splashscreen.SplashScreenReactPackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList()
);
new BasePackageList().getPackageList());
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
@@ -66,7 +67,8 @@ public class MainApplication extends Application implements ReactApplication {
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* Loads Flipper in React Native templates. Call this in the onCreate method
* with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
@@ -77,9 +79,9 @@ public class MainApplication extends Application implements ReactApplication {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
* We use reflection here to pick up the class that initializes Flipper,
* since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.rajarsheechatterjee.LNReader.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
@@ -20,7 +20,6 @@ public class BasePackageList {
new expo.modules.localization.LocalizationPackage(),
new expo.modules.notifications.NotificationsPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.splashscreen.SplashScreenPackage(),
new expo.modules.sqlite.SQLitePackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 67 KiB

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

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long
@@ -6,4 +6,5 @@
<color name="colorPrimaryDark">#202125</color>
<color name="navigationBarColor">#202125</color>
<color name="notification_icon_color">#00adb5</color>
<color name="ic_launcher_background">#222831</color>
</resources>
+11 -7
View File
@@ -8,16 +8,20 @@
<item name="android:textColor">#000000</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar">
<style name="SplashScreenTheme" parent="SplashScreen_SplashTheme">
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
</style>
<style name="Theme.App.SplashScreen" parent="AppTheme">
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<!-- Customize your splash screen theme here -->
<!-- <item name="icon">@drawable/no_icon</item> -->
<item name="android:windowBackground">@drawable/splashscreen</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
</style>
</resources>
+14
View File
@@ -22,6 +22,20 @@ buildscript {
allprojects {
repositories {
exclusiveContent {
// We get React Native's Android binaries exclusively through npm,
// from a local Maven repo inside node_modules/react-native/.
// (The use of exclusiveContent prevents looking elsewhere like Maven Central
// and potentially getting a wrong version.)
filter {
includeGroup "com.facebook.react"
}
forRepository {
maven {
url "$rootDir/../node_modules/react-native/android"
}
}
}
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+3
View File
@@ -6,4 +6,7 @@ includeUnimodulesProjects()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':react-native-lottie-splash-screen'
project(':react-native-lottie-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-lottie-splash-screen/android')
include ':app'
+1
View File
@@ -20,6 +20,7 @@ module.exports = function (api) {
},
},
],
'react-native-reanimated/plugin',
],
};
};
+687 -876
View File
File diff suppressed because it is too large Load Diff
+14 -8
View File
@@ -1,6 +1,6 @@
{
"name": "lnreader",
"version": "1.1.12",
"version": "1.1.14",
"private": true,
"scripts": {
"android": "react-native run-android",
@@ -8,18 +8,22 @@
"start": "react-native start",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"prettier": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\"",
"buildRelease": "cd android && gradlew clean && gradlew assembleRelease",
"buildRelease": "cd android && ./gradlew clean && ./gradlew assembleRelease",
"open": "open ./android/app/build/outputs/apk/release/",
"sources:madara": "node scripts/generateMadaraSource.cjs",
"source:generator": "node scripts/generateSource.cjs",
"strings": "node scripts/stringTypes.cjs",
"prepare": "husky install"
},
"dependencies": {
"@gorhom/bottom-sheet": "^4.4.5",
"@react-native-async-storage/async-storage": "^1.15.11",
"@react-native-community/clipboard": "^1.5.1",
"@react-native-community/masked-view": "^0.1.11",
"@react-native-community/slider": "^4.1.12",
"@react-native-cookies/cookies": "^6.2.1",
"@react-native-picker/picker": "^2.4.1",
"@react-navigation/drawer": "^6.0.1",
"@react-navigation/material-bottom-tabs": "^6.0.9",
"@react-navigation/native": "^6.0.6",
"@react-navigation/stack": "^6.0.11",
@@ -38,29 +42,32 @@
"expo-linking": "~2.2.3",
"expo-localization": "~10.1.0",
"expo-notifications": "~0.11.6",
"expo-splash-screen": "^0.11.4",
"expo-sqlite": "~9.1.0",
"expo-web-browser": "~9.1.0",
"i18n-js": "^3.8.0",
"lodash": "^4.17.21",
"lottie-react-native": "^5.1.4",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"qs": "^6.10.3",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "^0.64.3",
"react-native-background-actions": "^2.6.5",
"react-native-device-info": "^8.4.7",
"react-native-easing-gradient": "^1.0.0",
"react-native-error-boundary": "^1.1.16",
"react-native-fast-image": "^8.5.11",
"react-native-gesture-handler": "~1.10.2",
"react-native-gesture-handler": "~2.1.3",
"react-native-lottie-splash-screen": "^1.0.1",
"react-native-markdown-display": "^7.0.0-alpha.2",
"react-native-mmkv": "^2.4.3",
"react-native-pager-view": "^5.4.9",
"react-native-paper": "^5.0.0-rc.4",
"react-native-reanimated": "^2.2.4",
"react-native-paper": "^5.1.2",
"react-native-reanimated": "^2.13.0",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.9.0",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.1.1",
"react-native-tts": "^4.1.0",
@@ -71,7 +78,6 @@
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"rn-fetch-blob": "^0.12.0",
"rn-sliding-up-panel": "^2.4.4",
"sanitize-html": "^2.7.0"
},
"devDependencies": {
@@ -0,0 +1,98 @@
import React from 'react';
import { StyleSheet, View, Text, StatusBar } from 'react-native';
import ErrorBoundary from 'react-native-error-boundary';
import { Button, List } from '@components';
import { useTheme } from '@hooks';
import { ButtonVariation } from '@components/Button/Button';
interface ErrorFallbackProps {
error: Error;
resetError: () => void;
}
export const ErrorFallback: React.FC<ErrorFallbackProps> = ({
error,
resetError,
}) => {
const theme = useTheme();
return (
<View style={[styles.mainCtn, { backgroundColor: theme.background }]}>
<StatusBar translucent={true} backgroundColor="transparent" />
<View style={styles.errorInfoCtn}>
<Text style={[styles.errorTitle, { color: theme.onSurface }]}>
An Unexpected Error Ocurred
</Text>
<Text style={[styles.errorDesc, { color: theme.onSurface }]}>
The application ran into an unexpected error. We suggest you
screenshot this message and then share it in our support channel on
Discord.
</Text>
<Text
style={[
styles.errorCtn,
{
backgroundColor: theme.surfaceVariant,
color: theme.onSurfaceVariant,
},
]}
numberOfLines={20}
>
{`${error.message}\n\n${error.stack}`}
</Text>
</View>
<List.Divider theme={theme} />
<Button
variation={ButtonVariation.DEFAULT}
onPress={resetError}
title={'Restart the application'}
style={styles.buttonCtn}
theme={theme}
/>
</View>
);
};
interface AppErrorBoundaryProps {
children: React.ReactNode | React.ReactElement;
}
const AppErrorBoundary: React.FC<AppErrorBoundaryProps> = ({ children }) => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>{children}</ErrorBoundary>
);
};
export default AppErrorBoundary;
const styles = StyleSheet.create({
mainCtn: {
flex: 1,
},
errorInfoCtn: {
flex: 1,
justifyContent: 'center',
padding: 16,
},
buttonCtn: {
margin: 16,
marginBottom: 32,
},
errorTitle: {
marginBottom: 8,
textAlign: 'center',
fontSize: 20,
},
errorDesc: {
lineHeight: 20,
marginVertical: 8,
},
errorCtn: {
borderRadius: 8,
lineHeight: 20,
marginVertical: 16,
paddingVertical: 16,
paddingHorizontal: 8,
},
});
@@ -0,0 +1,49 @@
import React, { ReactElement, Ref, useCallback } from 'react';
import { overlay } from 'react-native-paper';
import { default as BS, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MD3ThemeType } from '@theme/types';
import { StyleSheet } from 'react-native';
interface BottomSheetProps {
bottomSheetRef: Ref<BS>;
snapPoints: Array<number>;
height: number;
theme: MD3ThemeType;
children: ReactElement;
}
const BottomSheet: React.FC<BottomSheetProps> = ({
bottomSheetRef,
snapPoints,
height,
theme,
children,
}) => {
const { bottom } = useSafeAreaInsets();
const renderBackdrop = useCallback(
props => <BottomSheetBackdrop {...props} disappearsOnIndex={0} />,
[],
);
return (
<BS
index={-1}
backdropComponent={renderBackdrop}
snapPoints={[0.1].concat(snapPoints)}
enablePanDownToClose={true}
ref={bottomSheetRef}
handleStyle={styles.handle}
containerHeight={height + bottom}
containerStyle={{ paddingBottom: bottom }}
backgroundStyle={{ backgroundColor: overlay(2, theme.surface) }}
>
{children}
</BS>
);
};
const styles = StyleSheet.create({
handle: {
display: 'none',
},
});
export default BottomSheet;
+1 -1
View File
@@ -18,7 +18,7 @@ const ScreenContainer = ({ children, theme }) => {
);
};
const Row = ({ children, style }) => (
const Row = ({ children, style = {} }) => (
<View style={[styles.row, style]}>{children}</View>
);
+16 -14
View File
@@ -28,24 +28,26 @@ const ListItem = ({
}) => (
<PaperList.Item
title={title}
style={style}
style={[{ paddingHorizontal: 16, paddingVertical: 12 }, style]}
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
descriptionNumberOfLines={1}
left={() => (
<View style={{ justifyContent: 'center' }}>
{icon && (
<PaperList.Icon
color={theme.primary}
icon={icon}
style={{
marginVertical: 0,
}}
/>
)}
</View>
)}
left={
icon
? () => (
<View style={{ justifyContent: 'center' }}>
<PaperList.Icon
color={theme.primary}
icon={icon}
style={{
marginVertical: 0,
}}
/>
</View>
)
: undefined
}
right={() =>
right && (
<MaterialCommunityIcons
+21 -12
View File
@@ -1,6 +1,7 @@
import React, { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import { StyleSheet, Text, View } from 'react-native';
import color from 'color';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import { MD3ThemeType } from '../../theme/types';
@@ -66,21 +67,23 @@ const Divider = ({ theme }: { theme: MD3ThemeType }) => (
const InfoItem = ({
title,
icon,
icon = 'information-outline',
theme,
}: {
title: string;
icon: string;
theme: MD3ThemeType;
}) => (
<PaperList.Item
title={title}
titleStyle={[{ color: theme.textColorSecondary }, styles.infoItem]}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
<View style={styles.infoCtn}>
<MaterialIcon size={20} color={theme.onSurfaceVariant} name={icon} />
<Text style={[styles.infoMsg, { color: theme.onSurfaceVariant }]}>
{title}
</Text>
</View>
);
const Icon = ({ icon, theme }: { icon: string; theme: MD3ThemeType }) => (
<PaperList.Icon color={theme.primary} icon={icon} style={{ margin: 0 }} />
);
export default {
@@ -89,6 +92,7 @@ export default {
Item,
Divider,
InfoItem,
Icon,
};
const styles = StyleSheet.create({
@@ -105,7 +109,12 @@ const styles = StyleSheet.create({
icon: {
marginVertical: 0,
},
infoItem: {
fontSize: 14,
infoCtn: {
paddingVertical: 12,
paddingHorizontal: 16,
},
infoMsg: {
marginTop: 12,
fontSize: 12,
},
});
+6 -2
View File
@@ -40,8 +40,10 @@ const Searchbar: React.FC<SearcbarProps> = ({
const searchbarRef = useRef<any>(null);
const focusSearchbar = () => searchbarRef.current.focus();
const { top } = useSafeAreaInsets();
const { top, right, left } = useSafeAreaInsets();
const marginTop = top + 8;
const marginRight = right + 16;
const marginLeft = left + 16;
return (
<View
@@ -49,6 +51,8 @@ const Searchbar: React.FC<SearcbarProps> = ({
styles.searchbarContainer,
{
marginTop,
marginRight,
marginLeft,
backgroundColor: color(theme.primary).alpha(0.08).string(),
},
]}
@@ -108,7 +112,7 @@ const styles = StyleSheet.create({
searchbarContainer: {
marginHorizontal: 16,
marginBottom: 12,
minHeight: 46,
minHeight: 56,
borderRadius: 50,
overflow: 'hidden',
zIndex: 1,
+60 -41
View File
@@ -9,44 +9,33 @@ import { txnErrorCallback } from '@database/utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const insertChaptersQuery =
'INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)';
const insertChaptersQuery = `
INSERT INTO chapters (
chapterUrl, chapterName, releaseDate,
novelId
)
values
(?, ?, ?, ?)
`;
export const insertChapters = async (
novelId: number,
chapters: ChapterItem[],
chapters?: ChapterItem[],
) => {
db.transaction(
tx => {
chapters.map(chapter =>
tx.executeSql(
insertChaptersQuery,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
],
(_txObj, _res) => {},
(_txObj, _error) => false,
// console.log(
// "Error ",
// error,
// " Chapter URL: ",
// chapter.chapterUrl,
// " Novel ID ",
// novelId
// )
),
);
},
_err => {
// console.log('Error', err);
},
() => {
// console.log('Success ')
},
);
if (!chapters?.length) {
return;
}
db.transaction(tx => {
chapters.map(chapter =>
tx.executeSql(insertChaptersQuery, [
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
]),
);
});
};
const getChaptersQuery = (sort = 'ORDER BY chapterId ASC', filter = '') =>
@@ -62,10 +51,7 @@ export const getChapters = (novelId: number, sort: string, filter: string) => {
const _array = (rows as any)._array;
resolve(_array);
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
txnErrorCallback,
);
}),
);
@@ -261,7 +247,10 @@ const createImageFolder = async (
await RNFetchBlob.fs.mkdir(p);
}
};
await mkdirIfNot(path);
await RNFetchBlob.fs.createFile(path, '.nomedia', 'utf8');
if (data) {
const { sourceId, novelId, chapterId } = data;
await mkdirIfNot(`${path}/${sourceId}/`);
@@ -378,9 +367,7 @@ const deleteDownloadedImages = async (
}
}
}
} catch (error) {
showToast(error.message);
}
} catch (error) {}
};
export const deleteChapter = async (
@@ -418,6 +405,38 @@ export const deleteChapter = async (
});
};
export const deleteChapters = async (
sourceId: number,
chapters?: ChapterItem[],
) => {
if (!chapters?.length) {
return;
}
const chapterIdsString = chapters
?.map(chapter => chapter.chapterId)
.toString();
const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE chapterId IN (${chapterIdsString})`;
const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId IN (${chapterIdsString})`;
await Promise.all(
chapters?.map(chapter =>
deleteDownloadedImages(sourceId, chapter.novelId, chapter.chapterId),
),
);
db.transaction(tx => {
tx.executeSql(
updateIsDownloadedQuery,
undefined,
undefined,
txnErrorCallback,
);
tx.executeSql(deleteChapterQuery, undefined, undefined, txnErrorCallback);
});
};
const getLastReadChapterQuery = `
SELECT chapters.*
FROM history
+5 -4
View File
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { txnErrorCallback } from '@database/utils/helpers';
import * as SQLite from 'expo-sqlite';
import { showToast } from '../../hooks/showToast';
import { sourceManager } from '../../sources/sourceManager';
@@ -36,7 +37,7 @@ export const fetchAndInsertChapterInDb = async (
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(txObj, res) => {},
(_txObj, error) => showToast(error),
txnErrorCallback,
);
});
};
@@ -55,7 +56,7 @@ export const deleteChapterFromDb = (chapterId: number) => {
deleteChapterFromDbQuery,
[chapterId],
(txObj, res) => {},
(_txObj, error) => showToast(error.message),
txnErrorCallback,
);
});
};
@@ -122,13 +123,13 @@ export const deleteReadChaptersFromDb = (chapterId: number) => {
updateChaptersDeletedQuery,
[],
(txObj, res) => {},
(txObj, error) => showToast(error.message),
txnErrorCallback,
);
tx.executeSql(
deleteReadChaptersFromDbQuery,
[],
(txObj, res) => showToast('Deleted read chapters.'),
(txObj, error) => showToast(error.message),
txnErrorCallback,
);
});
};
+1 -1
View File
@@ -143,7 +143,7 @@ export const restoreLibrary = async novel => {
async (txObj, { insertId }) => {
const chapters = await fetchChapters(novel.sourceId, novel.novelUrl);
if (chapters) {
if (chapters.length) {
await insertChapters(insertId, chapters);
resolve();
+178
View File
@@ -0,0 +1,178 @@
import * as SQLite from 'expo-sqlite';
import { countBy } from 'lodash';
import { LibraryStats } from '../types';
import { txnErrorCallback } from '../utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryStatsQuery = `
SELECT COUNT(*) as novelsCount, COUNT(DISTINCT sourceId) as sourcesCount
FROM novels
WHERE novels.followed = 1
`;
const getChaptersReadCountQuery = `
SELECT COUNT(*) as chaptersRead
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.read = 1 AND novels.followed = 1
`;
const getChaptersTotalCountQuery = `
SELECT COUNT(*) as chaptersCount
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE novels.followed = 1
`;
const getChaptersUnreadCountQuery = `
SELECT COUNT(*) as chaptersUnread
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.read = 0 AND novels.followed = 1
`;
const getChaptersDownloadedCountQuery = `
SELECT COUNT(*) as chaptersDownloaded
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.downloaded = 1 AND novels.followed = 1
`;
const getNovelGenresQuery = `
SELECT genre
FROM novels
WHERE novels.followed = 1
`;
const getNovelStatusQuery = `
SELECT status
FROM novels
WHERE novels.followed = 1
`;
export const getLibraryStatsFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getLibraryStatsQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersTotalCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersTotalCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersReadCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersReadCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersUnreadCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersUnreadCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersDownloadedCountFromDb =
async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersDownloadedCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getNovelGenresFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getNovelGenresQuery,
undefined,
(txObj, { rows: { _array } }) => {
let genres: string[] = [];
_array.forEach((item: { genre: string }) => {
const novelGenres = item.genre?.split(/\s*,\s*/);
if (novelGenres?.length) {
genres.push(...novelGenres);
}
});
resolve({ genres: countBy(genres) });
},
txnErrorCallback,
);
});
});
};
export const getNovelStatusFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getNovelStatusQuery,
undefined,
(txObj, { rows: { _array } }) => {
let status: string[] = [];
_array.forEach((item: { status: string }) => {
status.push(item.status);
});
resolve({ status: countBy(status) });
},
txnErrorCallback,
);
});
});
};
+1 -1
View File
@@ -35,7 +35,7 @@ const getUpdatesFromDbQuery = `
WHERE
date(updates.updateTime) > date('now','-3 months')
ORDER BY
updates.updateTime DESC, chapters.releaseDate DESC
updates.updateTime DESC, chapters.chapterName DESC, chapters.releaseDate DESC
`;
export const getUpdatesFromDb = async (): Promise<Update[]> => {
+12
View File
@@ -11,6 +11,7 @@ export interface NovelInfo {
author?: string;
status?: string;
followed: number;
categoryIds: string;
}
export interface LibraryNovelInfo extends NovelInfo {
@@ -74,3 +75,14 @@ export interface Category {
lastUpdatedAt: Date;
sort: number | null;
}
export interface LibraryStats {
novelsCount?: number;
chaptersCount?: number;
chaptersRead?: number;
chaptersUnread?: number;
chaptersDownloaded?: number;
sourcesCount?: number;
genres?: Record<string, number>;
status?: Record<string, number>;
}
+62
View File
@@ -0,0 +1,62 @@
import { useMMKVObject } from 'react-native-mmkv';
import { MMKVStorage } from '@utils/mmkv/mmkv';
import { showToast } from './showToast';
export const SOURCE_STORAGE = 'SOURCE_STORAGE';
interface SourceStorage {
cookies?: any;
}
export type SourceStorageMap = Record<number, Partial<SourceStorage>>;
const useSourceStorage = ({ sourceId = -1 }: { sourceId?: number }) => {
const [values, setValues] = useMMKVObject<SourceStorageMap>(
SOURCE_STORAGE,
MMKVStorage,
);
const setSourceStorage = (key: keyof SourceStorage, value: any): void => {
setValues({
...values,
[sourceId]: {
...values?.[sourceId],
[key]: value,
},
});
};
const clearCookies = (): void => {
// let tempStorage = {};
// if (values) {
// tempStorage = Object.fromEntries(
// Object.entries(values).map(([id, sourceData]) => {
// delete sourceData.cookies;
// return [id, sourceData];
// }),
// );
// }
setValues({});
showToast('Cookies cleared');
};
const sourceStorage = values?.[sourceId];
return {
...sourceStorage,
setSourceStorage,
clearCookies,
};
};
export default useSourceStorage;
export const getSourceStorage = (sourceId: number) => {
const rawSettings = MMKVStorage.getString(SOURCE_STORAGE) || '{}';
const parsedSettings: Partial<SourceStorageMap> = JSON.parse(rawSettings);
return parsedSettings[sourceId] || {};
};
+5 -2
View File
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
import Tts from 'react-native-tts';
import { htmlToText } from '../sources/helpers/htmlToText';
export const useTextToSpeech = html => {
export const useTextToSpeech = (html, markChapterAdRead) => {
const [ttsStatus, setTtsStatus] = useState();
const [ttsPosition, setTtsPosition] = useState({ end: 0 });
@@ -14,7 +14,10 @@ export const useTextToSpeech = html => {
setTtsStatus('progress');
setTtsPosition(event);
});
Tts.addEventListener('tts-finish', () => setTtsStatus('finish'));
Tts.addEventListener('tts-finish', () => {
setTtsStatus('finish');
markChapterAdRead();
});
Tts.addEventListener('tts-cancel', () => setTtsStatus('paused'));
return () => Tts.stop();
@@ -1,211 +0,0 @@
import { SafeAreaProviderCompat } from '@react-navigation/elements';
import {
MaterialBottomTabDescriptorMap,
MaterialBottomTabNavigationConfig,
MaterialBottomTabNavigationHelpers,
} from '@react-navigation/material-bottom-tabs/lib/typescript/src/types';
import {
CommonActions,
Link,
ParamListBase,
Route,
TabNavigationState,
useLinkBuilder,
} from '@react-navigation/native';
import * as React from 'react';
import { Platform, StyleSheet, Text } from 'react-native';
import { BottomNavigation } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
type Props = MaterialBottomTabNavigationConfig & {
state: TabNavigationState<ParamListBase>;
navigation: MaterialBottomTabNavigationHelpers;
descriptors: MaterialBottomTabDescriptorMap;
};
type Scene = { route: { key: string } };
// Optionally require vector-icons referenced from react-native-paper:
// https://github.com/callstack/react-native-paper/blob/4b26429c49053eaa4c3e0fae208639e01093fa87/src/components/MaterialCommunityIcon.tsx#L14
let MaterialCommunityIcons: React.ComponentType<
React.ComponentProps<
typeof import('react-native-vector-icons/MaterialCommunityIcons').default
>
>;
try {
// Optionally require vector-icons
MaterialCommunityIcons =
require('react-native-vector-icons/MaterialCommunityIcons').default;
} catch (e: any) {
let isErrorLogged = false;
// Fallback component for icons
MaterialCommunityIcons = ({
name,
color,
size,
selectionColor: _0,
onLayout: _1,
...rest
}) => {
if (!isErrorLogged) {
if (
!/(Cannot find module|Module not found|Cannot resolve module)/.test(
e.message,
)
) {
console.error(e);
}
console.warn(
`Tried to use the icon '${name}' in a component from '@react-navigation/material-bottom-tabs', but 'react-native-vector-icons/MaterialCommunityIcons' could not be loaded.`,
"To remove this warning, try installing 'react-native-vector-icons' or use another method to specify icon: https://reactnavigation.org/docs/material-bottom-tab-navigator/#tabbaricon.",
);
isErrorLogged = true;
}
return (
// @ts-expect-error: we're passing icon props to text, but we don't care about it since it's just fallback
<Text
{...rest}
style={[
styles.icon,
{
color: typeof color !== 'number' ? color : undefined,
fontSize: size,
},
]}
>
□
</Text>
);
};
}
function MaterialBottomTabViewInner({
state,
navigation,
descriptors,
...rest
}: Props) {
const insets = useSafeAreaInsets();
const buildLink = useLinkBuilder();
return (
<BottomNavigation
{...rest}
navigationState={state}
onIndexChange={(index: number) =>
navigation.dispatch({
...CommonActions.navigate({
name: state.routes[index].name,
merge: true,
}),
target: state.key,
})
}
renderScene={({ route }) => descriptors[route.key].render()}
renderTouchable={
Platform.OS === 'web'
? ({
onPress,
route,
accessibilityRole: _0,
borderless: _1,
centered: _2,
rippleColor: _3,
style,
...rest
}) => {
return (
<Link
{...rest}
// @ts-expect-error: to could be undefined, but it doesn't affect functionality
to={buildLink(route.name, route.params)}
accessibilityRole="link"
onPress={(e: any) => {
if (
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
(e.button == null || e.button === 0) // ignore everything but left clicks
) {
e.preventDefault();
onPress?.(e);
}
}}
style={[styles.touchable, style]}
/>
);
}
: undefined
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
if (typeof options.tabBarIcon === 'string') {
return (
<MaterialCommunityIcons
name={options.tabBarIcon}
color={color}
size={24}
style={styles.icon}
/>
);
}
if (typeof options.tabBarIcon === 'function') {
return options.tabBarIcon({ focused, color });
}
return null;
}}
getLabelText={({ route }: Scene) => {
const { options } = descriptors[route.key];
return options.tabBarLabel !== undefined
? options.tabBarLabel
: options.title !== undefined
? options.title
: (route as Route<string>).name;
}}
getColor={({ route }) => descriptors[route.key].options.tabBarColor}
getBadge={({ route }) => descriptors[route.key].options.tabBarBadge}
getAccessibilityLabel={({ route }) =>
descriptors[route.key].options.tabBarAccessibilityLabel
}
getTestID={({ route }) => descriptors[route.key].options.tabBarTestID}
onTabPress={({ route, preventDefault }) => {
const event = navigation.emit({
type: 'tabPress',
target: route.key,
canPreventDefault: true,
});
if (event.defaultPrevented) {
preventDefault();
}
}}
safeAreaInsets={insets}
/>
);
}
export default function MaterialBottomTabView(props: Props) {
return (
<SafeAreaProviderCompat>
<MaterialBottomTabViewInner {...props} />
</SafeAreaProviderCompat>
);
}
const styles = StyleSheet.create({
icon: {
backgroundColor: 'transparent',
},
touchable: {
display: 'flex',
justifyContent: 'center',
},
});
@@ -1,71 +0,0 @@
import {
createNavigatorFactory,
DefaultNavigatorOptions,
ParamListBase,
TabActionHelpers,
TabNavigationState,
TabRouter,
TabRouterOptions,
useNavigationBuilder,
} from '@react-navigation/native';
import * as React from 'react';
import {
MaterialBottomTabNavigationConfig,
MaterialBottomTabNavigationEventMap,
MaterialBottomTabNavigationOptions,
} from '@react-navigation/material-bottom-tabs/lib/typescript/src/types';
import MaterialBottomTabView from './MaterialBottomTabView';
type Props = DefaultNavigatorOptions<
ParamListBase,
TabNavigationState<ParamListBase>,
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationEventMap
> &
TabRouterOptions &
MaterialBottomTabNavigationConfig;
function MaterialBottomTabNavigator({
id,
initialRouteName,
backBehavior,
children,
screenListeners,
screenOptions,
...rest
}: Props) {
const { state, descriptors, navigation, NavigationContent } =
useNavigationBuilder<
TabNavigationState<ParamListBase>,
TabRouterOptions,
TabActionHelpers<ParamListBase>,
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationEventMap
>(TabRouter, {
id,
initialRouteName,
backBehavior,
children,
screenListeners,
screenOptions,
});
return (
<NavigationContent>
<MaterialBottomTabView
{...rest}
state={state}
navigation={navigation}
descriptors={descriptors}
/>
</NavigationContent>
);
}
export default createNavigatorFactory<
TabNavigationState<ParamListBase>,
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationEventMap,
typeof MaterialBottomTabNavigator
>(MaterialBottomTabNavigator);
+9 -12
View File
@@ -1,6 +1,6 @@
import React from 'react';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Color from 'color';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import Library from '../screens/library/LibraryScreen';
import Updates from '../screens/updates/UpdatesScreen';
@@ -10,9 +10,7 @@ import More from '../screens/more/MoreScreen';
import { useSettings } from '../hooks/reduxHooks';
import { getString } from '../../strings/translations';
import createMaterialBottomTabNavigator from './BottomNavigation/createMaterialBottomTabNavigator';
import { useTheme } from '@hooks/useTheme';
import Color from 'color';
const Tab = createMaterialBottomTabNavigator();
@@ -27,15 +25,14 @@ const BottomNavigator = () => {
return (
<Tab.Navigator
theme={{
colors: {
...theme,
background: theme.background,
elevation: {
level2: Color(theme.primary).alpha(0.08).string(),
},
},
barStyle={{
backgroundColor: Color(theme.surface)
.mix(Color(theme.primary), 0.08)
.rgb()
.string(),
}}
theme={{ colors: theme }}
activeColor={theme.onSecondaryContainer}
shifting={!showLabelsInNav}
>
<Tab.Screen
+3 -3
View File
@@ -6,7 +6,6 @@ import { createStackNavigator } from '@react-navigation/stack';
import { setBarColor } from '../theme/utils/setBarColor';
import { useTheme } from '../hooks/useTheme';
import { useGithubUpdateChecker } from '../hooks/githubUpdateChecker';
import * as SplashScreen from 'expo-splash-screen';
/**
* Navigators
@@ -31,6 +30,7 @@ import BrowseSettings from '../screens/browse/BrowseSettings';
import { useAppDispatch } from '@redux/hooks';
import { updateLibraryAction } from '@redux/updates/updates.actions';
import { useSettings } from '@hooks/reduxHooks';
import WebviewScreen from '@screens/WebviewScreen/WebviewScreen';
const Stack = createStackNavigator();
@@ -41,7 +41,6 @@ const MainNavigator = () => {
useEffect(() => {
const timer = setTimeout(async () => {
await SplashScreen.hideAsync();
setBarColor(theme);
}, 500);
@@ -59,7 +58,7 @@ const MainNavigator = () => {
const { isNewVersion, latestRelease } = useGithubUpdateChecker() || {};
return (
<NavigationContainer theme={{ colors: { background: theme.background } }}>
<NavigationContainer theme={{ colors: theme }}>
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="BottomNavigator" component={BottomNavigator} />
@@ -76,6 +75,7 @@ const MainNavigator = () => {
<Stack.Screen name="Migration" component={Migration} />
<Stack.Screen name="SourceNovels" component={SourceNovels} />
<Stack.Screen name="MigrateNovel" component={MigrateNovel} />
<Stack.Screen name="WebviewScreen" component={WebviewScreen} />
</Stack.Navigator>
</NavigationContainer>
);
+2
View File
@@ -15,6 +15,7 @@ import Downloads from '../screens/more/DownloadsScreen';
import AppearanceSettings from '../screens/settings/SettingsAppearanceScreen';
import CategoriesScreen from '@screens/Categories/CategoriesScreen';
import LibrarySettings from '@screens/settings/SettingsLibraryScreen/SettingsLibraryScreen';
import StatsScreen from '@screens/StatsScreen/StatsScreen';
const Stack = createStackNavigator();
@@ -40,6 +41,7 @@ const MoreStack = () => (
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
<Stack.Screen name="Downloads" component={Downloads} />
<Stack.Screen name="Categories" component={CategoriesScreen} />
<Stack.Screen name="Statistics" component={StatsScreen} />
</Stack.Navigator>
);
+10 -13
View File
@@ -15,6 +15,7 @@ import {
BOOKMARK_CHAPTER,
MARK_PREVIOUS_CHAPTERS_READ,
MARK_PREVIOUS_CHAPTERS_UNREAD,
ALL_CHAPTER_DELETED,
} from './novel.types';
import { fetchNovel } from '../../services/Source/source';
@@ -34,6 +35,7 @@ import {
markPreviuschaptersRead,
markPreviousChaptersUnread,
getNextChapter,
deleteChapters,
} from '../../database/queries/ChapterQueries';
import { deleteUpdateFromDb } from '../../database/queries/UpdateQueries';
import {
@@ -257,10 +259,7 @@ export const markPreviousChaptersUnreadAction =
};
export const markChapterUnreadAction =
(chapters, novelId) => async (dispatch, getState) => {
const { sort = 'ORDER BY chapterId ASC', filter = '' } =
getState().preferenceReducer.novelSettings[novelId];
(chapters, novelId, sort, filter) => async dispatch => {
chapters.map(chapter => markChapterUnread(chapter.chapterId));
const chaps = await getChapters(novelId, sort, filter);
@@ -444,16 +443,14 @@ export const deleteChapterAction =
*/
export const deleteAllChaptersAction =
(sourceId, chapters) => async dispatch => {
await Promise.all(
chapters.map(async chapter => {
await deleteChapter(sourceId, chapter.novelId, chapter.chapterId);
const chapterIds = chapters.map(chapter => chapter.chapterId);
await deleteChapters(sourceId, chapters);
dispatch({
type: ALL_CHAPTER_DELETED,
payload: chapterIds,
});
dispatch({
type: CHAPTER_DELETED,
payload: chapter.chapterId,
});
}),
);
showToast('Chapters deleted');
};
+8
View File
@@ -117,6 +117,14 @@ const novelReducer = (state = initialState, action) => {
: chapter,
),
};
case CHAPTER_DELETED:
return {
...state,
chapters: state.chapters.map(chapter => ({
...chapter,
downloaded: 0,
})),
};
case UPDATE_LAST_READ:
return {
...state,
+2
View File
@@ -10,6 +10,8 @@ export const UPDATE_LAST_READ = 'UPDATE_LAST_READ';
export const CHAPTER_DOWNLOADED = 'CHAPTER_DOWNLOADED';
export const CHAPTER_DELETED = 'CHAPTER_DELETED';
export const ALL_CHAPTER_DELETED = 'ALL_CHAPTER_DELETED';
export const CHAPTER_DOWNLOADING = 'CHAPTER_DOWNLOADING';
export const CHAPTER_READ = 'CHAPTER_READ';
export const CHAPTER_UNREAD = 'CHAPTER_UNREAD';
+2
View File
@@ -31,6 +31,7 @@ export const initialState = {
*/
downloadedOnlyMode: false,
useLibraryFAB: false,
/**
* Browse settings
@@ -72,6 +73,7 @@ export const initialState = {
autoScrollOffset: null,
verticalSeekbar: true,
removeExtraParagraphSpacing: false,
useChapterDrawerSwipeNavigation: true,
reader: {
theme: '#292832',
+7 -1
View File
@@ -1,9 +1,15 @@
import { SET_LAST_UPDATE_TIME } from './updates.types';
import BackgroundService from 'react-native-background-actions';
import { SET_LAST_UPDATE_TIME } from './updates.types';
import { showToast } from '../../hooks/showToast';
import { updateLibrary } from '../../services/updates/updates';
export const updateLibraryAction = props => async (dispatch, getState) => {
if (BackgroundService.isRunning()) {
showToast('An update is already running');
return;
}
showToast('Updating library');
dispatch({ type: SET_LAST_UPDATE_TIME, payload: Date.now() });
@@ -1,19 +1,12 @@
import React, { useCallback, useRef } from 'react';
import { useNavigation } from '@react-navigation/native';
import * as WebBrowser from 'expo-web-browser';
import { FAB, Portal } from 'react-native-paper';
import Bottomsheet from 'rn-sliding-up-panel';
import {
Container,
LoadingScreenV2,
LoadingMoreIndicator,
ErrorScreenV2,
SearchbarV2,
} from '@components/index';
import { Container, ErrorScreenV2, SearchbarV2 } from '@components/index';
import NovelList from '@components/NovelList';
import NovelCover from '@components/NovelCover';
import FilterBottomSheet from './components/BottomSheet';
import Bottomsheet from '@gorhom/bottom-sheet';
import FilterBottomSheet from './components/FilterBottomSheet';
import { useCategorySettings, useSearch } from '../../hooks';
import { useTheme } from '@hooks/useTheme';
@@ -25,6 +18,8 @@ import { StyleSheet } from 'react-native';
import { useLibraryNovels } from '../../screens/library/hooks/useLibrary';
import { insertNovelInLibrary } from '../../database/queries/NovelQueriesV2';
import { LibraryNovelInfo } from '../../database/types';
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
interface BrowseSourceScreenProps {
route: {
@@ -80,7 +75,11 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
};
const handleOpenWebView = async () => {
WebBrowser.openBrowserAsync(sourceUrl);
navigate('WebviewScreen', {
sourceId,
name: sourceName,
url: sourceUrl,
});
};
const { library, setLibrary } = useLibraryNovels();
@@ -104,8 +103,8 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
[sourceId],
);
const { bottom } = useSafeAreaInsets();
const filterSheetRef = useRef<Bottomsheet | null>(null);
return (
<Container>
<SearchbarV2
@@ -120,7 +119,7 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
theme={theme}
/>
{isLoading || isSearching ? (
<LoadingScreenV2 theme={theme} />
<SourceScreenSkeletonLoading theme={theme} />
) : errorMessage || novelList.length === 0 ? (
<ErrorScreenV2
error={errorMessage || getString('sourceScreen.noResultsFound')}
@@ -173,20 +172,24 @@ const BrowseSourceScreen: React.FC<BrowseSourceScreenProps> = ({ route }) => {
}}
ListFooterComponent={
hasNextPage && !searchText ? (
<LoadingMoreIndicator theme={theme} />
<SourceScreenSkeletonLoading theme={theme} />
) : undefined
}
/>
)}
{!showLatestNovels && filterValues?.length ? (
<>
<FAB
icon={'filter-variant'}
style={[styles.filterFab, { backgroundColor: theme.primary }]}
style={[
styles.filterFab,
{ backgroundColor: theme.primary, marginBottom: bottom },
]}
label={'Filter'}
uppercase={false}
color={theme.onPrimary}
onPress={() => filterSheetRef?.current?.show()}
onPress={() => filterSheetRef?.current?.expand()}
/>
<Portal>
<FilterBottomSheet
@@ -1,7 +1,9 @@
import React, { LegacyRef, useState } from 'react';
import { Animated, Pressable, StyleSheet, Text, View } from 'react-native';
import React, { Ref, useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import color from 'color';
import Bottomsheet from 'rn-sliding-up-panel';
import BottomSheet from '../../../components/BottomSheet/BottomSheet';
import BottomSheetType, { BottomSheetView } from '@gorhom/bottom-sheet';
import { useTheme } from '@hooks/useTheme';
import {
@@ -116,41 +118,37 @@ const FilterItem: React.FC<FilterItemProps> = ({
};
interface BottomSheetProps {
filterSheetRef: Ref<BottomSheetType> | null;
filtersValues: SourceFilter[] | undefined;
filterSheetRef: LegacyRef<Bottomsheet> | null;
setFilters: (filters?: SelectedFilter) => void;
clearFilters: () => void;
}
const BottomSheet: React.FC<BottomSheetProps> = ({
const FilterBottomSheet: React.FC<BottomSheetProps> = ({
filterSheetRef,
filtersValues,
clearFilters,
setFilters,
}) => {
const theme = useTheme();
const [animatedValue] = useState(new Animated.Value(0));
const [selectedFilters, setSelectedFilters] = useState<
SelectedFilter | undefined
>();
return (
<Bottomsheet
animatedValue={animatedValue}
ref={filterSheetRef}
draggableRange={{ top: 600, bottom: 0 }}
snappingPoints={[0, 400, 600]}
showBackdrop={true}
backdropOpacity={0.3}
<BottomSheet
bottomSheetRef={filterSheetRef}
snapPoints={[400, 600]}
height={600}
theme={theme}
>
<View
<BottomSheetView
style={[
styles.container,
{ backgroundColor: overlay(2, theme.surface) },
]}
>
<View
<BottomSheetView
style={[
styles.buttonContainer,
{ borderBottomColor: dividerColor(theme.isDark) },
@@ -167,14 +165,14 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
/>
<Button
title={'Filter'}
textColor={theme.colorButtonText}
textColor={theme.textColorPrimary}
onPress={() => {
setFilters(selectedFilters);
filterSheetRef?.current?.hide();
filterSheetRef?.current?.collapse();
}}
theme={theme}
/>
</View>
</BottomSheetView>
<FlatList
contentContainerStyle={styles.filterContainer}
data={filtersValues}
@@ -187,12 +185,12 @@ const BottomSheet: React.FC<BottomSheetProps> = ({
/>
)}
/>
</View>
</Bottomsheet>
</BottomSheetView>
</BottomSheet>
);
};
export default BottomSheet;
export default FilterBottomSheet;
const styles = StyleSheet.create({
container: {
+4 -3
View File
@@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react';
import { FAB, Portal } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import { Appbar, EmptyView, LoadingScreenV2 } from '@components/index';
import { Appbar, EmptyView } from '@components/index';
import AddCategoryModal from './components/AddCategoryModal';
import {
@@ -17,6 +17,7 @@ import { getString } from '@strings/translations';
import { Category } from '../../database/types';
import CategoryCard from './components/CategoryCard';
import { orderBy } from 'lodash';
import CategorySkeletonLoading from './components/CategorySkeletonLoading';
const CategoriesScreen = () => {
const theme = useTheme();
@@ -78,7 +79,6 @@ const CategoriesScreen = () => {
setTrue: showCategoryModal,
setFalse: closeCategoryModal,
} = useBoolean();
return (
<>
<Appbar
@@ -87,7 +87,7 @@ const CategoriesScreen = () => {
theme={theme}
/>
{isLoading ? (
<LoadingScreenV2 theme={theme} />
<CategorySkeletonLoading width={360.7} height={89.5} theme={theme} />
) : (
<FlatList
data={categories}
@@ -139,6 +139,7 @@ const styles = StyleSheet.create({
bottom: 16,
},
contentCtn: {
flexGrow: 1,
paddingVertical: 16,
paddingBottom: 100,
},
@@ -0,0 +1,52 @@
import React, { memo } from 'react';
import { StyleSheet, View } from 'react-native';
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder';
import { LinearGradient } from 'expo-linear-gradient';
import { MD3ThemeType } from '@theme/types';
import getLoadingColors from '@utils/getLoadingColors';
interface Props {
width: number;
height: number;
theme: MD3ThemeType;
}
const CategorySkeletonLoading: React.FC<Props> = ({ height, width, theme }) => {
const ShimmerPlaceHolder = createShimmerPlaceholder(LinearGradient);
const [highlightColor, backgroundColor] = getLoadingColors(theme);
const renderLoadingCard = (item: number, index: number) => {
return (
<View key={index}>
<ShimmerPlaceHolder
style={styles.categoryCard}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={height}
width={width}
/>
</View>
);
};
const items = [];
for (let index = 0; index < Math.random() * 6 + 3; index++) {
items.push(0);
}
return <View style={styles.contentCtn}>{items.map(renderLoadingCard)}</View>;
};
const styles = StyleSheet.create({
categoryCard: {
marginHorizontal: 16,
marginBottom: 8,
borderRadius: 12,
},
contentCtn: {
paddingVertical: 16,
paddingBottom: 100,
},
});
export default memo(CategorySkeletonLoading);
@@ -1,11 +1,4 @@
import {
ActivityIndicator,
FlatList,
Pressable,
StyleSheet,
Text,
View,
} from 'react-native';
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import React, { useCallback, useMemo } from 'react';
import color from 'color';
@@ -21,6 +14,8 @@ import GlobalSearchNovelItem from './GlobalSearchNovelItem';
import { useLibraryNovels } from '@screens/library/hooks/useLibrary';
import { insertNovelInLibrary } from '../../../database/queries/NovelQueriesV2';
import { LibraryNovelInfo } from '../../../database/types';
import GlobalSearchSkeletonLoading from '@screens/browse/loadingAnimation/GlobalSearchSkeletonLoading';
import { useCategorySettings } from '@hooks/useSettings';
interface GlobalSearchResultsListProps {
searchResults: GlobalSearchResult[];
@@ -35,6 +30,7 @@ const GlobalSearchResultsList: React.FC<GlobalSearchResultsListProps> = ({
const navigation = useNavigation<StackNavigationProp<any>>();
const keyExtractor = useCallback(item => item.source.sourceId.toString(), []);
const { library, setLibrary } = useLibraryNovels();
const { defaultCategoryId = 1 } = useCategorySettings();
const novelInLibrary = (sourceId: number, novelUrl: string) =>
library?.some(
@@ -88,7 +84,7 @@ const GlobalSearchResultsList: React.FC<GlobalSearchResultsListProps> = ({
/>
</Pressable>
{item.isLoading ? (
<ActivityIndicator color={theme.primary} style={styles.loading} />
<GlobalSearchSkeletonLoading theme={theme} />
) : item.error ? (
<Text style={[styles.error, { color: errorColor }]}>
{item.error}
@@ -145,6 +141,7 @@ const GlobalSearchResultsList: React.FC<GlobalSearchResultsListProps> = ({
novelItem.sourceId,
novelItem.novelUrl,
inLibrary,
defaultCategoryId,
);
}}
/>
@@ -168,12 +165,6 @@ const styles = StyleSheet.create({
paddingTop: 8,
paddingBottom: 60,
},
loading: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 16,
},
sourceHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
+198
View File
@@ -0,0 +1,198 @@
import React, { useEffect, useState } from 'react';
import { ScrollView, StyleSheet, Text, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useTheme } from '@hooks/useTheme';
import { getString } from '@strings/translations';
import { Appbar, ErrorScreenV2, LoadingScreenV2 } from '@components';
import { LibraryStats } from '@database/types';
import {
getChaptersDownloadedCountFromDb,
getChaptersReadCountFromDb,
getChaptersTotalCountFromDb,
getChaptersUnreadCountFromDb,
getLibraryStatsFromDb,
getNovelGenresFromDb,
getNovelStatusFromDb,
} from '@database/queries/StatsQueries';
import { Row } from '@components/Common';
import { overlay } from 'react-native-paper';
const StatsScreen = () => {
const theme = useTheme();
const { goBack } = useNavigation();
const [isLoading, setIsLoading] = useState(true);
const [stats, setStats] = useState<LibraryStats>({});
const [error, setError] = useState();
const getStats = async () => {
try {
const res = await Promise.all([
getLibraryStatsFromDb(),
getChaptersTotalCountFromDb(),
getChaptersReadCountFromDb(),
getChaptersUnreadCountFromDb(),
getChaptersDownloadedCountFromDb(),
getNovelGenresFromDb(),
getNovelStatusFromDb(),
]);
setStats(Object.assign(...res));
} catch (err) {
setError(err);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
getStats();
}, []);
const Header = (
<Appbar
title={getString('statsScreen.title')}
handleGoBack={goBack}
theme={theme}
/>
);
if (isLoading) {
return (
<>
{Header}
<LoadingScreenV2 theme={theme} />
</>
);
}
if (error) {
return (
<>
{Header}
<ErrorScreenV2 theme={theme} />
</>
);
}
return (
<>
{Header}
<ScrollView
style={styles.screenCtn}
contentContainerStyle={styles.contentCtn}
>
<Text style={[styles.header, { color: theme.textColorSecondary }]}>
{getString('moreScreen.settingsScreen.generalSettings')}
</Text>
<Row style={styles.statsRow}>
<StatsCard
label={getString('statsScreen.titlesInLibrary')}
value={stats.novelsCount}
/>
<StatsCard
label={getString('statsScreen.readChapters')}
value={stats.chaptersRead}
/>
<StatsCard
label={getString('statsScreen.totalChapters')}
value={stats.chaptersCount}
/>
</Row>
<Row style={styles.statsRow}>
<StatsCard
label={getString('statsScreen.unreadChapters')}
value={stats.chaptersUnread}
/>
<StatsCard
label={getString('statsScreen.downloadedChapters')}
value={stats.chaptersDownloaded}
/>
</Row>
<Row style={styles.statsRow}>
<StatsCard label="Sources" value={stats.sourcesCount} />
</Row>
<Text style={[styles.header, { color: theme.textColorSecondary }]}>
{getString('statsScreen.genreDistribution')}
</Text>
<Row style={[styles.statsRow, styles.genreRow]}>
{Object.entries(stats.genres || {}).map(item => (
<StatsCard key={item[0]} label={item[0]} value={item[1]} />
))}
</Row>
<Text style={[styles.header, { color: theme.textColorSecondary }]}>
{getString('statsScreen.statusDistribution')}
</Text>
<Row style={[styles.statsRow, styles.genreRow]}>
{Object.entries(stats.status || {}).map(item => (
<StatsCard key={item[0]} label={item[0]} value={item[1]} />
))}
</Row>
</ScrollView>
</>
);
};
export default StatsScreen;
export const StatsCard: React.FC<{ label: string; value?: number }> = ({
label,
value = 0,
}) => {
const theme = useTheme();
if (!label) {
return null;
}
return (
<View
style={[
styles.statsCardCtn,
{
backgroundColor: theme.isDark
? overlay(2, theme.surface)
: theme.secondaryContainer,
},
]}
>
<Text style={[styles.statsVal, { color: theme.primary }]}>{value}</Text>
<Text style={{ color: theme.textColorPrimary }}> {label}</Text>
</View>
);
};
const styles = StyleSheet.create({
statsCardCtn: {
elevation: 1,
margin: 4,
paddingHorizontal: 8,
paddingVertical: 12,
borderRadius: 12,
alignItems: 'center',
justifyContent: 'center',
},
statsVal: {
fontWeight: 'bold',
fontSize: 16,
},
screenCtn: {
paddingHorizontal: 16,
},
contentCtn: {
paddingBottom: 40,
},
statsRow: {
marginBottom: 8,
justifyContent: 'center',
},
genreRow: {
flexWrap: 'wrap',
},
header: {
paddingVertical: 16,
fontWeight: 'bold',
},
});
@@ -0,0 +1,50 @@
import React, { useEffect } from 'react';
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
import WebView from 'react-native-webview';
import CookieManager from '@react-native-cookies/cookies';
import { Appbar } from '@components';
import { useTheme } from '@hooks/useTheme';
import useSourceStorage from '@hooks/useSourceStorage';
import { defaultUserAgentString } from '@utils/fetch/fetch';
type ReaderScreenRouteProps = RouteProp<{
params: {
name: string;
sourceId: number;
url: string;
};
}>;
const WebviewScreen = () => {
const theme = useTheme();
const { goBack } = useNavigation();
const {
params: { name, sourceId, url },
} = useRoute<ReaderScreenRouteProps>();
const { setSourceStorage } = useSourceStorage({ sourceId });
useEffect(() => {
CookieManager.get(url, true).then(cookies => {
const cloudflareCookie = cookies?.cf_clearance;
if (cloudflareCookie) {
const cloudflareCookieString = `${cloudflareCookie.name}=${cloudflareCookie.value}`;
setSourceStorage('cookies', cloudflareCookieString);
}
});
}, []);
return (
<>
<Appbar mode="small" title={name} handleGoBack={goBack} theme={theme} />
<WebView
startInLoadingState
userAgent={defaultUserAgentString}
source={{ uri: url }}
/>
</>
);
};
export default WebviewScreen;
+3 -2
View File
@@ -4,12 +4,13 @@ import { StyleSheet, View, ActivityIndicator, FlatList } from 'react-native';
import * as WebBrowser from 'expo-web-browser';
import { ErrorView } from '../../../components/ErrorView/ErrorView';
import { LoadingScreenV2, SearchbarV2 } from '@components';
import { SearchbarV2 } from '@components';
import { showToast } from '../../../hooks/showToast';
import { scrapeSearchResults, scrapeTopNovels } from './MyAnimeListScraper';
import MalNovelCard from './MalNovelCard/MalNovelCard';
import { useTheme } from '@hooks/useTheme';
import MalLoading from '../loadingAnimation/MalLoading';
const BrowseMalScreen = ({ navigation, route }) => {
const theme = useTheme();
@@ -143,7 +144,7 @@ const BrowseMalScreen = ({ navigation, route }) => {
]}
/>
{loading ? (
<LoadingScreenV2 theme={theme} />
<MalLoading theme={theme} />
) : (
<FlatList
contentContainerStyle={styles.novelsContainer}
@@ -0,0 +1,48 @@
import React, { memo } from 'react';
import { View, StyleSheet } from 'react-native';
import { MD3ThemeType } from '@theme/types';
import LoadingNovel from './LoadingNovel';
import getLoadingColors from '@utils/getLoadingColors';
interface Props {
theme: MD3ThemeType;
}
const GlobalSearchSkeletonLoading: React.FC<Props> = ({ theme }) => {
const styles = createStyleSheet();
const [highlightColor, backgroundColor] = getLoadingColors(theme);
const items: Array<number> = [1, 2, 3, 4];
return (
<View style={[styles.container, styles.row]}>
{items.map((item: number, index: number) => {
return (
<LoadingNovel
key={index}
backgroundColor={backgroundColor}
highlightColor={highlightColor}
pictureHeight={153.1}
/>
);
})}
</View>
);
};
const createStyleSheet = () => {
return StyleSheet.create({
container: {
marginHorizontal: 4,
marginBottom: 6,
marginTop: 6,
overflow: 'hidden',
},
row: {
flexDirection: 'row',
paddingHorizontal: 3,
},
});
};
export default memo(GlobalSearchSkeletonLoading);
@@ -0,0 +1,120 @@
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder';
import { LinearGradient } from 'expo-linear-gradient';
import { DisplayModes } from '@screens/library/constants/constants';
interface Props {
backgroundColor: string;
highlightColor: string;
pictureHeight: number;
pictureWidth: number;
displayMode: DisplayModes;
}
const LoadingNovel: React.FC<Props> = ({
backgroundColor,
highlightColor,
pictureHeight,
pictureWidth,
displayMode,
}) => {
const ShimmerPlaceHolder = createShimmerPlaceholder(LinearGradient);
let randomNumber = Math.random();
randomNumber < 0.1 ? (randomNumber = 0) : null;
// pictureWidth = pictureWidth === undefined ? 114.5 : pictureWidth;
const styles = createStyleSheet(
pictureHeight + (displayMode === 2 || displayMode === 0 ? 9.6 : 54.6),
pictureWidth,
);
if (displayMode !== 3) {
return (
<View style={styles.loadingContainer}>
<ShimmerPlaceHolder
style={styles.picture}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={pictureHeight}
width={pictureWidth}
/>
{displayMode === 2 || displayMode === 0 ? null : (
<>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={pictureWidth}
/>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={randomNumber * pictureWidth}
/>
</>
)}
</View>
);
} else {
const chapterNumberWidth = 10 * Math.floor(Math.random() * 7);
const textWidth = 304.7 - chapterNumberWidth;
return (
<View style={styles.listLoadingContainer}>
<ShimmerPlaceHolder
style={styles.picture}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={40}
width={40}
/>
<ShimmerPlaceHolder
style={styles.listText}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={18}
width={textWidth}
/>
<ShimmerPlaceHolder
style={styles.picture}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={20}
width={chapterNumberWidth}
/>
</View>
);
}
};
const createStyleSheet = (pictureHeight: number, pictureWidth: number) => {
return StyleSheet.create({
loadingContainer: {
padding: 4.8,
marginBottom: 4,
width: pictureWidth + 9.6,
height: pictureHeight,
overflow: 'hidden',
},
text: {
borderRadius: 8,
marginTop: 5,
},
picture: {
borderRadius: 4,
},
listLoadingContainer: {
marginHorizontal: 12,
marginVertical: 8,
flexDirection: 'row',
alignItems: 'center',
},
listText: {
marginLeft: 16,
marginRight: 8,
borderRadius: 4,
},
listChapter: {
borderRadius: 4,
paddingHorizontal: 4,
},
});
};
export default LoadingNovel;
@@ -0,0 +1,88 @@
import React, { memo } from 'react';
import { View, Dimensions, StyleSheet } from 'react-native';
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder';
import { LinearGradient } from 'expo-linear-gradient';
import { MD3ThemeType } from '@theme/types';
import { getDialogBackground } from '@theme/colors';
import getLoadingColors from '@utils/getLoadingColors';
interface Props {
theme: MD3ThemeType;
}
const MalLoading: React.FC<Props> = ({ theme }) => {
const ShimmerPlaceHolder = createShimmerPlaceholder(LinearGradient);
const styles = createStyleSheet(theme);
const [highlightColor, backgroundColor] = getLoadingColors(theme);
const renderLoadingRect = (item: number, index: number) => {
let randomNumber = Math.random();
randomNumber < 0.1 ? (randomNumber = 0.1) : null;
return (
<View key={index} style={styles.loadingContainer}>
<ShimmerPlaceHolder
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={120 + Math.random() * 28}
width={100}
/>
<View style={styles.loadingText}>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={Dimensions.get('window').width - 140}
/>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={Dimensions.get('window').width - 140}
/>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={randomNumber * (Dimensions.get('window').width - 140)}
/>
</View>
</View>
);
};
const items: Array<number> = [0, 1, 2, 3, 4];
return <View style={styles.container}>{items.map(renderLoadingRect)}</View>;
};
const createStyleSheet = (theme: MD3ThemeType) => {
return StyleSheet.create({
container: {
position: 'relative',
flexGrow: 1,
// height: 150,
backgroundColor: 'transparent',
marginBottom: 8,
marginTop: -3,
overflow: 'hidden',
},
loadingContainer: {
margin: 10,
borderRadius: 8,
backgroundColor: getDialogBackground(theme),
width: Dimensions.get('window').width - 20,
overflow: 'hidden',
flexDirection: 'row',
elevation: 1,
},
text: {
borderRadius: 8,
marginVertical: 5,
},
loadingText: {
margin: 10,
height: 10,
width: Dimensions.get('window').width - 140,
},
});
};
export default memo(MalLoading);
@@ -0,0 +1,92 @@
import React, { memo, useMemo } from 'react';
import { View, StyleSheet, useWindowDimensions } from 'react-native';
import { MD3ThemeType } from '@theme/types';
import getLoadingColors from '@utils/getLoadingColors';
import LoadingNovel from '@screens/browse/loadingAnimation/LoadingNovel';
import { useLibrarySettings } from '@hooks/useSettings';
import { useDeviceOrientation } from '../../../services/utils/helpers';
import { DisplayModes } from '@screens/library/constants/constants';
interface Props {
theme: MD3ThemeType;
}
const SourceScreenSkeletonLoading: React.FC<Props> = ({ theme }) => {
const [highlightColor, backgroundColor] = getLoadingColors(theme);
const { displayMode = DisplayModes.Comfortable, novelsPerRow = 3 } =
useLibrarySettings();
const window = useWindowDimensions();
const styles = createStyleSheet();
const orientation = useDeviceOrientation();
const numColumns = useMemo(
() => (orientation === 'landscape' ? 6 : novelsPerRow),
[orientation, novelsPerRow],
);
const [pictureHeight, pictureWidth] = useMemo(() => {
let height = (window.width / numColumns) * (4 / 3);
let width = (window.width - 12 - 9.6 * numColumns) / numColumns;
return [height, width];
}, [numColumns]);
const renderLoadingNovel = (item: number, index: number) => {
let randomNumber = Math.random();
randomNumber < 0.1 ? (randomNumber = 0) : null;
return (
<LoadingNovel
key={index}
backgroundColor={backgroundColor}
highlightColor={highlightColor}
pictureHeight={pictureHeight}
pictureWidth={pictureWidth}
displayMode={displayMode}
/>
);
};
const renderLoading = (item: number, index: number) => {
const offset = Math.pow(10, item);
const items: Array<number> = [1 * offset];
if (displayMode !== DisplayModes.List) {
for (let i = 2; i <= numColumns; i++) {
items.push(i * offset);
}
}
return (
<View key={index} style={styles.row}>
{items.map(renderLoadingNovel)}
</View>
);
};
let items: Array<number> = [];
if (displayMode === DisplayModes.List) {
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
} else {
for (let i = 1; i * pictureHeight < window.height - 100; i++) {
items.push(i);
}
}
return <View style={styles.container}>{items.map(renderLoading)}</View>;
};
const createStyleSheet = () => {
return StyleSheet.create({
container: {
flexGrow: 1,
marginHorizontal: 4,
marginBottom: 8,
marginTop: 2,
overflow: 'visible',
},
row: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingHorizontal: 1,
},
});
};
export default memo(SourceScreenSkeletonLoading);
@@ -11,6 +11,7 @@ import { getDialogBackground } from '@theme/colors';
import { Button } from '@components';
import { ButtonVariation } from '@components/Button/Button';
import { getString } from '@strings/translations';
import { openNovel } from '@utils/handleNavigateParams';
const MigrationNovelList = ({ data, theme, library, navigation }) => {
const [selectedNovel, setSelectedNovel] = useState(false);
@@ -28,10 +29,13 @@ const MigrationNovelList = ({ data, theme, library, navigation }) => {
theme={theme}
onPress={() => showModal(item.sourceId, item.novelUrl, item.novelName)}
onLongPress={() =>
navigation.navigate('Novel', {
...item,
sourceId: item.sourceId,
})
navigation.navigate(
'Novel',
openNovel({
...item,
sourceId: item.sourceId,
}),
)
}
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
/>
@@ -17,6 +17,7 @@ import { sourceManager } from '../../../sources/sourceManager';
import { useBrowseSettings, useSourcesReducer } from '../../../redux/hooks';
import { useLibraryNovels } from '@screens/library/hooks/useLibrary';
import { Appbar } from '@components';
import GlobalSearchSkeletonLoading from '../loadingAnimation/GlobalSearchSkeletonLoading';
const MigrationNovels = ({ navigation, route }) => {
const { sourceId, novelName } = route.params;
@@ -108,10 +109,7 @@ const MigrationNovels = ({ navigation, route }) => {
{item.error ? (
<Text style={[styles.error, colorError]}>{item.error}</Text>
) : item.loading ? (
<ActivityIndicator
color={theme.primary}
style={{ marginVertical: 16 }}
/>
<GlobalSearchSkeletonLoading theme={theme} />
) : (
<MigrationNovelList
data={item.novels}
+16 -42
View File
@@ -4,12 +4,7 @@ import { useNavigation } from '@react-navigation/native';
import dayjs from 'dayjs';
import { Portal } from 'react-native-paper';
import {
EmptyView,
ErrorScreenV2,
LoadingScreenV2,
SearchbarV2,
} from '../../components/index';
import { EmptyView, ErrorScreenV2, SearchbarV2 } from '../../components/index';
import HistoryCard from './components/HistoryCard/HistoryCard';
import { useSearch, useHistory, useBoolean } from '@hooks/index';
@@ -20,6 +15,15 @@ import { convertDateToISOString } from '../../database/utils/convertDateToISOStr
import { History } from '../../database/types';
import { getString } from '@strings/translations';
import ClearHistoryDialog from './components/ClearHistoryDialog';
import {
openChapter,
openChapterChapterTypes,
openChapterNovelTypes,
openChapterFunctionTypes,
openNovel,
openNovelProps,
} from '@utils/handleNavigateParams';
import HistorySkeletonLoading from './components/HistorySkeletonLoading';
const HistoryScreen = () => {
const theme = useTheme();
@@ -72,46 +76,16 @@ const HistoryScreen = () => {
};
const handleNavigateToChapter = (
sourceId: number,
novelId: number,
novelName: string,
novelUrl: string,
chapterId: number,
chapterUrl: string,
chapterName: string,
bookmark: number,
novel: openChapterNovelTypes,
chapter: openChapterChapterTypes,
) =>
navigate(
'Chapter' as never,
{
sourceId,
novelId,
novelName,
novelUrl,
chapterId,
chapterUrl,
bookmark,
chapterName,
} as never,
openChapter(novel, chapter) as openChapterFunctionTypes as never,
);
const handleNavigateToNovel = (
sourceId: number,
novelId: number,
novelUrl: string,
novelName: string,
novelCover: string,
) =>
navigate(
'Novel' as never,
{
sourceId,
novelId,
novelUrl,
novelName,
novelCover,
} as never,
);
const handleNavigateToNovel = (novel: openNovelProps) =>
navigate('Novel' as never, openNovel(novel) as openNovelProps as never);
const {
value: clearHistoryDialogVisible,
@@ -136,7 +110,7 @@ const HistoryScreen = () => {
theme={theme}
/>
{isLoading ? (
<LoadingScreenV2 theme={theme} />
<HistorySkeletonLoading theme={theme} />
) : error ? (
<ErrorScreenV2 error={error} theme={theme} />
) : (
@@ -12,27 +12,20 @@ import { parseChapterNumber } from '../../../../utils/parseChapterNumber';
import { History } from '../../../../database/types';
import { MD3ThemeType } from '../../../../theme/types';
import { coverPlaceholderColor } from '../../../../theme/colors';
import {
openChapterChapterTypes,
openChapterNovelTypes,
openNovelProps,
} from '@utils/handleNavigateParams';
interface HistoryCardProps {
history: History;
handleNavigateToChapter: (
sourceId: number,
novelId: number,
novelName: string,
novelUrl: string,
chapterId: number,
chapterUrl: string,
chapterName: string,
isBookmarked: number,
novel: openChapterNovelTypes,
chapter: openChapterChapterTypes,
) => void;
handleRemoveFromHistory: (historyId: number) => void;
handleNavigateToNovel: (
sourceId: number,
novelId: number,
novelUrl: string,
novelName: string,
novelCover: string,
) => void;
handleNavigateToNovel: (novel: openNovelProps) => void;
theme: MD3ThemeType;
}
@@ -70,13 +63,13 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
style={styles.container}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
onPress={() =>
handleNavigateToNovel(
handleNavigateToNovel({
sourceId,
novelId,
novelUrl,
novelName,
novelCover,
)
})
}
>
<View style={styles.imageAndNameContainer}>
@@ -103,14 +96,14 @@ const HistoryCard: React.FC<HistoryCardProps> = ({
name="play"
onPress={() =>
handleNavigateToChapter(
sourceId,
novelId,
novelName,
novelUrl,
chapterId,
chapterUrl,
chapterName,
bookmark,
{ sourceId, novelName, novelUrl },
{
novelId,
chapterId,
chapterUrl,
chapterName,
bookmark,
},
)
}
theme={theme}
@@ -0,0 +1,118 @@
import React, { memo } from 'react';
import { StyleSheet, View } from 'react-native';
import { createShimmerPlaceholder } from 'react-native-shimmer-placeholder';
import { LinearGradient } from 'expo-linear-gradient';
import { MD3ThemeType } from '@theme/types';
import getLoadingColors from '@utils/getLoadingColors';
interface Props {
theme: MD3ThemeType;
}
const HistorySkeletonLoading: React.FC<Props> = ({ theme }) => {
const ShimmerPlaceHolder = createShimmerPlaceholder(LinearGradient);
const [highlightColor, backgroundColor] = getLoadingColors(theme);
const renderLoadingChapter = (item: number, index: number) => {
return (
<>
{index === 0 || Math.random() > 0.6 ? (
<ShimmerPlaceHolder
style={styles.date}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={19.3}
width={Math.random() * 40 + 50}
/>
) : null}
<View style={styles.chapterCtn} key={index}>
<ShimmerPlaceHolder
style={styles.picture}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={80}
width={56}
/>
<View style={styles.textCtn}>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={16}
width={208.7}
/>
<ShimmerPlaceHolder
style={styles.text}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={12}
width={208.7}
/>
</View>
<View style={styles.buttonCtn}>
<ShimmerPlaceHolder
style={styles.button}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={24}
width={24}
/>
</View>
<View style={styles.buttonCtn}>
<ShimmerPlaceHolder
style={styles.button}
shimmerColors={[backgroundColor, highlightColor, backgroundColor]}
height={24}
width={24}
/>
</View>
</View>
</>
);
};
const items = [];
for (let index = 0; index < Math.random() * 3 + 3; index++) {
items.push(0);
}
return <View>{items.map(renderLoadingChapter)}</View>;
};
const styles = StyleSheet.create({
contentCtn: {
paddingVertical: 8,
},
textCtn: {
marginTop: 5,
marginBottom: 2,
borderRadius: 6,
},
text: {
marginBottom: 4,
borderRadius: 6,
},
chapterCtn: {
marginVertical: 8,
flexDirection: 'row',
alignItems: 'center',
},
picture: {
marginHorizontal: 16,
borderRadius: 4,
height: 80,
width: 56,
},
button: {
borderRadius: 12.5,
},
buttonCtn: {
justifyContent: 'center',
alignItems: 'center',
height: 40,
width: 40,
},
date: {
marginHorizontal: 16,
marginVertical: 8,
borderRadius: 6,
},
});
export default memo(HistorySkeletonLoading);
+70 -10
View File
@@ -1,6 +1,6 @@
import React, { useMemo, useRef, useState } from 'react';
import { StyleSheet, Text, useWindowDimensions } from 'react-native';
import Bottomsheet from 'rn-sliding-up-panel';
import { StyleSheet, Text, useWindowDimensions, View } from 'react-native';
import BottomSheet from '@gorhom/bottom-sheet';
import {
NavigationState,
SceneRendererProps,
@@ -10,7 +10,7 @@ import {
import { useNavigation } from '@react-navigation/native';
import color from 'color';
import { LoadingScreenV2, SearchbarV2, Button } from '@components/index';
import { SearchbarV2, Button } from '@components/index';
import { LibraryView } from './components/LibraryListView';
import LibraryBottomSheet from './components/LibraryBottomSheet/LibraryBottomSheet';
import { Banner } from './components/Banner';
@@ -20,7 +20,7 @@ import { useLibrary } from './hooks/useLibrary';
import { useTheme } from '@hooks/useTheme';
import useSearch from '@hooks/useSearch';
import { getString } from '@strings/translations';
import { Portal } from 'react-native-paper';
import { FAB, Portal } from 'react-native-paper';
import { useLibrarySettings } from '@hooks/useSettings';
import {
markAllChaptersRead,
@@ -32,6 +32,12 @@ import useBoolean from '@hooks/useBoolean';
import { debounce, intersection } from 'lodash';
import { ButtonVariation } from '@components/Button/Button';
import { useBackHandler } from '@hooks/useBackHandler';
import { openChapter } from '@utils/handleNavigateParams';
import useHistory from '@hooks/useHistory';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useSettings } from '@hooks/reduxHooks';
import SourceScreenSkeletonLoading from '@screens/browse/loadingAnimation/SourceScreenSkeletonLoading';
import { Row } from '@components/Common';
type State = NavigationState<{
key: string;
@@ -48,6 +54,12 @@ const LibraryScreen = () => {
incognitoMode = false,
} = useLibrarySettings();
const { useLibraryFAB = false } = useSettings();
const { isLoading: isHistoryLoading, history, error } = useHistory();
const { right: rightInset } = useSafeAreaInsets();
const onChangeText = debounce((text: string) => {
setSearchText(text);
}, 100);
@@ -72,7 +84,7 @@ const LibraryScreen = () => {
const [index, setIndex] = useState(0);
const bottomSheetRef = useRef<Bottomsheet | null>(null);
const bottomSheetRef = useRef<BottomSheet | null>(null);
const renderTabBar = (
props: SceneRendererProps & { navigationState: State },
@@ -91,9 +103,20 @@ const LibraryScreen = () => {
styles.tabBar,
]}
renderLabel={({ route, color }) => (
<Text style={{ color }}>{`${route.title} ${
showNumberOfNovels ? '(' + (route as any).novels.length + ')' : ''
}`}</Text>
<Row>
<Text style={{ color }}>{route.title}</Text>
<View
style={[styles.badgeCtn, { backgroundColor: theme.surfaceVariant }]}
>
{showNumberOfNovels && (
<Text
style={[styles.badgetText, { color: theme.onSurfaceVariant }]}
>
{(route as any)?.novels.length}
</Text>
)}
</View>
</Row>
)}
inactiveColor={theme.secondary}
activeColor={theme.primary}
@@ -154,7 +177,7 @@ const LibraryScreen = () => {
}
: {
iconName: 'filter-variant',
onPress: () => bottomSheetRef.current?.show(),
onPress: () => bottomSheetRef.current?.expand(),
},
]}
theme={theme}
@@ -188,7 +211,7 @@ const LibraryScreen = () => {
renderTabBar={renderTabBar}
renderScene={({ route }) =>
isLoading ? (
<LoadingScreenV2 theme={theme} />
<SourceScreenSkeletonLoading theme={theme} />
) : (
<>
{searchText ? (
@@ -222,6 +245,29 @@ const LibraryScreen = () => {
onIndexChange={setIndex}
initialLayout={{ width: layout.width }}
/>
{useLibraryFAB &&
!isHistoryLoading &&
history &&
history.length !== 0 &&
!error && (
<FAB
style={[
styles.fab,
{ backgroundColor: theme.primary, marginRight: rightInset + 16 },
]}
small
color={theme.onPrimary}
uppercase={false}
label={'Resume'}
icon="play"
onPress={() => {
navigate(
'Chapter' as never,
openChapter(history[0], history[0]) as never,
);
}}
/>
)}
<SetCategoryModal
novelId={selectedNovelIds}
currentCategoryIds={selectedNovelCategoryIds}
@@ -285,4 +331,18 @@ const styles = StyleSheet.create({
globalSearchBtnLabel: {
fontWeight: 'bold',
},
fab: {
position: 'absolute',
margin: 16,
right: 0,
bottom: 0,
},
badgeCtn: {
borderRadius: 50,
marginHorizontal: 6,
paddingHorizontal: 6,
},
badgetText: {
fontSize: 12,
},
});
@@ -1,12 +1,5 @@
import React, { LegacyRef, useMemo, useState } from 'react';
import {
Animated,
StyleSheet,
Text,
useWindowDimensions,
View,
} from 'react-native';
import Bottomsheet from 'rn-sliding-up-panel';
import React, { Ref, useMemo, useState } from 'react';
import { StyleSheet, Text, useWindowDimensions, View } from 'react-native';
import { SceneMap, TabBar, TabView } from 'react-native-tab-view';
import color from 'color';
@@ -25,10 +18,14 @@ import {
import { FlashList } from '@shopify/flash-list';
import { RadioButton } from '@components/RadioButton/RadioButton';
import { overlay } from 'react-native-paper';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import {
default as BottomSheetType,
BottomSheetView,
} from '@gorhom/bottom-sheet';
import BottomSheet from '@components/BottomSheet/BottomSheet';
interface LibraryBottomSheetProps {
bottomSheetRef: LegacyRef<Bottomsheet> | null;
bottomSheetRef: Ref<BottomSheetType> | null;
}
const FirstRoute = () => {
@@ -168,7 +165,6 @@ const LibraryBottomSheet: React.FC<LibraryBottomSheetProps> = ({
bottomSheetRef,
}) => {
const theme = useTheme();
const [animatedValue] = useState(new Animated.Value(0));
const layout = useWindowDimensions();
@@ -210,21 +206,14 @@ const LibraryBottomSheet: React.FC<LibraryBottomSheetProps> = ({
third: ThirdRoute,
});
const { bottom } = useSafeAreaInsets();
const bottomSheetHeight = 480 + bottom;
return (
<Bottomsheet
animatedValue={animatedValue}
ref={bottomSheetRef}
draggableRange={{ top: bottomSheetHeight, bottom: 0 }}
snappingPoints={[0, bottomSheetHeight]}
showBackdrop={true}
backdropOpacity={0.3}
height={bottomSheetHeight}
<BottomSheet
bottomSheetRef={bottomSheetRef}
snapPoints={[520]}
height={520}
theme={theme}
>
<View
<BottomSheetView
style={[
styles.bottomSheetCtn,
{ backgroundColor: overlay(2, theme.surface) },
@@ -238,8 +227,8 @@ const LibraryBottomSheet: React.FC<LibraryBottomSheetProps> = ({
initialLayout={{ width: layout.width }}
style={styles.tabView}
/>
</View>
</Bottomsheet>
</BottomSheetView>
</BottomSheet>
);
};
@@ -15,6 +15,7 @@ import { getString } from '@strings/translations';
import { useAppDispatch } from '@redux/hooks';
import { useTheme } from '@hooks/useTheme';
import { updateLibraryAction } from '@redux/updates/updates.actions';
import { openNovel } from '@utils/handleNavigateParams';
interface Props {
categoryId: number;
@@ -45,7 +46,7 @@ export const LibraryView: React.FC<Props> = ({
if (selectedNovelIds.length) {
setSelectedNovelIds(xor(selectedNovelIds, [item.novelId]));
} else {
navigate('Novel' as never, item as never);
navigate('Novel' as never, openNovel(item) as never);
dispatch(setNovel(item));
}
}}
+3 -3
View File
@@ -3,11 +3,11 @@ import { ScrollView } from 'react-native';
import * as Linking from 'expo-linking';
import { List } from '../../components/List';
import { ScreenContainer } from '../../components/Common';
import { MoreHeader } from './components/MoreHeader';
import { appVersion, releaseDate } from '../../utils/versionUtils';
import { useTheme } from '@hooks/useTheme';
import { List } from '@components';
const AboutScreen = ({ navigation }) => {
const theme = useTheme();
@@ -24,14 +24,14 @@ const AboutScreen = ({ navigation }) => {
<List.Section>
<List.Item
title="Version"
description={`Preview ${appVersion} (${releaseDate})`}
description={`Stable ${appVersion} (${releaseDate})`}
theme={theme}
/>
<List.Item
title="What's new"
onPress={() =>
Linking.openURL(
`https://github.com/LNReader/lnreader-preview/releases/tag/v${appVersion}`,
`https://github.com/LNReader/lnreader/releases/tag/v${appVersion}`,
)
}
theme={theme}
+10 -20
View File
@@ -7,7 +7,7 @@ import { useDispatch, useSelector } from 'react-redux';
import { ScreenContainer } from '../../components/Common';
import EmptyView from '../../components/EmptyView';
import { Appbar, LoadingScreenV2 } from '@components';
import { Appbar, List } from '@components';
import {
deleteDownloads,
getDownloadedChapters,
@@ -22,6 +22,8 @@ import {
import UpdatesItem from '../updates/components/UpdatesItem';
import RemoveDownloadsDialog from './components/RemoveDownloadsDialog';
import { openChapter, openNovel } from '@utils/handleNavigateParams';
import UpdatesSkeletonLoading from '@screens/updates/components/UpdatesSkeletonLoading';
const Downloads = ({ navigation }) => {
const theme = useTheme();
@@ -54,29 +56,13 @@ const Downloads = ({ navigation }) => {
}, []);
const onPress = useCallback(
item =>
navigation.navigate('Chapter', {
chapterId: item.chapterId,
chapterUrl: item.chapterUrl,
sourceId: item.sourceId,
novelUrl: item.novelUrl,
chapterName: item.chapterName,
novelId: item.novelId,
novelName: item.novelName,
bookmark: item.bookmark,
}),
item => navigation.navigate('Chapter', openChapter(item, item)),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const onPressCover = useCallback(
item =>
navigation.navigate('Novel', {
sourceId: item.sourceId,
novelUrl: item.novelUrl,
novelName: item.novelName,
novelCover: item.novelCover,
}),
item => navigation.navigate('Novel', openNovel(item)),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
@@ -128,8 +114,12 @@ const Downloads = ({ navigation }) => {
/>
)}
</Appbar>
<List.InfoItem
title="Downloads are saved in a SQLite Database."
theme={theme}
/>
{loading ? (
<LoadingScreenV2 theme={theme} />
<UpdatesSkeletonLoading theme={theme} />
) : (
<FlatList
contentContainerStyle={styles.flatList}
+12 -2
View File
@@ -32,7 +32,7 @@ const MoreScreen = ({ navigation }) => {
setLibrarySettings({ incognitoMode: !incognitoMode });
return (
<ScreenContainer theme={theme}>
<>
<MoreHeader title="More" navigation={navigation} theme={theme} />
<ScrollView style={{ flex: 1 }}>
<List.Section>
@@ -136,6 +136,16 @@ const MoreScreen = ({ navigation }) => {
}
theme={theme}
/>
<List.Item
title={getString('statsScreen.title')}
icon="chart-line"
onPress={() =>
navigation.navigate('MoreStack', {
screen: 'Statistics',
})
}
theme={theme}
/>
<List.Divider theme={theme} />
<List.Item
title={getString('moreScreen.settings')}
@@ -159,7 +169,7 @@ const MoreScreen = ({ navigation }) => {
/>
</List.Section>
</ScrollView>
</ScreenContainer>
</>
);
};
+2 -2
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Appbar } from '@components';
import { Appbar, List } from '@components';
export const MoreHeader = ({ title, navigation, theme, goBack }) => (
<>
@@ -19,6 +19,7 @@ export const MoreHeader = ({ title, navigation, theme, goBack }) => (
/>
</View>
</View>
<List.Divider theme={theme} />
</>
);
@@ -27,7 +28,6 @@ const styles = StyleSheet.create({
paddingTop: 16,
paddingBottom: 24,
alignItems: 'center',
elevation: 4,
},
logo: {
height: 80,
+61 -54
View File
@@ -65,6 +65,8 @@ import DownloadCustomChapterModal from './components/DownloadCustomChapterModal'
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import useBoolean from '@hooks/useBoolean';
import { useCategorySettings } from '@hooks/useSettings';
import { openChapter } from '../../utils/handleNavigateParams';
import NovelScreenLoading from './components/LoadingAnimation/NovelScreenLoading';
const Novel = ({ route, navigation }) => {
const item = route.params;
@@ -72,7 +74,7 @@ const Novel = ({ route, navigation }) => {
const theme = useTheme();
const dispatch = useDispatch();
const { top: topInset } = useSafeAreaInsets();
const { top: topInset, bottom: bottomInset } = useSafeAreaInsets();
const progressViewOffset = topInset + 32;
const { novel, chapters, loading, updating } = useNovel();
@@ -199,7 +201,9 @@ const Novel = ({ route, navigation }) => {
list.push({
icon: 'check-outline',
onPress: () => {
dispatch(markChapterUnreadAction(selected, novel.novelId));
dispatch(
markChapterUnreadAction(selected, novel.novelId, sort, filter),
);
setSelected([]);
},
});
@@ -251,6 +255,10 @@ const Novel = ({ route, navigation }) => {
}
setSelected(sel => [...sel, chapter]);
} else {
if (selected.length === chapters.length) {
return;
}
/**
* Select custom range
*/
@@ -282,13 +290,12 @@ const Novel = ({ route, navigation }) => {
}
};
const navigateToChapter = chapter =>
navigation.navigate('Chapter', {
sourceId,
novelUrl,
novelName,
...chapter,
});
const navigateToChapter = chapter => {
navigation.navigate(
'Chapter',
openChapter({ sourceId, novelUrl, novelName }, chapter),
);
};
const showProgressPercentage = chapter => {
const savedProgress =
@@ -350,6 +357,7 @@ const Novel = ({ route, navigation }) => {
return (
<Provider>
<View style={[styles.container, { backgroundColor: theme.background }]}>
{loading && <NovelScreenLoading theme={theme} />}
<FadeView
style={{
position: 'absolute',
@@ -616,60 +624,59 @@ const Novel = ({ route, navigation }) => {
</Row>
</View>
)}
<FlashList
ref={ref => (flatlistRef.current = ref)}
estimatedItemSize={100}
data={!loading && chapters}
extraData={[downloadQueue, selected]}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={5}
windowSize={15}
initialNumToRender={7}
renderItem={renderItem}
contentContainerStyle={{ paddingBottom: 100 }}
ListHeaderComponent={
<NovelInfoHeader
item={item}
novel={novel}
theme={theme}
filter={filter}
loading={loading}
lastRead={lastReadChapter}
setCustomNovelCover={setCustomNovelCover}
dispatch={dispatch}
chapters={chapters}
navigation={navigation}
trackerSheetRef={trackerSheetRef}
novelBottomSheetRef={novelBottomSheetRef}
deleteDownloadsSnackbar={deleteDownloadsSnackbar}
/>
}
refreshControl={refreshControl()}
/>
<View style={{ minHeight: 3, flex: 1 }}>
<FlashList
ref={ref => (flatlistRef.current = ref)}
estimatedItemSize={61.1}
data={!loading && chapters}
extraData={[downloadQueue, selected]}
keyExtractor={keyExtractor}
removeClippedSubviews={true}
maxToRenderPerBatch={5}
windowSize={15}
initialNumToRender={7}
renderItem={renderItem}
contentContainerStyle={{ paddingBottom: 100 }}
ListHeaderComponent={
<NovelInfoHeader
item={item}
novel={novel}
theme={theme}
filter={filter}
loading={loading}
lastRead={lastReadChapter}
setCustomNovelCover={setCustomNovelCover}
dispatch={dispatch}
chapters={chapters}
navigation={navigation}
trackerSheetRef={trackerSheetRef}
novelBottomSheetRef={novelBottomSheetRef}
deleteDownloadsSnackbar={deleteDownloadsSnackbar}
/>
}
refreshControl={refreshControl()}
/>
</View>
{useFabForContinueReading &&
!loading &&
chapters.length > 0 &&
lastReadChapter && (
<FAB
style={[styles.fab, { backgroundColor: theme.primary }]}
style={[
styles.fab,
{ backgroundColor: theme.primary, marginBottom: bottomInset },
]}
small
color={theme.onPrimary}
uppercase={false}
label={novel.unread ? 'Start' : 'Resume'}
icon="play"
onPress={() =>
navigation.navigate('Chapter', {
chapterId: lastReadChapter.chapterId,
chapterUrl: lastReadChapter.chapterUrl,
novelUrl: novel.novelUrl,
novelId: lastReadChapter.novelId,
sourceId: novel.sourceId,
chapterName: lastReadChapter.chapterName,
novelName: novel.novelName,
bookmark: lastReadChapter.bookmark,
})
}
onPress={() => {
navigation.navigate(
'Chapter',
openChapter(novel, lastReadChapter),
);
}}
/>
)}
<Portal>
@@ -701,9 +708,9 @@ const Novel = ({ route, navigation }) => {
<JumpToChapterModal
modalVisible={jumpToChapterModal}
hideModal={() => showJumpToChapterModal(false)}
theme={theme}
chapters={chapters}
novel={novel}
chapterListRef={flatlistRef.current}
navigation={navigation}
/>
<EditInfoModal
@@ -1,7 +1,7 @@
import React from 'react';
import { ActivityIndicator } from 'react-native';
import { IconButton, Menu } from 'react-native-paper';
import { IconButton, Menu, overlay } from 'react-native-paper';
export const DownloadButton = ({
downloadQueue,
@@ -23,7 +23,7 @@ export const DownloadButton = ({
anchor={
<DeleteChapterButton theme={theme} onPress={showDeleteChapterMenu} />
}
contentStyle={{ backgroundColor: theme.menuColor }}
contentStyle={{ backgroundColor: overlay(2, theme.surface) }}
>
<Menu.Item
onPress={() => deleteChapter(chapter.chapterId, chapter.chapterName)}
@@ -202,7 +202,7 @@ const TrackerButton = ({ theme, isTracked, onPress }) => (
);
const NovelGenres = ({ theme, genre }) => {
const data = genre.split(',');
const data = genre.split(/,\s*/);
const renderItem = ({ item }) => <Chip label={item} theme={theme} />;
@@ -250,7 +250,7 @@ const styles = StyleSheet.create({
backgroundColor: coverPlaceholderColor,
},
novelTitle: {
fontSize: 18,
fontSize: 20,
},
novelAuthor: {
marginVertical: 6,

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