Compare commits

..

108 Commits

Author SHA1 Message Date
Rajarshee Chatterjee e514876170 Release v1.1.6
Lint / ESLint (push) Has been cancelled
2021-10-27 11:08:41 +05:30
Rajarshee Chatterjee 911d25bb84 Minor fixes 2021-10-27 11:08:29 +05:30
Rajarshee Chatterjee b6d2e43d3d Add bottom inset padding 2021-10-27 07:34:47 +05:30
Rajarshee Chatterjee 7375eb7ad0 Fix splitscreen view crash (Closes #77) 2021-10-27 06:48:46 +05:30
Rajarshee Chatterjee 627592d477 Add IndoWebNovel and Fix MeioNovel
Closes LNReader/lnreader-sources#93
LNReader/lnreader-sources#88
2021-10-27 06:02:03 +05:30
Rajarshee Chatterjee dbe2d13b80 fix(source): Yushubo
Closes LNReader/lnreader-sources#92
2021-10-26 22:04:57 +05:30
Rajarshee Chatterjee 97e172a2aa Add NovelRingan
LNReader/lnreader-sources#88
2021-10-26 19:59:55 +05:30
Rajarshee Chatterjee ba083d1afe Add NovelSpace
Closes LNReader/lnreader-sources#91
2021-10-26 18:48:20 +05:30
Rajarshee Chatterjee 9e614c6753 Fix search bar icon spacing 2021-10-26 18:04:07 +05:30
Rajarshee Chatterjee 5054e60195 Add Cleanup function 2021-10-26 18:03:17 +05:30
Rajarshee Chatterjee 2aa36393a6 Add MoreNovel
LNReader/lnreader-sources#88
2021-10-26 17:45:03 +05:30
Rajarshee Chatterjee c77b166fec Fix RainOfSnow language
Closes LNReader/lnreader-sources#87
2021-10-26 08:43:35 +05:30
Rajarshee Chatterjee 73276bd05f fix(source): LunarLetters 2021-10-25 09:16:32 +05:30
Rajarshee Chatterjee 7f47661468 Add MtlReader and MyLoveNovel
Closes LNReader/lnreader-sources#72 & LNReader/lnreader-sources#82
2021-10-25 08:35:12 +05:30
Rajarshee Chatterjee ec63de8115 Fix Arabic sources
Closes LNReader/lnreader-sources#86
2021-10-24 09:13:12 +05:30
Rajarshee Chatterjee ee277bfbf1 Fix fullscreen mode crash 2021-10-21 15:44:40 +05:30
Rajarshee Chatterjee e330eb122f fix(source): Madara 2021-10-20 20:21:52 +05:30
Rajarshee Chatterjee d84935a6a6 Fix UpdateScreen search crash 2021-10-20 20:21:34 +05:30
Rajarshee Chatterjee 6d852dc138 Fix workflow 2021-10-19 16:15:13 +05:30
Bread Genie 4bd5e9b4f5 ci: add eslint check (#86)
* ci: add eslint check

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

* fix(eslint): fixed eslint warnings and errors

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

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

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

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

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 48 KiB

+12
View File
@@ -0,0 +1,12 @@
name: Lint
on: [push, pull_request]
jobs:
build:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm i
- name: Run ESLint
run: npm run lint
+3 -1
View File
@@ -70,4 +70,6 @@ buck-out/
.expo/
web-build/
# @end expo-cli
# @end expo-cli
.vscode/
+1
View File
@@ -0,0 +1 @@
**/node_modules
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
};
+32 -32
View File
@@ -1,43 +1,43 @@
import React, { useEffect } from "react";
import { StatusBar } from "react-native";
import React, {useEffect} from 'react';
import {StatusBar} from 'react-native';
import { useFonts } from "expo-font";
import { Provider as PaperProvider } from "react-native-paper";
import { Provider } from "react-redux";
import { persistor, store } from "./src/redux/store";
import { PersistGate } from "redux-persist/lib/integration/react";
import * as SplashScreen from "expo-splash-screen";
import {useFonts} from 'expo-font';
import {Provider as PaperProvider} from 'react-native-paper';
import {Provider} from 'react-redux';
import {persistor, store} from './src/redux/store';
import {PersistGate} from 'redux-persist/lib/integration/react';
import * as SplashScreen from 'expo-splash-screen';
import Main from "./src/navigators/Main";
import Main from './src/navigators/Main';
import { createDB } from "./src/database/DBHelper";
import { fonts } from "./src/theme/fonts";
import {createDB} from './src/database/DBHelper';
import {fonts} from './src/theme/fonts';
import {SafeAreaProvider} from 'react-native-safe-area-context';
const App = () => {
const [loaded] = useFonts(fonts);
const [loaded] = useFonts(fonts);
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
}, []);
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
}, []);
if (!loaded) {
return null;
}
if (!loaded) {
return null;
}
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<PaperProvider>
<StatusBar
translucent={true}
backgroundColor="transparent"
/>
<Main />
</PaperProvider>
</PersistGate>
</Provider>
);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
);
};
export default App;
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
rajarshee.adm@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+41
View File
@@ -0,0 +1,41 @@
# Contributing Guide
Contributions are welcome and are greatly appreciated!
## Setting up your environment
After forking to your own github org or account, do the following steps to get started:
```bash
# clone your fork to your local machine
git clone https://github.com/<your-account-name>/lnreader.git
# step into local repo
cd lnreader
# install dependencies
npm install
# run metro for development
npm start
```
### Developing on Android
While the packager is running and you have an Android device or emulator connected to your computer, build and launch the Android app.
```
npm run android
```
### Style & Linting
This codebase's linting rules are enforced using [ESLint](http://eslint.org/).
It is recommended that you install an eslint plugin for your editor of choice when working on this
codebase, however you can always check to see if the source code is compliant by running:
```bash
npm run lint
```
+1 -1
View File
@@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.2"
versionName "1.1.6"
}
splits {
abi {
+88 -37
View File
@@ -1,38 +1,89 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.rajarsheechatterjee.LNReader">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@rajarsheechatterjee/LNReader"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="41.0.0"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="lnreader"/>
<data android:scheme="com.rajarsheechatterjee.LNReader"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
<manifest 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"/>
<!-- Storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<meta-data
android:name="expo.modules.updates.EXPO_UPDATE_URL"
android:value="https://exp.host/@rajarsheechatterjee/LNReader"/>
<meta-data
android:name="expo.modules.updates.EXPO_SDK_VERSION"
android:value="41.0.0"/>
<meta-data
android:name="expo.modules.updates.ENABLED"
android:value="false"/>
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH"
android:value="ALWAYS"/>
<meta-data
android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS"
android:value="0"/>
<meta-data
android:name="expo.modules.notifications.default_notification_icon"
android:resource="@drawable/notification_icon"/>
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.App.SplashScreen"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="lnreader"/>
<data android:scheme="com.rajarsheechatterjee.LNReader"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
</manifest>
File diff suppressed because one or more lines are too long
@@ -3,8 +3,11 @@ import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle;
import android.graphics.Color;
import android.view.WindowManager;
import android.os.Build;
import android.view.View;
import android.view.Window;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
@@ -26,19 +29,25 @@ public class MainActivity extends ReactActivity {
sendBroadcast(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
super.onCreate(savedInstanceState);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
}
Window w = getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
super.onCreate(null);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
}
/**
@@ -46,6 +46,7 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
packages.add(new NavigationBarColorPackage());
return packages;
}
@@ -13,6 +13,7 @@ public class BasePackageList {
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.haptics.HapticsPackage(),
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.lineargradient.LinearGradientPackage(),
@@ -0,0 +1,170 @@
package com.rajarsheechatterjee.LNReader;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.os.Build;
import android.app.Activity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.UiThread;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
import com.facebook.react.uimanager.IllegalViewOperationException;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
public class NavigationBarColorModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBarColor";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity";
private static final String ERROR_API_LEVEL = "API_LEVEl";
private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported";
private static ReactApplicationContext reactContext = null;
private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
public NavigationBarColorModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
public void setNavigationBarTheme(Activity activity, Boolean light) {
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Window window = activity.getWindow();
int flags = window.getDecorView().getSystemUiVisibility();
if (light) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
window.getDecorView().setSystemUiVisibility(flags);
}
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("EXAMPLE_CONSTANT", "example");
return constants;
}
@ReactMethod
public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) {
final WritableMap map = Arguments.createMap();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(String.valueOf(color));
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
} else {
window.setNavigationBarColor(Color.parseColor(String.valueOf(color)));
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}
} else {
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));
}
} else {
promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE));
}
}
@ReactMethod
public void hideNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
View decorView = getCurrentActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
@ReactMethod
public void showNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
Window w = getCurrentActivity().getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
View decorView = w.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
}
@@ -0,0 +1,25 @@
package com.rajarsheechatterjee.LNReader;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NavigationBarColorPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NavigationBarColorModule(reactContext));
return modules;
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

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

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

+1 -1
View File
@@ -1,4 +1,4 @@
module.exports = function(api) {
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
+1 -1
View File
@@ -1,5 +1,5 @@
import 'react-native-gesture-handler';
import { registerRootComponent } from 'expo';
import {registerRootComponent} from 'expo';
import App from './App';
+1 -1
View File
@@ -1,4 +1,4 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require('expo/metro-config');
const {getDefaultConfig} = require('expo/metro-config');
module.exports = getDefaultConfig(__dirname);
+22867 -19777
View File
File diff suppressed because it is too large Load Diff
+65 -71
View File
@@ -1,73 +1,67 @@
{
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "6.0.0",
"@react-native-community/slider": "3.0.3",
"@react-navigation/material-bottom-tabs": "^5.3.10",
"@react-navigation/native": "^5.8.10",
"@react-navigation/stack": "^5.12.8",
"expo": "^41.0.1",
"expo-document-picker": "^9.1.2",
"expo-file-system": "~11.0.2",
"expo-font": "~9.1.0",
"expo-linear-gradient": "~9.1.0",
"expo-linking": "~2.2.3",
"expo-notifications": "~0.11.6",
"expo-splash-screen": "^0.11.2",
"expo-sqlite": "~9.1.0",
"expo-updates": "~0.5.4",
"expo-web-browser": "~9.1.0",
"moment": "^2.29.1",
"qs": "^6.10.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "^0.64.2",
"react-native-background-actions": "^2.6.1",
"react-native-cheerio": "^1.0.0-rc.4",
"react-native-device-info": "^8.1.3",
"react-native-easing-gradient": "^0.1.2",
"react-native-fast-image": "^8.3.4",
"react-native-fit-image": "^1.5.5",
"react-native-gesture-handler": "~1.10.2",
"react-native-navigation-bar-color": "^2.0.1",
"react-native-pager-view": "^5.1.10",
"react-native-paper": "^4.5.0",
"react-native-reanimated": "^2.1.0",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "~3.0.0",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.0.1",
"react-native-unimodules": "~0.13.3",
"react-native-vector-icons": "^7.1.0",
"react-native-web": "^0.16.5",
"react-native-webview": "^11.6.2",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"rn-sliding-up-panel": "^2.4.4"
},
"devDependencies": {
"@babel/core": "~7.9.0"
},
"prettier": {
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
},
"private": true,
"name": "lnreader",
"version": "1.0.0"
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"lint": "eslint ."
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "6.0.0",
"@react-native-community/slider": "3.0.3",
"@react-navigation/bottom-tabs": "^5.11.15",
"@react-navigation/material-bottom-tabs": "^5.3.10",
"@react-navigation/native": "^5.8.10",
"@react-navigation/stack": "^5.12.8",
"expo": "^41.0.1",
"expo-document-picker": "^9.1.2",
"expo-file-system": "~11.0.2",
"expo-font": "~9.1.0",
"expo-haptics": "~10.0.0",
"expo-linear-gradient": "~9.1.0",
"expo-linking": "~2.2.3",
"expo-notifications": "~0.11.6",
"expo-splash-screen": "^0.11.2",
"expo-sqlite": "~9.1.0",
"expo-updates": "~0.5.4",
"expo-web-browser": "~9.1.0",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "^0.64.2",
"react-native-background-actions": "^2.6.1",
"react-native-cheerio": "^1.0.0-rc.4",
"react-native-device-info": "^8.1.3",
"react-native-easing-gradient": "^0.1.2",
"react-native-fast-image": "^8.3.4",
"react-native-gesture-handler": "~1.10.2",
"react-native-pager-view": "^5.1.10",
"react-native-paper": "^4.5.0",
"react-native-reanimated": "^2.1.0",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "~3.0.0",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.0.1",
"react-native-tts": "^4.1.0",
"react-native-unimodules": "~0.13.3",
"react-native-vector-icons": "^7.1.0",
"react-native-web": "^0.16.5",
"react-native-webview": "^11.6.2",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"rn-sliding-up-panel": "^2.4.4"
},
"devDependencies": {
"@babel/core": "~7.9.0",
"@react-native-community/eslint-config": "^3.0.1",
"eslint": "^7.32.0"
},
"private": true,
"name": "lnreader",
"version": "1.1.6"
}
+39 -34
View File
@@ -1,42 +1,47 @@
import React from "react";
import { Dimensions, StyleSheet, View } from "react-native";
import React from 'react';
import {Dimensions, StyleSheet, View} from 'react-native';
import { IconButton } from "react-native-paper";
import {IconButton} from 'react-native-paper';
export const Actionbar = ({ actions, theme }) => {
export const Actionbar = ({active, actions, theme, style}) => {
if (active) {
return (
<View
style={[
styles.actionbarContainer,
{ backgroundColor: theme.colorPrimary },
]}
>
{actions.map(
(action, index) =>
action.icon && (
<IconButton
key={index}
icon={action.icon}
color={theme.textColorPrimary}
onPress={action.onPress}
/>
)
)}
</View>
<View
style={[
styles.actionbarContainer,
{backgroundColor: theme.colorPrimary},
style,
]}
>
{actions.map(
(action, index) =>
action.icon && (
<IconButton
key={index}
icon={action.icon}
color={theme.textColorPrimary}
onPress={action.onPress}
/>
),
)}
</View>
);
} else {
return null;
}
};
const styles = StyleSheet.create({
actionbarContainer: {
position: "absolute",
width: Dimensions.get("window").width - 32,
bottom: 0,
margin: 16,
borderRadius: 6,
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 8,
paddingVertical: 4,
elevation: 2,
},
actionbarContainer: {
position: 'absolute',
width: Dimensions.get('window').width - 32,
bottom: 0,
margin: 16,
borderRadius: 6,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 8,
paddingVertical: 4,
elevation: 2,
},
});
+25 -19
View File
@@ -1,24 +1,30 @@
import React from "react";
import React from 'react';
import PropTypes from 'prop-types';
import { Appbar as MaterialAppbar } from "react-native-paper";
import {Appbar as MaterialAppbar} from 'react-native-paper';
import { useTheme } from "../hooks/reduxHooks";
import {useTheme} from '../hooks/reduxHooks';
import {ViewPropTypes} from 'react-native';
export const Appbar = ({ title, onBackAction, style, children }) => {
const theme = useTheme();
export const Appbar = ({title, onBackAction, style, children}) => {
const theme = useTheme();
return (
<MaterialAppbar.Header
style={[{ backgroundColor: theme.colorPrimary }, style]}
>
{onBackAction && (
<MaterialAppbar.BackAction onPress={onBackAction} />
)}
<MaterialAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
{children}
</MaterialAppbar.Header>
);
return (
<MaterialAppbar.Header
style={[{backgroundColor: theme.colorPrimary}, style]}
>
{onBackAction && <MaterialAppbar.BackAction onPress={onBackAction} />}
<MaterialAppbar.Content
title={title}
titleStyle={{color: theme.textColorPrimary}}
/>
{children}
</MaterialAppbar.Header>
);
};
Appbar.propTypes = {
title: PropTypes.string,
onBackAction: PropTypes.func,
style: ViewPropTypes.style,
};
+13 -13
View File
@@ -1,17 +1,17 @@
import React from "react";
import { View } from "react-native";
import React from 'react';
import {View} from 'react-native';
const BottomSheetHandle = ({ theme }) => (
<View
style={{
backgroundColor: "rgba(255,255,255,0.4)",
height: 4,
width: 40,
borderRadius: 50,
top: 8,
alignSelf: "center",
}}
/>
const BottomSheetHandle = ({theme}) => (
<View
style={{
backgroundColor: `${theme.textColorPrimary}66`,
height: 4,
width: 40,
borderRadius: 50,
top: 8,
alignSelf: 'center',
}}
/>
);
export default BottomSheetHandle;
+53 -65
View File
@@ -1,73 +1,61 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import React from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import {Checkbox as PaperCheckbox} from 'react-native-paper';
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const Checkbox = ({ label, status, onPress, theme }) => (
<View
style={[
styles.checkboxContainer,
status && { backgroundColor: theme.rippleColor },
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
>
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
{status && (
<MaterialCommunityIcons
name="check"
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
</Pressable>
</View>
export const Checkbox = ({label, status, onPress, disabled, theme}) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={styles.pressable}
onPress={onPress}
disabled={disabled}
>
<PaperCheckbox
status={
status === 'indeterminate'
? 'indeterminate'
: status
? 'checked'
: 'unchecked'
}
onPress={onPress}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
disabled={disabled}
/>
<Text style={{color: theme.textColorPrimary, marginLeft: 12}}>{label}</Text>
</Pressable>
);
export const SortItem = ({ label, status, onPress, theme }) => (
<View
style={[
styles.checkboxContainer,
status && { backgroundColor: theme.rippleColor },
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
>
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
{status && (
<MaterialCommunityIcons
name={status === "asc" ? "arrow-up" : "arrow-down"}
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
</Pressable>
</View>
export const SortItem = ({label, status, onPress, theme}) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={[styles.pressable, {paddingVertical: 16, paddingLeft: 64}]}
onPress={onPress}
>
{status && (
<MaterialCommunityIcons
name={status === 'asc' ? 'arrow-up' : 'arrow-down'}
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
<Text style={{color: theme.textColorPrimary}}>{label}</Text>
</Pressable>
);
const styles = StyleSheet.create({
checkboxContainer: {
borderRadius: 8,
overflow: "hidden",
marginVertical: 4,
},
pressable: {
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: "row",
justifyContent: "space-between",
},
icon: {
position: "absolute",
right: 16,
alignSelf: "center",
},
pressable: {
paddingHorizontal: 16,
paddingVertical: 6,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
position: 'absolute',
left: 24,
alignSelf: 'center',
},
});
+49
View File
@@ -0,0 +1,49 @@
import React from 'react';
import {View, Text, Pressable, StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
export const Chip = ({label, onPress, theme}) => {
return (
<View style={[styles.container, {backgroundColor: theme.colorPrimary}]}>
<Pressable
style={styles.content}
onPress={onPress}
android_ripple={{color: theme.rippleColor}}
>
<Text style={[styles.label, {color: theme.textColorSecondary}]}>
{label}
</Text>
</Pressable>
</View>
);
};
Chip.propTypes = {
label: PropTypes.string,
onPress: PropTypes.func,
theme: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
container: {
borderRadius: 8,
overflow: 'hidden',
alignSelf: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
height: 32,
marginHorizontal: 4,
elevation: 1,
},
content: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 16,
},
label: {
fontSize: 14,
},
});
+131 -81
View File
@@ -1,98 +1,148 @@
import React, { useState } from "react";
import { StyleSheet, Text } from "react-native";
import React, {useState} from 'react';
import {FlatList, Pressable, StyleSheet, Text, View} from 'react-native';
import { Modal, Portal, TextInput } from "react-native-paper";
import {Modal, Portal, TextInput} from 'react-native-paper';
const ColorPickerModal = ({
theme,
color,
title,
onSubmit,
hideModal,
modalVisible,
showAccentColors,
theme,
color,
title,
onSubmit,
hideModal,
modalVisible,
showAccentColors,
}) => {
const [text, setText] = useState(color);
const [error, setError] = useState();
const [text, setText] = useState(color);
const [error, setError] = useState();
const onDismiss = () => {
hideModal();
setText();
setError();
};
const onDismiss = () => {
hideModal();
setText();
setError();
};
const onChangeText = (text) => setText(text);
const onChangeText = txt => setText(txt);
const onSubmitEditing = () => {
const re = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
const onSubmitEditing = () => {
const re = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
if (text.match(re)) {
onSubmit(text);
hideModal();
} else {
setError("Enter a valid hex color code");
}
};
if (text.match(re)) {
onSubmit(text);
hideModal();
} else {
setError('Enter a valid hex color code');
}
};
const textInputTheme = {
colors: {
primary: color,
placeholder: theme.textColorHint,
text: theme.textColorPrimary,
background: "transparent",
},
};
const textInputTheme = {
colors: {
primary: color,
placeholder: theme.textColorHint,
text: theme.textColorPrimary,
background: 'transparent',
},
};
return (
<Portal>
<Modal
visible={modalVisible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: theme.colorPrimary },
]}
>
<Text
style={[
styles.modalTitle,
{ color: theme.textColorPrimary },
]}
>
{title}
</Text>
<TextInput
value={text}
defaultValue={color}
placeholder="Hex Color Code (E.g. #3399FF)"
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
mode="outlined"
theme={textInputTheme}
underlineColor={theme.textColorHint}
dense
error={error}
const accentColors = [
'#EF5350',
'#EC407A',
'#AB47BC',
'#7E57C2',
'#5C6BC0',
'#42A5F5',
'#29B6FC',
'#26C6DA',
'#26A69A',
'#66BB6A',
'#9CCC65',
'#D4E157',
'#FFEE58',
'#FFCA28',
'#FFA726',
'#FF7043',
'#8D6E63',
'#BDBDBD',
'#78909C',
'#000000',
];
return (
<Portal>
<Modal
visible={modalVisible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{backgroundColor: theme.colorPrimary},
]}
>
<Text style={[styles.modalTitle, {color: theme.textColorPrimary}]}>
{title}
</Text>
{showAccentColors && (
<FlatList
contentContainerStyle={{marginBottom: 8}}
data={accentColors}
numColumns={4}
keyExtractor={item => item}
renderItem={({item}) => (
<View
style={{
borderRadius: 4,
overflow: 'hidden',
backgroundColor: item,
flex: 1 / 4,
height: 40,
marginHorizontal: 4,
marginVertical: 4,
}}
>
<Pressable
style={{flex: 1}}
android_ripple={{
color: 'rgba(0,0,0,0.12)',
}}
onPress={() => {
onSubmit(item);
hideModal();
}}
/>
<Text style={styles.errorText}>{error}</Text>
</Modal>
</Portal>
);
</View>
)}
/>
)}
<TextInput
value={text}
defaultValue={color}
placeholder="Hex Color Code (E.g. #3399FF)"
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
mode="outlined"
theme={textInputTheme}
underlineColor={theme.textColorHint}
dense
error={error}
/>
<Text style={styles.errorText}>{error}</Text>
</Modal>
</Portal>
);
};
export default ColorPickerModal;
const styles = StyleSheet.create({
modalContainer: {
margin: 30,
padding: 20,
borderRadius: 8,
},
modalTitle: {
fontSize: 18,
marginBottom: 16,
},
errorText: {
color: "#FF0033",
paddingTop: 8,
},
modalContainer: {
margin: 30,
padding: 20,
borderRadius: 8,
},
modalTitle: {
fontSize: 18,
marginBottom: 16,
},
errorText: {
color: '#FF0033',
paddingTop: 8,
},
});
+25 -14
View File
@@ -1,21 +1,32 @@
import React from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
const ScreenContainer = ({ children, theme }) => (
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
{children}
const ScreenContainer = ({children, theme}) => {
const {bottom} = useSafeAreaInsets();
return (
<View
style={{
flex: 1,
backgroundColor: theme.colorPrimaryDark,
paddingBottom: bottom,
}}
>
{children}
</View>
);
};
const Row = ({children, style}) => (
<View style={[styles.row, style]}>{children}</View>
);
const Row = ({ children, style }) => (
<View style={[styles.row, style]}>{children}</View>
);
export { ScreenContainer, Row };
export {ScreenContainer, Row};
const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
});
+110
View File
@@ -0,0 +1,110 @@
/* eslint-disable no-bitwise */
import PropTypes from 'prop-types';
import React, {PureComponent, Children} from 'react';
import {View, Animated, StyleSheet} from 'react-native';
export default class FadeView extends PureComponent {
static defaultProps = {
animationDuration: 225,
active: false,
removeHiddenSubviews: true,
};
static propTypes = {
animationDuration: PropTypes.number,
active: PropTypes.bool,
removeHiddenSubviews: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
constructor(props) {
super(props);
this.renderChild = this.renderChild.bind(this);
this.mounted = false;
let {active} = this.props;
this.state = {
progress: new Animated.Value(Number(active)),
animating: false,
};
}
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
UNSAFE_componentWillReceiveProps({active, animationDuration}) {
let {progress} = this.state;
if (active ^ this.props.active) {
this.setState({animating: true});
Animated.timing(progress, {
toValue: Number(active),
duration: animationDuration,
useNativeDriver: true,
}).start(() => {
if (this.mounted) {
this.setState({animating: false});
}
});
}
}
renderChild(child, index) {
let {active, removeHiddenSubviews} = this.props;
let {animating, progress} = this.state;
let opacity = progress.interpolate({
inputRange: [0, 1],
outputRange: index ? [0, 1] : [1, 0],
});
let hidden = active ^ !!index;
if (removeHiddenSubviews) {
if (!animating && hidden) {
return null;
}
}
let pointerEvents = hidden ? 'none' : 'box-none';
return (
<Animated.View
style={[styles.container, {opacity}]}
pointerEvents={pointerEvents}
>
{child}
</Animated.View>
);
}
render() {
let {children, ...props} = this.props;
return <View {...props}>{Children.map(children, this.renderChild)}</View>;
}
}
const styles = StyleSheet.create({
container: {
// ...StyleSheet.absoluteFillObject,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
});
+56 -64
View File
@@ -1,72 +1,64 @@
import React from "react";
import { Pressable, View } from "react-native";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import React from 'react';
import {Pressable, View} from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ToggleButton = ({ icon, selected, theme, color, onPress }) => (
<View
style={{
borderRadius: 8,
overflow: "hidden",
marginHorizontal: 4,
}}
export const ToggleButton = ({icon, selected, theme, color, onPress}) => (
<View
style={{
borderRadius: 8,
overflow: 'hidden',
marginHorizontal: 4,
}}
>
<Pressable
android_ripple={{color: theme.rippleColor}}
style={{
padding: 10,
alignItems: 'center',
justifyContent: 'center',
}}
onPress={onPress}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 10,
alignItems: "center",
justifyContent: "center",
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={icon}
color={
selected
? theme.colorAccent
: color
? color
: theme.textColorPrimary
}
size={24}
/>
</Pressable>
</View>
<MaterialCommunityIcons
name={icon}
color={
selected ? theme.colorAccent : color ? color : theme.textColorPrimary
}
size={24}
/>
</Pressable>
</View>
);
export const ToggleColorButton = ({
selected,
backgroundColor,
textColor,
onPress,
selected,
backgroundColor,
textColor,
onPress,
}) => (
<View
style={{
borderRadius: 50,
overflow: "hidden",
marginHorizontal: 6,
height: 44,
width: 44,
}}
<View
style={{
borderRadius: 50,
overflow: 'hidden',
marginHorizontal: 6,
height: 44,
width: 44,
}}
>
<Pressable
android_ripple={{color: textColor}}
style={{
flex: 1,
padding: 10,
backgroundColor: backgroundColor,
alignItems: 'center',
justifyContent: 'center',
}}
onPress={onPress}
>
<Pressable
android_ripple={{ color: textColor }}
style={{
flex: 1,
padding: 10,
backgroundColor: backgroundColor,
alignItems: "center",
justifyContent: "center",
}}
onPress={onPress}
>
{selected && (
<MaterialCommunityIcons
name="check"
color={textColor}
size={24}
/>
)}
</Pressable>
</View>
{selected && (
<MaterialCommunityIcons name="check" color={textColor} size={24} />
)}
</Pressable>
</View>
);
+8
View File
@@ -0,0 +1,8 @@
/* eslint-disable no-unused-vars */
import React from 'react';
import {StyleSheet} from 'react-native';
export const Dialog = ({visible, theme}) => {};
const styles = StyleSheet.create({});
+37 -43
View File
@@ -1,51 +1,45 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { useSelector } from "react-redux";
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {useSelector} from 'react-redux';
const EmptyView = ({ icon, description, style, children, iconStyle }) => {
const theme = useSelector((state) => state.settingsReducer.theme);
const EmptyView = ({icon, description, style, children, iconStyle}) => {
const theme = useSelector(state => state.settingsReducer.theme);
return (
<View style={styles.emptyViewContainer}>
<Text
style={[
styles.emptyViewIcon,
{ color: theme.textColorHint },
style,
iconStyle,
]}
>
{icon}
</Text>
<Text
style={[
styles.emptyViewText,
{ color: theme.textColorHint },
style,
]}
>
{description}
</Text>
{children}
</View>
);
return (
<View style={styles.emptyViewContainer}>
<Text
style={[
styles.emptyViewIcon,
{color: theme.textColorHint},
style,
iconStyle,
]}
>
{icon}
</Text>
<Text style={[styles.emptyViewText, {color: theme.textColorHint}, style]}>
{description}
</Text>
{children}
</View>
);
};
export default EmptyView;
const styles = StyleSheet.create({
emptyViewContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: "bold",
marginTop: 10,
textAlign: "center",
paddingHorizontal: 30,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: 'bold',
marginTop: 10,
textAlign: 'center',
paddingHorizontal: 30,
},
});
+22 -22
View File
@@ -1,28 +1,28 @@
import React from "react";
import { Text, View } from "react-native";
import React from 'react';
import {Text, View} from 'react-native';
import { IconButton } from "react-native-paper";
import { Row } from "./Common";
import {IconButton} from 'react-native-paper';
// import {Row} from './Common';
import EmptyView from "./EmptyView";
import EmptyView from './EmptyView';
const ErrorView = ({ theme, onRetry, error, openWebView }) => (
<View style={{ flex: 1, justifyContent: "center" }}>
<EmptyView
icon="(; ̄Д ̄)"
description={error}
style={{ color: theme.textColorSecondary }}
>
<IconButton
icon="reload"
size={25}
style={{ margin: 0, marginTop: 24 }}
color={theme.text}
onPress={onRetry}
/>
<Text style={{ color: theme.textColorPrimary }}>Retry</Text>
</EmptyView>
</View>
const ErrorView = ({theme, onRetry, error, openWebView}) => (
<View style={{flex: 1, justifyContent: 'center'}}>
<EmptyView
icon="(; ̄Д ̄)"
description={error}
style={{color: theme.textColorSecondary}}
>
<IconButton
icon="reload"
size={25}
style={{margin: 0, marginTop: 24}}
color={theme.text}
onPress={onRetry}
/>
<Text style={{color: theme.textColorPrimary}}>Retry</Text>
</EmptyView>
</View>
);
export default ErrorView;
+67 -67
View File
@@ -1,75 +1,75 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import { IconButton } from "react-native-paper";
import React from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import {IconButton} from 'react-native-paper';
const icons = ["(・o・;)", "Σ(ಠ_ಠ)", "ಥ_ಥ", "(˘・_・˘)", "(; ̄Д ̄)", "(・Д・。"];
// const icons = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
export const ErrorView = ({ errorName, actions, theme }) => (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text style={[styles.emptyViewIcon, { color: theme.textColorHint }]}>
{/* {icons[Math.floor(Math.random() * 5)]} */}
ಥ_ಥ
</Text>
<Text style={[styles.emptyViewText, { color: theme.textColorHint }]}>
{errorName}
</Text>
<View style={{ flexDirection: "row" }}>
{actions.map((action) => (
<View
style={{
borderRadius: 4,
overflow: "hidden",
margin: 16,
}}
>
<Pressable
android_ripple={{
color: theme.rippleColor,
borderless: false,
}}
onPress={action.onPress}
style={{
justifyContent: "center",
alignItems: "center",
paddingBottom: 8,
paddingHorizontal: 20,
minWidth: 100,
}}
>
<IconButton
icon={action.icon}
color={theme.textColorHint}
size={24}
style={{ margin: 0 }}
/>
<Text
style={{
fontSize: 12,
color: theme.textColorHint,
}}
>
{action.name}
</Text>
</Pressable>
</View>
))}
export const ErrorView = ({errorName, actions, theme}) => (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={[styles.emptyViewIcon, {color: theme.textColorHint}]}>
{/* {icons[Math.floor(Math.random() * 5)]} */}
ಥ_ಥ
</Text>
<Text style={[styles.emptyViewText, {color: theme.textColorHint}]}>
{errorName}
</Text>
<View style={{flexDirection: 'row'}}>
{actions.map(action => (
<View
style={{
borderRadius: 4,
overflow: 'hidden',
margin: 16,
}}
>
<Pressable
android_ripple={{
color: theme.rippleColor,
borderless: false,
}}
onPress={action.onPress}
style={{
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 8,
paddingHorizontal: 20,
minWidth: 100,
}}
>
<IconButton
icon={action.icon}
color={theme.textColorHint}
size={24}
style={{margin: 0}}
/>
<Text
style={{
fontSize: 12,
color: theme.textColorHint,
}}
>
{action.name}
</Text>
</Pressable>
</View>
))}
</View>
</View>
);
const styles = StyleSheet.create({
emptyViewContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: "bold",
marginTop: 10,
textAlign: "center",
paddingHorizontal: 30,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: 'bold',
marginTop: 10,
textAlign: 'center',
paddingHorizontal: 30,
},
});
-2
View File
@@ -1,2 +0,0 @@
import { NativeModules } from "react-native";
module.exports = NativeModules.FullScreen;
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import {Pressable, StyleSheet, ViewPropTypes} from 'react-native';
import PropTypes from 'prop-types';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const IconButton = ({
icon,
size,
color,
onPress,
containerStyle,
theme,
}) => (
<Pressable
style={[styles.container, containerStyle]}
android_ripple={{color: theme.rippleColor, borderless: true, radius: 20}}
onPress={onPress}
>
<MaterialCommunityIcons name={icon} size={size} color={color} />
</Pressable>
);
IconButton.defaultProps = {
size: 24,
color: '#FFFFFF',
};
IconButton.propTypes = {
icon: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
color: PropTypes.string,
onPress: PropTypes.func,
containerStyle: ViewPropTypes.style,
theme: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
container: {
margin: 6,
},
});
+114 -122
View File
@@ -1,143 +1,135 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import React from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import { List as PaperList, Divider as PaperDivider } from "react-native-paper";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import {List as PaperList, Divider as PaperDivider} from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const ListSection = ({ children }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
const ListSection = ({children}) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const ListSubHeader = ({ children, theme }) => (
<PaperList.Subheader
style={[styles.listSubHeader, { color: theme.colorAccent }]}
>
{children}
</PaperList.Subheader>
const ListSubHeader = ({children, theme}) => (
<PaperList.Subheader
style={[styles.listSubHeader, {color: theme.colorAccent}]}
>
{children}
</PaperList.Subheader>
);
const ListItem = ({
title,
description,
icon,
onPress,
theme,
right,
iconColor,
titleStyle,
style,
title,
description,
icon,
onPress,
theme,
right,
iconColor,
titleStyle,
style,
}) => (
<PaperList.Item
title={title}
style={style}
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
descriptionNumberOfLines={1}
left={() => (
<View style={{ justifyContent: "center" }}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{
marginVertical: 0,
}}
/>
)}
</View>
)}
right={() =>
right && (
<MaterialCommunityIcons
name={right}
color={iconColor || "#47a84a"}
size={23}
style={{ marginRight: 16, textAlignVertical: "center" }}
/>
)
}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
);
const Divider = ({ theme }) => (
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
);
const InfoItem = ({ title, icon, theme }) => (
<PaperList.Item
title={title}
titleStyle={{ color: theme.textColorSecondary, fontSize: 14 }}
titleNumberOfLines={5}
left={() =>
icon && (
<PaperList.Icon
color={theme.textColorSecondary}
icon={icon}
style={{ marginVertical: 0 }}
/>
)
}
/>
);
const ColorItem = ({ title, description, theme, onPress }) => (
<Pressable
style={{
padding: 16,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
android_ripple={{ color: theme.rippleColor }}
onPress={onPress}
>
<View>
<Text style={{ color: theme.textColorPrimary, fontSize: 16 }}>
{title}
</Text>
<Text style={{ color: theme.textColorSecondary }}>
{description}
</Text>
</View>
<View
<PaperList.Item
title={title}
style={style}
titleStyle={[{color: theme.textColorPrimary}, titleStyle]}
description={description}
descriptionStyle={{color: theme.textColorSecondary}}
descriptionNumberOfLines={1}
left={() => (
<View style={{justifyContent: 'center'}}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{
backgroundColor: description,
height: 24,
width: 24,
borderRadius: 50,
marginRight: 16,
marginVertical: 0,
}}
/>
)}
</View>
)}
right={() =>
right && (
<MaterialCommunityIcons
name={right}
color={iconColor || '#47a84a'}
size={23}
style={{marginRight: 16, textAlignVertical: 'center'}}
/>
</Pressable>
)
}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
);
const Icon = ({ icon, theme }) => (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
const Divider = ({theme}) => (
<PaperDivider style={{backgroundColor: theme.dividerColor, height: 1}} />
);
const InfoItem = ({title, icon, theme}) => (
<PaperList.Item
title={title}
titleStyle={{color: theme.textColorSecondary, fontSize: 14}}
titleNumberOfLines={5}
left={() =>
icon && (
<PaperList.Icon
color={theme.textColorSecondary}
icon={icon}
style={{marginVertical: 0}}
/>
)
}
/>
);
const ColorItem = ({title, description, theme, onPress}) => (
<Pressable
style={{
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
android_ripple={{color: theme.rippleColor}}
onPress={onPress}
>
<View>
<Text style={{color: theme.textColorPrimary, fontSize: 16}}>{title}</Text>
<Text style={{color: theme.textColorSecondary}}>{description}</Text>
</View>
<View
style={{
backgroundColor: description,
height: 24,
width: 24,
borderRadius: 50,
marginRight: 16,
}}
/>
</Pressable>
);
const Icon = ({icon, theme}) => (
<PaperList.Icon color={theme.colorAccent} icon={icon} style={{margin: 0}} />
);
export const List = {
Section: ListSection,
SubHeader: ListSubHeader,
Item: ListItem,
Divider,
InfoItem,
ColorItem,
Icon,
Section: ListSection,
SubHeader: ListSubHeader,
Item: ListItem,
Divider,
InfoItem,
ColorItem,
Icon,
};
const styles = StyleSheet.create({
listSection: {
flex: 1,
marginVertical: 0,
},
listSubHeader: {
paddingBottom: 5,
},
listSection: {
flex: 1,
marginVertical: 0,
},
listSubHeader: {
paddingBottom: 5,
},
});
+70 -70
View File
@@ -1,80 +1,80 @@
import React from "react";
import { StyleSheet, View, Text, Pressable } from "react-native";
import React from 'react';
import {StyleSheet, View, Text, Pressable} from 'react-native';
import FastImage from "react-native-fast-image";
import FastImage from 'react-native-fast-image';
import {defaultCoverUri} from '../sources/helpers/constants';
const ListView = ({
item,
downloadBadge,
unreadBadge,
inLibraryBadge,
theme,
onPress,
isSelected,
onLongPress,
item,
downloadBadge,
unreadBadge,
inLibraryBadge,
theme,
onPress,
isSelected,
onLongPress,
}) => {
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[
styles.listView,
isSelected && { backgroundColor: theme.rippleColor },
]}
onPress={onPress}
onLongPress={onLongPress}
>
<FastImage
source={{
uri:
item.novelCover && !item.novelCover.startsWith("/")
? item.novelCover
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
}}
style={[
styles.extensionIcon,
inLibraryBadge && { opacity: 0.5 },
]}
/>
<Text
style={[{ color: theme.textColorPrimary }, styles.novelName]}
numberOfLines={1}
>
{item.novelName}
</Text>
<View style={styles.badgeContainer}>
{downloadBadge}
{unreadBadge}
{inLibraryBadge}
</View>
</Pressable>
);
const uri =
item.novelCover && !item.novelCover.startsWith('/')
? item.novelCover
: defaultCoverUri;
return (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={[
styles.listView,
isSelected && {backgroundColor: theme.rippleColor},
]}
onPress={onPress}
onLongPress={onLongPress}
>
<FastImage
source={{
uri,
}}
style={[styles.extensionIcon, inLibraryBadge && {opacity: 0.5}]}
/>
<Text
style={[{color: theme.textColorPrimary}, styles.novelName]}
numberOfLines={1}
>
{item.novelName}
</Text>
<View style={styles.badgeContainer}>
{downloadBadge}
{unreadBadge}
{inLibraryBadge}
</View>
</Pressable>
);
};
export default ListView;
const styles = StyleSheet.create({
listView: {
flex: 1,
flexDirection: "row",
alignItems: "center",
paddingVertical: 8,
paddingHorizontal: 12,
},
extensionIcon: {
width: 40,
height: 40,
borderRadius: 4,
},
novelName: {
flex: 1,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
flexWrap: "wrap",
},
badgeContainer: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
},
listView: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 12,
},
extensionIcon: {
width: 40,
height: 40,
borderRadius: 4,
},
novelName: {
flex: 1,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
flexWrap: 'wrap',
},
badgeContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'flex-end',
},
});
@@ -1,8 +1,8 @@
import React from "react";
import { ActivityIndicator, View } from "react-native";
import React from 'react';
import {ActivityIndicator, View} from 'react-native';
export const LoadingScreen = ({ theme }) => (
<View style={{ flex: 1, justifyContent: "center" }}>
<ActivityIndicator size={50} color={theme.colorAccent} />
</View>
export const LoadingScreen = ({theme}) => (
<View style={{flex: 1, justifyContent: 'center'}}>
<ActivityIndicator size={50} color={theme.colorAccent} />
</View>
);
+66 -71
View File
@@ -1,80 +1,75 @@
import React, { useState } from "react";
import { StyleSheet, View, Text } from "react-native";
import { Portal, Modal, Button } from "react-native-paper";
import * as Linking from "expo-linking";
import { useTheme } from "../hooks/reduxHooks";
import React, {useState} from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {Portal, Modal, Button} from 'react-native-paper';
import * as Linking from 'expo-linking';
import {useTheme} from '../hooks/reduxHooks';
const NewUpdateDialog = ({ newVersion }) => {
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
const NewUpdateDialog = ({newVersion}) => {
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
const theme = useTheme();
const theme = useTheme();
return (
<Portal>
<Modal
visible={newUpdateDialog}
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{ backgroundColor: theme.colorPrimaryDark },
]}
>
<Text
style={[
styles.modalHeader,
{ color: theme.textColorPrimary },
]}
>
{`New update available ${newVersion.tag_name}`}
</Text>
<Text style={{ color: theme.textColorSecondary, fontSize: 16 }}>
{newVersion.body}
</Text>
<View
style={{
flexDirection: "row",
marginTop: 30,
justifyContent: "flex-end",
}}
>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: "none",
}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => showNewUpdateDialog(false)}
>
Cancel
</Button>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: "none",
}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => Linking.openURL(newVersion.downloadUrl)}
>
Install
</Button>
</View>
</Modal>
</Portal>
);
return (
<Portal>
<Modal
visible={newUpdateDialog}
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{backgroundColor: theme.colorPrimaryDark},
]}
>
<Text style={[styles.modalHeader, {color: theme.textColorPrimary}]}>
{`New update available ${newVersion.tag_name}`}
</Text>
<Text style={{color: theme.textColorSecondary, fontSize: 16}}>
{newVersion.body}
</Text>
<View
style={{
flexDirection: 'row',
marginTop: 30,
justifyContent: 'flex-end',
}}
>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: 'none',
}}
theme={{colors: {primary: theme.colorAccent}}}
onPress={() => showNewUpdateDialog(false)}
>
Cancel
</Button>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: 'none',
}}
theme={{colors: {primary: theme.colorAccent}}}
onPress={() => Linking.openURL(newVersion.downloadUrl)}
>
Install
</Button>
</View>
</Modal>
</Portal>
);
};
export default NewUpdateDialog;
const styles = StyleSheet.create({
containerStyle: {
padding: 20,
margin: 20,
borderRadius: 6,
},
modalHeader: {
fontSize: 20,
marginBottom: 10,
},
containerStyle: {
padding: 20,
margin: 20,
borderRadius: 6,
},
modalHeader: {
fontSize: 20,
marginBottom: 10,
},
});
+275 -280
View File
@@ -1,317 +1,312 @@
import React, { useCallback } from "react";
import React, {memo, useCallback} from 'react';
import {
StyleSheet,
View,
Text,
useWindowDimensions,
Pressable,
} from "react-native";
StyleSheet,
View,
Text,
useWindowDimensions,
Pressable,
} from 'react-native';
import { LinearGradient } from "expo-linear-gradient";
import FastImage from "react-native-fast-image";
import {LinearGradient} from 'expo-linear-gradient';
import FastImage from 'react-native-fast-image';
import ListView from "./ListView";
import ListView from './ListView';
import { getDeviceOrientation } from "../services/utils/helpers";
import { useSettings } from "../hooks/reduxHooks";
import {useDeviceOrientation} from '../services/utils/helpers';
import {useSettings} from '../hooks/reduxHooks';
import {defaultCoverUri} from '../sources/helpers/constants';
const NovelCover = ({
item,
onPress,
libraryStatus,
theme,
isSelected,
onLongPress,
selectedNovels,
item,
onPress,
libraryStatus,
theme,
isSelected,
onLongPress,
selectedNovels,
}) => {
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
useSettings();
const {displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges} =
useSettings();
const window = useWindowDimensions();
const window = useWindowDimensions();
const orientation = getDeviceOrientation();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () =>
orientation === "landscape" ? 6 : novelsPerRow;
const getNovelsPerRow = () =>
orientation === 'landscape' ? 6 : novelsPerRow;
const getHeight = useCallback(
() => (window.width / getNovelsPerRow()) * (4 / 3),
[]
);
const getHeight = useCallback(
() => (window.width / getNovelsPerRow()) * (4 / 3),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const selectNovel = () => onLongPress(item.novelId);
const selectNovel = () => onLongPress && onLongPress(item.novelId);
return displayMode !== 2 ? (
<View
style={[
{
flex: 1 / getNovelsPerRow(),
borderRadius: 6,
overflow: "hidden",
margin: 2,
},
isSelected && {
backgroundColor: theme.colorAccent,
opacity: 0.8,
},
]}
>
<Pressable
android_ripple={{ color: theme.colorAccent }}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0
? selectNovel
: onPress
}
onLongPress={selectNovel}
>
<>
<View style={styles.badgeContainer}>
{libraryStatus && <InLibraryBadge theme={theme} />}
{showDownloadBadges && item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)}
{showUnreadBadges && item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)}
</View>
<FastImage
source={{
uri:
item.novelCover &&
!item.novelCover.startsWith("/")
? item.novelCover
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
}}
style={[
{ height: getHeight(), borderRadius: 4 },
libraryStatus && { opacity: 0.5 },
]}
/>
<View style={styles.compactTitleContainer}>
{displayMode === 0 && (
<CompactTitle novelName={item.novelName} />
)}
</View>
{displayMode === 1 && (
<ComfortableTitle
novelName={item.novelName}
theme={theme}
/>
)}
</>
</Pressable>
const uri =
item.novelCover && !item.novelCover.startsWith('/')
? item.novelCover
: defaultCoverUri;
return displayMode !== 2 ? (
<View
style={[
{
flex: 1 / getNovelsPerRow(),
borderRadius: 6,
overflow: 'hidden',
margin: 2,
},
isSelected && {
backgroundColor: theme.colorAccent,
opacity: 0.8,
},
]}
>
<Pressable
android_ripple={{color: theme.colorAccent}}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
}
onLongPress={selectNovel}
>
<View style={styles.badgeContainer}>
{libraryStatus && <InLibraryBadge theme={theme} />}
{showDownloadBadges && item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)}
{showUnreadBadges && item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)}
</View>
) : (
<ListView
item={item}
downloadBadge={
showDownloadBadges &&
item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)
}
unreadBadge={
showUnreadBadges &&
item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)
}
inLibraryBadge={libraryStatus && <InLibraryBadge theme={theme} />}
theme={theme}
onPress={
selectedNovels && selectedNovels.length > 0
? selectNovel
: onPress
}
onLongPress={selectNovel}
isSelected={isSelected}
<FastImage
source={{uri}}
style={[
{height: getHeight(), borderRadius: 4},
libraryStatus && {opacity: 0.5},
]}
/>
);
<View style={styles.compactTitleContainer}>
{displayMode === 0 && <CompactTitle novelName={item.novelName} />}
</View>
{displayMode === 1 && (
<ComfortableTitle novelName={item.novelName} theme={theme} />
)}
</Pressable>
</View>
) : (
<ListView
item={item}
downloadBadge={
showDownloadBadges &&
item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)
}
unreadBadge={
showUnreadBadges &&
item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)
}
inLibraryBadge={libraryStatus && <InLibraryBadge theme={theme} />}
theme={theme}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
}
onLongPress={selectNovel}
isSelected={isSelected}
/>
);
};
export default NovelCover;
export default memo(NovelCover);
const ComfortableTitle = ({ theme, novelName }) => (
<Text
const ComfortableTitle = ({theme, novelName}) => (
<Text
numberOfLines={2}
style={[
styles.title,
{
color: theme.textColorPrimary,
padding: 4,
},
]}
>
{novelName}
</Text>
);
const CompactTitle = ({novelName}) => (
<View style={styles.titleContainer}>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.7)']}
style={styles.linearGradient}
>
<Text
numberOfLines={2}
style={[
styles.title,
{
color: theme.textColorPrimary,
padding: 4,
},
styles.title,
{
color: 'rgba(255,255,255,1)',
textShadowColor: 'rgba(0, 0, 0, 0.75)',
textShadowOffset: {width: -1, height: 1},
textShadowRadius: 10,
},
]}
>
>
{novelName}
</Text>
</Text>
</LinearGradient>
</View>
);
const CompactTitle = ({ novelName }) => (
<View style={styles.titleContainer}>
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.7)"]}
style={styles.linearGradient}
>
<Text
numberOfLines={2}
style={[styles.title, { color: "rgba(255,255,255,1)" }]}
>
{novelName}
</Text>
</LinearGradient>
</View>
);
const InLibraryBadge = ({ theme }) => (
<Text
style={[
styles.inLibraryBadge,
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
borderRadius: 4,
},
]}
>
In library
</Text>
const InLibraryBadge = ({theme}) => (
<Text
style={[
styles.inLibraryBadge,
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
borderRadius: 4,
},
]}
>
In library
</Text>
);
const UnreadBadge = ({
chaptersDownloaded,
chaptersUnread,
showDownloadBadges,
theme,
chaptersDownloaded,
chaptersUnread,
showDownloadBadges,
theme,
}) => (
<Text
style={[
styles.unreadBadge,
!chaptersDownloaded && {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
!showDownloadBadges && {
borderRadius: 4,
},
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
},
]}
>
{chaptersUnread}
</Text>
<Text
style={[
styles.unreadBadge,
!chaptersDownloaded && {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
!showDownloadBadges && {
borderRadius: 4,
},
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
},
]}
>
{chaptersUnread}
</Text>
);
const DownloadBadge = ({
chaptersDownloaded,
showUnreadBadges,
chaptersUnread,
chaptersDownloaded,
showUnreadBadges,
chaptersUnread,
}) => (
<Text
style={[
styles.downloadBadge,
!chaptersUnread && {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
!showUnreadBadges && {
borderRadius: 4,
},
]}
>
{chaptersDownloaded}
</Text>
<Text
style={[
styles.downloadBadge,
!chaptersUnread && {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
!showUnreadBadges && {
borderRadius: 4,
},
]}
>
{chaptersDownloaded}
</Text>
);
const styles = StyleSheet.create({
titleContainer: {
flex: 1,
borderRadius: 4,
},
title: {
fontFamily: "pt-sans-bold",
fontSize: 14,
padding: 8,
textShadowColor: "rgba(0, 0, 0, 0.75)",
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
linearGradient: {
borderRadius: 4,
},
opac: {
padding: 4.8,
borderRadius: 4,
flex: 1,
},
extensionIcon: {
width: 42,
height: 42,
borderRadius: 4,
},
listView: {
flex: 1,
paddingVertical: 8,
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center",
borderRadius: 4,
},
titleContainer: {
flex: 1,
borderRadius: 4,
},
title: {
fontFamily: 'pt-sans-bold',
fontSize: 14,
padding: 8,
},
linearGradient: {
borderRadius: 4,
},
opac: {
padding: 4.8,
borderRadius: 4,
flex: 1,
},
extensionIcon: {
width: 42,
height: 42,
borderRadius: 4,
},
listView: {
flex: 1,
paddingVertical: 8,
paddingHorizontal: 12,
flexDirection: 'row',
alignItems: 'center',
borderRadius: 4,
},
downloadBadge: {
backgroundColor: "#2979FF",
color: "#FFFFFF",
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
unreadBadge: {
color: "#FFFFFF",
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
inLibraryBadge: {
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
compactTitleContainer: {
position: "absolute",
bottom: 4,
left: 4,
right: 4,
},
badgeContainer: {
position: "absolute",
zIndex: 1,
top: 8,
left: 8,
flexDirection: "row",
},
downloadBadge: {
backgroundColor: '#2979FF',
color: '#FFFFFF',
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
unreadBadge: {
color: '#FFFFFF',
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
inLibraryBadge: {
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
compactTitleContainer: {
position: 'absolute',
bottom: 4,
left: 4,
right: 4,
},
badgeContainer: {
position: 'absolute',
zIndex: 1,
top: 8,
left: 8,
flexDirection: 'row',
},
});
+46 -46
View File
@@ -1,61 +1,61 @@
import React, { useCallback } from "react";
import { StyleSheet, FlatList } from "react-native";
import React, {useCallback} from 'react';
import {StyleSheet, FlatList} from 'react-native';
import { useSettings } from "../hooks/reduxHooks";
import { getDeviceOrientation } from "../services/utils/helpers";
import {useSettings} from '../hooks/reduxHooks';
import {useDeviceOrientation} from '../services/utils/helpers';
const NovelList = ({
data,
onScroll,
onEndReached,
renderItem,
refreshControl,
ListEmptyComponent,
ListFooterComponent,
data,
onScroll,
onEndReached,
renderItem,
refreshControl,
ListEmptyComponent,
ListFooterComponent,
}) => {
const { displayMode, novelsPerRow } = useSettings();
const {displayMode, novelsPerRow} = useSettings();
const orientation = getDeviceOrientation();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () => {
if (displayMode === 2) {
return 1;
}
const getNovelsPerRow = () => {
if (displayMode === 2) {
return 1;
}
if (orientation === "landscape") {
return 6;
} else {
return novelsPerRow;
}
};
if (orientation === 'landscape') {
return 6;
} else {
return novelsPerRow;
}
};
const keyExtractor = useCallback((item) => item.novelUrl, []);
const keyExtractor = useCallback(item => item.novelUrl, []);
return (
<FlatList
contentContainerStyle={[
styles.flatListCont,
displayMode !== 2 && { paddingHorizontal: 4 },
]}
numColumns={getNovelsPerRow()}
key={[orientation, getNovelsPerRow()]}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
refreshControl={refreshControl}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onScroll={onScroll}
onEndReached={onEndReached}
/>
);
return (
<FlatList
contentContainerStyle={[
styles.flatListCont,
displayMode !== 2 && {paddingHorizontal: 4},
]}
numColumns={getNovelsPerRow()}
key={[orientation, getNovelsPerRow()]}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
refreshControl={refreshControl}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onScroll={onScroll}
onEndReached={onEndReached}
/>
);
};
export default NovelList;
const styles = StyleSheet.create({
flatListCont: {
flexGrow: 1,
paddingVertical: 8,
},
flatListCont: {
flexGrow: 1,
paddingBottom: 56,
},
});
+33 -33
View File
@@ -1,40 +1,40 @@
import React from "react";
import { StyleSheet, View, Text } from "react-native";
import { RadioButton as MaterialRadioButton } from "react-native-paper";
import React from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {RadioButton as MaterialRadioButton} from 'react-native-paper';
export const RadioButtonGroup = ({ children, value, onValueChange }) => (
<MaterialRadioButton.Group onValueChange={onValueChange} value={value}>
{children}
</MaterialRadioButton.Group>
export const RadioButtonGroup = ({children, value, onValueChange}) => (
<MaterialRadioButton.Group onValueChange={onValueChange} value={value}>
{children}
</MaterialRadioButton.Group>
);
export const RadioButton = ({ value, label, theme, labelStyle }) => (
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[
styles.radioButtonLabel,
{ color: theme.textColorPrimary },
labelStyle,
]}
>
{label}
</Text>
</View>
export const RadioButton = ({value, label, theme, labelStyle}) => (
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[
styles.radioButtonLabel,
{color: theme.textColorPrimary},
labelStyle,
]}
>
{label}
</Text>
</View>
);
const styles = StyleSheet.create({
radioButtonContainer: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 8,
},
radioButtonLabel: {
marginLeft: 16,
fontSize: 16,
},
radioButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
radioButtonLabel: {
marginLeft: 16,
fontSize: 16,
},
});
+33
View File
@@ -0,0 +1,33 @@
import React from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import {RadioButton as PaperRadioButton} from 'react-native-paper';
export const RadioButton = ({label, status, onPress, style, theme}) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={[styles.pressable, style]}
onPress={onPress}
>
<PaperRadioButton
status={status ? 'checked' : 'unchecked'}
onPress={onPress}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text style={{color: theme.textColorPrimary, marginLeft: 12}}>{label}</Text>
</Pressable>
);
const styles = StyleSheet.create({
pressable: {
paddingHorizontal: 16,
paddingVertical: 6,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
position: 'absolute',
left: 24,
alignSelf: 'center',
},
});
+77 -88
View File
@@ -1,97 +1,86 @@
import React, { useRef } from "react";
import { StyleSheet, View, TextInput, Text, StatusBar } from "react-native";
import React, {useRef} from 'react';
import {StyleSheet, View, TextInput, StatusBar, Pressable} from 'react-native';
import { TouchableRipple, IconButton } from "react-native-paper";
import { Row } from "../Common";
import {Row} from '../Common';
import {IconButton} from '../IconButton/IconButton';
export const Searchbar = ({
theme,
placeholder,
searchText,
backAction,
onBackAction,
clearSearchbar,
onChangeText,
onSubmitEditing,
actions,
theme,
placeholder,
searchText,
backAction,
onBackAction,
clearSearchbar,
onChangeText,
onSubmitEditing,
actions,
}) => {
const searchRef = useRef(null);
const searchRef = useRef(null);
return (
<TouchableRipple
borderless
onPress={() => searchRef.current.focus()}
style={[
styles.searchbarContainer,
{ backgroundColor: theme.searchBarColor },
]}
rippleColor={theme.rippleColor}
>
<View style={styles.container}>
<Row style={{ flex: 1 }}>
{backAction && (
<IconButton
icon={backAction}
color={theme.textColorSecondary}
style={{ marginLeft: 0 }}
size={23}
onPress={onBackAction}
/>
)}
<TextInput
ref={searchRef}
style={{
fontSize: 16,
color: theme.textColorSecondary,
}}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
/>
</Row>
{searchText !== "" && (
<IconButton
icon="close"
color={theme.textColorSecondary}
style={{ marginRight: 0 }}
size={23}
onPress={clearSearchbar}
/>
)}
{actions &&
actions.map((action, index) => (
<IconButton
key={index}
icon={action.icon}
color={action.color ?? theme.textColorSecondary}
style={{ marginRight: 0 }}
size={23}
onPress={action.onPress}
/>
))}
</View>
</TouchableRipple>
);
return (
<View style={styles.searchBar}>
<Pressable
onPress={() => searchRef.current.focus()}
style={[styles.pressable, {backgroundColor: theme.searchBarColor}]}
android_ripple={{color: theme.rippleColor}}
>
<Row style={{flex: 1}}>
{backAction && (
<IconButton
icon={backAction}
color={theme.textColorPrimary}
onPress={onBackAction}
theme={theme}
containerStyle={{marginLeft: 0}}
/>
)}
<TextInput
ref={searchRef}
style={{fontSize: 16, color: theme.textColorPrimary, marginLeft: 4}}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
/>
</Row>
{searchText !== '' && (
<IconButton
icon="close"
color={theme.textColorPrimary}
onPress={clearSearchbar}
theme={theme}
/>
)}
{actions &&
actions.map((action, index) => (
<IconButton
key={index}
icon={action.icon}
color={action.color ?? theme.textColorPrimary}
onPress={action.onPress}
theme={theme}
containerStyle={{marginRight: 0, marginLeft: 16}}
/>
))}
</Pressable>
</View>
);
};
const styles = StyleSheet.create({
searchbarContainer: {
marginTop: StatusBar.currentHeight + 8,
height: 46,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
marginBottom: 6,
borderRadius: 8,
marginHorizontal: 12,
elevation: 2,
},
container: {
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
searchBar: {
marginTop: StatusBar.currentHeight + 8,
marginBottom: 8,
marginHorizontal: 16,
height: 48,
borderRadius: 50,
overflow: 'hidden',
},
pressable: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
},
});
+42 -48
View File
@@ -1,56 +1,50 @@
import React from "react";
import { Pressable, StyleSheet, View, Text } from "react-native";
import React from 'react';
import {Pressable, StyleSheet, View, Text} from 'react-native';
import { Switch, List } from "react-native-paper";
import {Switch, List} from 'react-native-paper';
const SwitchSetting = ({ label, description, onPress, theme, icon, value }) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.container}
onPress={onPress}
>
<View style={{ flexDirection: "row", flex: 1 }}>
{icon && (
<List.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
/>
)}
<View style={[icon && { marginLeft: 16 }]}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 16,
flex: 1,
textAlignVertical: "center",
}}
>
{label}
</Text>
{description && (
<Text style={{ color: theme.textColorSecondary }}>
{description}
</Text>
)}
</View>
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={{ marginRight: 8 }}
/>
</Pressable>
const SwitchSetting = ({label, description, onPress, theme, icon, value}) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={styles.container}
onPress={onPress}
>
<View style={{flexDirection: 'row', flex: 1}}>
{icon && (
<List.Icon color={theme.colorAccent} icon={icon} style={{margin: 0}} />
)}
<View style={[icon && {marginLeft: 16}]}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 16,
flex: 1,
textAlignVertical: 'center',
}}
>
{label}
</Text>
{description && (
<Text style={{color: theme.textColorSecondary}}>{description}</Text>
)}
</View>
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={{marginRight: 8}}
/>
</Pressable>
);
export default SwitchSetting;
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: "row",
justifyContent: "space-between",
},
container: {
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: 'row',
justifyContent: 'space-between',
},
});
+172
View File
@@ -0,0 +1,172 @@
import React from 'react';
import {View, Text, /* StyleSheet, */ Pressable} from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ThemePicker = ({theme, currentTheme, onPress}) => (
<View
style={{
justifyContent: 'center',
alignItems: 'center',
marginRight: 16,
}}
>
<View
style={{
backgroundColor: theme.colorPrimaryDark,
borderWidth: 3.6,
borderColor:
currentTheme.id === theme.id
? theme.colorAccent
: currentTheme.colorPrimaryDark,
width: 95,
height: 140,
borderRadius: 16,
overflow: 'hidden',
elevation: 1,
}}
>
<Pressable style={{flex: 1}} onPress={onPress}>
{currentTheme.id === theme.id && (
<MaterialCommunityIcons
name="check"
color={theme.colorButtonText}
size={15}
style={{
backgroundColor: theme.colorAccent,
position: 'absolute',
top: 5,
right: 5,
elevation: 2,
borderRadius: 50,
padding: 1.6,
}}
/>
)}
<View
style={{
height: 20,
backgroundColor: theme.colorPrimary,
elevation: 1,
justifyContent: 'center',
}}
>
<View
style={{
backgroundColor: theme.textColorPrimary,
width: 44,
height: 10,
marginLeft: 8,
borderRadius: 50,
}}
/>
</View>
<View style={{padding: 8}}>
<View
style={{
height: 18,
backgroundColor: theme.textColorSecondary,
borderRadius: 4,
}}
/>
<View style={{paddingVertical: 4, flexDirection: 'row'}}>
<View
style={{
height: 10,
width: 44,
backgroundColor: theme.textColorPrimary,
borderRadius: 50,
}}
/>
<View
style={{
height: 10,
width: 16,
marginLeft: 4,
backgroundColor: theme.colorAccent,
borderRadius: 50,
}}
/>
</View>
<View style={{flexDirection: 'row'}}>
<View
style={{
height: 10,
width: 24,
backgroundColor: theme.textColorSecondary,
borderRadius: 50,
}}
/>
<View
style={{
height: 10,
width: 24,
backgroundColor: theme.textColorSecondary,
borderRadius: 50,
marginLeft: 4,
}}
/>
</View>
</View>
<View
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 24,
backgroundColor: theme.colorPrimary,
justifyContent: 'center',
}}
>
<View
style={{
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 16,
}}
>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.textColorPrimary,
opacity: 0.54,
}}
/>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.colorAccent,
}}
/>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.textColorPrimary,
opacity: 0.54,
}}
/>
</View>
</View>
</Pressable>
</View>
<Text
style={{
color: currentTheme.textColorPrimary,
fontSize: 12,
paddingVertical: 4,
}}
>
{theme.name}
</Text>
</View>
);
// const styles = StyleSheet.create({});
+20
View File
@@ -0,0 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Text as NativeText, TextPropTypes} from 'react-native';
const Text = ({type, children, style, theme}) => {
const color =
type === 'primary'
? theme.textColorPrimary
: type === 'secondary'
? theme.textColorSecondary
: theme.textColorHint;
return <NativeText style={[style, {color}]}>{children}</NativeText>;
};
Text.propType = {
style: TextPropTypes.style,
type: PropTypes.oneOf(['primary', 'secondary', 'hint']),
theme: PropTypes.object.isRequired,
};
+39 -37
View File
@@ -1,55 +1,57 @@
import * as SQLite from "expo-sqlite";
import * as SQLite from 'expo-sqlite';
import {
createNovelTableQuery,
createUrlIndexQuery,
createLibraryIndexQuery,
} from "./tables/NovelTable";
createNovelTableQuery,
createUrlIndexQuery,
createLibraryIndexQuery,
} from './tables/NovelTable';
import {
createChapterTableQuery,
createNovelIdIndexQuery,
createUnreadChaptersIndexQuery,
} from "./tables/ChapterTable";
createChapterTableQuery,
createNovelIdIndexQuery,
createUnreadChaptersIndexQuery,
} from './tables/ChapterTable';
import {
createHistoryTableQuery,
createChapterIdIndexQuery,
} from "./tables/HistoryTable";
import { createDownloadTableQuery } from "./tables/DownloadTable";
createHistoryTableQuery,
createChapterIdIndexQuery,
} from './tables/HistoryTable';
import {createDownloadTableQuery} from './tables/DownloadTable';
import {
createUpdatesTableQuery,
updatesSeedDataQuery,
} from "./tables/UpdateTable";
createUpdatesTableQuery,
// updatesSeedDataQuery,
} from './tables/UpdateTable';
/**
* Database Version = 2
*/
const dbName = "lnreader.db";
const dbName = 'lnreader.db';
const db = SQLite.openDatabase(dbName);
export const createDB = () => {
db.transaction((tx) => {
tx.executeSql(createNovelTableQuery);
tx.executeSql(createChapterTableQuery);
tx.executeSql(createHistoryTableQuery);
tx.executeSql(createDownloadTableQuery);
tx.executeSql(createUpdatesTableQuery);
db.transaction(tx => {
tx.executeSql(createNovelTableQuery);
tx.executeSql(createChapterTableQuery);
tx.executeSql(createHistoryTableQuery);
tx.executeSql(createDownloadTableQuery);
tx.executeSql(createUpdatesTableQuery);
//Db indexes
tx.executeSql(createUrlIndexQuery);
tx.executeSql(createLibraryIndexQuery);
tx.executeSql(createNovelIdIndexQuery);
tx.executeSql(createUnreadChaptersIndexQuery);
tx.executeSql(createChapterIdIndexQuery);
});
/**
* Indexes
*/
tx.executeSql(createUrlIndexQuery);
tx.executeSql(createLibraryIndexQuery);
tx.executeSql(createNovelIdIndexQuery);
tx.executeSql(createUnreadChaptersIndexQuery);
tx.executeSql(createChapterIdIndexQuery);
});
};
export const deleteDb = () => {
db.transaction((tx) => {
tx.executeSql("DROP TABLE novels");
tx.executeSql("DROP TABLE chapters");
tx.executeSql("DROP TABLE history");
tx.executeSql("DROP TABLE downloads");
tx.executeSql("DROP TABLE updates");
});
db.transaction(tx => {
tx.executeSql('DROP TABLE novels');
tx.executeSql('DROP TABLE chapters');
tx.executeSql('DROP TABLE history');
tx.executeSql('DROP TABLE downloads');
tx.executeSql('DROP TABLE updates');
});
};
+291 -230
View File
@@ -1,211 +1,259 @@
import * as SQLite from "expo-sqlite";
import { getSource } from "../../sources/sources";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
import {getSource} from '../../sources/sources';
const db = SQLite.openDatabase('lnreader.db');
const insertChaptersQuery = `INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)`;
const insertChaptersQuery =
'INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)';
export const insertChapters = async (novelId, chapters) => {
db.transaction((tx) => {
chapters.map(
(chapter) =>
tx.executeSql(
insertChaptersQuery,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
],
(txObj, res) => {},
(txObj, error) => {}
// console.log(
// "Error ",
// error,
// " Chapter URL: ",
// chapter.chapterUrl,
// " Novel ID ",
// novelId
// )
),
(txObj, res) => {
console.log("Success");
},
(txObj, error) => console.log("Error ", error)
);
});
db.transaction(tx => {
chapters.map(
chapter =>
tx.executeSql(
insertChaptersQuery,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
],
(txObj, res) => {},
(txObj, error) => {},
// console.log(
// "Error ",
// error,
// " Chapter URL: ",
// chapter.chapterUrl,
// " Novel ID ",
// novelId
// )
),
(txObj, res) => {
// console.log('Success');
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const getChaptersQuery = (sort = "ORDER BY chapterId ASC", filter = "") =>
`SELECT * FROM chapters WHERE novelId = ? ${filter} ${sort}`;
const getChaptersQuery = (sort = 'ORDER BY chapterId ASC', filter = '') =>
`SELECT * FROM chapters WHERE novelId = ? ${filter} ${sort}`;
export const getChapters = (novelId, sort, filter) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getChaptersQuery(sort, filter),
[novelId],
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getChaptersQuery(sort, filter),
[novelId],
(txObj, {rows: {_array}}) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
const getChapterQuery = `SELECT * FROM downloads WHERE downloadChapterId = ?`;
const getChapterQuery = 'SELECT * FROM downloads WHERE downloadChapterId = ?';
export const getChapterFromDB = async (chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getChapterQuery,
[chapterId],
(tx, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => console.log("Error ", error)
);
})
);
export const getChapterFromDB = async chapterId => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getChapterQuery,
[chapterId],
(txObj, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const getNextChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId > ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM chapters WHERE novelId = ? AND chapterId > ?',
[novelId, chapterId],
(txObj, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const getPrevChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId < ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(results.rows.length - 1));
},
(txObj, error) => console.log("Error ", error)
);
})
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM chapters WHERE novelId = ? AND chapterId < ?',
[novelId, chapterId],
(txObj, results) => {
resolve(results.rows.item(results.rows.length - 1));
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
const markChapterReadQuery =
'UPDATE chapters SET `read` = 1 WHERE chapterId = ?';
export const markChapterRead = async chapterId => {
db.transaction(tx => {
tx.executeSql(
markChapterReadQuery,
[chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const markChapterReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE chapterId = ?`;
const markChapterUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE chapterId = ?';
export const markChapterRead = async (chapterId) => {
db.transaction((tx) => {
tx.executeSql(
markChapterReadQuery,
[chapterId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markAllChaptersReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE novelId = ?`;
export const markAllChaptersRead = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
markAllChaptersReadQuery,
[novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markAllChaptersUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE novelId = ?`;
export const markAllChaptersUnread = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
markAllChaptersUnreadQuery,
[novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const isChapterDownloadedQuery = `SELECT * FROM downloads WHERE downloadChapterId=?`;
export const isChapterDownloaded = async (chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
isChapterDownloadedQuery,
[chapterId],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => console.log("Error ", error)
);
})
export const markChapterUnread = async chapterId => {
db.transaction(tx => {
tx.executeSql(
markChapterUnreadQuery,
[chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const downloadChapterQuery = `INSERT INTO downloads (downloadChapterId, chapterName, chapterText) VALUES (?, ?, ?)`;
const markAllChaptersReadQuery =
'UPDATE chapters SET `read` = 1 WHERE novelId = ?';
export const markAllChaptersRead = async novelId => {
db.transaction(tx => {
tx.executeSql(
markAllChaptersReadQuery,
[novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const markAllChaptersUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE novelId = ?';
export const markAllChaptersUnread = async novelId => {
db.transaction(tx => {
tx.executeSql(
markAllChaptersUnreadQuery,
[novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const isChapterDownloadedQuery =
'SELECT * FROM downloads WHERE downloadChapterId=?';
export const isChapterDownloaded = async chapterId => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
isChapterDownloadedQuery,
[chapterId],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
const downloadChapterQuery =
'INSERT INTO downloads (downloadChapterId, chapterName, chapterText) VALUES (?, ?, ?)';
export const downloadChapter = async (
sourceId,
novelUrl,
chapterUrl,
chapterId
sourceId,
novelUrl,
chapterUrl,
chapterId,
) => {
const source = getSource(sourceId);
const source = getSource(sourceId);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
db.transaction((tx) => {
tx.executeSql(
`UPDATE chapters SET downloaded = 1 WHERE chapterId = ?`,
[chapterId]
);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(tx, res) => {
// console.log(`Downloaded Chapter ${chapter.chapterUrl}`);
},
(txObj, error) => console.log("Error ", error)
);
});
db.transaction(tx => {
tx.executeSql('UPDATE chapters SET downloaded = 1 WHERE chapterId = ?', [
chapterId,
]);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(txObj, res) => {
// console.log(`Downloaded Chapter ${chapter.chapterUrl}`);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const deleteChapter = async (chapterId) => {
const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE chapterId=?`;
const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId=?`;
export const deleteChapter = async chapterId => {
const updateIsDownloadedQuery =
'UPDATE chapters SET downloaded = 0 WHERE chapterId=?';
const deleteChapterQuery = 'DELETE FROM downloads WHERE downloadChapterId=?';
db.transaction((tx) => {
tx.executeSql(
updateIsDownloadedQuery,
[chapterId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
tx.executeSql(
deleteChapterQuery,
[chapterId],
(tx, res) => console.log(`Chapter deleted`),
(txObj, error) => console.log("Error ", error)
);
});
db.transaction(tx => {
tx.executeSql(
updateIsDownloadedQuery,
[chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
tx.executeSql(
deleteChapterQuery,
[chapterId],
(txObj, res) => {
// console.log('Chapter deleted');
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const getLastReadChapterQuery = `
@@ -216,56 +264,67 @@ const getLastReadChapterQuery = `
WHERE history.historyNovelId = ?
`;
export const getLastReadChapter = async (novelId) => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
getLastReadChapterQuery,
[novelId],
(tx, { rows }) => resolve(rows.item(0)),
(txObj, error) => console.log("Error ", error)
);
});
export const getLastReadChapter = async novelId => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getLastReadChapterQuery,
[novelId],
(txObj, {rows}) => resolve(rows.item(0)),
(txObj, error) => {
// console.log('Error ', error)
},
);
});
});
};
const bookmarkChapterQuery = `UPDATE chapters SET bookmark = ? WHERE chapterId = ?`;
const bookmarkChapterQuery =
'UPDATE chapters SET bookmark = ? WHERE chapterId = ?';
export const bookmarkChapter = async (bookmark, chapterId) => {
db.transaction((tx) => {
tx.executeSql(
bookmarkChapterQuery,
[!bookmark, chapterId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
db.transaction(tx => {
tx.executeSql(
bookmarkChapterQuery,
[!bookmark, chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const markPreviuschaptersReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE chapterId < ? AND novelId = ?`;
const markPreviuschaptersReadQuery =
'UPDATE chapters SET `read` = 1 WHERE chapterId < ? AND novelId = ?';
export const markPreviuschaptersRead = async (chapterId, novelId) => {
db.transaction((tx) => {
tx.executeSql(
markPreviuschaptersReadQuery,
[chapterId, novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
db.transaction(tx => {
tx.executeSql(
markPreviuschaptersReadQuery,
[chapterId, novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const markPreviousChaptersUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE chapterId < ? AND novelId = ?`;
const markPreviousChaptersUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE chapterId < ? AND novelId = ?';
export const markPreviousChaptersUnread = async (chapterId, novelId) => {
db.transaction((tx) => {
tx.executeSql(
markPreviousChaptersUnreadQuery,
[chapterId, novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
db.transaction(tx => {
tx.executeSql(
markPreviousChaptersUnreadQuery,
[chapterId, novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log(error)
},
);
});
};
const getDownloadedChaptersQuery = `
@@ -276,23 +335,25 @@ const getDownloadedChaptersQuery = `
WHERE chapters.downloaded = 1`;
export const getDownloadedChapters = () => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getDownloadedChaptersQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getDownloadedChaptersQuery,
null,
(txObj, {rows: {_array}}) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const deleteDownloads = async () => {
db.transaction((tx) => {
tx.executeSql(`UPDATE chapters SET downloaded = 0`);
tx.executeSql("DELETE FROM downloads; VACCUM;");
});
db.transaction(tx => {
tx.executeSql('UPDATE chapters SET downloaded = 0');
tx.executeSql('DELETE FROM downloads; VACCUM;');
});
};
+43 -36
View File
@@ -1,5 +1,7 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import {showToast} from '../../hooks/showToast';
const getHistoryQuery = `
SELECT history.*, chapters.*, novels.*
@@ -14,49 +16,54 @@ const getHistoryQuery = `
`;
export const getHistoryFromDb = async () => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
getHistoryQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
});
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getHistoryQuery,
null,
(txObj, {rows: {_array}}) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
});
};
// const insertHistoryQuery =
export const insertHistory = async (novelId, chapterId) => {
db.transaction((tx) => {
tx.executeSql(
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId, historyTimeRead) VALUES (?, ?, (datetime('now','localtime')))",
[novelId, chapterId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
tx.executeSql("UPDATE novels SET unread = 0 WHERE novelId = ?", [
novelId,
]);
});
db.transaction(tx => {
tx.executeSql(
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId, historyTimeRead) VALUES (?, ?, (datetime('now','localtime')))",
[novelId, chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
tx.executeSql('UPDATE novels SET unread = 0 WHERE novelId = ?', [novelId]);
});
};
export const deleteHistory = async (historyId) => {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM history WHERE historyId = ?",
[historyId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
export const deleteHistory = async historyId => {
db.transaction(tx => {
tx.executeSql(
'DELETE FROM history WHERE historyId = ?',
[historyId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const deleteAllHistory = async () => {
db.transaction((tx) => {
tx.executeSql("DELETE FROM history; VACCUM;");
});
db.transaction(tx => {
tx.executeSql('DELETE FROM history; VACCUM;');
});
showToast('History deleted.');
};
+34 -30
View File
@@ -1,5 +1,5 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryQuery = (sort, filter) => `
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
@@ -18,28 +18,30 @@ const getLibraryQuery = (sort, filter) => `
GROUP BY chapters.novelId
) AS D
ON novels.novelId = D.novelId
WHERE novels.followed = 1 ${filter ? "AND " + filter : ""}
${sort ? "ORDER BY " + sort : ""}
WHERE novels.followed = 1 ${filter ? 'AND ' + filter : ''}
${sort ? 'ORDER BY ' + sort : ''}
`;
export const getLibrary = (sort, filter) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getLibraryQuery(sort, filter),
null,
(txObj, { rows: { _array } }) => {
// console.log(_array);
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getLibraryQuery(sort, filter),
null,
(txObj, {rows: {_array}}) => {
// console.log(_array);
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error);
},
);
}),
);
};
const searchLibraryQuery = (searchText, sort, filter) =>
`
`
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
FROM novels
LEFT JOIN (
@@ -57,19 +59,21 @@ const searchLibraryQuery = (searchText, sort, filter) =>
) AS D
ON novels.novelId = D.novelId
WHERE novels.followed = 1 AND novelName LIKE '%${searchText}%' ${
filter ? "AND " + filter : ""
}
${sort ? "ORDER BY " + sort : ""} `;
filter ? 'AND ' + filter : ''
}
${sort ? 'ORDER BY ' + sort : ''} `;
export const searchLibrary = (searchText, sort, filter) => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
searchLibraryQuery(searchText, sort, filter),
null,
(txObj, { rows: { _array } }) => resolve(_array),
(txObj, error) => console.log("Error ", error)
);
});
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
searchLibraryQuery(searchText, sort, filter),
null,
(txObj, {rows: {_array}}) => resolve(_array),
(txObj, error) => {
// console.log('Error ', error);
},
);
});
});
};
+233 -214
View File
@@ -1,242 +1,261 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import BackgroundService from "react-native-background-actions";
import * as DocumentPicker from "expo-document-picker";
import BackgroundService from 'react-native-background-actions';
import * as DocumentPicker from 'expo-document-picker';
import { fetchChapters, fetchNovel } from "../../services/Source/source";
import { insertChapters } from "./ChapterQueries";
import {fetchChapters, fetchNovel} from '../../services/Source/source';
import {insertChapters} from './ChapterQueries';
import { showToast } from "../../hooks/showToast";
import {showToast} from '../../hooks/showToast';
const insertNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const insertNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const insertNovel = async (novel) => {
return new Promise((resolve, reject) =>
db.transaction((tx) =>
tx.executeSql(
insertNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
],
(txObj, { insertId }) => resolve(insertId),
(txObj, error) => console.log("Error ", error)
)
)
);
export const insertNovel = async novel => {
return new Promise((resolve, reject) =>
db.transaction(tx =>
tx.executeSql(
insertNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
],
(txObj, {insertId}) => resolve(insertId),
(txObj, error) => {
// console.log('Error ', error)
},
),
),
);
};
export const followNovel = async (followed, novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET followed = ? WHERE novelId = ?",
[!followed, novelId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
};
export const unfollowNovel = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET followed = 0 WHERE novelId = ?",
[novelId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
};
const checkNovelInCacheQuery = `SELECT * FROM novels WHERE novelUrl=? LIMIT 1`;
export const checkNovelInCache = (novelUrl) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
checkNovelInCacheQuery,
[novelUrl],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => console.log("Error ", error)
);
})
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET followed = ? WHERE novelId = ?',
[!followed, novelId],
(txo, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const unfollowNovel = async novelId => {
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET followed = 0 WHERE novelId = ?',
[novelId],
(txo, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const checkNovelInCacheQuery = 'SELECT * FROM novels WHERE novelUrl=? LIMIT 1';
export const checkNovelInCache = novelUrl => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
checkNovelInCacheQuery,
[novelUrl],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const getNovel = async (sourceId, novelUrl) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?`,
[novelUrl, sourceId],
(txObj, { rows }) => resolve(rows.item(0)),
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?',
[novelUrl, sourceId],
(txObj, {rows}) => resolve(rows.item(0)),
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const deleteNovelCache = () => {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM novels WHERE followed = 0",
null,
(txObj, res) => showToast("Entries deleted"),
(txObj, error) => console.log("Error ", error)
);
});
};
const restoreFromBackupQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed, unread) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
export const restoreLibrary = async (novel) => {
return new Promise((resolve, reject) =>
db.transaction((tx) =>
tx.executeSql(
restoreFromBackupQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
novel.followed,
novel.unread,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl
);
await insertChapters(insertId, chapters);
},
(txObj, error) => console.log("Error ", error)
)
)
db.transaction(tx => {
tx.executeSql(
'DELETE FROM novels WHERE followed = 0',
null,
(txObj, res) => showToast('Entries deleted'),
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const migrateNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const restoreFromBackupQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed, unread) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const restoreLibrary = async novel => {
return new Promise((resolve, reject) =>
db.transaction(tx =>
tx.executeSql(
restoreFromBackupQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
novel.followed,
novel.unread,
],
async (txObj, {insertId}) => {
const chapters = await fetchChapters(novel.sourceId, novel.novelUrl);
await insertChapters(insertId, chapters);
},
(txObj, error) => {
// console.log('Error ', error)
},
),
),
);
};
const migrateNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const migrateNovel = async (sourceId, novelUrl) => {
try {
const novel = await fetchNovel(sourceId, novelUrl);
try {
const novel = await fetchNovel(sourceId, novelUrl);
const options = {
taskName: "Migration",
taskTitle: `Migrating ${novel.novelName} to new source`,
taskDesc: novel.source,
taskIcon: {
name: "notification_icon",
type: "drawable",
},
color: "#00adb5",
parameters: {
delay: 1000,
},
progressBar: {
max: 1,
value: 0,
indeterminate: true,
},
};
const options = {
taskName: 'Migration',
taskTitle: `Migrating ${novel.novelName} to new source`,
taskDesc: novel.source,
taskIcon: {
name: 'notification_icon',
type: 'drawable',
},
color: '#00adb5',
parameters: {
delay: 1000,
},
progressBar: {
max: 1,
value: 0,
indeterminate: true,
},
};
const veryIntensiveTask = async () => {
await new Promise(async (resolve) => {
db.transaction((tx) =>
tx.executeSql(
migrateNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
1,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl
);
await insertChapters(insertId, chapters);
resolve();
},
(txObj, error) => console.log("Error ", error)
)
);
});
};
const veryIntensiveTask = async () => {
await new Promise(async resolve => {
db.transaction(tx =>
tx.executeSql(
migrateNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
1,
],
async (txObj, {insertId}) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl,
);
await insertChapters(insertId, chapters);
resolve();
},
(txObj, error) => {
// console.log('Error ', error)
},
),
);
});
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({
progressBar: { value: 1, indeterminate: false },
});
await BackgroundService.stop();
} catch (error) {
showToast(error.message);
}
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({
progressBar: {value: 1, indeterminate: false},
});
} catch (error) {
showToast(error.message);
}
};
export const updateNovelInfo = async (info, novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET novelName = ?, novelSummary = ?, author = ?, genre = ?, status = ? WHERE novelId = ?",
[
info.novelName,
info.novelSummary,
info.author,
info.genre,
info.status,
novelId,
],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET novelName = ?, novelSummary = ?, author = ?, genre = ?, status = ? WHERE novelId = ?',
[
info.novelName,
info.novelSummary,
info.author,
info.genre,
info.status,
novelId,
],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const pickCustomNovelCover = async novelId => {
const image = await DocumentPicker.getDocumentAsync({type: 'image/*'});
if (image.type === 'success' && image.uri) {
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET novelCover = ? WHERE novelId = ?',
[image.uri, novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const setCustomNovelCover = async (novelId) => {
const image = await DocumentPicker.getDocumentAsync({ type: "image/*" });
if (image.uri) {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET novelCover = ? WHERE novelId = ?",
[image.uri, novelId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
return image.uri;
}
return image.uri;
}
};
+30 -22
View File
@@ -1,5 +1,5 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
const getUpdatesQuery = `
SELECT chapters.chapterId, chapters.read, chapters.downloaded, chapters.chapterUrl, chapters.chapterName, novels.novelUrl, novels.novelId, novels.novelCover, novels.novelName, novels.sourceId, updates.updateTime, updates.updateId
@@ -8,31 +8,39 @@ const getUpdatesQuery = `
ON updates.chapterId = chapters.chapterId
JOIN novels
ON updates.novelId = novels.novelId
WHERE date(updates.updateTime) > date('now','-3 months')
ORDER BY updates.updateTime DESC
`;
export const getUpdates = async () => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
getUpdatesQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
});
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getUpdatesQuery,
null,
(txObj, {rows: {_array}}) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
});
};
export const deleteNovelUpdates = (novelId) => {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM updates WHERE novelId = ?",
[novelId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
export const deleteNovelUpdates = novelId => {
db.transaction(tx => {
tx.executeSql(
'DELETE FROM updates WHERE novelId = ?',
[novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error);
},
);
});
};
export const clearUpdates = () =>
db.transaction(tx => tx.executeSql('DELETE FROM updates; VACCUM;'));
+15 -3
View File
@@ -1,4 +1,4 @@
export const createChapterTableQuery = `
const createChapterTableQuery = `
CREATE TABLE IF NOT EXISTS chapters(
chapterId INTEGER PRIMARY KEY AUTOINCREMENT,
chapterUrl TEXT,
@@ -13,6 +13,18 @@ export const createChapterTableQuery = `
ON DELETE CASCADE
)`;
export const createNovelIdIndexQuery = `CREATE INDEX IF NOT EXISTS chapterNovelIdIndex ON chapters(novelId)`;
const createNovelIdIndexQuery = `
CREATE INDEX
IF NOT EXISTS
chapterNovelIdIndex ON chapters(novelId)`;
export const createUnreadChaptersIndexQuery = `CREATE INDEX IF NOT EXISTS chapterUnreadByNovelIndex ON chapters(novelId, \`read\`) WHERE \`read\`=0`;
const createUnreadChaptersIndexQuery = `
CREATE INDEX
IF NOT EXISTS
chapterUnreadByNovelIndex ON chapters(novelId, \`read\`) WHERE \`read\`=0`;
export {
createChapterTableQuery,
createNovelIdIndexQuery,
createUnreadChaptersIndexQuery,
};
+2 -1
View File
@@ -8,4 +8,5 @@ export const createHistoryTableQuery = `
ON DELETE CASCADE
)`;
export const createChapterIdIndexQuery = `CREATE INDEX IF NOT EXISTS historyChapterIdIndex ON history(historyChapterId)`;
export const createChapterIdIndexQuery =
'CREATE INDEX IF NOT EXISTS historyChapterIdIndex ON history(historyChapterId)';
+4 -2
View File
@@ -16,6 +16,8 @@ export const createNovelTableQuery = `
unread INTEGER DEFAULT 1
)`;
export const createUrlIndexQuery = `CREATE INDEX IF NOT EXISTS novelUrlIndex ON novels(novelUrl)`;
export const createUrlIndexQuery =
'CREATE INDEX IF NOT EXISTS novelUrlIndex ON novels(novelUrl)';
export const createLibraryIndexQuery = `CREATE INDEX IF NOT EXISTS novelFollowedIndex ON novels(followed)`;
export const createLibraryIndexQuery =
'CREATE INDEX IF NOT EXISTS novelFollowedIndex ON novels(followed)';
+39 -34
View File
@@ -1,46 +1,51 @@
import { useState, useEffect } from "react";
import {useState, useEffect} from 'react';
export const githubUpdateChecker = () => {
const latestReleaseUrl =
"https://api.github.com/repos/rajarsheechatterjee/lnreader/releases/latest";
import {showToast} from './../hooks/showToast';
const [checking, setChecking] = useState(true);
const [latestRelease, setLatestRelease] = useState();
export const useGithubUpdateChecker = () => {
const latestReleaseUrl =
'https://api.github.com/repos/rajarsheechatterjee/lnreader/releases/latest';
const checkForRelease = async () => {
let res = await fetch(latestReleaseUrl);
res = await res.json();
const [checking, setChecking] = useState(true);
const [latestRelease, setLatestRelease] = useState();
const release = {
tag_name: res.tag_name,
body: res.body,
downloadUrl: res.assets[0].browser_download_url,
};
const checkForRelease = async () => {
let res = await fetch(latestReleaseUrl);
res = await res.json();
setLatestRelease(release);
setChecking(false);
const release = {
tag_name: res.tag_name,
body: res.body,
downloadUrl: res.assets[0].browser_download_url,
};
const isNewVersion = (versionTag) => {
let currentVersion = "v1.1.2";
const regex = /[^\\d.]/;
setLatestRelease(release);
setChecking(false);
};
let newVersion = versionTag.replace(regex, "");
currentVersion = currentVersion.replace(regex, "");
const isNewVersion = versionTag => {
let currentVersion = 'v1.1.6';
const regex = /[^\\d.]/;
return newVersion !== currentVersion;
let newVersion = versionTag.replace(regex, '');
currentVersion = currentVersion.replace(regex, '');
return newVersion !== currentVersion;
};
useEffect(() => {
checkForRelease().catch(e => {
showToast(`Could not connect to github:\n${`${e}`.split(':')[1].trim()}`);
// console.error(e);
});
}, []);
if (!checking) {
const data = {
latestRelease,
isNewVersion: isNewVersion(latestRelease.tag_name),
};
useEffect(() => {
checkForRelease();
}, []);
if (!checking) {
const data = {
latestRelease,
isNewVersion: isNewVersion(latestRelease.tag_name),
};
return data;
}
return data;
}
};
+68 -70
View File
@@ -1,131 +1,129 @@
import { useSelector } from "react-redux";
import { darkTheme } from "../theme/theme";
import {useSelector} from 'react-redux';
import {darkTheme} from '../theme/theme';
const useTheme = () => {
const theme = useSelector((state) => state.settingsReducer.theme);
const theme = useSelector(state => state.settingsReducer.theme);
return theme ?? darkTheme;
return theme || darkTheme;
};
const useReaderSettings = () => {
const readerSettings = useSelector((state) => state.settingsReducer.reader);
const readerSettings = useSelector(state => state.settingsReducer.reader);
return readerSettings;
return readerSettings;
};
const useSettings = () => {
const settings = useSelector((state) => state.settingsReducer);
const settings = useSelector(state => state.settingsReducer);
return settings;
return settings;
};
const useLibrary = () => {
const library = useSelector((state) => state.libraryReducer.novels);
const library = useSelector(state => state.libraryReducer.novels);
return library;
return library;
};
const useNovel = () => {
const novel = useSelector((state) => state.novelReducer);
const novel = useSelector(state => state.novelReducer);
return novel;
return novel;
};
const useChapter = () => {
const chapter = useSelector((state) => state.chapterReducer);
const chapter = useSelector(state => state.chapterReducer);
return chapter;
return chapter;
};
const findNovel = (novelId) => {
let novel = useSelector((state) => state.preferenceReducer.novelSettings);
novel = novel[novelId];
const useFindNovel = novelId => {
let novel = useSelector(state => state.preferenceReducer.novelSettings);
novel = novel[novelId];
return novel;
return novel;
};
const usePreferences = (novelId) => {
let sort, filter, position, showChapterTitles;
const usePreferences = novelId => {
let sort, filter, position, showChapterTitles;
const novel = findNovel(novelId);
const novel = useFindNovel(novelId);
if (novel) {
sort = novel.sort;
filter = novel.filter;
position = novel.position;
showChapterTitles = novel.showChapterTitles;
}
if (novel) {
sort = novel.sort;
filter = novel.filter;
position = novel.position;
showChapterTitles = novel.showChapterTitles;
}
return { sort, filter, position, showChapterTitles };
return {sort, filter, position, showChapterTitles};
};
const useSavedSettings = () => {
const settings = useSelector(
(state) => state.preferenceReducer.novelSettings
);
const settings = useSelector(state => state.preferenceReducer.novelSettings);
return settings;
return settings;
};
const useContinueReading = (chapters, novelId) => {
let lastReadChapter, chapterId, novel, position;
let lastReadChapter, chapterId, novel, position;
novel = findNovel(novelId);
if (novel) {
chapterId = novel.lastRead;
position = novel.position;
}
novel = useFindNovel(novelId);
if (novel) {
chapterId = novel.lastRead;
position = novel.position;
}
if (chapterId) {
lastReadChapter = chapters.find(
(obj) => obj.chapterId === chapterId && obj.read === 0
);
}
if (chapterId) {
lastReadChapter = chapters.find(
obj => obj.chapterId === chapterId && obj.read === 0,
);
}
if (!lastReadChapter) {
lastReadChapter = chapters.find((obj) => obj.read === 0);
}
if (!lastReadChapter) {
lastReadChapter = chapters.find(obj => obj.read === 0);
}
return { lastReadChapter, position };
return {lastReadChapter, position};
};
const usePosition = (novelId, chapterId) => {
let novel, position;
let novel, position;
novel = findNovel(novelId);
novel = useFindNovel(novelId);
if (novel) {
if (novel.position) {
position = novel.position[chapterId] || null;
}
if (novel) {
if (novel.position) {
position = novel.position[chapterId] || null;
}
}
return position;
return position;
};
const useTrackingStatus = () => {
const tracker = useSelector((state) => state.trackerReducer);
const tracker = useSelector(state => state.trackerReducer);
return tracker;
return tracker;
};
const useLibraryFilters = () => {
const filters = useSelector((state) => state.libraryReducer.filters);
const filters = useSelector(state => state.libraryReducer.filters);
return filters;
return filters;
};
export {
useTheme,
useReaderSettings,
useLibrary,
useSettings,
usePreferences,
useContinueReading,
useTrackingStatus,
useNovel,
useChapter,
useSavedSettings,
useLibraryFilters,
usePosition,
useTheme,
useReaderSettings,
useLibrary,
useSettings,
usePreferences,
useContinueReading,
useTrackingStatus,
useNovel,
useChapter,
useSavedSettings,
useLibraryFilters,
usePosition,
};
+3 -3
View File
@@ -1,5 +1,5 @@
import { ToastAndroid } from "react-native";
import {ToastAndroid} from 'react-native';
export const showToast = (message) => {
ToastAndroid.show(message, ToastAndroid.SHORT);
export const showToast = message => {
ToastAndroid.show(message, ToastAndroid.SHORT);
};
+10
View File
@@ -0,0 +1,10 @@
import {useState} from 'react';
export const useModal = () => {
const [visible, setVisible] = useState(false);
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
return {visible, showModal, hideModal};
};
+87 -89
View File
@@ -1,104 +1,102 @@
import React from "react";
import React from 'react';
import { createMaterialBottomTabNavigator } from "@react-navigation/material-bottom-tabs";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import Library from "../screens/Library/Library";
import Updates from "../screens/Updates/Updates";
import History from "../screens/History/History";
import Browse from "../screens/Browse/Browse";
import More from "../screens/More/More";
import Library from '../screens/library/LibraryScreen';
import Updates from '../screens/updates/UpdatesScreen';
import History from '../screens/history/HistoryScreen';
import Browse from '../screens/browse/BrowseScreen';
import More from '../screens/more/MoreScreen';
import { useSettings, useTheme } from "../hooks/reduxHooks";
import {useSettings, useTheme} from '../hooks/reduxHooks';
const Tab = createMaterialBottomTabNavigator();
const BottomNavigator = () => {
const theme = useTheme();
const theme = useTheme();
const {bottom} = useSafeAreaInsets();
const {
showHistoryTab = true,
showUpdatesTab = true,
showLabelsInNav = false,
} = useSettings();
const {
showHistoryTab = true,
showUpdatesTab = true,
showLabelsInNav = false,
} = useSettings();
return (
<Tab.Navigator
barStyle={{ backgroundColor: theme.colorPrimary }}
activeColor={theme.colorAccent}
shifting={!showLabelsInNav}
>
<Tab.Screen
name="Library"
component={Library}
options={{
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="book-variant-multiple"
color={color}
size={24}
/>
),
}}
return (
<Tab.Navigator
barStyle={{backgroundColor: theme.colorPrimary, paddingBottom: bottom}}
activeColor={theme.colorAccent}
shifting={!showLabelsInNav}
>
<Tab.Screen
name="Library"
component={Library}
options={{
tabBarIcon: ({color}) => (
<MaterialCommunityIcons
name="book-variant-multiple"
color={color}
size={24}
/>
{showUpdatesTab && (
<Tab.Screen
name="Updates"
component={Updates}
options={{
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="alert-decagram-outline"
color={color}
size={24}
/>
),
}}
/>
)}
{showHistoryTab && (
<Tab.Screen
name="History"
component={History}
options={{
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="history"
color={color}
size={24}
/>
),
}}
/>
)}
<Tab.Screen
name="Browse"
component={Browse}
options={{
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="compass-outline"
color={color}
size={24}
/>
),
}}
),
}}
/>
{showUpdatesTab && (
<Tab.Screen
name="Updates"
component={Updates}
options={{
tabBarIcon: ({color}) => (
<MaterialCommunityIcons
name="alert-decagram-outline"
color={color}
size={24}
/>
),
}}
/>
)}
{showHistoryTab && (
<Tab.Screen
name="History"
component={History}
options={{
tabBarIcon: ({color}) => (
<MaterialCommunityIcons name="history" color={color} size={24} />
),
}}
/>
)}
<Tab.Screen
name="Browse"
component={Browse}
options={{
tabBarIcon: ({color}) => (
<MaterialCommunityIcons
name="compass-outline"
color={color}
size={24}
/>
<Tab.Screen
name="More"
component={More}
options={{
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="dots-horizontal"
color={color}
size={24}
/>
),
}}
),
}}
/>
<Tab.Screen
name="More"
component={More}
options={{
tabBarIcon: ({color}) => (
<MaterialCommunityIcons
name="dots-horizontal"
color={color}
size={24}
/>
</Tab.Navigator>
);
),
}}
/>
</Tab.Navigator>
);
};
export default BottomNavigator;
+55 -67
View File
@@ -1,81 +1,69 @@
import React, { useEffect } from "react";
import { View } from "react-native";
import React, {useEffect} from 'react';
import {View} from 'react-native';
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import { setBarColor } from "../theme/utils/setBarColor";
import { useTheme } from "../hooks/reduxHooks";
import { githubUpdateChecker } from "../hooks/githubUpdateChecker";
import * as SplashScreen from "expo-splash-screen";
import {setBarColor} from '../theme/utils/setBarColor';
import {useTheme} from '../hooks/reduxHooks';
import {useGithubUpdateChecker} from '../hooks/githubUpdateChecker';
import * as SplashScreen from 'expo-splash-screen';
// Navigators
import BottomNavigator from "./BottomNavigator";
/**
* Navigators
*/
import BottomNavigator from './BottomNavigator';
import MoreStack from './MoreStack';
// Screens
import Novel from "../screens/Novel/Novel";
import Chapter from "../screens/Chapter/Chapter";
import Extension from "../screens/Extension/Extension";
import GlobalSearch from "../screens/Browse/globalsearch/GlobalSearch";
import Migration from "../screens/Browse/migration/Migration";
import SourceNovels from "../screens/Browse/SourceNovels";
import MigrateNovel from "../screens/Browse/migration/MigrationNovels";
import MoreStack from "./More";
import MalTopNovels from "../screens/Browse/discover/MalTopNovels";
import NewUpdateDialog from "../components/NewUpdateDialog";
import BrowseSettings from "../screens/Browse/BrowseSettings";
/**
* Screens
*/
import Novel from '../screens/novel/NovelScreen';
import Reader from '../screens/reader/ReaderScreen';
import Extension from '../screens/source/SourceScreen';
import GlobalSearch from '../screens/browse/globalsearch/GlobalSearch';
import Migration from '../screens/browse/migration/Migration';
import SourceNovels from '../screens/browse/SourceNovels';
import MigrateNovel from '../screens/browse/migration/MigrationNovels';
import MalTopNovels from '../screens/browse/discover/MalTopNovels';
import NewUpdateDialog from '../components/NewUpdateDialog';
import BrowseSettings from '../screens/browse/SettingsBrowseScreen';
const Stack = createStackNavigator();
const MainNavigator = () => {
const theme = useTheme();
const theme = useTheme();
useEffect(() => {
setTimeout(async () => {
await SplashScreen.hideAsync();
setBarColor(theme);
}, 500);
}, [theme]);
useEffect(() => {
setTimeout(async () => {
await SplashScreen.hideAsync();
setBarColor(theme);
}, 500);
}, [theme]);
const { isNewVersion, latestRelease } = githubUpdateChecker() || {};
const {isNewVersion, latestRelease} = useGithubUpdateChecker() || {};
return (
<NavigationContainer
theme={{ colors: { background: theme.colorPrimaryDark } }}
>
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen
name="BottomNavigator"
component={BottomNavigator}
/>
<Stack.Screen name="Novel" component={Novel} />
<Stack.Screen name="Chapter" component={Chapter} />
<Stack.Screen name="MoreStack" component={MoreStack} />
<Stack.Screen name="Extension" component={Extension} />
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
<Stack.Screen
name="BrowseSettings"
component={BrowseSettings}
/>
<Stack.Screen
name="GlobalSearch"
component={GlobalSearch}
/>
<Stack.Screen name="Migration" component={Migration} />
<Stack.Screen
name="SourceNovels"
component={SourceNovels}
/>
<Stack.Screen
name="MigrateNovel"
component={MigrateNovel}
/>
</Stack.Navigator>
</View>
</NavigationContainer>
);
return (
<NavigationContainer theme={{colors: {background: theme.colorPrimaryDark}}}>
<View style={{flex: 1, backgroundColor: theme.colorPrimaryDark}}>
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="BottomNavigator" component={BottomNavigator} />
<Stack.Screen name="Novel" component={Novel} />
<Stack.Screen name="Chapter" component={Reader} />
<Stack.Screen name="MoreStack" component={MoreStack} />
<Stack.Screen name="Extension" component={Extension} />
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
<Stack.Screen name="BrowseSettings" component={BrowseSettings} />
<Stack.Screen name="GlobalSearch" component={GlobalSearch} />
<Stack.Screen name="Migration" component={Migration} />
<Stack.Screen name="SourceNovels" component={SourceNovels} />
<Stack.Screen name="MigrateNovel" component={MigrateNovel} />
</Stack.Navigator>
</View>
</NavigationContainer>
);
};
export default MainNavigator;
-40
View File
@@ -1,40 +0,0 @@
import React from "react";
import { createStackNavigator } from "@react-navigation/stack";
// Screens
import About from "../screens/More/About";
import Settings from "../screens/More/Settings";
import TrackerSettings from "../screens/More/TrackerSettings";
import ReaderSettings from "../screens/More/ReaderSettings";
import BackupSettings from "../screens/More/BackupSettings";
import AdvancedSettings from "../screens/More/AdvancedSettings";
import GeneralSettings from "../screens/More/GeneralSettings";
import DownloadQueue from "../screens/More/DownloadQueue";
import Downloads from "../screens/More/Downloads";
const Stack = createStackNavigator();
const stackNavigatorConfig = { headerShown: false };
const SettingsStack = () => (
<Stack.Navigator screenOptions={stackNavigatorConfig}>
<Stack.Screen name="Settings" component={Settings} />
<Stack.Screen name="GeneralSettings" component={GeneralSettings} />
<Stack.Screen name="ReaderSettings" component={ReaderSettings} />
<Stack.Screen name="TrackerSettings" component={TrackerSettings} />
<Stack.Screen name="BackupSettings" component={BackupSettings} />
<Stack.Screen name="AdvancedSettings" component={AdvancedSettings} />
</Stack.Navigator>
);
const MoreStack = () => (
<Stack.Navigator screenOptions={stackNavigatorConfig}>
<Stack.Screen name="SettingsStack" component={SettingsStack} />
<Stack.Screen name="About" component={About} />
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
<Stack.Screen name="Downloads" component={Downloads} />
</Stack.Navigator>
);
export default MoreStack;
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
// Screens
import About from '../screens/more/About';
import Settings from '../screens/setting/SettingsScreen';
import TrackerSettings from '../screens/setting/SettingsTrackerScreen';
import ReaderSettings from '../screens/setting/SettingsReaderScreen';
import BackupSettings from '../screens/setting/SettingsBackupScreen';
import AdvancedSettings from '../screens/setting/SettingsAdvancedScreen';
import GeneralSettings from '../screens/setting/SettingsGeneralScreen';
import DownloadQueue from '../screens/more/DownloadQueueScreen';
import Downloads from '../screens/more/DownloadsScreen';
import AppearanceSettings from '../screens/setting/SettingsAppearanceScreen';
const Stack = createStackNavigator();
const stackNavigatorConfig = {headerShown: false};
const SettingsStack = () => (
<Stack.Navigator screenOptions={stackNavigatorConfig}>
<Stack.Screen name="Settings" component={Settings} />
<Stack.Screen name="GeneralSettings" component={GeneralSettings} />
<Stack.Screen name="ReaderSettings" component={ReaderSettings} />
<Stack.Screen name="TrackerSettings" component={TrackerSettings} />
<Stack.Screen name="BackupSettings" component={BackupSettings} />
<Stack.Screen name="AppearanceSettings" component={AppearanceSettings} />
<Stack.Screen name="AdvancedSettings" component={AdvancedSettings} />
</Stack.Navigator>
);
const MoreStack = () => (
<Stack.Navigator screenOptions={stackNavigatorConfig}>
<Stack.Screen name="SettingsStack" component={SettingsStack} />
<Stack.Screen name="About" component={About} />
<Stack.Screen name="DownloadQueue" component={DownloadQueue} />
<Stack.Screen name="Downloads" component={Downloads} />
</Stack.Navigator>
);
export default MoreStack;
+4 -4
View File
@@ -1,4 +1,4 @@
export const SET_DOWNLOAD_QUEUE = "SET_DOWNLOAD_QUEUE";
export const CANCEL_DOWNLOAD = "CANCEL_DOWNLOAD";
export const PAUSE_DOWNLOADS = "PAUSE_DOWNLOADS";
export const RESUME_DOWNLOADS = "RESUME_DOWNLOADS";
export const SET_DOWNLOAD_QUEUE = 'SET_DOWNLOAD_QUEUE';
export const CANCEL_DOWNLOAD = 'CANCEL_DOWNLOAD';
export const PAUSE_DOWNLOADS = 'PAUSE_DOWNLOADS';
export const RESUME_DOWNLOADS = 'RESUME_DOWNLOADS';
+82 -84
View File
@@ -1,102 +1,100 @@
import {
SET_DOWNLOAD_QUEUE,
CANCEL_DOWNLOAD,
PAUSE_DOWNLOADS,
} from "./donwloads.types";
import BackgroundService from "react-native-background-actions";
import { downloadChapter } from "../../database/queries/ChapterQueries";
import { CHAPTER_DOWNLOADED } from "../novel/novel.types";
import { showToast } from "../../hooks/showToast";
import {CANCEL_DOWNLOAD, PAUSE_DOWNLOADS} from './donwloads.types';
import BackgroundService from 'react-native-background-actions';
import {downloadChapter} from '../../database/queries/ChapterQueries';
import {CHAPTER_DOWNLOADED} from '../novel/novel.types';
import {showToast} from '../../hooks/showToast';
export const pauseDownloads = () => async (dispatch) => {
showToast("Download paused");
import * as Notifications from 'expo-notifications';
await BackgroundService.stop();
export const pauseDownloads = () => async dispatch => {
showToast('Download paused');
dispatch({ type: PAUSE_DOWNLOADS });
await BackgroundService.stop();
dispatch({type: PAUSE_DOWNLOADS});
};
export const resumeDownloads = (chapters) => async (dispatch) => {
showToast("Download resumed");
export const resumeDownloads = chapters => async dispatch => {
showToast('Download resumed');
const options = {
taskName: "Library Update",
taskTitle: chapters[0].chapterName,
taskDesc: "0/" + chapters.length,
taskIcon: {
name: "notification_icon",
type: "drawable",
},
color: "#00adb5",
parameters: {
delay: 1000,
},
linkingURI: "lnreader://downloads",
progressBar: {
max: chapters.length,
value: 0,
},
};
const options = {
taskName: 'Library Update',
taskTitle: chapters[0].chapterName,
taskDesc: '0/' + chapters.length,
taskIcon: {
name: 'notification_icon',
type: 'drawable',
},
color: '#00adb5',
parameters: {
delay: 1000,
},
linkingURI: 'lnreader://downloads',
progressBar: {
max: chapters.length,
value: 0,
},
};
const sleep = (time) =>
new Promise((resolve) => setTimeout(() => resolve(), time));
const sleep = time =>
new Promise(resolve => setTimeout(() => resolve(), time));
const veryIntensiveTask = async (taskData) => {
await new Promise(async (resolve) => {
for (
let i = 0;
BackgroundService.isRunning() && i < chapters.length;
i++
) {
if (BackgroundService.isRunning()) {
if (!chapters[i].downloaded) {
await downloadChapter(
chapters[i].sourceId,
chapters[i].novelUrl,
chapters[i].chapterUrl,
chapters[i].chapterId
);
}
const veryIntensiveTask = async taskData => {
await new Promise(async resolve => {
for (
let i = 0;
BackgroundService.isRunning() && i < chapters.length;
i++
) {
if (BackgroundService.isRunning()) {
if (!chapters[i].downloaded) {
await downloadChapter(
chapters[i].sourceId,
chapters[i].novelUrl,
chapters[i].chapterUrl,
chapters[i].chapterId,
);
}
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapters[i].chapterId,
});
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapters[i].chapterId,
});
await BackgroundService.updateNotification({
taskTitle: chapters[i].chapterName,
taskDesc: i + 1 + "/" + chapters.length,
progressBar: {
max: chapters.length,
value: i + 1,
},
});
await BackgroundService.updateNotification({
taskTitle: chapters[i].chapterName,
taskDesc: i + 1 + '/' + chapters.length,
progressBar: {
max: chapters.length,
value: i + 1,
},
});
if (i + 1 === chapters.length) {
resolve();
await BackgroundService.stop();
if (i + 1 === chapters.length) {
resolve();
await BackgroundService.stop();
Notifications.scheduleNotificationAsync({
content: {
title: "Downloader",
body: "Download completed",
},
trigger: null,
});
}
await sleep(taskData.delay);
}
}
});
};
Notifications.scheduleNotificationAsync({
content: {
title: 'Downloader',
body: 'Download completed',
},
trigger: null,
});
}
await sleep(taskData.delay);
}
}
});
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.start(veryIntensiveTask, options);
dispatch({ type: PAUSE_DOWNLOADS });
dispatch({type: PAUSE_DOWNLOADS});
};
export const cancelDownload = () => async (dispatch) => {
await BackgroundService.stop();
export const cancelDownload = () => async dispatch => {
await BackgroundService.stop();
dispatch({ type: CANCEL_DOWNLOAD });
dispatch({type: CANCEL_DOWNLOAD});
};
+25 -25
View File
@@ -1,35 +1,35 @@
import { CHAPTER_DOWNLOADED } from "../novel/novel.types";
import { CANCEL_DOWNLOAD, SET_DOWNLOAD_QUEUE } from "./donwloads.types";
import {CHAPTER_DOWNLOADED} from '../novel/novel.types';
import {CANCEL_DOWNLOAD, SET_DOWNLOAD_QUEUE} from './donwloads.types';
const initialState = {
downloadQueue: [],
downloadQueue: [],
};
const downloadsReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case SET_DOWNLOAD_QUEUE:
return {
...state,
downloadQueue: [...state.downloadQueue, ...payload],
};
switch (type) {
case SET_DOWNLOAD_QUEUE:
return {
...state,
downloadQueue: [...state.downloadQueue, ...payload],
};
case CHAPTER_DOWNLOADED:
return {
...state,
downloadQueue: state.downloadQueue.filter(
(chapter) => chapter.chapterId !== payload
),
};
case CANCEL_DOWNLOAD:
return {
...state,
downloadQueue: [],
};
default:
return state;
}
case CHAPTER_DOWNLOADED:
return {
...state,
downloadQueue: state.downloadQueue.filter(
chapter => chapter.chapterId !== payload,
),
};
case CANCEL_DOWNLOAD:
return {
...state,
downloadQueue: [],
};
default:
return state;
}
};
export default downloadsReducer;
+43 -41
View File
@@ -1,57 +1,59 @@
import {
GET_LIBRARY_NOVELS,
GET_LIBRARY_SEARCH_RESULTS,
SET_LIBRARY_LOADING,
SORT_FILTER_LIBRARY,
} from "./library.types";
import {
getLibrary,
searchLibrary,
} from "../../database/queries/LibraryQueries";
import { restoreBackup } from "../../services/backup";
GET_LIBRARY_NOVELS,
GET_LIBRARY_SEARCH_RESULTS,
SET_LIBRARY_LOADING,
SORT_FILTER_LIBRARY,
} from './library.types';
import {getLibrary, searchLibrary} from '../../database/queries/LibraryQueries';
import {restoreBackup} from '../../services/backup';
export const getLibraryAction = (sort, filter) => async (dispatch) => {
// console.log("1.", sort, filter);
const res = await getLibrary(sort, filter);
export const getLibraryAction =
(sort, filter) => async (dispatch, getState) => {
const {downloadedOnlyMode = false} = getState().settingsReducer;
const res = await getLibrary(
sort,
downloadedOnlyMode ? 'chaptersDownloaded > 0' : filter,
);
dispatch({
type: GET_LIBRARY_NOVELS,
payload: res,
type: GET_LIBRARY_NOVELS,
payload: res,
});
};
};
export const searchLibraryAction =
(searchText, sort, filter) => async (dispatch) => {
const res = await searchLibrary(searchText, sort, filter);
dispatch({
type: GET_LIBRARY_SEARCH_RESULTS,
payload: res,
});
};
export const filterLibrary = (sort, filter) => async (dispatch) => {
dispatch({ type: SORT_FILTER_LIBRARY, payload: { sort, filter } });
const res = await getLibrary(sort, filter);
(searchText, sort, filter) => async dispatch => {
const res = await searchLibrary(searchText, sort, filter);
dispatch({
type: GET_LIBRARY_NOVELS,
payload: res,
type: GET_LIBRARY_SEARCH_RESULTS,
payload: res,
});
};
export const filterLibrary = (sort, filter) => async dispatch => {
dispatch({type: SORT_FILTER_LIBRARY, payload: {sort, filter}});
const res = await getLibrary(sort, filter);
dispatch({
type: GET_LIBRARY_NOVELS,
payload: res,
});
};
export const restoreLibraryAction = () => async (dispatch) => {
dispatch({ type: SET_LIBRARY_LOADING });
export const restoreLibraryAction = () => async dispatch => {
dispatch({type: SET_LIBRARY_LOADING});
const res = await restoreBackup();
const res = await restoreBackup();
if (res) {
const novels = await getLibrary();
if (res) {
const novels = await getLibrary();
dispatch({
type: GET_LIBRARY_NOVELS,
payload: novels,
});
}
dispatch({
type: GET_LIBRARY_NOVELS,
payload: novels,
});
}
};
+26 -26
View File
@@ -1,36 +1,36 @@
import {
GET_LIBRARY_NOVELS,
GET_LIBRARY_SEARCH_RESULTS,
SET_LIBRARY_LOADING,
SORT_FILTER_LIBRARY,
} from "./library.types";
GET_LIBRARY_NOVELS,
GET_LIBRARY_SEARCH_RESULTS,
SET_LIBRARY_LOADING,
SORT_FILTER_LIBRARY,
} from './library.types';
const initialState = {
novels: [],
searchResults: [],
filters: { sort: "novels.novelId ASC", filter: "" },
loading: true,
novels: [],
searchResults: [],
filters: {sort: 'novels.novelId ASC', filter: ''},
loading: true,
};
const libraryReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case SET_LIBRARY_LOADING:
return { ...state, loading: true };
case GET_LIBRARY_NOVELS:
return { ...state, novels: payload, loading: false };
case GET_LIBRARY_SEARCH_RESULTS:
return { ...state, novels: payload, loading: false };
case SORT_FILTER_LIBRARY:
// console.log("2.", payload.sort, payload.filter);
return {
...state,
filters: { sort: payload.sort, filter: payload.filter },
};
default:
return state;
}
switch (type) {
case SET_LIBRARY_LOADING:
return {...state, loading: true};
case GET_LIBRARY_NOVELS:
return {...state, novels: payload, loading: false};
case GET_LIBRARY_SEARCH_RESULTS:
return {...state, novels: payload, loading: false};
case SORT_FILTER_LIBRARY:
// console.log("2.", payload.sort, payload.filter);
return {
...state,
filters: {sort: payload.sort, filter: payload.filter},
};
default:
return state;
}
};
export default libraryReducer;
+4 -4
View File
@@ -1,5 +1,5 @@
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
export const GET_LIBRARY_SEARCH_RESULTS = "GET_LIBRARY_SEARCH_RESULTS";
export const SET_LIBRARY_LOADING = "SET_LIBRARY_LOADING";
export const GET_LIBRARY_NOVELS = 'GET_LIBRARY_NOVELS';
export const GET_LIBRARY_SEARCH_RESULTS = 'GET_LIBRARY_SEARCH_RESULTS';
export const SET_LIBRARY_LOADING = 'SET_LIBRARY_LOADING';
export const SORT_FILTER_LIBRARY = "SORT_FILTER_LIBRARY";
export const SORT_FILTER_LIBRARY = 'SORT_FILTER_LIBRARY';
+401 -398
View File
@@ -1,437 +1,440 @@
import { ToastAndroid } from "react-native";
import {ToastAndroid} from 'react-native';
import {
LOADING_NOVEL,
GET_NOVEL,
GET_CHAPTERS,
NOVEL_ERROR,
FETCHING_NOVEL,
SET_NOVEL,
UPDATE_IN_LIBRARY,
CHAPTER_READ,
CHAPTER_DOWNLOADING,
CHAPTER_DOWNLOADED,
CHAPTER_DELETED,
UPDATE_NOVEL,
SET_NOVEL_SETTINGS,
CHAPTER_UNREAD,
BOOKMARK_CHAPTER,
MARK_PREVIOUS_CHAPTERS_READ,
MARK_PREVIOUS_CHAPTERS_UNREAD,
} from "./novel.types";
LOADING_NOVEL,
GET_NOVEL,
GET_CHAPTERS,
FETCHING_NOVEL,
SET_NOVEL,
UPDATE_IN_LIBRARY,
CHAPTER_READ,
CHAPTER_DOWNLOADED,
CHAPTER_DELETED,
UPDATE_NOVEL,
SET_NOVEL_SETTINGS,
BOOKMARK_CHAPTER,
MARK_PREVIOUS_CHAPTERS_READ,
MARK_PREVIOUS_CHAPTERS_UNREAD,
} from './novel.types';
import { updateNovel } from "../../services/updates";
import { fetchNovel } from "../../services/Source/source";
import {updateNovel} from '../../services/updates';
import {fetchNovel} from '../../services/Source/source';
import {
followNovel,
insertNovel,
getNovel,
} from "../../database/queries/NovelQueries";
followNovel,
insertNovel,
getNovel,
} from '../../database/queries/NovelQueries';
import {
getChapters,
insertChapters,
markChapterRead,
downloadChapter,
deleteChapter,
markChapterUnread,
bookmarkChapter,
markPreviuschaptersRead,
markPreviousChaptersUnread,
getNextChapterFromDB,
} from "../../database/queries/ChapterQueries";
import { deleteNovelUpdates } from "../../database/queries/UpdateQueries";
getChapters,
insertChapters,
markChapterRead,
downloadChapter,
deleteChapter,
markChapterUnread,
bookmarkChapter,
markPreviuschaptersRead,
markPreviousChaptersUnread,
getNextChapterFromDB,
} from '../../database/queries/ChapterQueries';
import {deleteNovelUpdates} from '../../database/queries/UpdateQueries';
import {
SAVE_SCROLL_POSITION,
SET_CHAPTER_LIST_PREF,
SET_LAST_READ,
} from "../preferences/preference.types";
import { GET_LIBRARY_NOVELS } from "../library/library.types";
import { getLibrary } from "../../database/queries/LibraryQueries";
import { showToast } from "../../hooks/showToast";
import { store } from "../store";
SAVE_SCROLL_POSITION,
SET_CHAPTER_LIST_PREF,
SET_LAST_READ,
} from '../preferences/preference.types';
import {GET_LIBRARY_NOVELS} from '../library/library.types';
import {getLibrary} from '../../database/queries/LibraryQueries';
import {showToast} from '../../hooks/showToast';
import * as Notifications from "expo-notifications";
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
};
},
handleNotification: async () => {
return {
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
};
},
});
import BackgroundService from "react-native-background-actions";
import { setDownloadQueue } from "../downloads/downloads.actions";
import { SET_DOWNLOAD_QUEUE } from "../downloads/donwloads.types";
import BackgroundService from 'react-native-background-actions';
import {SET_DOWNLOAD_QUEUE} from '../downloads/donwloads.types';
export const setNovel = (novel) => async (dispatch) => {
dispatch({ type: SET_NOVEL, payload: novel });
export const setNovel = novel => async dispatch => {
dispatch({type: SET_NOVEL, payload: novel});
};
export const getNovelAction =
(followed, sourceId, novelUrl, novelId, sort, filter) =>
async (dispatch) => {
try {
dispatch({ type: LOADING_NOVEL });
if (followed === 1) {
/**
* If novel is followed directly get the chapters from db.
*/
const chapters = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chapters,
});
} else {
dispatch({ type: FETCHING_NOVEL });
/**
* Check if novel is cached.
*/
const novel = await getNovel(sourceId, novelUrl);
if (novel) {
/**
* Get chapters from db.
*/
novel.chapters = await getChapters(
novel.novelId,
sort,
filter
);
dispatch({
type: GET_NOVEL,
payload: novel,
});
} else {
/**
* Fetch novel from source.
*/
const fetchedNovel = await fetchNovel(sourceId, novelUrl);
/**
* Insert novel in db.
*/
const fetchedNovelId = await insertNovel(fetchedNovel);
await insertChapters(fetchedNovelId, fetchedNovel.chapters);
/**
* Get novel from db.
*/
const novel = await getNovel(sourceId, novelUrl);
novel.chapters = await getChapters(novel.novelId);
dispatch({
type: GET_NOVEL,
payload: novel,
});
}
}
} catch (error) {
showToast(error.message);
// dispatch({ type: NOVEL_ERROR });
}
};
export const sortAndFilterChapters =
(novelId, sort, filter) => async (dispatch) => {
dispatch({ type: FETCHING_NOVEL });
(followed, sourceId, novelUrl, novelId, sort, filter) => async dispatch => {
try {
dispatch({type: LOADING_NOVEL});
if (followed === 1) {
/**
* If novel is followed directly get the chapters from db.
*/
const chapters = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chapters,
});
dispatch({
type: SET_CHAPTER_LIST_PREF,
payload: { novelId, sort, filter },
});
};
export const showChapterTitlesAction = (novelId, value) => async (dispatch) => {
dispatch({
type: SET_NOVEL_SETTINGS,
payload: { novelId, value: value },
});
};
export const followNovelAction = (novel) => async (dispatch) => {
await followNovel(novel.followed, novel.novelId);
if (!novel.followNovel) {
deleteNovelUpdates(novel.novelId);
}
dispatch({
type: UPDATE_IN_LIBRARY,
payload: { novelUrl: novel.novelUrl, followed: !novel.followed },
});
const res = await getLibrary();
dispatch({
type: GET_LIBRARY_NOVELS,
payload: res,
});
showToast(!novel.followed ? "Added to library" : "Removed from library");
};
export const bookmarkChapterAction = (chapters) => async (dispatch) => {
chapters.map((chapter) => {
bookmarkChapter(chapter.bookmark, chapter.chapterId);
dispatch({
type: BOOKMARK_CHAPTER,
payload: {
bookmark: chapter.bookmark,
chapterId: chapter.chapterId,
},
});
});
};
export const markChapterReadAction =
(chapterId, novelId) => async (dispatch) => {
await markChapterRead(chapterId);
dispatch({
type: CHAPTER_READ,
payload: { chapterId },
});
const nextChapter = await getNextChapterFromDB(novelId, chapterId);
dispatch({
type: SET_LAST_READ,
payload: { novelId, chapterId: nextChapter.chapterId },
type: GET_CHAPTERS,
payload: chapters,
});
} else {
dispatch({type: FETCHING_NOVEL});
/**
* Reset progress on marked read
* Check if novel is cached.
*/
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position: 0,
percentage: 0,
chapterId,
novelId,
},
});
};
let novel = await getNovel(sourceId, novelUrl);
export const markPreviousChaptersReadAction =
(chapterId, novelId) => async (dispatch) => {
await markPreviuschaptersRead(chapterId, novelId);
if (novel) {
/**
* Get chapters from db.
*/
novel.chapters = await getChapters(novel.novelId, sort, filter);
dispatch({
type: GET_NOVEL,
payload: novel,
});
} else {
/**
* Fetch novel from source.
*/
const fetchedNovel = await fetchNovel(sourceId, novelUrl);
dispatch({
type: MARK_PREVIOUS_CHAPTERS_READ,
payload: chapterId,
});
/**
* Insert novel in db.
*/
const fetchedNovelId = await insertNovel(fetchedNovel);
await insertChapters(fetchedNovelId, fetchedNovel.chapters);
showToast("Marked previous chapters read");
};
/**
* Get novel from db.
*/
novel = await getNovel(sourceId, novelUrl);
novel.chapters = await getChapters(novel.novelId);
export const markChaptersRead =
(chapters, novelId, sort, filter) => async (dispatch) => {
try {
chapters.map((chapter) => markChapterRead(chapter.chapterId));
const chaps = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chaps,
});
} catch (error) {
showToast(error.message);
dispatch({
type: GET_NOVEL,
payload: novel,
});
}
};
}
} catch (error) {
showToast(error.message);
// dispatch({ type: NOVEL_ERROR });
}
};
export const markPreviousChaptersUnreadAction =
(chapterId, novelId) => async (dispatch) => {
await markPreviousChaptersUnread(chapterId, novelId);
export const sortAndFilterChapters =
(novelId, sort, filter) => async dispatch => {
dispatch({type: FETCHING_NOVEL});
dispatch({
type: MARK_PREVIOUS_CHAPTERS_UNREAD,
payload: chapterId,
});
};
const chapters = await getChapters(novelId, sort, filter);
export const markChapterUnreadAction =
(chapters, novelId) => async (dispatch) => {
const { sort = "ORDER BY chapterId ASC", filter = "" } =
store.getState().preferenceReducer.novelSettings[novelId];
chapters.map((chapter) => markChapterUnread(chapter.chapterId));
const chaps = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chaps,
});
};
export const downloadChapterAction =
(sourceId, novelUrl, chapterUrl, chapterName, chapterId) =>
async (dispatch) => {
// dispatch({
// type: CHAPTER_DOWNLOADING,
// payload: chapterId,
// });
dispatch({
type: SET_DOWNLOAD_QUEUE,
payload: [{ chapterId, chapterName }],
});
await downloadChapter(sourceId, novelUrl, chapterUrl, chapterId);
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapterId,
});
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
};
export const downloadAllChaptersAction =
(sourceId, novelUrl, chaps) => async (dispatch) => {
try {
let chapters = chaps.filter((chapter) => chapter.downloaded === 0);
chapters = chapters.map((chapter) => ({
...chapter,
sourceId,
novelUrl,
}));
dispatch({ type: SET_DOWNLOAD_QUEUE, payload: chapters });
const options = {
taskName: "Library Update",
taskTitle: chapters[0].chapterName,
taskDesc: "0/" + chapters.length,
taskIcon: {
name: "notification_icon",
type: "drawable",
},
color: "#00adb5",
linkingURI: "lnreader://downloads/redirect",
parameters: {
delay: 1000,
},
progressBar: {
max: chapters.length,
value: 0,
},
};
const sleep = (time) =>
new Promise((resolve) => setTimeout(() => resolve(), time));
const veryIntensiveTask = async (taskData) => {
await new Promise(async (resolve) => {
for (
let i = 0;
BackgroundService.isRunning() && i < chapters.length;
i++
) {
if (BackgroundService.isRunning()) {
// dispatch({
// type: CHAPTER_DOWNLOADING,
// payload: chapters[i].chapterId,
// });
if (!chapters[i].downloaded) {
await downloadChapter(
sourceId,
novelUrl,
chapters[i].chapterUrl,
chapters[i].chapterId
);
}
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapters[i].chapterId,
});
await BackgroundService.updateNotification({
taskTitle: chapters[i].chapterName,
taskDesc: i + 1 + "/" + chapters.length,
progressBar: {
max: chapters.length,
value: i + 1,
},
});
if (i + 1 === chapters.length) {
resolve();
await BackgroundService.stop();
Notifications.scheduleNotificationAsync({
content: {
title: "Downloader",
body: "Download completed",
},
trigger: null,
});
}
await sleep(taskData.delay);
}
}
});
};
await BackgroundService.start(veryIntensiveTask, options);
} catch (error) {
showToast(error.message);
}
};
export const deleteChapterAction =
(chapterId, chapterName) => async (dispatch) => {
await deleteChapter(chapterId);
dispatch({
type: CHAPTER_DELETED,
payload: chapterId,
});
showToast(`Deleted ${chapterName}`);
};
export const deleteAllChaptersAction = (chapters) => async (dispatch) => {
await chapters.map((chapter) => {
deleteChapter(chapter.chapterId);
dispatch({
type: CHAPTER_DELETED,
payload: chapter.chapterId,
});
dispatch({
type: GET_CHAPTERS,
payload: chapters,
});
showToast("Chapters deleted");
dispatch({
type: SET_CHAPTER_LIST_PREF,
payload: {novelId, sort, filter},
});
};
export const showChapterTitlesAction = (novelId, value) => async dispatch => {
dispatch({
type: SET_NOVEL_SETTINGS,
payload: {novelId, value: value},
});
};
export const followNovelAction = novel => async dispatch => {
await followNovel(novel.followed, novel.novelId);
if (!novel.followNovel) {
deleteNovelUpdates(novel.novelId);
}
dispatch({
type: UPDATE_IN_LIBRARY,
payload: {novelUrl: novel.novelUrl, followed: !novel.followed},
});
const res = await getLibrary();
dispatch({
type: GET_LIBRARY_NOVELS,
payload: res,
});
showToast(!novel.followed ? 'Added to library' : 'Removed from library');
};
export const bookmarkChapterAction = chapters => async dispatch => {
chapters.map(chapter => {
bookmarkChapter(chapter.bookmark, chapter.chapterId);
dispatch({
type: BOOKMARK_CHAPTER,
payload: {
bookmark: chapter.bookmark,
chapterId: chapter.chapterId,
},
});
});
};
export const markChapterReadAction = (chapterId, novelId) => async dispatch => {
await markChapterRead(chapterId);
dispatch({
type: CHAPTER_READ,
payload: {chapterId},
});
const nextChapter = await getNextChapterFromDB(novelId, chapterId);
dispatch({
type: SET_LAST_READ,
payload: {novelId, chapterId: nextChapter.chapterId},
});
/**
* Reset progress on marked read
*/
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position: 0,
percentage: 0,
chapterId,
novelId,
},
});
};
export const markPreviousChaptersReadAction =
(chapterId, novelId) => async dispatch => {
await markPreviuschaptersRead(chapterId, novelId);
dispatch({
type: MARK_PREVIOUS_CHAPTERS_READ,
payload: chapterId,
});
showToast('Marked previous chapters read');
};
export const markChaptersRead =
(chapters, novelId, sort, filter) => async dispatch => {
try {
chapters.map(chapter => markChapterRead(chapter.chapterId));
const chaps = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chaps,
});
} catch (error) {
showToast(error.message);
}
};
export const markPreviousChaptersUnreadAction =
(chapterId, novelId) => async dispatch => {
await markPreviousChaptersUnread(chapterId, novelId);
dispatch({
type: MARK_PREVIOUS_CHAPTERS_UNREAD,
payload: chapterId,
});
};
export const markChapterUnreadAction =
(chapters, novelId) => async (dispatch, getState) => {
const {sort = 'ORDER BY chapterId ASC', filter = ''} =
getState().preferenceReducer.novelSettings[novelId];
chapters.map(chapter => markChapterUnread(chapter.chapterId));
const chaps = await getChapters(novelId, sort, filter);
dispatch({
type: GET_CHAPTERS,
payload: chaps,
});
};
export const downloadChapterAction =
(sourceId, novelUrl, chapterUrl, chapterName, chapterId) =>
async dispatch => {
// dispatch({
// type: CHAPTER_DOWNLOADING,
// payload: chapterId,
// });
dispatch({
type: SET_DOWNLOAD_QUEUE,
payload: [{chapterId, chapterName}],
});
await downloadChapter(sourceId, novelUrl, chapterUrl, chapterId);
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapterId,
});
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
};
export const downloadAllChaptersAction =
(sourceId, novelUrl, chaps) => async (dispatch, getState) => {
try {
const downloadQueue = getState().downloadsReducer.downloadQueue;
/**
* Filter downloaded chapters
*/
let chapters = chaps.filter(chapter => chapter.downloaded === 0);
/**
* Filter chapters already in download queue
*/
chapters = chapters.filter(
chapter =>
!downloadQueue.some(obj => obj.chapterId === chapter.chapterId),
);
chapters = chapters.map(chapter => ({
...chapter,
sourceId,
novelUrl,
}));
if (chapters.length > 0) {
dispatch({type: SET_DOWNLOAD_QUEUE, payload: chapters});
const options = {
taskName: 'Library Update',
taskTitle: chapters[0].chapterName,
taskDesc: '0/' + chapters.length,
taskIcon: {
name: 'notification_icon',
type: 'drawable',
},
color: '#00adb5',
linkingURI: 'lnreader://downloads/redirect',
parameters: {
delay: 1000,
},
progressBar: {
max: chapters.length,
value: 0,
},
};
const sleep = time =>
new Promise(resolve => setTimeout(() => resolve(), time));
const veryIntensiveTask = async taskData => {
await new Promise(async resolve => {
for (
let i = 0;
BackgroundService.isRunning() && i < chapters.length;
i++
) {
if (BackgroundService.isRunning()) {
// dispatch({
// type: CHAPTER_DOWNLOADING,
// payload: chapters[i].chapterId,
// });
if (!chapters[i].downloaded) {
await downloadChapter(
sourceId,
novelUrl,
chapters[i].chapterUrl,
chapters[i].chapterId,
);
}
dispatch({
type: CHAPTER_DOWNLOADED,
payload: chapters[i].chapterId,
});
await BackgroundService.updateNotification({
taskTitle: chapters[i].chapterName,
taskDesc: i + 1 + '/' + chapters.length,
progressBar: {
max: chapters.length,
value: i + 1,
},
});
if (i + 1 === chapters.length) {
resolve();
Notifications.scheduleNotificationAsync({
content: {
title: 'Downloader',
body: 'Download completed',
},
trigger: null,
});
}
await sleep(taskData.delay);
}
}
});
};
await BackgroundService.start(veryIntensiveTask, options);
}
} catch (error) {
showToast(error.message);
}
};
export const deleteChapterAction =
(chapterId, chapterName) => async dispatch => {
await deleteChapter(chapterId);
dispatch({
type: CHAPTER_DELETED,
payload: chapterId,
});
showToast(`Deleted ${chapterName}`);
};
export const deleteAllChaptersAction = chapters => async dispatch => {
await chapters.map(chapter => {
deleteChapter(chapter.chapterId);
dispatch({
type: CHAPTER_DELETED,
payload: chapter.chapterId,
});
});
showToast('Chapters deleted');
};
export const updateNovelAction =
(sourceId, novelUrl, novelId, sort, filter) => async (dispatch) => {
dispatch({ type: FETCHING_NOVEL });
(sourceId, novelUrl, novelId, sort, filter) => async dispatch => {
dispatch({type: FETCHING_NOVEL});
await updateNovel(sourceId, novelUrl, novelId);
await updateNovel(sourceId, novelUrl, novelId);
let novel = await getNovel(sourceId, novelUrl);
let chapters = await getChapters(novel.novelId, sort, filter);
let novel = await getNovel(sourceId, novelUrl);
let chapters = await getChapters(novel.novelId, sort, filter);
dispatch({
type: UPDATE_NOVEL,
payload: { novel, chapters },
});
};
dispatch({
type: UPDATE_NOVEL,
payload: {novel, chapters},
});
};
+138 -146
View File
@@ -1,157 +1,149 @@
import {
CHAPTER_DOWNLOADING,
CHAPTER_DOWNLOADED,
CHAPTER_DELETED,
GET_CHAPTERS,
LOADING_NOVEL,
GET_NOVEL,
FETCHING_NOVEL,
SET_NOVEL,
UPDATE_IN_LIBRARY,
NOVEL_ERROR,
CHAPTER_READ,
UPDATE_NOVEL,
UPDATE_LAST_READ,
CHAPTER_UNREAD,
BOOKMARK_CHAPTER,
MARK_PREVIOUS_CHAPTERS_READ,
MARK_PREVIOUS_CHAPTERS_UNREAD,
} from "./novel.types";
CHAPTER_DOWNLOADING,
CHAPTER_DOWNLOADED,
CHAPTER_DELETED,
GET_CHAPTERS,
LOADING_NOVEL,
GET_NOVEL,
FETCHING_NOVEL,
SET_NOVEL,
UPDATE_IN_LIBRARY,
NOVEL_ERROR,
CHAPTER_READ,
UPDATE_NOVEL,
UPDATE_LAST_READ,
CHAPTER_UNREAD,
BOOKMARK_CHAPTER,
MARK_PREVIOUS_CHAPTERS_READ,
MARK_PREVIOUS_CHAPTERS_UNREAD,
} from './novel.types';
const initialState = {
novel: {},
chapters: [],
loading: true,
updating: false,
downloading: [],
novel: {},
chapters: [],
loading: true,
updating: false,
downloading: [],
};
const novelReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case LOADING_NOVEL:
return { ...state, loading: true };
case FETCHING_NOVEL:
return { ...state, updating: true };
case SET_NOVEL:
return { ...state, novel: payload };
case GET_NOVEL:
return {
...state,
novel: payload,
chapters: payload.chapters,
loading: false,
updating: false,
};
case GET_CHAPTERS:
return {
...state,
chapters: payload,
loading: false,
updating: false,
};
case UPDATE_IN_LIBRARY:
return {
...state,
novel: { ...state.novel, followed: payload.followed },
};
case UPDATE_NOVEL:
return {
...state,
novel: payload.novel,
chapters: payload.chapters,
loading: false,
updating: false,
};
case CHAPTER_READ:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId === payload.chapterId
? { ...chapter, read: 1 }
: chapter
),
};
case CHAPTER_UNREAD:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId === payload.chapterId
? { ...chapter, read: 0 }
: chapter
),
};
case BOOKMARK_CHAPTER:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId === payload.chapterId
? { ...chapter, bookmark: !payload.bookmark }
: chapter
),
};
case CHAPTER_DOWNLOADING:
return {
...state,
downloading: [...state.downloading, payload],
};
case CHAPTER_DOWNLOADED:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId === payload
? { ...chapter, downloaded: 1 }
: chapter
),
downloading: state.downloading.filter(
(chapterId) => chapterId !== payload
),
};
case CHAPTER_DELETED:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId === payload
? { ...chapter, downloaded: 0 }
: chapter
),
};
case UPDATE_LAST_READ:
return {
...state,
novel: { ...state.novel, lastRead: payload },
};
switch (type) {
case LOADING_NOVEL:
return {...state, loading: true};
case FETCHING_NOVEL:
return {...state, updating: true};
case SET_NOVEL:
return {...state, novel: payload};
case GET_NOVEL:
return {
...state,
novel: payload,
chapters: payload.chapters,
loading: false,
updating: false,
};
case GET_CHAPTERS:
return {
...state,
chapters: payload,
loading: false,
updating: false,
};
case UPDATE_IN_LIBRARY:
return {
...state,
novel: {...state.novel, followed: payload.followed},
};
case UPDATE_NOVEL:
return {
...state,
novel: payload.novel,
chapters: payload.chapters,
loading: false,
updating: false,
};
case CHAPTER_READ:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId === payload.chapterId
? {...chapter, read: 1}
: chapter,
),
};
case CHAPTER_UNREAD:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId === payload.chapterId
? {...chapter, read: 0}
: chapter,
),
};
case BOOKMARK_CHAPTER:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId === payload.chapterId
? {...chapter, bookmark: !payload.bookmark}
: chapter,
),
};
case CHAPTER_DOWNLOADING:
return {
...state,
downloading: [...state.downloading, payload],
};
case CHAPTER_DOWNLOADED:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId === payload ? {...chapter, downloaded: 1} : chapter,
),
downloading: state.downloading.filter(
chapterId => chapterId !== payload,
),
};
case CHAPTER_DELETED:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId === payload ? {...chapter, downloaded: 0} : chapter,
),
};
case UPDATE_LAST_READ:
return {
...state,
novel: {...state.novel, lastRead: payload},
};
case MARK_PREVIOUS_CHAPTERS_READ:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId < payload
? { ...chapter, read: 1 }
: chapter
),
};
case MARK_PREVIOUS_CHAPTERS_UNREAD:
return {
...state,
chapters: state.chapters.map((chapter) =>
chapter.chapterId < payload
? { ...chapter, read: 0 }
: chapter
),
};
case NOVEL_ERROR:
return {
...state,
novel: {},
chapters: [],
loading: false,
updating: false,
};
default:
return state;
}
case MARK_PREVIOUS_CHAPTERS_READ:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId < payload ? {...chapter, read: 1} : chapter,
),
};
case MARK_PREVIOUS_CHAPTERS_UNREAD:
return {
...state,
chapters: state.chapters.map(chapter =>
chapter.chapterId < payload ? {...chapter, read: 0} : chapter,
),
};
case NOVEL_ERROR:
return {
...state,
novel: {},
chapters: [],
loading: false,
updating: false,
};
default:
return state;
}
};
export default novelReducer;
+20 -20
View File
@@ -1,23 +1,23 @@
export const LOADING_NOVEL = "LOADING_NOVEL";
export const FETCHING_NOVEL = "FETCHING_NOVEL";
export const LOADING_CHAPTERS = "LOADING_CHAPTERS";
export const GET_NOVEL = "GET_NOVEL";
export const SET_NOVEL = "SET_NOVEL";
export const UPDATE_NOVEL = "UPDATE_NOVEL";
export const UPDATE_IN_LIBRARY = "UPDATE_IN_LIBRARY";
export const GET_CHAPTERS = "GET_CHAPTERS";
export const UPDATE_LAST_READ = "UPDATE_LAST_READ";
export const LOADING_NOVEL = 'LOADING_NOVEL';
export const FETCHING_NOVEL = 'FETCHING_NOVEL';
export const LOADING_CHAPTERS = 'LOADING_CHAPTERS';
export const GET_NOVEL = 'GET_NOVEL';
export const SET_NOVEL = 'SET_NOVEL';
export const UPDATE_NOVEL = 'UPDATE_NOVEL';
export const UPDATE_IN_LIBRARY = 'UPDATE_IN_LIBRARY';
export const GET_CHAPTERS = 'GET_CHAPTERS';
export const UPDATE_LAST_READ = 'UPDATE_LAST_READ';
export const CHAPTER_DOWNLOADED = "CHAPTER_DOWNLOADED";
export const CHAPTER_DELETED = "CHAPTER_DELETED";
export const CHAPTER_DOWNLOADING = "CHAPTER_DOWNLOADING";
export const CHAPTER_READ = "CHAPTER_READ";
export const CHAPTER_UNREAD = "CHAPTER_UNREAD";
export const GET_CHAPTER = "GET_CHAPTER";
export const NOVEL_ERROR = "NOVEL_ERROR";
export const BOOKMARK_CHAPTER = "BOOKMARK_CHAPTER";
export const CHAPTER_DOWNLOADED = 'CHAPTER_DOWNLOADED';
export const CHAPTER_DELETED = 'CHAPTER_DELETED';
export const CHAPTER_DOWNLOADING = 'CHAPTER_DOWNLOADING';
export const CHAPTER_READ = 'CHAPTER_READ';
export const CHAPTER_UNREAD = 'CHAPTER_UNREAD';
export const GET_CHAPTER = 'GET_CHAPTER';
export const NOVEL_ERROR = 'NOVEL_ERROR';
export const BOOKMARK_CHAPTER = 'BOOKMARK_CHAPTER';
export const SET_NOVEL_SETTINGS = "SET_NOVEL_SETTINGS";
export const SET_NOVEL_SETTINGS = 'SET_NOVEL_SETTINGS';
export const MARK_PREVIOUS_CHAPTERS_READ = "MARK_PREVIOUS_CHAPTERS_READ";
export const MARK_PREVIOUS_CHAPTERS_UNREAD = "MARK_PREVIOUS_CHAPTERS_UNREAD";
export const MARK_PREVIOUS_CHAPTERS_READ = 'MARK_PREVIOUS_CHAPTERS_READ';
export const MARK_PREVIOUS_CHAPTERS_UNREAD = 'MARK_PREVIOUS_CHAPTERS_UNREAD';
+12 -12
View File
@@ -1,14 +1,14 @@
import { SAVE_SCROLL_POSITION } from "../preferences/preference.types";
import {SAVE_SCROLL_POSITION} from '../preferences/preference.types';
export const saveScrollPosition =
(position, percentage, chapterId, novelId) => async (dispatch) => {
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position,
percentage,
chapterId,
novelId,
},
});
};
(position, percentage, chapterId, novelId) => async dispatch => {
dispatch({
type: SAVE_SCROLL_POSITION,
payload: {
position,
percentage,
chapterId,
novelId,
},
});
};
+62 -62
View File
@@ -1,72 +1,72 @@
import { SET_NOVEL_SETTINGS } from "../novel/novel.types";
import {SET_NOVEL_SETTINGS} from '../novel/novel.types';
import {
SAVE_SCROLL_POSITION,
SET_CHAPTER_LIST_PREF,
SET_LAST_READ,
} from "./preference.types";
SAVE_SCROLL_POSITION,
SET_CHAPTER_LIST_PREF,
SET_LAST_READ,
} from './preference.types';
const initialState = {
novelSettings: {},
novelSettings: {},
};
const novelReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case SET_LAST_READ:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
lastRead: payload.chapterId,
},
},
};
case SET_CHAPTER_LIST_PREF:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
sort: payload.sort,
filter: payload.filter,
},
},
};
case SET_NOVEL_SETTINGS:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
showChapterTitles: payload.value,
},
},
};
case SAVE_SCROLL_POSITION:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
position: {
...state.novelSettings[payload.novelId].position,
[payload.chapterId]: {
position: payload.position,
percentage: payload.percentage,
},
},
},
},
};
default:
return state;
}
switch (type) {
case SET_LAST_READ:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
lastRead: payload.chapterId,
},
},
};
case SET_CHAPTER_LIST_PREF:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
sort: payload.sort,
filter: payload.filter,
},
},
};
case SET_NOVEL_SETTINGS:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
showChapterTitles: payload.value,
},
},
};
case SAVE_SCROLL_POSITION:
return {
...state,
novelSettings: {
...state.novelSettings,
[payload.novelId]: {
...state.novelSettings[payload.novelId],
position: {
...state.novelSettings[payload.novelId].position,
[payload.chapterId]: {
position: payload.position,
percentage: payload.percentage,
},
},
},
},
};
default:
return state;
}
};
export default novelReducer;
+3 -3
View File
@@ -1,3 +1,3 @@
export const SET_CHAPTER_LIST_PREF = "SET_CHAPTER_LIST_PREF";
export const SET_LAST_READ = "SET_LAST_READ";
export const SAVE_SCROLL_POSITION = "SAVE_SCROLL_POSITION";
export const SET_CHAPTER_LIST_PREF = 'SET_CHAPTER_LIST_PREF';
export const SET_LAST_READ = 'SET_LAST_READ';
export const SAVE_SCROLL_POSITION = 'SAVE_SCROLL_POSITION';
+43 -43
View File
@@ -1,58 +1,58 @@
import {
SET_APP_THEME,
SET_NOVELS_PER_ROW,
SET_READER_SETTINGS,
SET_APP_SETTINGS,
SET_ACCENT_COLOR,
SET_RIPPLE_COLOR,
SET_AMOLED_MODE,
} from "./settings.types";
SET_APP_THEME,
SET_NOVELS_PER_ROW,
SET_READER_SETTINGS,
SET_APP_SETTINGS,
SET_ACCENT_COLOR,
SET_RIPPLE_COLOR,
SET_AMOLED_MODE,
} from './settings.types';
export const setAppTheme = (val) => (dispatch) => {
dispatch({
type: SET_APP_THEME,
payload: val,
});
export const setAppTheme = val => dispatch => {
dispatch({
type: SET_APP_THEME,
payload: val,
});
};
export const setNovelsPerRow = (val) => (dispatch) => {
dispatch({
type: SET_NOVELS_PER_ROW,
payload: val,
});
export const setNovelsPerRow = val => dispatch => {
dispatch({
type: SET_NOVELS_PER_ROW,
payload: val,
});
};
export const setAppSettings = (key, val) => (dispatch) => {
dispatch({
type: SET_APP_SETTINGS,
payload: { key, val },
});
export const setAppSettings = (key, val) => dispatch => {
dispatch({
type: SET_APP_SETTINGS,
payload: {key, val},
});
};
export const setReaderSettings = (key, val) => (dispatch) => {
dispatch({
type: SET_READER_SETTINGS,
payload: { key, val },
});
export const setReaderSettings = (key, val) => dispatch => {
dispatch({
type: SET_READER_SETTINGS,
payload: {key, val},
});
};
export const setAccentColor = (val) => (dispatch) => {
dispatch({
type: SET_ACCENT_COLOR,
payload: val,
});
export const setAccentColor = val => dispatch => {
dispatch({
type: SET_ACCENT_COLOR,
payload: val,
});
};
export const setRippleColor = (val) => (dispatch) => {
dispatch({
type: SET_RIPPLE_COLOR,
payload: val,
});
export const setRippleColor = val => dispatch => {
dispatch({
type: SET_RIPPLE_COLOR,
payload: val,
});
};
export const setAmoledMode = (id, val) => (dispatch) => {
dispatch({
type: SET_AMOLED_MODE,
payload: { id, val },
});
export const setAmoledMode = (id, val) => dispatch => {
dispatch({
type: SET_AMOLED_MODE,
payload: {id, val},
});
};
+166 -112
View File
@@ -1,37 +1,45 @@
import {
SET_APP_THEME,
SET_NOVELS_PER_ROW,
SET_READER_SETTINGS,
SET_APP_SETTINGS,
SET_ACCENT_COLOR,
SET_RIPPLE_COLOR,
SET_AMOLED_MODE,
} from "./settings.types";
SET_APP_THEME,
SET_NOVELS_PER_ROW,
SET_READER_SETTINGS,
SET_APP_SETTINGS,
SET_ACCENT_COLOR,
SET_RIPPLE_COLOR,
SET_AMOLED_MODE,
} from './settings.types';
import {
lightTheme,
darkTheme,
midnightDuskTheme,
greenAppleTheme,
irisBlueTheme,
strawberryDaiquiri,
takoTheme,
yinYangTheme,
springBlossomTheme,
colorsAmoled,
oceanicTheme,
} from "../../theme/theme";
lightTheme,
darkTheme,
midnightDuskTheme,
greenAppleTheme,
irisBlueTheme,
strawberryDaiquiri,
takoTheme,
yinYangTheme,
springBlossomTheme,
colorsAmoled,
oceanicTheme,
yangTheme,
yotsubaTheme,
takoLightTheme,
tealTheme,
} from '../../theme/theme';
const themes = {
1: lightTheme,
2: darkTheme,
3: midnightDuskTheme,
4: greenAppleTheme,
5: irisBlueTheme,
6: oceanicTheme,
7: strawberryDaiquiri,
8: takoTheme,
9: yinYangTheme,
10: springBlossomTheme,
1: lightTheme,
2: darkTheme,
3: midnightDuskTheme,
4: greenAppleTheme,
5: irisBlueTheme,
6: oceanicTheme,
7: strawberryDaiquiri,
8: takoTheme,
9: yinYangTheme,
10: springBlossomTheme,
11: yangTheme,
12: yotsubaTheme,
13: takoLightTheme,
14: tealTheme,
};
/**
@@ -40,96 +48,142 @@ const themes = {
* 0 -> Compact
* 1 -> Comfortable
* 2 -> List
* 3 -> No title
*/
const initialState = {
theme: darkTheme,
displayMode: 0,
novelsPerRow: 3,
reader: {
theme: 1,
textColor: "rgba(255,255,255,0.7)",
textSize: 16,
textAlign: "left",
padding: 5,
fontFamily: "",
lineHeight: 1.5,
customCSS: null,
},
showDownloadBadges: true,
showUnreadBadges: true,
showNumberOfNovels: false,
showScrollPercentage: true,
swipeGestures: true,
incognitoMode: false,
searchAllSources: false,
textSelectable: false,
updateLibraryOnLaunch: false,
downloadNewChapters: false,
showHistoryTab: true,
showUpdatesTab: true,
showLabelsInNav: false,
useWebViewForChapter: false,
/**
* General settings
*/
incognitoMode: false,
/**
* Appearence settings
*/
theme: darkTheme,
displayMode: 0,
novelsPerRow: 3,
showHistoryTab: true,
showUpdatesTab: true,
showLabelsInNav: false,
useFabForContinueReading: false,
/**
* Library settings
*/
showDownloadBadges: true,
showUnreadBadges: true,
showNumberOfNovels: false,
downloadedOnlyMode: false,
/**
* Browse settings
*/
searchAllSources: false,
onlyShowPinnedSources: false,
/**
* Update settings
*/
onlyUpdateOngoingNovels: false,
updateLibraryOnLaunch: false,
downloadNewChapters: false,
/**
* Novel settings
*/
hideBackdrop: false,
/**
* Reader settings
*/
fullScreenMode: true,
swipeGestures: true,
showScrollPercentage: true,
useWebViewForChapter: false,
showBatteryAndTime: false,
autoScroll: false,
autoScrollInterval: 10,
textSelectable: false,
verticalSeekbar: true,
reader: {
theme: 1,
textColor: 'rgba(255,255,255,0.7)',
textSize: 16,
textAlign: 'left',
padding: 5,
fontFamily: '',
lineHeight: 1.5,
customCSS: null,
},
};
const settingsReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case SET_APP_THEME:
return { ...state, theme: themes[payload] };
case SET_NOVELS_PER_ROW:
return {
...state,
novelsPerRow: state.displayMode !== 2 ? payload : 1,
};
case SET_ACCENT_COLOR:
return {
...state,
theme: {
...state.theme,
colorAccent: payload,
},
};
case SET_RIPPLE_COLOR:
return {
...state,
theme: {
...state.theme,
rippleColor: payload,
},
};
case SET_AMOLED_MODE:
return {
...state,
theme: payload.val
? {
...colorsAmoled,
colorAccent: state.theme.colorAccent,
rippleColor: state.theme.rippleColor,
colorButtonText: state.theme.colorButtonText,
id: state.theme.id,
name: state.theme.name,
}
: themes[payload.id],
};
switch (type) {
case SET_APP_THEME:
return {...state, theme: themes[payload]};
case SET_NOVELS_PER_ROW:
return {
...state,
novelsPerRow: state.displayMode !== 2 ? payload : 1,
};
case SET_ACCENT_COLOR:
return {
...state,
theme: {
...state.theme,
colorAccent: payload,
},
};
case SET_RIPPLE_COLOR:
return {
...state,
theme: {
...state.theme,
rippleColor: payload,
},
};
case SET_AMOLED_MODE:
return {
...state,
theme: payload.val
? {
...colorsAmoled,
colorAccent: state.theme.colorAccent,
rippleColor: state.theme.rippleColor,
colorButtonText: state.theme.colorButtonText,
id: state.theme.id,
name: state.theme.name,
}
: themes[payload.id],
};
case SET_APP_SETTINGS:
return {
...state,
[payload.key]: payload.val,
};
case SET_READER_SETTINGS:
return {
...state,
reader: {
...state.reader,
[payload.key]: payload.val,
},
};
default:
return state;
}
case SET_APP_SETTINGS:
return {
...state,
[payload.key]: payload.val,
};
case SET_READER_SETTINGS:
return {
...state,
reader: {
...state.reader,
[payload.key]: payload.val,
},
};
default:
return state;
}
};
export default settingsReducer;
+7 -7
View File
@@ -1,7 +1,7 @@
export const SET_APP_SETTINGS = "SET_APP_SETTINGS";
export const SET_APP_THEME = "SET_APP_THEME";
export const SET_NOVELS_PER_ROW = "SET_NOVELS_PER_ROW";
export const SET_READER_SETTINGS = "SET_READER_SETTINGS";
export const SET_ACCENT_COLOR = "SET_ACCENT_COLOR";
export const SET_RIPPLE_COLOR = "SET_RIPPLE_COLOR";
export const SET_AMOLED_MODE = "SET_AMOLED_MODE";
export const SET_APP_SETTINGS = 'SET_APP_SETTINGS';
export const SET_APP_THEME = 'SET_APP_THEME';
export const SET_NOVELS_PER_ROW = 'SET_NOVELS_PER_ROW';
export const SET_READER_SETTINGS = 'SET_READER_SETTINGS';
export const SET_ACCENT_COLOR = 'SET_ACCENT_COLOR';
export const SET_RIPPLE_COLOR = 'SET_RIPPLE_COLOR';
export const SET_AMOLED_MODE = 'SET_AMOLED_MODE';
+45 -45
View File
@@ -1,57 +1,57 @@
import {
ENABLE_DISCOVER,
FILTER_LANGUAGE,
GET_SOURCES,
PIN_SOURCES,
SEARCH_SOURCES,
} from "./source.types";
import { showToast } from "../../hooks/showToast";
import sources from "../../sources/sources.json";
ENABLE_DISCOVER,
FILTER_LANGUAGE,
GET_SOURCES,
PIN_SOURCES,
SEARCH_SOURCES,
} from './source.types';
import {showToast} from '../../hooks/showToast';
import sources from '../../sources/sources.json';
export const getSourcesAction = () => async (dispatch) => {
try {
/**
* Sort sources aplhabetically
*/
let sortedSources = sources.sort((a, b) =>
a.sourceName.localeCompare(b.sourceName)
);
dispatch({
type: GET_SOURCES,
payload: sortedSources,
});
} catch (error) {
showToast(error.message);
}
};
export const searchSourcesAction = (searchText) => async (dispatch) => {
const res = searchText.toLowerCase();
export const getSourcesAction = () => async dispatch => {
try {
/**
* Sort sources aplhabetically
*/
let sortedSources = sources.sort((a, b) =>
a.sourceName.localeCompare(b.sourceName),
);
dispatch({
type: SEARCH_SOURCES,
payload: res,
type: GET_SOURCES,
payload: sortedSources,
});
} catch (error) {
showToast(error.message);
}
};
export const pinSourceAction = (sourceId) => async (dispatch) => {
dispatch({
type: PIN_SOURCES,
payload: sourceId,
});
export const searchSourcesAction = searchText => async dispatch => {
const res = searchText.toLowerCase();
dispatch({
type: SEARCH_SOURCES,
payload: res,
});
};
export const filterLanguage = (language) => async (dispatch) => {
dispatch({
type: FILTER_LANGUAGE,
payload: language,
});
export const pinSourceAction = sourceId => async dispatch => {
dispatch({
type: PIN_SOURCES,
payload: sourceId,
});
};
export const enableDiscover = (key) => async (dispatch) => {
dispatch({
type: ENABLE_DISCOVER,
payload: key,
});
export const filterLanguage = language => async dispatch => {
dispatch({
type: FILTER_LANGUAGE,
payload: language,
});
};
export const enableDiscover = key => async dispatch => {
dispatch({
type: ENABLE_DISCOVER,
payload: key,
});
};
+52 -54
View File
@@ -1,65 +1,63 @@
import {
GET_SOURCES,
SEARCH_SOURCES,
PIN_SOURCES,
FILTER_LANGUAGE,
ENABLE_DISCOVER,
} from "./source.types";
GET_SOURCES,
SEARCH_SOURCES,
PIN_SOURCES,
FILTER_LANGUAGE,
ENABLE_DISCOVER,
} from './source.types';
const initialState = {
sources: [],
pinnedSources: [],
search: [],
pinned: [],
filters: [],
showMyAnimeList: true,
loading: true,
sources: [],
pinnedSources: [],
search: [],
pinned: [],
filters: [],
showMyAnimeList: true,
loading: true,
};
const sourceReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case GET_SOURCES:
return { ...state, sources: payload, loading: false };
case PIN_SOURCES:
return {
...state,
pinned:
state.pinned.indexOf(payload) === -1
? [...state.pinned, payload]
: state.pinned.filter((source) => source !== payload),
loading: false,
};
case SEARCH_SOURCES:
return {
...state,
search: state.sources.filter((source) =>
source.sourceName.toLowerCase().includes(payload)
),
};
switch (type) {
case GET_SOURCES:
return {...state, sources: payload, loading: false};
case PIN_SOURCES:
return {
...state,
pinned:
state.pinned.indexOf(payload) === -1
? [...state.pinned, payload]
: state.pinned.filter(source => source !== payload),
loading: false,
};
case SEARCH_SOURCES:
return {
...state,
search: state.sources.filter(source =>
source.sourceName.toLowerCase().includes(payload),
),
};
case FILTER_LANGUAGE:
if (!state.filters) {
state.filters = [];
}
return {
...state,
filters:
state.filters.indexOf(payload) === -1
? [...state.filters, payload]
: state.filters.filter(
(language) => language !== payload
),
};
case ENABLE_DISCOVER:
return {
...state,
[payload]: !state[payload],
};
default:
return state;
}
case FILTER_LANGUAGE:
if (!state.filters) {
state.filters = [];
}
return {
...state,
filters:
state.filters.indexOf(payload) === -1
? [...state.filters, payload]
: state.filters.filter(language => language !== payload),
};
case ENABLE_DISCOVER:
return {
...state,
[payload]: !state[payload],
};
default:
return state;
}
};
export default sourceReducer;
+5 -5
View File
@@ -1,5 +1,5 @@
export const GET_SOURCES = "GET_SOURCES";
export const SEARCH_SOURCES = "SEARCH_SOURCES";
export const PIN_SOURCES = "PIN_SOURCES";
export const FILTER_LANGUAGE = "FILTER_LANGUAGE";
export const ENABLE_DISCOVER = "ENABLE_DISCOVER";
export const GET_SOURCES = 'GET_SOURCES';
export const SEARCH_SOURCES = 'SEARCH_SOURCES';
export const PIN_SOURCES = 'PIN_SOURCES';
export const FILTER_LANGUAGE = 'FILTER_LANGUAGE';
export const ENABLE_DISCOVER = 'ENABLE_DISCOVER';
+26 -26
View File
@@ -1,35 +1,35 @@
import { createStore, applyMiddleware, combineReducers } from "redux";
import { persistStore, persistReducer } from "redux-persist";
import AsyncStorage from "@react-native-async-storage/async-storage";
import thunk from "redux-thunk";
import {createStore, applyMiddleware, combineReducers} from 'redux';
import {persistStore, persistReducer} from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
import thunk from 'redux-thunk';
import settingsReducer from "./settings/settings.reducer";
import libraryReducer from "./library/library.reducer";
import sourceReducer from "./source/source.reducers";
import novelReducer from "./novel/novel.reducer";
import updatesReducer from "./updates/updates.reducer";
import trackerReducer from "./tracker/tracker.reducer";
import preferenceReducer from "./preferences/preference.reducer";
import downloadsReducer from "./downloads/downloads.reducer";
import settingsReducer from './settings/settings.reducer';
import libraryReducer from './library/library.reducer';
import sourceReducer from './source/source.reducers';
import novelReducer from './novel/novel.reducer';
import updatesReducer from './updates/updates.reducer';
import trackerReducer from './tracker/tracker.reducer';
import preferenceReducer from './preferences/preference.reducer';
import downloadsReducer from './downloads/downloads.reducer';
const persistConfig = {
key: "root",
storage: AsyncStorage,
blacklist: ["novelReducer"],
key: 'root',
storage: AsyncStorage,
blacklist: ['novelReducer'],
};
const persistedReducer = persistReducer(
persistConfig,
combineReducers({
settingsReducer,
libraryReducer,
sourceReducer,
novelReducer,
updatesReducer,
trackerReducer,
preferenceReducer,
downloadsReducer,
})
persistConfig,
combineReducers({
settingsReducer,
libraryReducer,
sourceReducer,
novelReducer,
updatesReducer,
trackerReducer,
preferenceReducer,
downloadsReducer,
}),
);
export const store = createStore(persistedReducer, applyMiddleware(thunk));
+39 -39
View File
@@ -1,56 +1,56 @@
import { findListItem, updateItem } from "../../services/Trackers/myAnimeList";
import {findListItem, updateItem} from '../../services/Trackers/myAnimeList';
import {
REMOVE_TRACKER,
SET_TRACKER,
TRACK_NOVEL,
UNTRACK_NOVEL,
UPDATE_TRACKER,
UPDATE_CHAPTERS_READ,
} from "./tracker.types";
REMOVE_TRACKER,
SET_TRACKER,
TRACK_NOVEL,
UNTRACK_NOVEL,
UPDATE_TRACKER,
UPDATE_CHAPTERS_READ,
} from './tracker.types';
import { updateMalChaptersRead } from "../../services/Trackers/myAnimeList";
import {updateMalChaptersRead} from '../../services/Trackers/myAnimeList';
export const setTracker = (res) => async (dispatch) => {
dispatch({
type: SET_TRACKER,
payload: res,
});
export const setTracker = res => async dispatch => {
dispatch({
type: SET_TRACKER,
payload: res,
});
};
export const removeTracker = () => async (dispatch) => {
dispatch({ type: REMOVE_TRACKER });
export const removeTracker = () => async dispatch => {
dispatch({type: REMOVE_TRACKER});
};
export const trackNovel = (trackObj, accessToken) => async (dispatch) => {
let res = await findListItem(trackObj.id, accessToken);
trackObj.num_chapters = res.num_chapters;
trackObj.my_list_status = {
num_chapters_read: res.my_list_status.num_chapters_read,
score: res.my_list_status.score,
status: res.my_list_status.status,
};
export const trackNovel = (trackObj, accessToken) => async dispatch => {
let res = await findListItem(trackObj.id, accessToken);
trackObj.num_chapters = res.num_chapters;
trackObj.my_list_status = {
num_chapters_read: res.my_list_status.num_chapters_read,
score: res.my_list_status.score,
status: res.my_list_status.status,
};
dispatch({ type: TRACK_NOVEL, payload: trackObj });
dispatch({type: TRACK_NOVEL, payload: trackObj});
};
export const updateTracker = (malId, accessToken, body) => async (dispatch) => {
let res = await updateItem(malId, accessToken, body);
export const updateTracker = (malId, accessToken, body) => async dispatch => {
let res = await updateItem(malId, accessToken, body);
dispatch({ type: UPDATE_TRACKER, payload: { malId, ...res } });
dispatch({type: UPDATE_TRACKER, payload: {malId, ...res}});
};
export const untrackNovel = (id) => async (dispatch) => {
dispatch({ type: UNTRACK_NOVEL, payload: id });
export const untrackNovel = id => async dispatch => {
dispatch({type: UNTRACK_NOVEL, payload: id});
};
export const updateChaptersRead =
(malId, accessToken, chaptersRead) => async (dispatch) => {
let res = await updateMalChaptersRead(malId, accessToken, {
num_chapters_read: chaptersRead,
});
(malId, accessToken, chaptersRead) => async dispatch => {
let res = await updateMalChaptersRead(malId, accessToken, {
num_chapters_read: chaptersRead,
});
dispatch({
type: UPDATE_CHAPTERS_READ,
payload: { malId, chaptersRead: res },
});
};
dispatch({
type: UPDATE_CHAPTERS_READ,
payload: {malId, chaptersRead: res},
});
};
+69 -71
View File
@@ -1,81 +1,79 @@
import {
REMOVE_TRACKER,
SET_TRACKER,
TRACK_NOVEL,
UNTRACK_NOVEL,
UPDATE_CHAPTERS_READ,
UPDATE_TRACKER,
} from "./tracker.types";
REMOVE_TRACKER,
SET_TRACKER,
TRACK_NOVEL,
UNTRACK_NOVEL,
UPDATE_CHAPTERS_READ,
UPDATE_TRACKER,
} from './tracker.types';
const initialState = {
tracker: null,
trackedNovels: [],
tracker: null,
trackedNovels: [],
};
const trackerReducer = (state = initialState, action) => {
const { type, payload } = action;
const {type, payload} = action;
switch (type) {
case SET_TRACKER:
return {
...state,
tracker: { ...payload, name: "MyAnimeList" },
};
case REMOVE_TRACKER:
return {
...state,
tracker: null,
};
case TRACK_NOVEL:
return {
...state,
trackedNovels: state.trackedNovels.some(
(obj) => obj.novelId === payload.novelId
)
? state.trackedNovels
: [...state.trackedNovels, payload],
};
case UPDATE_TRACKER:
return {
...state,
trackedNovels: state.trackedNovels.map((novel) =>
novel.id === payload.malId
? {
...novel,
my_list_status: {
num_chapters_read: payload.num_chapters_read,
status: payload.status,
score: payload.score,
},
}
: novel
),
};
case UNTRACK_NOVEL:
return {
...state,
trackedNovels: state.trackedNovels.filter(
(id) => id === payload
),
};
case UPDATE_CHAPTERS_READ:
return {
...state,
trackedNovels: state.trackedNovels.map((novel) =>
novel.id === payload.malId
? {
...novel,
my_list_status: {
...novel.my_list_status,
num_chapters_read: payload.chaptersRead,
},
}
: novel
),
};
default:
return state;
}
switch (type) {
case SET_TRACKER:
return {
...state,
tracker: {...payload, name: 'MyAnimeList'},
};
case REMOVE_TRACKER:
return {
...state,
tracker: null,
};
case TRACK_NOVEL:
return {
...state,
trackedNovels: state.trackedNovels.some(
obj => obj.novelId === payload.novelId,
)
? state.trackedNovels
: [...state.trackedNovels, payload],
};
case UPDATE_TRACKER:
return {
...state,
trackedNovels: state.trackedNovels.map(novel =>
novel.id === payload.malId
? {
...novel,
my_list_status: {
num_chapters_read: payload.num_chapters_read,
status: payload.status,
score: payload.score,
},
}
: novel,
),
};
case UNTRACK_NOVEL:
return {
...state,
trackedNovels: state.trackedNovels.filter(id => id === payload),
};
case UPDATE_CHAPTERS_READ:
return {
...state,
trackedNovels: state.trackedNovels.map(novel =>
novel.id === payload.malId
? {
...novel,
my_list_status: {
...novel.my_list_status,
num_chapters_read: payload.chaptersRead,
},
}
: novel,
),
};
default:
return state;
}
};
export default trackerReducer;

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