Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e514876170 | |||
| 911d25bb84 | |||
| b6d2e43d3d | |||
| 7375eb7ad0 | |||
| 627592d477 | |||
| dbe2d13b80 | |||
| 97e172a2aa | |||
| ba083d1afe | |||
| 9e614c6753 | |||
| 5054e60195 | |||
| 2aa36393a6 | |||
| c77b166fec | |||
| 73276bd05f | |||
| 7f47661468 | |||
| ec63de8115 | |||
| ee277bfbf1 | |||
| e330eb122f | |||
| d84935a6a6 | |||
| 6d852dc138 | |||
| 4bd5e9b4f5 | |||
| 4fc480d2e9 | |||
| 3760b5ae4e | |||
| f286f9337e | |||
| bca620ea37 | |||
| eb9516adda | |||
| 8dbec40b9d | |||
| bdac5161d2 | |||
| 6eca36c295 | |||
| 5cccc7c556 | |||
| 5350bf92de | |||
| 662610b227 | |||
| 8d53125184 | |||
| a61d08f443 | |||
| d8cd2b184a | |||
| 33ceb60850 | |||
| ef1a9d1f35 | |||
| dabd0c5401 | |||
| 3ae029c77b | |||
| efe96d45ea | |||
| 06ca51a841 | |||
| b947ec2665 | |||
| c262c1e1de | |||
| 19d089fa7a | |||
| 9447d10c79 | |||
| d2e2314a3e | |||
| 9fe74ad137 | |||
| 5877448f9d | |||
| fb5d5cf186 | |||
| 8faac73558 | |||
| 3f9c662c68 | |||
| ecce374abe | |||
| 48a67402ff | |||
| 7c4da7f761 | |||
| 3daa11f642 | |||
| f26292f73a | |||
| 49160b7d98 | |||
| e40af59230 | |||
| 8084cdbffb | |||
| dc9529c322 | |||
| 91168a335b | |||
| a6b3eb9350 | |||
| 0ce4e6b985 | |||
| 6684848a42 | |||
| 87d12cabb3 | |||
| e01fc21a49 | |||
| f0cfd9066b | |||
| c2cc6ea96b | |||
| eeeea6b4e0 | |||
| ddce153b53 | |||
| 38e45ca097 | |||
| a6a60607a7 | |||
| d3e772f0dd | |||
| 095d46681f | |||
| 3e4e967197 | |||
| c5b4e7b902 | |||
| e9421a5fd0 | |||
| afef0d63ce | |||
| e68a5c5f82 | |||
| ff1635b817 | |||
| f1ca221b91 | |||
| 5a1106fc68 | |||
| 3b49a1e516 | |||
| 85acbc8c4a | |||
| 66e0be5e84 | |||
| 2234006272 | |||
| eca00ddcfa | |||
| 2328e55105 | |||
| b8a25b9172 | |||
| 9af8d55985 | |||
| 4777f453a4 | |||
| 4a0195762d | |||
| e8785c2905 | |||
| 1302cbde16 | |||
| 43d0adced8 | |||
| 0c8b30cd72 | |||
| cc86fc7da4 | |||
| 625acdb585 | |||
| 93617e60bd | |||
| d75c607f67 | |||
| dd087ff485 | |||
| 5da3e05bba | |||
| e628c3cfd6 | |||
| deba021384 | |||
| c6d2ba8442 | |||
| 6a3736dced | |||
| f63ae573ba | |||
| 8f3020a544 | |||
| 04e32a4234 |
@@ -0,0 +1,3 @@
|
||||
# Windows files
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native-community',
|
||||
rules: {
|
||||
'react-native/no-inline-styles': 'off',
|
||||
'no-console': 'warn',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
# Windows files should use crlf line endings
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
*.bat text eol=crlf
|
||||
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 48 KiB |
@@ -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
|
||||
@@ -70,4 +70,6 @@ buck-out/
|
||||
.expo/
|
||||
web-build/
|
||||
|
||||
# @end expo-cli
|
||||
# @end expo-cli
|
||||
|
||||
.vscode/
|
||||
@@ -0,0 +1 @@
|
||||
**/node_modules
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
arrowParens: 'avoid',
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 29 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 266 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,4 +1,4 @@
|
||||
module.exports = function(api) {
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import {registerRootComponent} from 'expo';
|
||||
|
||||
import App from './App';
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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({});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import { NativeModules } from "react-native";
|
||||
module.exports = NativeModules.FullScreen;
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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({});
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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.');
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;'));
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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)';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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';
|
||||
|
||||
@@ -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});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||