Add ESLint and Prettier
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# Windows files
|
||||
[*.bat]
|
||||
end_of_line = crlf
|
||||
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native-community',
|
||||
rules: {
|
||||
'react-native/no-inline-styles': 'off',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
# Windows files should use crlf line endings
|
||||
# https://help.github.com/articles/dealing-with-line-endings/
|
||||
*.bat text eol=crlf
|
||||
+45
-45
@@ -1,45 +1,45 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
install-and-test:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install npm dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
build-android:
|
||||
name: Build app
|
||||
needs: install-and-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd android && chmod +x ./gradlew
|
||||
|
||||
- name: Build Android Release
|
||||
run: |
|
||||
cd android && ./gradlew assembleRelease --no-daemon
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
jobs:
|
||||
install-and-test:
|
||||
name: Install dependencies
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install npm dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
build-android:
|
||||
name: Build app
|
||||
needs: install-and-test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd android && chmod +x ./gradlew
|
||||
|
||||
- name: Build Android Release
|
||||
run: |
|
||||
cd android && ./gradlew assembleRelease --no-daemon
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
**/node_modules
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
bracketSpacing: false,
|
||||
singleQuote: true,
|
||||
trailingComma: 'all',
|
||||
arrowParens: 'avoid',
|
||||
};
|
||||
@@ -1,43 +1,40 @@
|
||||
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';
|
||||
|
||||
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}>
|
||||
<PaperProvider>
|
||||
<StatusBar translucent={true} backgroundColor="transparent" />
|
||||
<Main />
|
||||
</PaperProvider>
|
||||
</PersistGate>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/icon_alt.png" align="center" width="128" />
|
||||
<p>
|
||||
<h1 align="center">
|
||||
LNReader
|
||||
</h1>
|
||||
<p align="center">
|
||||
LNReader is a Tachiyomi-like, free and open source light novel reader for Android.
|
||||
</p>
|
||||
<div align="center">
|
||||
<a href="https://github.com/LNReader/lnreader/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/LNReader/lnreader/CI">
|
||||
</a>
|
||||
<a href="https://discord.gg/QdcWN4MD63">
|
||||
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=7289DA&label=Discord&color=17cf48">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/releases/latest">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/screenshots.png" align="center" />
|
||||
</p>
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/rajarsheechatterjee/LNReader/releases).
|
||||
|
||||
## Sources
|
||||
LNReader does not have any affiliation with the content providers available.
|
||||
|
||||
Source requests should be created at [LNReader/lnreader-sources](https://github.com/LNReader/lnreader-sources).
|
||||
|
||||
## License
|
||||
[MIT ](https://github.com/rajarsheechatterjee/lnreader/blob/main/LICENSE)
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/icon_alt.png" align="center" width="128" />
|
||||
<p>
|
||||
<h1 align="center">
|
||||
LNReader
|
||||
</h1>
|
||||
<p align="center">
|
||||
LNReader is a Tachiyomi-like, free and open source light novel reader for Android.
|
||||
</p>
|
||||
<div align="center">
|
||||
<a href="https://github.com/LNReader/lnreader/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/LNReader/lnreader/CI">
|
||||
</a>
|
||||
<a href="https://discord.gg/QdcWN4MD63">
|
||||
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=7289DA&label=Discord&color=17cf48">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/releases/latest">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/screenshots.png" align="center" />
|
||||
</p>
|
||||
|
||||
## Download
|
||||
Get the app from our [releases page](https://github.com/rajarsheechatterjee/LNReader/releases).
|
||||
|
||||
## Sources
|
||||
LNReader does not have any affiliation with the content providers available.
|
||||
|
||||
Source requests should be created at [LNReader/lnreader-sources](https://github.com/LNReader/lnreader-sources).
|
||||
|
||||
## License
|
||||
[MIT ](https://github.com/rajarsheechatterjee/lnreader/blob/main/LICENSE)
|
||||
|
||||
+219
-219
@@ -1,219 +1,219 @@
|
||||
apply plugin: "com.android.application"
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
* and bundleReleaseJsAndAssets).
|
||||
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||
* bundle directly from the development server. Below you can see all the possible configurations
|
||||
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||
*
|
||||
* project.ext.react = [
|
||||
* // the name of the generated asset file containing your JS bundle
|
||||
* bundleAssetName: "index.android.bundle",
|
||||
*
|
||||
* // the entry file for bundle generation. If none specified and
|
||||
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
|
||||
* // default. Can be overridden with ENTRY_FILE environment variable.
|
||||
* entryFile: "index.android.js",
|
||||
*
|
||||
* // https://reactnative.dev/docs/performance#enable-the-ram-format
|
||||
* bundleCommand: "ram-bundle",
|
||||
*
|
||||
* // whether to bundle JS and assets in debug mode
|
||||
* bundleInDebug: false,
|
||||
*
|
||||
* // whether to bundle JS and assets in release mode
|
||||
* bundleInRelease: true,
|
||||
*
|
||||
* // whether to bundle JS and assets in another build variant (if configured).
|
||||
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'bundleIn${productFlavor}${buildType}'
|
||||
* // 'bundleIn${buildType}'
|
||||
* // bundleInFreeDebug: true,
|
||||
* // bundleInPaidRelease: true,
|
||||
* // bundleInBeta: true,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // where to put the JS bundle asset in debug mode
|
||||
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||
*
|
||||
* // where to put the JS bundle asset in release mode
|
||||
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in debug mode
|
||||
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in release mode
|
||||
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||
*
|
||||
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||
* // for example, you might want to remove it from here.
|
||||
* inputExcludes: ["android/**", "ios/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
enableHermes: true
|
||||
]
|
||||
|
||||
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
|
||||
apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
* - An APK that only works on ARM devices
|
||||
* - An APK that only works on x86 devices
|
||||
* The advantage is the size of the APK is reduced by about 4MB.
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore.
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
*
|
||||
* This should be set on project.ext.react and mirrored here. If it is not set
|
||||
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||
*/
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.rajarsheechatterjee.LNReader'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.1.5"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
// For each separate APK per architecture, set a unique version code as described here:
|
||||
// https://developer.android.com/studio/build/configure-apk-splits.html
|
||||
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
}
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
}
|
||||
addUnimodulesDependencies()
|
||||
|
||||
if (enableHermes) {
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/";
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
apply plugin: "com.android.application"
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
* and bundleReleaseJsAndAssets).
|
||||
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||
* bundle directly from the development server. Below you can see all the possible configurations
|
||||
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||
*
|
||||
* project.ext.react = [
|
||||
* // the name of the generated asset file containing your JS bundle
|
||||
* bundleAssetName: "index.android.bundle",
|
||||
*
|
||||
* // the entry file for bundle generation. If none specified and
|
||||
* // "index.android.js" exists, it will be used. Otherwise "index.js" is
|
||||
* // default. Can be overridden with ENTRY_FILE environment variable.
|
||||
* entryFile: "index.android.js",
|
||||
*
|
||||
* // https://reactnative.dev/docs/performance#enable-the-ram-format
|
||||
* bundleCommand: "ram-bundle",
|
||||
*
|
||||
* // whether to bundle JS and assets in debug mode
|
||||
* bundleInDebug: false,
|
||||
*
|
||||
* // whether to bundle JS and assets in release mode
|
||||
* bundleInRelease: true,
|
||||
*
|
||||
* // whether to bundle JS and assets in another build variant (if configured).
|
||||
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'bundleIn${productFlavor}${buildType}'
|
||||
* // 'bundleIn${buildType}'
|
||||
* // bundleInFreeDebug: true,
|
||||
* // bundleInPaidRelease: true,
|
||||
* // bundleInBeta: true,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // where to put the JS bundle asset in debug mode
|
||||
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||
*
|
||||
* // where to put the JS bundle asset in release mode
|
||||
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in debug mode
|
||||
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in release mode
|
||||
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||
*
|
||||
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||
* // for example, you might want to remove it from here.
|
||||
* inputExcludes: ["android/**", "ios/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
enableHermes: true
|
||||
]
|
||||
|
||||
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
|
||||
apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
* - An APK that only works on ARM devices
|
||||
* - An APK that only works on x86 devices
|
||||
* The advantage is the size of the APK is reduced by about 4MB.
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore.
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
|
||||
/**
|
||||
* Whether to enable the Hermes VM.
|
||||
*
|
||||
* This should be set on project.ext.react and mirrored here. If it is not set
|
||||
* on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
|
||||
* and the benefits of using Hermes will therefore be sharply reduced.
|
||||
*/
|
||||
def enableHermes = project.ext.react.get("enableHermes", false);
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.rajarsheechatterjee.LNReader'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.1.5"
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('debug.keystore')
|
||||
storePassword 'android'
|
||||
keyAlias 'androiddebugkey'
|
||||
keyPassword 'android'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
// For each separate APK per architecture, set a unique version code as described here:
|
||||
// https://developer.android.com/studio/build/configure-apk-splits.html
|
||||
def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
//noinspection GradleDynamicVersion
|
||||
implementation "com.facebook.react:react-native:+" // From node_modules
|
||||
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
|
||||
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.fbjni'
|
||||
}
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
exclude group:'com.squareup.okhttp3', module:'okhttp'
|
||||
}
|
||||
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
|
||||
exclude group:'com.facebook.flipper'
|
||||
}
|
||||
addUnimodulesDependencies()
|
||||
|
||||
if (enableHermes) {
|
||||
def hermesPath = "../../node_modules/hermes-engine/android/";
|
||||
debugImplementation files(hermesPath + "hermes-debug.aar")
|
||||
releaseImplementation files(hermesPath + "hermes-release.aar")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
<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 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>
|
||||
@@ -1,71 +1,71 @@
|
||||
package com.rajarsheechatterjee.LNReader;
|
||||
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;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||
|
||||
import expo.modules.splashscreen.singletons.SplashScreen;
|
||||
import expo.modules.splashscreen.SplashScreenImageResizeMode;
|
||||
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
// Added automatically by Expo Config
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Intent intent = new Intent("onConfigurationChanged");
|
||||
intent.putExtra("newConfig", newConfig);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// SplashScreen.show(...) has to be called after super.onCreate(...)
|
||||
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
|
||||
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
getWindow().setAttributes(layoutParams);
|
||||
}
|
||||
|
||||
Window w = getWindow();
|
||||
w.setStatusBarColor(Color.TRANSPARENT);
|
||||
w.setNavigationBarColor(Color.TRANSPARENT);
|
||||
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "main";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
package com.rajarsheechatterjee.LNReader;
|
||||
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;
|
||||
import com.facebook.react.ReactRootView;
|
||||
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
|
||||
|
||||
import expo.modules.splashscreen.singletons.SplashScreen;
|
||||
import expo.modules.splashscreen.SplashScreenImageResizeMode;
|
||||
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
// Added automatically by Expo Config
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
Intent intent = new Intent("onConfigurationChanged");
|
||||
intent.putExtra("newConfig", newConfig);
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// SplashScreen.show(...) has to be called after super.onCreate(...)
|
||||
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
|
||||
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
getWindow().setAttributes(layoutParams);
|
||||
}
|
||||
|
||||
Window w = getWindow();
|
||||
w.setStatusBarColor(Color.TRANSPARENT);
|
||||
w.setNavigationBarColor(Color.TRANSPARENT);
|
||||
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "main";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReactActivityDelegate createReactActivityDelegate() {
|
||||
return new ReactActivityDelegate(this, getMainComponentName()) {
|
||||
@Override
|
||||
protected ReactRootView createRootView() {
|
||||
return new RNGestureHandlerEnabledRootView(MainActivity.this);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+183
-183
@@ -1,183 +1,183 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
-1
@@ -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);
|
||||
|
||||
Generated
+22879
-19810
File diff suppressed because it is too large
Load Diff
+66
-74
@@ -1,74 +1,66 @@
|
||||
{
|
||||
"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-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",
|
||||
"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-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-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"
|
||||
},
|
||||
"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/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",
|
||||
"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-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-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.5"
|
||||
}
|
||||
|
||||
@@ -1,47 +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 = ({ active, actions, theme, style }) => {
|
||||
if (active) {
|
||||
return (
|
||||
<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;
|
||||
}
|
||||
export const Actionbar = ({active, actions, theme, style}) => {
|
||||
if (active) {
|
||||
return (
|
||||
<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,
|
||||
},
|
||||
});
|
||||
|
||||
+17
-19
@@ -1,24 +1,22 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
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';
|
||||
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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: `${theme.textColorPrimary}66`,
|
||||
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,85 +1,85 @@
|
||||
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 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 },
|
||||
]}
|
||||
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}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={styles.pressable}
|
||||
onPress={onPress}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: status ? theme.colorAccent : theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{status && (
|
||||
<MaterialCommunityIcons
|
||||
name="check"
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: status ? theme.colorAccent : theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{status && (
|
||||
<MaterialCommunityIcons
|
||||
name="check"
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
export const SortItem = ({ label, status, onPress, theme }) => (
|
||||
<View
|
||||
style={[
|
||||
styles.checkboxContainer,
|
||||
status && { backgroundColor: theme.rippleColor },
|
||||
]}
|
||||
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}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={styles.pressable}
|
||||
onPress={onPress}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: status ? theme.colorAccent : theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{status && (
|
||||
<MaterialCommunityIcons
|
||||
name={status === "asc" ? "arrow-up" : "arrow-down"}
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: status ? theme.colorAccent : theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
{status && (
|
||||
<MaterialCommunityIcons
|
||||
name={status === 'asc' ? 'arrow-up' : 'arrow-down'}
|
||||
color={theme.colorAccent}
|
||||
size={21}
|
||||
style={styles.icon}
|
||||
/>
|
||||
)}
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
|
||||
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",
|
||||
},
|
||||
checkboxContainer: {
|
||||
borderRadius: 8,
|
||||
overflow: 'hidden',
|
||||
marginVertical: 4,
|
||||
},
|
||||
pressable: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
icon: {
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
+130
-135
@@ -1,153 +1,148 @@
|
||||
import React, { useState } from "react";
|
||||
import { FlatList, Pressable, StyleSheet, Text, View } 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 = text => setText(text);
|
||||
|
||||
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',
|
||||
},
|
||||
};
|
||||
|
||||
const accentColors = [
|
||||
"#EF5350",
|
||||
"#EC407A",
|
||||
"#AB47BC",
|
||||
"#7E57C2",
|
||||
"#5C6BC0",
|
||||
"#42A5F5",
|
||||
"#29B6FC",
|
||||
"#26C6DA",
|
||||
"#26A69A",
|
||||
"#66BB6A",
|
||||
"#9CCC65",
|
||||
"#D4E157",
|
||||
"#FFEE58",
|
||||
"#FFCA28",
|
||||
"#FFA726",
|
||||
"#FF7043",
|
||||
"#8D6E63",
|
||||
"#BDBDBD",
|
||||
"#78909C",
|
||||
"#000000",
|
||||
];
|
||||
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();
|
||||
}}
|
||||
/>
|
||||
</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}
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
+19
-19
@@ -1,27 +1,27 @@
|
||||
import React from "react";
|
||||
import { View, StyleSheet } from "react-native";
|
||||
import React from 'react';
|
||||
import {View, StyleSheet} from 'react-native';
|
||||
|
||||
const ScreenContainer = ({ children, theme }) => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
paddingBottom: 16,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</View>
|
||||
const ScreenContainer = ({children, theme}) => (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
paddingBottom: 16,
|
||||
}}
|
||||
>
|
||||
{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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,110 +1,108 @@
|
||||
import PropTypes from "prop-types";
|
||||
import React, { PureComponent, Children } from "react";
|
||||
import { View, Animated, StyleSheet } from "react-native";
|
||||
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,
|
||||
static defaultProps = {
|
||||
animationDuration: 225,
|
||||
|
||||
active: false,
|
||||
removeHiddenSubviews: true,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
animationDuration: PropTypes.number,
|
||||
componentDidMount() {
|
||||
this.mounted = true;
|
||||
}
|
||||
|
||||
active: PropTypes.bool,
|
||||
removeHiddenSubviews: PropTypes.bool,
|
||||
componentWillUnmount() {
|
||||
this.mounted = false;
|
||||
}
|
||||
|
||||
children: PropTypes.oneOfType([
|
||||
PropTypes.arrayOf(PropTypes.node),
|
||||
PropTypes.node,
|
||||
]),
|
||||
};
|
||||
UNSAFE_componentWillReceiveProps({active, animationDuration}) {
|
||||
let {progress} = this.state;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
if (active ^ this.props.active) {
|
||||
this.setState({animating: true});
|
||||
|
||||
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 });
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
renderChild(child, index) {
|
||||
let { active, removeHiddenSubviews } = this.props;
|
||||
let { animating, progress } = this.state;
|
||||
let pointerEvents = hidden ? 'none' : 'box-none';
|
||||
|
||||
let opacity = progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: index ? [0, 1] : [1, 0],
|
||||
});
|
||||
return (
|
||||
<Animated.View
|
||||
style={[styles.container, {opacity}]}
|
||||
pointerEvents={pointerEvents}
|
||||
>
|
||||
{child}
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
let hidden = active ^ !!index;
|
||||
render() {
|
||||
let {children, ...props} = this.props;
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
return <View {...props}>{Children.map(children, this.renderChild)}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
// ...StyleSheet.absoluteFillObject,
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
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>
|
||||
);
|
||||
|
||||
+37
-43
@@ -1,51 +1,45 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
import React from 'react';
|
||||
import {StyleSheet, Text, View} from 'react-native';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
const EmptyView = ({ icon, description, style, children, iconStyle }) => {
|
||||
const theme = useSelector((state) => state.settingsReducer.theme);
|
||||
const EmptyView = ({icon, description, style, children, iconStyle}) => {
|
||||
const theme = useSelector(state => state.settingsReducer.theme);
|
||||
|
||||
return (
|
||||
<View style={styles.emptyViewContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewIcon,
|
||||
{ color: theme.textColorHint },
|
||||
style,
|
||||
iconStyle,
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewText,
|
||||
{ color: theme.textColorHint },
|
||||
style,
|
||||
]}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View style={styles.emptyViewContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewIcon,
|
||||
{color: theme.textColorHint},
|
||||
style,
|
||||
iconStyle,
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
</Text>
|
||||
<Text style={[styles.emptyViewText, {color: theme.textColorHint}, style]}>
|
||||
{description}
|
||||
</Text>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyView;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
emptyViewContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
emptyViewIcon: {
|
||||
fontSize: 45,
|
||||
},
|
||||
emptyViewText: {
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
textAlign: "center",
|
||||
paddingHorizontal: 30,
|
||||
},
|
||||
emptyViewContainer: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
emptyViewIcon: {
|
||||
fontSize: 45,
|
||||
},
|
||||
emptyViewText: {
|
||||
fontWeight: 'bold',
|
||||
marginTop: 10,
|
||||
textAlign: 'center',
|
||||
paddingHorizontal: 30,
|
||||
},
|
||||
});
|
||||
|
||||
+22
-22
@@ -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 +1,2 @@
|
||||
import { NativeModules } from "react-native";
|
||||
import {NativeModules} from 'react-native';
|
||||
module.exports = NativeModules.FullScreen;
|
||||
|
||||
+114
-122
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
+67
-70
@@ -1,80 +1,77 @@
|
||||
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';
|
||||
|
||||
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>
|
||||
);
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
+273
-283
@@ -1,322 +1,312 @@
|
||||
import React, { useCallback } from "react";
|
||||
import React, {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 {getDeviceOrientation} from '../services/utils/helpers';
|
||||
import {useSettings} from '../hooks/reduxHooks';
|
||||
|
||||
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 = getDeviceOrientation();
|
||||
|
||||
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),
|
||||
[],
|
||||
);
|
||||
|
||||
const selectNovel = () => onLongPress && onLongPress(item.novelId);
|
||||
const selectNovel = () => onLongPress && onLongPress(item.novelId);
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View
|
||||
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={[
|
||||
{
|
||||
flex: 1 / getNovelsPerRow(),
|
||||
borderRadius: 6,
|
||||
overflow: "hidden",
|
||||
margin: 2,
|
||||
},
|
||||
isSelected && {
|
||||
backgroundColor: theme.colorAccent,
|
||||
opacity: 0.8,
|
||||
},
|
||||
{height: getHeight(), borderRadius: 4},
|
||||
libraryStatus && {opacity: 0.5},
|
||||
]}
|
||||
>
|
||||
<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>
|
||||
</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} />}
|
||||
/>
|
||||
<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}
|
||||
onPress={
|
||||
selectedNovels && selectedNovels.length > 0
|
||||
? selectNovel
|
||||
: onPress
|
||||
}
|
||||
onLongPress={selectNovel}
|
||||
isSelected={isSelected}
|
||||
/>
|
||||
);
|
||||
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;
|
||||
|
||||
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)",
|
||||
textShadowColor: "rgba(0, 0, 0, 0.75)",
|
||||
textShadowOffset: { width: -1, height: 1 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{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,
|
||||
},
|
||||
linearGradient: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
padding: 4.8,
|
||||
borderRadius: 4,
|
||||
flex: 1,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 4,
|
||||
},
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'pt-sans-bold',
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
linearGradient: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
padding: 4.8,
|
||||
borderRadius: 4,
|
||||
flex: 1,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderRadius: 4,
|
||||
},
|
||||
|
||||
downloadBadge: {
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
unreadBadge: {
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
inLibraryBadge: {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
compactTitleContainer: {
|
||||
position: "absolute",
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
badgeContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
top: 8,
|
||||
left: 8,
|
||||
flexDirection: "row",
|
||||
},
|
||||
downloadBadge: {
|
||||
backgroundColor: '#2979FF',
|
||||
color: '#FFFFFF',
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
unreadBadge: {
|
||||
color: '#FFFFFF',
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
inLibraryBadge: {
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
compactTitleContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 4,
|
||||
left: 4,
|
||||
right: 4,
|
||||
},
|
||||
badgeContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 1,
|
||||
top: 8,
|
||||
left: 8,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
});
|
||||
|
||||
+46
-46
@@ -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 {getDeviceOrientation} 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 = getDeviceOrientation();
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,97 +1,96 @@
|
||||
import React, { useRef } from "react";
|
||||
import { StyleSheet, View, TextInput, Text, StatusBar } from "react-native";
|
||||
import React, {useRef} from 'react';
|
||||
import {StyleSheet, View, TextInput, Text, StatusBar} from 'react-native';
|
||||
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { Row } from "../Common";
|
||||
import {TouchableRipple, IconButton} from 'react-native-paper';
|
||||
import {Row} from '../Common';
|
||||
|
||||
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.textColorPrimary}
|
||||
style={{ marginLeft: 0 }}
|
||||
size={23}
|
||||
onPress={onBackAction}
|
||||
/>
|
||||
)}
|
||||
<TextInput
|
||||
ref={searchRef}
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={theme.textColorSecondary}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
defaultValue={searchText}
|
||||
/>
|
||||
</Row>
|
||||
{searchText !== "" && (
|
||||
<IconButton
|
||||
icon="close"
|
||||
color={theme.textColorPrimary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
onPress={clearSearchbar}
|
||||
/>
|
||||
)}
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<IconButton
|
||||
key={index}
|
||||
icon={action.icon}
|
||||
color={action.color ?? theme.textColorPrimary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
onPress={action.onPress}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
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.textColorPrimary}
|
||||
style={{marginLeft: 0}}
|
||||
size={23}
|
||||
onPress={onBackAction}
|
||||
/>
|
||||
)}
|
||||
<TextInput
|
||||
ref={searchRef}
|
||||
style={{
|
||||
fontSize: 16,
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
placeholder={placeholder}
|
||||
placeholderTextColor={theme.textColorSecondary}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
defaultValue={searchText}
|
||||
/>
|
||||
</Row>
|
||||
{searchText !== '' && (
|
||||
<IconButton
|
||||
icon="close"
|
||||
color={theme.textColorPrimary}
|
||||
style={{marginRight: 0}}
|
||||
size={23}
|
||||
onPress={clearSearchbar}
|
||||
/>
|
||||
)}
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<IconButton
|
||||
key={index}
|
||||
icon={action.icon}
|
||||
color={action.color ?? theme.textColorPrimary}
|
||||
style={{marginRight: 0}}
|
||||
size={23}
|
||||
onPress={action.onPress}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
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",
|
||||
},
|
||||
searchbarContainer: {
|
||||
marginTop: StatusBar.currentHeight + 8,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 12,
|
||||
marginBottom: 8,
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 16,
|
||||
elevation: 1,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,172 +1,172 @@
|
||||
import React from "react";
|
||||
import { View, Text, StyleSheet, Pressable } from "react-native";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
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 }) => (
|
||||
export const ThemePicker = ({theme, currentTheme, onPress}) => (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginRight: 16,
|
||||
}}
|
||||
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={{
|
||||
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,
|
||||
}}
|
||||
style={{
|
||||
height: 20,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
elevation: 1,
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<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
|
||||
style={{
|
||||
backgroundColor: theme.textColorPrimary,
|
||||
width: 44,
|
||||
height: 10,
|
||||
marginLeft: 8,
|
||||
borderRadius: 50,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
<View style={{padding: 8}}>
|
||||
<View
|
||||
style={{
|
||||
color: currentTheme.textColorPrimary,
|
||||
fontSize: 12,
|
||||
paddingVertical: 4,
|
||||
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',
|
||||
}}
|
||||
>
|
||||
{theme.name}
|
||||
</Text>
|
||||
<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({});
|
||||
|
||||
+37
-37
@@ -1,55 +1,55 @@
|
||||
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);
|
||||
});
|
||||
//Db 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,224 +1,223 @@
|
||||
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 (?, ?, ?, ?)`;
|
||||
|
||||
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 = ?`;
|
||||
|
||||
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],
|
||||
(tx, 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],
|
||||
(tx, 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],
|
||||
(tx, 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],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
export const markChapterRead = async chapterId => {
|
||||
db.transaction(tx => {
|
||||
tx.executeSql(
|
||||
markChapterReadQuery,
|
||||
[chapterId],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const markChapterUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE chapterId = ?`;
|
||||
|
||||
export const markChapterUnread = async (chapterId) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
markChapterUnreadQuery,
|
||||
[chapterId],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error)
|
||||
);
|
||||
});
|
||||
export const markChapterUnread = async chapterId => {
|
||||
db.transaction(tx => {
|
||||
tx.executeSql(
|
||||
markChapterUnreadQuery,
|
||||
[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)
|
||||
);
|
||||
});
|
||||
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)
|
||||
);
|
||||
});
|
||||
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 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],
|
||||
(tx, 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],
|
||||
(tx, res) => {},
|
||||
(txObj, error) => console.log('Error ', error),
|
||||
);
|
||||
tx.executeSql(
|
||||
deleteChapterQuery,
|
||||
[chapterId],
|
||||
(tx, res) => console.log(`Chapter deleted`),
|
||||
(txObj, error) => console.log('Error ', error),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getLastReadChapterQuery = `
|
||||
@@ -229,56 +228,56 @@ 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],
|
||||
(tx, {rows}) => resolve(rows.item(0)),
|
||||
(txObj, error) => console.log('Error ', error),
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
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],
|
||||
(tx, res) => {},
|
||||
(txObj, error) => console.log('Error ', error),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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],
|
||||
(tx, res) => {},
|
||||
(tx, error) => console.log(error),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getDownloadedChaptersQuery = `
|
||||
@@ -289,23 +288,23 @@ 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,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 getHistoryQuery = `
|
||||
SELECT history.*, chapters.*, novels.*
|
||||
@@ -14,49 +14,47 @@ 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;');
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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,28 @@ 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 +57,19 @@ 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,241 +1,238 @@
|
||||
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(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
||||
|
||||
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)
|
||||
);
|
||||
});
|
||||
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)
|
||||
);
|
||||
});
|
||||
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)
|
||||
);
|
||||
})
|
||||
);
|
||||
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)
|
||||
);
|
||||
});
|
||||
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)
|
||||
)
|
||||
)
|
||||
);
|
||||
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 },
|
||||
});
|
||||
} 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 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)
|
||||
);
|
||||
});
|
||||
|
||||
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
|
||||
@@ -13,30 +13,30 @@ const getUpdatesQuery = `
|
||||
`;
|
||||
|
||||
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;"));
|
||||
db.transaction(tx => tx.executeSql('DELETE FROM updates; VACCUM;'));
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {useState, useEffect} from 'react';
|
||||
|
||||
export const githubUpdateChecker = () => {
|
||||
const latestReleaseUrl =
|
||||
"https://api.github.com/repos/rajarsheechatterjee/lnreader/releases/latest";
|
||||
const latestReleaseUrl =
|
||||
'https://api.github.com/repos/rajarsheechatterjee/lnreader/releases/latest';
|
||||
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [latestRelease, setLatestRelease] = useState();
|
||||
const [checking, setChecking] = useState(true);
|
||||
const [latestRelease, setLatestRelease] = useState();
|
||||
|
||||
const checkForRelease = async () => {
|
||||
let res = await fetch(latestReleaseUrl);
|
||||
res = await res.json();
|
||||
const checkForRelease = async () => {
|
||||
let res = await fetch(latestReleaseUrl);
|
||||
res = await res.json();
|
||||
|
||||
const release = {
|
||||
tag_name: res.tag_name,
|
||||
body: res.body,
|
||||
downloadUrl: res.assets[0].browser_download_url,
|
||||
};
|
||||
|
||||
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.5";
|
||||
const regex = /[^\\d.]/;
|
||||
setLatestRelease(release);
|
||||
setChecking(false);
|
||||
};
|
||||
|
||||
let newVersion = versionTag.replace(regex, "");
|
||||
currentVersion = currentVersion.replace(regex, "");
|
||||
const isNewVersion = versionTag => {
|
||||
let currentVersion = 'v1.1.5';
|
||||
const regex = /[^\\d.]/;
|
||||
|
||||
return newVersion !== currentVersion;
|
||||
let newVersion = versionTag.replace(regex, '');
|
||||
currentVersion = currentVersion.replace(regex, '');
|
||||
|
||||
return newVersion !== currentVersion;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkForRelease();
|
||||
}, []);
|
||||
|
||||
if (!checking) {
|
||||
const data = {
|
||||
latestRelease,
|
||||
isNewVersion: isNewVersion(latestRelease.tag_name),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkForRelease();
|
||||
}, []);
|
||||
|
||||
if (!checking) {
|
||||
const data = {
|
||||
latestRelease,
|
||||
isNewVersion: isNewVersion(latestRelease.tag_name),
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
+68
-70
@@ -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 findNovel = 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 = findNovel(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 = findNovel(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 = findNovel(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);
|
||||
};
|
||||
|
||||
@@ -1,109 +1,105 @@
|
||||
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 { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
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/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 { useSettings, useTheme } from "../hooks/reduxHooks";
|
||||
import {useSettings, useTheme} from '../hooks/reduxHooks';
|
||||
|
||||
const Tab = createMaterialBottomTabNavigator();
|
||||
|
||||
const BottomNavigator = () => {
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
const theme = useTheme();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const {
|
||||
showHistoryTab = true,
|
||||
showUpdatesTab = true,
|
||||
showLabelsInNav = false,
|
||||
} = useSettings();
|
||||
const {
|
||||
showHistoryTab = true,
|
||||
showUpdatesTab = true,
|
||||
showLabelsInNav = false,
|
||||
} = useSettings();
|
||||
|
||||
return (
|
||||
<Tab.Navigator
|
||||
barStyle={{
|
||||
backgroundColor: theme.colorPrimary,
|
||||
paddingBottom: insets.bottom,
|
||||
}}
|
||||
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: insets.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;
|
||||
|
||||
+48
-65
@@ -1,81 +1,64 @@
|
||||
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 {githubUpdateChecker} from '../hooks/githubUpdateChecker';
|
||||
import * as SplashScreen from 'expo-splash-screen';
|
||||
|
||||
// Navigators
|
||||
import BottomNavigator from "./BottomNavigator";
|
||||
import BottomNavigator from './BottomNavigator';
|
||||
|
||||
// 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";
|
||||
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';
|
||||
|
||||
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} = githubUpdateChecker() || {};
|
||||
|
||||
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={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>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainNavigator;
|
||||
|
||||
+28
-31
@@ -1,45 +1,42 @@
|
||||
import React from "react";
|
||||
import React from 'react';
|
||||
|
||||
import { createStackNavigator } from "@react-navigation/stack";
|
||||
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";
|
||||
import AppearanceSettings from "../screens/More/AppearanceSettings";
|
||||
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';
|
||||
import AppearanceSettings from '../screens/More/AppearanceSettings';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
const stackNavigatorConfig = { headerShown: false };
|
||||
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>
|
||||
<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>
|
||||
<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,102 @@
|
||||
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";
|
||||
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';
|
||||
|
||||
export const pauseDownloads = () => async (dispatch) => {
|
||||
showToast("Download paused");
|
||||
export const pauseDownloads = () => async dispatch => {
|
||||
showToast('Download paused');
|
||||
|
||||
await BackgroundService.stop();
|
||||
await BackgroundService.stop();
|
||||
|
||||
dispatch({ type: PAUSE_DOWNLOADS });
|
||||
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,62 +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, getState) => {
|
||||
const { downloadedOnlyMode = false } = getState().settingsReducer;
|
||||
(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,
|
||||
});
|
||||
};
|
||||
|
||||
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);
|
||||
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);
|
||||
|
||||
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';
|
||||
|
||||
+396
-403
@@ -1,442 +1,435 @@
|
||||
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,
|
||||
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';
|
||||
|
||||
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 {store} from '../store';
|
||||
|
||||
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 {setDownloadQueue} from '../downloads/downloads.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,
|
||||
},
|
||||
});
|
||||
};
|
||||
const 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.
|
||||
*/
|
||||
const 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, 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) => {
|
||||
try {
|
||||
/**
|
||||
* Filter downloaded chapters
|
||||
*/
|
||||
let chapters = chaps.filter((chapter) => chapter.downloaded === 0);
|
||||
|
||||
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,
|
||||
});
|
||||
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 => {
|
||||
try {
|
||||
/**
|
||||
* Filter downloaded chapters
|
||||
*/
|
||||
let chapters = chaps.filter(chapter => chapter.downloaded === 0);
|
||||
|
||||
chapters = chapters.map(chapter => ({
|
||||
...chapter,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
}));
|
||||
|
||||
if (chapters.length > 0) {
|
||||
dispatch({type: SET_DOWNLOAD_QUEUE, payload: chapters});
|
||||
const options = {
|
||||
taskName: 'Library Update',
|
||||
taskTitle: chapters[0].chapterName,
|
||||
taskDesc: '0/' + chapters.length,
|
||||
taskIcon: {
|
||||
name: 'notification_icon',
|
||||
type: 'drawable',
|
||||
},
|
||||
color: '#00adb5',
|
||||
linkingURI: 'lnreader://downloads/redirect',
|
||||
parameters: {
|
||||
delay: 1000,
|
||||
},
|
||||
progressBar: {
|
||||
max: chapters.length,
|
||||
value: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const sleep = time =>
|
||||
new Promise(resolve => setTimeout(() => resolve(), time));
|
||||
|
||||
const veryIntensiveTask = async taskData => {
|
||||
await new Promise(async resolve => {
|
||||
for (
|
||||
let i = 0;
|
||||
BackgroundService.isRunning() && i < chapters.length;
|
||||
i++
|
||||
) {
|
||||
if (BackgroundService.isRunning()) {
|
||||
// dispatch({
|
||||
// type: CHAPTER_DOWNLOADING,
|
||||
// payload: chapters[i].chapterId,
|
||||
// });
|
||||
|
||||
if (!chapters[i].downloaded) {
|
||||
await downloadChapter(
|
||||
sourceId,
|
||||
novelUrl,
|
||||
chapters[i].chapterUrl,
|
||||
chapters[i].chapterId,
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapters[i].chapterId,
|
||||
});
|
||||
|
||||
await BackgroundService.updateNotification({
|
||||
taskTitle: chapters[i].chapterName,
|
||||
taskDesc: i + 1 + '/' + chapters.length,
|
||||
progressBar: {
|
||||
max: chapters.length,
|
||||
value: i + 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (i + 1 === chapters.length) {
|
||||
resolve();
|
||||
|
||||
Notifications.scheduleNotificationAsync({
|
||||
content: {
|
||||
title: 'Downloader',
|
||||
body: 'Download completed',
|
||||
},
|
||||
trigger: null,
|
||||
});
|
||||
}
|
||||
await sleep(taskData.delay);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
await BackgroundService.start(veryIntensiveTask, options);
|
||||
}
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteChapterAction =
|
||||
(chapterId, chapterName) => async dispatch => {
|
||||
await deleteChapter(chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
showToast(`Deleted ${chapterName}`);
|
||||
};
|
||||
|
||||
export const deleteAllChaptersAction = chapters => async dispatch => {
|
||||
await chapters.map(chapter => {
|
||||
deleteChapter(chapter.chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
});
|
||||
|
||||
showToast('Chapters deleted');
|
||||
};
|
||||
|
||||
export const updateNovelAction =
|
||||
(sourceId, novelUrl, novelId, sort, filter) => async (dispatch) => {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
(sourceId, novelUrl, novelId, sort, filter) => async dispatch => {
|
||||
dispatch({type: FETCHING_NOVEL});
|
||||
|
||||
await updateNovel(sourceId, novelUrl, novelId);
|
||||
await updateNovel(sourceId, novelUrl, novelId);
|
||||
|
||||
let novel = await getNovel(sourceId, novelUrl);
|
||||
let chapters = await getChapters(novel.novelId, sort, filter);
|
||||
let novel = await getNovel(sourceId, novelUrl);
|
||||
let chapters = await getChapters(novel.novelId, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
};
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL,
|
||||
payload: {novel, chapters},
|
||||
});
|
||||
};
|
||||
|
||||
+138
-146
@@ -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,43 +1,43 @@
|
||||
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,
|
||||
yangTheme,
|
||||
yotsubaTheme,
|
||||
takoLightTheme,
|
||||
} from "../../theme/theme";
|
||||
lightTheme,
|
||||
darkTheme,
|
||||
midnightDuskTheme,
|
||||
greenAppleTheme,
|
||||
irisBlueTheme,
|
||||
strawberryDaiquiri,
|
||||
takoTheme,
|
||||
yinYangTheme,
|
||||
springBlossomTheme,
|
||||
colorsAmoled,
|
||||
oceanicTheme,
|
||||
yangTheme,
|
||||
yotsubaTheme,
|
||||
takoLightTheme,
|
||||
} 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,
|
||||
11: yangTheme,
|
||||
12: yotsubaTheme,
|
||||
13: takoLightTheme,
|
||||
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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -50,136 +50,136 @@ const themes = {
|
||||
*/
|
||||
|
||||
const initialState = {
|
||||
/**
|
||||
* General settings
|
||||
*/
|
||||
/**
|
||||
* General settings
|
||||
*/
|
||||
|
||||
incognitoMode: false,
|
||||
incognitoMode: false,
|
||||
|
||||
/**
|
||||
* Appearence settings
|
||||
*/
|
||||
/**
|
||||
* Appearence settings
|
||||
*/
|
||||
|
||||
theme: darkTheme,
|
||||
displayMode: 0,
|
||||
novelsPerRow: 3,
|
||||
showHistoryTab: true,
|
||||
showUpdatesTab: true,
|
||||
showLabelsInNav: false,
|
||||
useFabForContinueReading: false,
|
||||
theme: darkTheme,
|
||||
displayMode: 0,
|
||||
novelsPerRow: 3,
|
||||
showHistoryTab: true,
|
||||
showUpdatesTab: true,
|
||||
showLabelsInNav: false,
|
||||
useFabForContinueReading: false,
|
||||
|
||||
/**
|
||||
* Library settings
|
||||
*/
|
||||
/**
|
||||
* Library settings
|
||||
*/
|
||||
|
||||
showDownloadBadges: true,
|
||||
showUnreadBadges: true,
|
||||
showNumberOfNovels: false,
|
||||
downloadedOnlyMode: false,
|
||||
showDownloadBadges: true,
|
||||
showUnreadBadges: true,
|
||||
showNumberOfNovels: false,
|
||||
downloadedOnlyMode: false,
|
||||
|
||||
/**
|
||||
* Browse settings
|
||||
*/
|
||||
/**
|
||||
* Browse settings
|
||||
*/
|
||||
|
||||
searchAllSources: false,
|
||||
searchAllSources: false,
|
||||
|
||||
/**
|
||||
* Update settings
|
||||
*/
|
||||
/**
|
||||
* Update settings
|
||||
*/
|
||||
|
||||
onlyUpdateOngoingNovels: false,
|
||||
updateLibraryOnLaunch: false,
|
||||
downloadNewChapters: false,
|
||||
onlyUpdateOngoingNovels: false,
|
||||
updateLibraryOnLaunch: false,
|
||||
downloadNewChapters: false,
|
||||
|
||||
/**
|
||||
* Novel settings
|
||||
*/
|
||||
/**
|
||||
* Novel settings
|
||||
*/
|
||||
|
||||
hideBackdrop: false,
|
||||
hideBackdrop: false,
|
||||
|
||||
/**
|
||||
* Reader settings
|
||||
*/
|
||||
/**
|
||||
* Reader settings
|
||||
*/
|
||||
|
||||
fullScreenMode: true,
|
||||
swipeGestures: true,
|
||||
showScrollPercentage: true,
|
||||
useWebViewForChapter: false,
|
||||
showBatteryAndTime: false,
|
||||
autoScroll: false,
|
||||
autoScrollInterval: 10,
|
||||
textSelectable: false,
|
||||
fullScreenMode: true,
|
||||
swipeGestures: true,
|
||||
showScrollPercentage: true,
|
||||
useWebViewForChapter: false,
|
||||
showBatteryAndTime: false,
|
||||
autoScroll: false,
|
||||
autoScrollInterval: 10,
|
||||
textSelectable: false,
|
||||
|
||||
reader: {
|
||||
theme: 1,
|
||||
textColor: "rgba(255,255,255,0.7)",
|
||||
textSize: 16,
|
||||
textAlign: "left",
|
||||
padding: 5,
|
||||
fontFamily: "",
|
||||
lineHeight: 1.5,
|
||||
customCSS: null,
|
||||
},
|
||||
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';
|
||||
|
||||
+26
-26
@@ -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;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export const SET_TRACKER = "SET_TRACKER";
|
||||
export const REMOVE_TRACKER = "REMOVE_TRACKER";
|
||||
export const SET_TRACKER = 'SET_TRACKER';
|
||||
export const REMOVE_TRACKER = 'REMOVE_TRACKER';
|
||||
|
||||
export const TRACK_NOVEL = "TRACK_NOVEL";
|
||||
export const UNTRACK_NOVEL = "UNTRACK_NOVEL";
|
||||
export const TRACK_NOVEL = 'TRACK_NOVEL';
|
||||
export const UNTRACK_NOVEL = 'UNTRACK_NOVEL';
|
||||
|
||||
export const UPDATE_TRACKER = "UPDATE_TRACKER";
|
||||
export const UPDATE_TRACKER = 'UPDATE_TRACKER';
|
||||
|
||||
export const UPDATE_CHAPTERS_READ = "UPDATE_CHAPTERS_READ";
|
||||
export const UPDATE_CHAPTERS_READ = 'UPDATE_CHAPTERS_READ';
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
import { GET_UPDATES, SET_LAST_UPDATE_TIME } from "./updates.types";
|
||||
import {GET_UPDATES, SET_LAST_UPDATE_TIME} from './updates.types';
|
||||
|
||||
import { getUpdates } from "../../database/queries/UpdateQueries";
|
||||
import { updateAllNovels } from "../../services/updates";
|
||||
import {getUpdates} from '../../database/queries/UpdateQueries';
|
||||
import {updateAllNovels} from '../../services/updates';
|
||||
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
import {showToast} from '../../hooks/showToast';
|
||||
|
||||
export const getUpdatesAction = () => async (dispatch) => {
|
||||
const updates = await getUpdates();
|
||||
export const getUpdatesAction = () => async dispatch => {
|
||||
const updates = await getUpdates();
|
||||
|
||||
const groups = updates.reduce((groups, update) => {
|
||||
var dateParts = update.updateTime.split("-");
|
||||
var jsDate = new Date(
|
||||
dateParts[0],
|
||||
dateParts[1] - 1,
|
||||
dateParts[2].substr(0, 2)
|
||||
);
|
||||
const date = jsDate.toISOString();
|
||||
if (!groups[date]) {
|
||||
groups[date] = [];
|
||||
}
|
||||
groups[date].push(update);
|
||||
return groups;
|
||||
}, {});
|
||||
const groups = updates.reduce((groups, update) => {
|
||||
var dateParts = update.updateTime.split('-');
|
||||
var jsDate = new Date(
|
||||
dateParts[0],
|
||||
dateParts[1] - 1,
|
||||
dateParts[2].substr(0, 2),
|
||||
);
|
||||
const date = jsDate.toISOString();
|
||||
if (!groups[date]) {
|
||||
groups[date] = [];
|
||||
}
|
||||
groups[date].push(update);
|
||||
return groups;
|
||||
}, {});
|
||||
|
||||
const groupedUpdates = Object.keys(groups).map((date) => {
|
||||
return {
|
||||
date,
|
||||
chapters: groups[date],
|
||||
};
|
||||
});
|
||||
const groupedUpdates = Object.keys(groups).map(date => {
|
||||
return {
|
||||
date,
|
||||
chapters: groups[date],
|
||||
};
|
||||
});
|
||||
|
||||
dispatch({ type: GET_UPDATES, payload: groupedUpdates });
|
||||
dispatch({type: GET_UPDATES, payload: groupedUpdates});
|
||||
};
|
||||
|
||||
export const updateLibraryAction = () => async (dispatch, getState) => {
|
||||
showToast("Updating library");
|
||||
showToast('Updating library');
|
||||
|
||||
dispatch({ type: SET_LAST_UPDATE_TIME, payload: Date.now() });
|
||||
dispatch({type: SET_LAST_UPDATE_TIME, payload: Date.now()});
|
||||
|
||||
const { downloadNewChapters = false, onlyUpdateOngoingNovels = false } =
|
||||
getState().settingsReducer;
|
||||
const {downloadNewChapters = false, onlyUpdateOngoingNovels = false} =
|
||||
getState().settingsReducer;
|
||||
|
||||
await updateAllNovels({ downloadNewChapters, onlyUpdateOngoingNovels });
|
||||
await updateAllNovels({downloadNewChapters, onlyUpdateOngoingNovels});
|
||||
|
||||
dispatch(getUpdatesAction());
|
||||
dispatch(getUpdatesAction());
|
||||
};
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
import {
|
||||
LOAD_UPDATES,
|
||||
GET_UPDATES,
|
||||
SET_LAST_UPDATE_TIME,
|
||||
SHOW_LAST_UPDATE_TIME,
|
||||
} from "./updates.types";
|
||||
LOAD_UPDATES,
|
||||
GET_UPDATES,
|
||||
SET_LAST_UPDATE_TIME,
|
||||
SHOW_LAST_UPDATE_TIME,
|
||||
} from './updates.types';
|
||||
|
||||
const initialState = {
|
||||
updates: [],
|
||||
lastUpdateTime: null,
|
||||
showLastUpdateTime: true,
|
||||
loading: true,
|
||||
updates: [],
|
||||
lastUpdateTime: null,
|
||||
showLastUpdateTime: true,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const updateReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
const {type, payload} = action;
|
||||
|
||||
switch (type) {
|
||||
case LOAD_UPDATES:
|
||||
return { ...state, loading: true };
|
||||
case GET_UPDATES:
|
||||
return { ...state, updates: payload, loading: false };
|
||||
case SET_LAST_UPDATE_TIME:
|
||||
return {
|
||||
...state,
|
||||
lastUpdateTime: payload,
|
||||
};
|
||||
case SHOW_LAST_UPDATE_TIME:
|
||||
return {
|
||||
...state,
|
||||
showLastUpdateTime: payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
switch (type) {
|
||||
case LOAD_UPDATES:
|
||||
return {...state, loading: true};
|
||||
case GET_UPDATES:
|
||||
return {...state, updates: payload, loading: false};
|
||||
case SET_LAST_UPDATE_TIME:
|
||||
return {
|
||||
...state,
|
||||
lastUpdateTime: payload,
|
||||
};
|
||||
case SHOW_LAST_UPDATE_TIME:
|
||||
return {
|
||||
...state,
|
||||
showLastUpdateTime: payload,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default updateReducer;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export const GET_UPDATES = "GET_UPDATES";
|
||||
export const LOAD_UPDATES = "LOAD_UPDATES";
|
||||
export const SET_LAST_UPDATE_TIME = "SET_LAST_UPDATE_TIME";
|
||||
export const SHOW_LAST_UPDATE_TIME = "SHOW_LAST_UPDATE_TIME";
|
||||
export const GET_UPDATES = 'GET_UPDATES';
|
||||
export const LOAD_UPDATES = 'LOAD_UPDATES';
|
||||
export const SET_LAST_UPDATE_TIME = 'SET_LAST_UPDATE_TIME';
|
||||
export const SHOW_LAST_UPDATE_TIME = 'SHOW_LAST_UPDATE_TIME';
|
||||
|
||||
+154
-168
@@ -1,195 +1,181 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from "react-native";
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
} from 'react-native';
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import {
|
||||
getSourcesAction,
|
||||
searchSourcesAction,
|
||||
} from "../../redux/source/source.actions";
|
||||
import { useTheme } from "../../hooks/reduxHooks";
|
||||
import { showToast } from "../../hooks/showToast";
|
||||
getSourcesAction,
|
||||
searchSourcesAction,
|
||||
} from '../../redux/source/source.actions';
|
||||
import {useTheme} from '../../hooks/reduxHooks';
|
||||
import {showToast} from '../../hooks/showToast';
|
||||
|
||||
import { Searchbar } from "../../components/Searchbar/Searchbar";
|
||||
import SourceCard from "./components/SourceCard";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import DiscoverCard from "./discover/DiscoverCard";
|
||||
import {Searchbar} from '../../components/Searchbar/Searchbar';
|
||||
import SourceCard from './components/SourceCard';
|
||||
import EmptyView from '../../components/EmptyView';
|
||||
import DiscoverCard from './discover/DiscoverCard';
|
||||
|
||||
const Browse = ({ navigation }) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const Browse = ({navigation}) => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
let {
|
||||
sources,
|
||||
search,
|
||||
loading,
|
||||
pinned,
|
||||
filters = [],
|
||||
showMyAnimeList = true,
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
let {
|
||||
sources,
|
||||
search,
|
||||
loading,
|
||||
pinned,
|
||||
filters = [],
|
||||
showMyAnimeList = true,
|
||||
} = useSelector(state => state.sourceReducer);
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const isPinned = (sourceId) =>
|
||||
pinned.indexOf(sourceId) === -1 ? false : true;
|
||||
const isPinned = sourceId => (pinned.indexOf(sourceId) === -1 ? false : true);
|
||||
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
search = search.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
sources = sources.filter(source => filters.indexOf(source.lang) === -1);
|
||||
search = search.filter(source => filters.indexOf(source.lang) === -1);
|
||||
|
||||
let pinnedSources = sources.filter((source) => isPinned(source.sourceId));
|
||||
let pinnedSources = sources.filter(source => isPinned(source.sourceId));
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getSourcesAction());
|
||||
}, [getSourcesAction]);
|
||||
useEffect(() => {
|
||||
dispatch(getSourcesAction());
|
||||
}, [getSourcesAction]);
|
||||
|
||||
const onRefresh = () => {
|
||||
showToast("Updating extension list");
|
||||
setRefreshing(true);
|
||||
getSourcesAction();
|
||||
setRefreshing(false);
|
||||
};
|
||||
const onRefresh = () => {
|
||||
showToast('Updating extension list');
|
||||
setRefreshing(true);
|
||||
getSourcesAction();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
setSearchText("");
|
||||
};
|
||||
const clearSearchbar = () => {
|
||||
setSearchText('');
|
||||
};
|
||||
|
||||
const onChangeText = (text) => {
|
||||
setSearchText(text);
|
||||
dispatch(searchSourcesAction(text));
|
||||
};
|
||||
const onChangeText = text => {
|
||||
setSearchText(text);
|
||||
dispatch(searchSourcesAction(text));
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<SourceCard
|
||||
item={item}
|
||||
isPinned={isPinned(item.sourceId)}
|
||||
dispatch={dispatch}
|
||||
navigation={navigation}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<SourceCard
|
||||
item={item}
|
||||
isPinned={isPinned(item.sourceId)}
|
||||
dispatch={dispatch}
|
||||
navigation={navigation}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
||||
const SourceHeader = ({ title }) => (
|
||||
<Text
|
||||
style={[styles.sourcesHeader, { color: theme.textColorSecondary }]}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
const SourceHeader = ({title}) => (
|
||||
<Text style={[styles.sourcesHeader, {color: theme.textColorSecondary}]}>
|
||||
{title}
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search Source"
|
||||
backAction="magnify"
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
clearSearchbar={clearSearchbar}
|
||||
actions={[
|
||||
{
|
||||
icon: "book-search",
|
||||
onPress: () => navigation.navigate("GlobalSearch"),
|
||||
},
|
||||
{
|
||||
icon: "swap-vertical-variant",
|
||||
onPress: () => navigation.navigate("Migration"),
|
||||
},
|
||||
{
|
||||
icon: "cog-outline",
|
||||
onPress: () => navigation.navigate("BrowseSettings"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
return (
|
||||
<View style={[styles.container, {backgroundColor: theme.colorPrimaryDark}]}>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search Source"
|
||||
backAction="magnify"
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
clearSearchbar={clearSearchbar}
|
||||
actions={[
|
||||
{
|
||||
icon: 'book-search',
|
||||
onPress: () => navigation.navigate('GlobalSearch'),
|
||||
},
|
||||
{
|
||||
icon: 'swap-vertical-variant',
|
||||
onPress: () => navigation.navigate('Migration'),
|
||||
},
|
||||
{
|
||||
icon: 'cog-outline',
|
||||
onPress: () => navigation.navigate('BrowseSettings'),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1, paddingBottom: 32 }}
|
||||
data={!searchText ? sources : search}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
<FlatList
|
||||
contentContainerStyle={{flexGrow: 1, paddingBottom: 32}}
|
||||
data={!searchText ? sources : search}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinnedSources}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{showMyAnimeList && (
|
||||
<>
|
||||
<SourceHeader title="Discover" />
|
||||
{showMyAnimeList && (
|
||||
<DiscoverCard
|
||||
label="MyAnimeList"
|
||||
onPress={() => navigation.navigate('BrowseMal')}
|
||||
icon={require('../../../assets/mal.png')}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{pinnedSources.length > 0 && (
|
||||
<FlatList
|
||||
contentContainerStyle={{paddingBottom: 16}}
|
||||
data={pinnedSources}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinnedSources}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
{showMyAnimeList && (
|
||||
<>
|
||||
<SourceHeader title="Discover" />
|
||||
{showMyAnimeList && (
|
||||
<DiscoverCard
|
||||
label="MyAnimeList"
|
||||
onPress={() =>
|
||||
navigation.navigate("BrowseMal")
|
||||
}
|
||||
icon={require("../../../assets/mal.png")}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{pinnedSources.length > 0 && (
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 16 }}
|
||||
data={pinnedSources}
|
||||
keyExtractor={(item) =>
|
||||
item.sourceId.toString()
|
||||
}
|
||||
renderItem={renderItem}
|
||||
extraData={pinnedSources}
|
||||
ListHeaderComponent={
|
||||
<SourceHeader title="Pinned" />
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{sources.length > 0 && <SourceHeader title="Sources" />}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loading ? (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccent}
|
||||
style={{ marginTop: 16 }}
|
||||
/>
|
||||
) : (
|
||||
sources.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(・Д・。"
|
||||
description="Enable languages from settings"
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={["white"]}
|
||||
progressBackgroundColor={theme.colorAccent}
|
||||
/>
|
||||
}
|
||||
ListHeaderComponent={<SourceHeader title="Pinned" />}
|
||||
/>
|
||||
)}
|
||||
{sources.length > 0 && <SourceHeader title="Sources" />}
|
||||
</View>
|
||||
}
|
||||
ListEmptyComponent={
|
||||
loading ? (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccent}
|
||||
style={{marginTop: 16}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
) : (
|
||||
sources.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(・Д・。"
|
||||
description="Enable languages from settings"
|
||||
/>
|
||||
)
|
||||
)
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={refreshing}
|
||||
onRefresh={onRefresh}
|
||||
colors={['white']}
|
||||
progressBackgroundColor={theme.colorAccent}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Browse;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
sourcesHeader: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
sourcesHeader: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,142 +1,128 @@
|
||||
import React from "react";
|
||||
import { FlatList, Pressable, Text, View } from "react-native";
|
||||
import React from 'react';
|
||||
import {FlatList, Pressable, Text, View} from 'react-native';
|
||||
|
||||
import { Switch } from "react-native-paper";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import {Switch} from 'react-native-paper';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ScreenContainer } from "../../components/Common";
|
||||
import { List } from "../../components/List";
|
||||
import {Appbar} from '../../components/Appbar';
|
||||
import {ScreenContainer} from '../../components/Common';
|
||||
import {List} from '../../components/List';
|
||||
|
||||
import { useSettings, useTheme } from "../../hooks/reduxHooks";
|
||||
import { setAppSettings } from "../../redux/settings/settings.actions";
|
||||
import {useSettings, useTheme} from '../../hooks/reduxHooks';
|
||||
import {setAppSettings} from '../../redux/settings/settings.actions';
|
||||
import {
|
||||
enableDiscover,
|
||||
filterLanguage,
|
||||
} from "../../redux/source/source.actions";
|
||||
enableDiscover,
|
||||
filterLanguage,
|
||||
} from '../../redux/source/source.actions';
|
||||
|
||||
const BrowseSettings = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const BrowseSettings = ({navigation}) => {
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { filters = [], showMyAnimeList = true } = useSelector(
|
||||
(state) => state.sourceReducer
|
||||
);
|
||||
const languages = [
|
||||
"English",
|
||||
"Spanish",
|
||||
"Japanese",
|
||||
"Chinese",
|
||||
"Arabic",
|
||||
"Indonesian",
|
||||
"Russian",
|
||||
"Turkish",
|
||||
"French",
|
||||
"Portuguese (Brazil)",
|
||||
].sort();
|
||||
const {filters = [], showMyAnimeList = true} = useSelector(
|
||||
state => state.sourceReducer,
|
||||
);
|
||||
const languages = [
|
||||
'English',
|
||||
'Spanish',
|
||||
'Japanese',
|
||||
'Chinese',
|
||||
'Arabic',
|
||||
'Indonesian',
|
||||
'Russian',
|
||||
'Turkish',
|
||||
'French',
|
||||
'Portuguese (Brazil)',
|
||||
].sort();
|
||||
|
||||
const { searchAllSources = false } = useSettings();
|
||||
|
||||
const renderItem = ({ item }) => {
|
||||
return (
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() => dispatch(filterLanguage(item))}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>{item}</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={filters.indexOf(item) === -1 ? true : false}
|
||||
onValueChange={() => dispatch(filterLanguage(item))}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
const {searchAllSources = false} = useSettings();
|
||||
|
||||
const renderItem = ({item}) => {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Appbar onBackAction={navigation.goBack} title="Sources" />
|
||||
<FlatList
|
||||
contentContainerStyle={{ paddingBottom: 32 }}
|
||||
data={languages}
|
||||
keyExtractor={(item) => item}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
<List.SubHeader theme={theme}>
|
||||
Global Search
|
||||
</List.SubHeader>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"searchAllSources",
|
||||
!searchAllSources
|
||||
)
|
||||
)
|
||||
}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Search all sources
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={searchAllSources}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings(
|
||||
"searchAllSources",
|
||||
!searchAllSources
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>Discover</List.SubHeader>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(enableDiscover("showMyAnimeList"))
|
||||
}
|
||||
>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
MyAnimeList
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={showMyAnimeList}
|
||||
onValueChange={() =>
|
||||
dispatch(enableDiscover("showMyAnimeList"))
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>Languages</List.SubHeader>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Pressable
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onPress={() => dispatch(filterLanguage(item))}
|
||||
>
|
||||
<Text style={{color: theme.textColorPrimary}}>{item}</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={filters.indexOf(item) === -1 ? true : false}
|
||||
onValueChange={() => dispatch(filterLanguage(item))}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Appbar onBackAction={navigation.goBack} title="Sources" />
|
||||
<FlatList
|
||||
contentContainerStyle={{paddingBottom: 32}}
|
||||
data={languages}
|
||||
keyExtractor={item => item}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={
|
||||
<View>
|
||||
<List.SubHeader theme={theme}>Global Search</List.SubHeader>
|
||||
<Pressable
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onPress={() =>
|
||||
dispatch(setAppSettings('searchAllSources', !searchAllSources))
|
||||
}
|
||||
>
|
||||
<Text style={{color: theme.textColorPrimary}}>
|
||||
Search all sources
|
||||
</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={searchAllSources}
|
||||
onValueChange={() =>
|
||||
dispatch(
|
||||
setAppSettings('searchAllSources', !searchAllSources),
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>Discover</List.SubHeader>
|
||||
<Pressable
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
style={{
|
||||
padding: 16,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
onPress={() => dispatch(enableDiscover('showMyAnimeList'))}
|
||||
>
|
||||
<Text style={{color: theme.textColorPrimary}}>MyAnimeList</Text>
|
||||
<Switch
|
||||
color={theme.colorAccent}
|
||||
value={showMyAnimeList}
|
||||
onValueChange={() =>
|
||||
dispatch(enableDiscover('showMyAnimeList'))
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
<List.Divider theme={theme} />
|
||||
<List.SubHeader theme={theme}>Languages</List.SubHeader>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseSettings;
|
||||
|
||||
@@ -1,64 +1,56 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, FlatList, Text } from "react-native";
|
||||
import { useLibrary, useTheme } from "../../hooks/reduxHooks";
|
||||
import React from 'react';
|
||||
import {StyleSheet, View, FlatList, Text} from 'react-native';
|
||||
import {useLibrary, useTheme} from '../../hooks/reduxHooks';
|
||||
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import ListView from "../../components/ListView";
|
||||
import EmptyView from '../../components/EmptyView';
|
||||
import {Appbar} from '../../components/Appbar';
|
||||
import ListView from '../../components/ListView';
|
||||
|
||||
const SourceNovels = ({ navigation, route }) => {
|
||||
const sourceId = route.params;
|
||||
const theme = useTheme();
|
||||
const library = useLibrary();
|
||||
const SourceNovels = ({navigation, route}) => {
|
||||
const sourceId = route.params;
|
||||
const theme = useTheme();
|
||||
const library = useLibrary();
|
||||
|
||||
const sourceNovels = library.filter((novel) => novel.sourceId === sourceId);
|
||||
const sourceNovels = library.filter(novel => novel.sourceId === sourceId);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<ListView
|
||||
item={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate("MigrateNovel", {
|
||||
sourceId: item.sourceId,
|
||||
novelName: item.novelName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<ListView
|
||||
item={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate('MigrateNovel', {
|
||||
sourceId: item.sourceId,
|
||||
novelName: item.novelName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Appbar
|
||||
title="Select Novel"
|
||||
onBackAction={() => navigation.goBack()}
|
||||
/>
|
||||
<FlatList
|
||||
data={sourceNovels}
|
||||
keyExtractor={(item) => item.novelId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 20,
|
||||
textAlign: "center",
|
||||
}}
|
||||
>
|
||||
Your library does not have any novels from this source
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View style={[styles.container, {backgroundColor: theme.colorPrimaryDark}]}>
|
||||
<Appbar title="Select Novel" onBackAction={() => navigation.goBack()} />
|
||||
<FlatList
|
||||
data={sourceNovels}
|
||||
keyExtractor={item => item.novelId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 20,
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
Your library does not have any novels from this source
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default SourceNovels;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1 },
|
||||
container: {flex: 1},
|
||||
});
|
||||
|
||||
@@ -1,98 +1,94 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet, Pressable } from "react-native";
|
||||
import React from 'react';
|
||||
import {View, Text, Image, StyleSheet, Pressable} from 'react-native';
|
||||
|
||||
import { Button, IconButton } from "react-native-paper";
|
||||
import {Button, IconButton} from 'react-native-paper';
|
||||
|
||||
import { pinSourceAction } from "../../../redux/source/source.actions";
|
||||
import {pinSourceAction} from '../../../redux/source/source.actions';
|
||||
|
||||
const ExtensionCard = ({ item, theme, isPinned, dispatch, navigation }) => {
|
||||
const { sourceId, sourceName, icon, lang } = item;
|
||||
const ExtensionCard = ({item, theme, isPinned, dispatch, navigation}) => {
|
||||
const {sourceId, sourceName, icon, lang} = item;
|
||||
|
||||
const navigateToExtension = () =>
|
||||
navigation.navigate("Extension", {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
url: item.url,
|
||||
});
|
||||
const navigateToExtension = () =>
|
||||
navigation.navigate('Extension', {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
url: item.url,
|
||||
});
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.extensionCard}
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.extensionCard}
|
||||
onPress={navigateToExtension}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Image source={{uri: icon}} style={styles.extensionIcon} />
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
<Button
|
||||
labelStyle={{letterSpacing: 0}}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
>
|
||||
<Image source={{ uri: icon }} style={styles.extensionIcon} />
|
||||
<View style={styles.extensionDetails}>
|
||||
<View>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{ flexDirection: "row", alignItems: "center" }}>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={navigateToExtension}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={isPinned ? "pin" : "pin-outline"}
|
||||
animated
|
||||
size={21}
|
||||
onPress={() => dispatch(pinSourceAction(sourceId))}
|
||||
color={
|
||||
isPinned
|
||||
? theme.colorAccent
|
||||
: theme.textColorSecondary
|
||||
}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<IconButton
|
||||
icon={isPinned ? 'pin' : 'pin-outline'}
|
||||
animated
|
||||
size={21}
|
||||
onPress={() => dispatch(pinSourceAction(sourceId))}
|
||||
color={isPinned ? theme.colorAccent : theme.textColorSecondary}
|
||||
style={{margin: 2}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExtensionCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
extensionCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: "#C14033",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
extensionCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionDetails: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: '#C14033',
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,142 +1,134 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Pressable, Text, View, FlatList } from "react-native";
|
||||
import FastImage from "react-native-fast-image";
|
||||
import { Row } from "../../../components/Common";
|
||||
import React from 'react';
|
||||
import {StyleSheet, Pressable, Text, View, FlatList} from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
import {Row} from '../../../components/Common';
|
||||
|
||||
const GenreChip = ({ children, theme }) => (
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[styles.genreChip, { color: theme.textColorSecondary }]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Score "}`}
|
||||
</Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
const GenreChip = ({children, theme}) => (
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Text
|
||||
style={[styles.genreChip, {color: theme.textColorSecondary}]}
|
||||
numberOfLines={2}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${'Score '}`}
|
||||
</Text>
|
||||
{children}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const BrowseMalCard = ({ novel, theme, onPress }) => {
|
||||
return (
|
||||
const BrowseMalCard = ({novel, theme, onPress}) => {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
borderRadius: 8,
|
||||
elevation: 1,
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 8,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
}}
|
||||
>
|
||||
<FastImage
|
||||
source={{uri: novel.novelCover}}
|
||||
style={{
|
||||
width: 130,
|
||||
height: 180,
|
||||
borderTopLeftRadius: 8,
|
||||
borderBottomLeftRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
borderRadius: 8,
|
||||
elevation: 1,
|
||||
marginVertical: 4,
|
||||
marginHorizontal: 8,
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.novelUpdatesCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
}}
|
||||
>
|
||||
<FastImage
|
||||
source={{ uri: novel.novelCover }}
|
||||
style={{
|
||||
width: 130,
|
||||
height: 180,
|
||||
borderTopLeftRadius: 8,
|
||||
borderBottomLeftRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
<Text style={{color: theme.textColorPrimary, fontSize: 17}}>
|
||||
{novel.novelName}
|
||||
</Text>
|
||||
<GenreChip theme={theme}>{novel.score}</GenreChip>
|
||||
{novel.info && (
|
||||
<>
|
||||
<View style={{flexDirection: 'row'}}>
|
||||
<Text
|
||||
style={[styles.genreChip, {color: theme.textColorSecondary}]}
|
||||
>
|
||||
<Text
|
||||
style={{ color: theme.textColorPrimary, fontSize: 17 }}
|
||||
>
|
||||
{novel.novelName}
|
||||
</Text>
|
||||
<GenreChip theme={theme}>{novel.score}</GenreChip>
|
||||
{novel.info && (
|
||||
<>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Type "}`}
|
||||
</Text>
|
||||
{novel.info[1]}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${"Published "}`}
|
||||
</Text>
|
||||
{novel.info[2]}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${'Type '}`}
|
||||
</Text>
|
||||
{novel.info[1]}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
marginBottom: 4,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[styles.genreChip, {color: theme.textColorSecondary}]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{`${'Published '}`}
|
||||
</Text>
|
||||
{novel.info[2]}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseMalCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
novelUpdatesCard: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
genreChip: {
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
// fontStyle: "italic",
|
||||
},
|
||||
novelUpdatesCard: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
genreChip: {
|
||||
marginTop: 4,
|
||||
fontSize: 13,
|
||||
// fontStyle: "italic",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet, Pressable } from "react-native";
|
||||
import React from 'react';
|
||||
import {View, Text, Image, StyleSheet, Pressable} from 'react-native';
|
||||
|
||||
import { Button } from "react-native-paper";
|
||||
import {Button} from 'react-native-paper';
|
||||
|
||||
const DiscoverCard = ({ icon, label, onPress, theme }) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.discoverCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
const DiscoverCard = ({icon, label, onPress, theme}) => {
|
||||
return (
|
||||
<Pressable
|
||||
style={styles.discoverCard}
|
||||
onPress={onPress}
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
>
|
||||
<Image source={icon} style={styles.discoverIcon} />
|
||||
<View style={styles.label}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
<Image source={icon} style={styles.discoverIcon} />
|
||||
<View style={styles.label}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
<Button
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={onPress}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
{label}
|
||||
</Text>
|
||||
<Button
|
||||
labelStyle={{letterSpacing: 0}}
|
||||
uppercase={false}
|
||||
color={theme.colorAccent}
|
||||
onPress={onPress}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscoverCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
discoverCard: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
discoverIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
discoverCard: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
discoverIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
label: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginLeft: 16,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,192 +1,183 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, ActivityIndicator, FlatList } from "react-native";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {StyleSheet, View, ActivityIndicator, FlatList} from 'react-native';
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import * as WebBrowser from 'expo-web-browser';
|
||||
|
||||
import { Searchbar } from "../../../components/Searchbar/Searchbar";
|
||||
import { ErrorView } from "../../../components/ErrorView/ErrorView";
|
||||
import {Searchbar} from '../../../components/Searchbar/Searchbar';
|
||||
import {ErrorView} from '../../../components/ErrorView/ErrorView';
|
||||
|
||||
import { useTheme, useSettings } from "../../../hooks/reduxHooks";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
import { scrapeSearchResults, scrapeTopNovels } from "./MyAnimeListScraper";
|
||||
import BrowseMalCard from "./BrowseMalCard";
|
||||
import {useTheme, useSettings} from '../../../hooks/reduxHooks';
|
||||
import {showToast} from '../../../hooks/showToast';
|
||||
import {getDeviceOrientation} from '../../../services/utils/helpers';
|
||||
import {scrapeSearchResults, scrapeTopNovels} from './MyAnimeListScraper';
|
||||
import BrowseMalCard from './BrowseMalCard';
|
||||
|
||||
const BrowseMalScreen = ({ navigation, route }) => {
|
||||
const theme = useTheme();
|
||||
const BrowseMalScreen = ({navigation, route}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
const [limit, setLimit] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [novels, setNovels] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
const [limit, setLimit] = useState(0);
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const malUrl = "https://myanimelist.net/topmanga.php?type=lightnovels";
|
||||
const malUrl = 'https://myanimelist.net/topmanga.php?type=lightnovels';
|
||||
|
||||
const getNovels = async (limit) => {
|
||||
try {
|
||||
const data = await scrapeTopNovels(limit);
|
||||
const getNovels = async limit => {
|
||||
try {
|
||||
const data = await scrapeTopNovels(limit);
|
||||
|
||||
setNovels((novels) => novels.concat(data));
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
setNovels(novels => novels.concat(data));
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const clearSearchbar = () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setSearchText("");
|
||||
};
|
||||
const clearSearchbar = () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setSearchText('');
|
||||
};
|
||||
|
||||
const getSearchResults = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const getSearchResults = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const data = await scrapeSearchResults(searchText);
|
||||
const data = await scrapeSearchResults(searchText);
|
||||
|
||||
setNovels(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
setNovels(data);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
setError(error.message);
|
||||
setNovels([]);
|
||||
setLoading(false);
|
||||
showToast(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
getNovels();
|
||||
}, []);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<BrowseMalCard
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate("GlobalSearch", {
|
||||
novelName: item.novelName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<BrowseMalCard
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
navigation.navigate('GlobalSearch', {
|
||||
novelName: item.novelName,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const { displayMode, novelsPerRow } = useSettings();
|
||||
const {displayMode, novelsPerRow} = useSettings();
|
||||
|
||||
const orientation = getDeviceOrientation();
|
||||
const orientation = getDeviceOrientation();
|
||||
|
||||
const getNovelsPerRow = () => {
|
||||
if (displayMode === 2) {
|
||||
return 1;
|
||||
}
|
||||
const getNovelsPerRow = () => {
|
||||
if (displayMode === 2) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (orientation === "landscape") {
|
||||
return 6;
|
||||
} else {
|
||||
return novelsPerRow;
|
||||
}
|
||||
};
|
||||
|
||||
const isCloseToBottom = ({
|
||||
layoutMeasurement,
|
||||
contentOffset,
|
||||
contentSize,
|
||||
}) => {
|
||||
const paddingToBottom = 20;
|
||||
return (
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
const ListEmptyComponent = () => (
|
||||
<ErrorView
|
||||
errorName={error || "No results found"}
|
||||
actions={[
|
||||
{
|
||||
name: "Retry",
|
||||
onPress: () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setError();
|
||||
},
|
||||
icon: "reload",
|
||||
},
|
||||
]}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
if (orientation === 'landscape') {
|
||||
return 6;
|
||||
} else {
|
||||
return novelsPerRow;
|
||||
}
|
||||
};
|
||||
|
||||
const isCloseToBottom = ({layoutMeasurement, contentOffset, contentSize}) => {
|
||||
const paddingToBottom = 20;
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search MyAnimeList"
|
||||
backAction="arrow-left"
|
||||
onBackAction={() => navigation.goBack()}
|
||||
searchText={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={getSearchResults}
|
||||
clearSearchbar={clearSearchbar}
|
||||
actions={[
|
||||
{
|
||||
icon: "earth",
|
||||
onPress: () => WebBrowser.openBrowserAsync(malUrl),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{loading ? (
|
||||
<View style={{ flex: 1, justifyContent: "center" }}>
|
||||
<ActivityIndicator size={60} color={theme.colorAccent} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.novelsContainer}
|
||||
data={novels}
|
||||
keyExtractor={(item) => item.novelName}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (!searchText && isCloseToBottom(nativeEvent)) {
|
||||
getNovels(limit + 50);
|
||||
setLimit((limit) => limit + 50);
|
||||
}
|
||||
}}
|
||||
ListFooterComponent={
|
||||
!searchText && (
|
||||
<View style={{ paddingVertical: 16 }}>
|
||||
<ActivityIndicator color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
layoutMeasurement.height + contentOffset.y >=
|
||||
contentSize.height - paddingToBottom
|
||||
);
|
||||
};
|
||||
|
||||
const ListEmptyComponent = () => (
|
||||
<ErrorView
|
||||
errorName={error || 'No results found'}
|
||||
actions={[
|
||||
{
|
||||
name: 'Retry',
|
||||
onPress: () => {
|
||||
getNovels();
|
||||
setLoading(true);
|
||||
setError();
|
||||
},
|
||||
icon: 'reload',
|
||||
},
|
||||
]}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, {backgroundColor: theme.colorPrimaryDark}]}>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Search MyAnimeList"
|
||||
backAction="arrow-left"
|
||||
onBackAction={() => navigation.goBack()}
|
||||
searchText={searchText}
|
||||
onChangeText={text => setSearchText(text)}
|
||||
onSubmitEditing={getSearchResults}
|
||||
clearSearchbar={clearSearchbar}
|
||||
actions={[
|
||||
{
|
||||
icon: 'earth',
|
||||
onPress: () => WebBrowser.openBrowserAsync(malUrl),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{loading ? (
|
||||
<View style={{flex: 1, justifyContent: 'center'}}>
|
||||
<ActivityIndicator size={60} color={theme.colorAccent} />
|
||||
</View>
|
||||
) : (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.novelsContainer}
|
||||
data={novels}
|
||||
keyExtractor={item => item.novelName}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
onScroll={({nativeEvent}) => {
|
||||
if (!searchText && isCloseToBottom(nativeEvent)) {
|
||||
getNovels(limit + 50);
|
||||
setLimit(limit => limit + 50);
|
||||
}
|
||||
}}
|
||||
ListFooterComponent={
|
||||
!searchText && (
|
||||
<View style={{paddingVertical: 16}}>
|
||||
<ActivityIndicator color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default BrowseMalScreen;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
novelsContainer: {
|
||||
flexGrow: 1,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
novelsContainer: {
|
||||
flexGrow: 1,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,77 +1,75 @@
|
||||
import cheerio from "react-native-cheerio";
|
||||
import cheerio from 'react-native-cheerio';
|
||||
|
||||
const scrapeTopNovels = async (pageNo) => {
|
||||
const url =
|
||||
"https://myanimelist.net/topmanga.php?type=lightnovels&limit=" + pageNo;
|
||||
const scrapeTopNovels = async pageNo => {
|
||||
const url =
|
||||
'https://myanimelist.net/topmanga.php?type=lightnovels&limit=' + pageNo;
|
||||
|
||||
const res = await fetch(url);
|
||||
const body = await res.text();
|
||||
const res = await fetch(url);
|
||||
const body = await res.text();
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
const novels = [];
|
||||
|
||||
$("tr.ranking-list").each(function (res) {
|
||||
const novelId = $(this).find("img").attr("data-src").split("/");
|
||||
$('tr.ranking-list').each(function (res) {
|
||||
const novelId = $(this).find('img').attr('data-src').split('/');
|
||||
|
||||
const novelCover =
|
||||
'https://cdn.myanimelist.net/images/manga/' +
|
||||
novelId[7] +
|
||||
'/' +
|
||||
novelId[8].split('.')[0] +
|
||||
'.jpg';
|
||||
const novelName = $(this).find('h3').text();
|
||||
|
||||
const score = $(this)
|
||||
.find('.js-top-ranking-score-col > span.score-label')
|
||||
.text();
|
||||
|
||||
const info = $(this).find('div.information').text().split(/\s\s+/);
|
||||
const novel = {novelName, novelCover, score, info};
|
||||
|
||||
novels.push(novel);
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
const scrapeSearchResults = async searchTerm => {
|
||||
const url =
|
||||
'https://myanimelist.net/manga.php?q=' + searchTerm + '&cat=manga&type=2';
|
||||
|
||||
const res = await fetch(url);
|
||||
const body = await res.text();
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
$('.list')
|
||||
.find('tr')
|
||||
.each(function (res) {
|
||||
const novelName = $(this).find('a > strong').text();
|
||||
|
||||
if (novelName) {
|
||||
const novelId = $(this).find('img').attr('data-src').split('/');
|
||||
|
||||
const novelCover =
|
||||
"https://cdn.myanimelist.net/images/manga/" +
|
||||
novelId[7] +
|
||||
"/" +
|
||||
novelId[8].split(".")[0] +
|
||||
".jpg";
|
||||
const novelName = $(this).find("h3").text();
|
||||
'https://cdn.myanimelist.net/images/manga/' +
|
||||
novelId[7] +
|
||||
'/' +
|
||||
novelId[8].split('.')[0] +
|
||||
'.jpg';
|
||||
|
||||
const score = $(this)
|
||||
.find(".js-top-ranking-score-col > span.score-label")
|
||||
.text();
|
||||
const score = $(this).find('td:nth-child(5)').text().trim();
|
||||
|
||||
const info = $(this).find("div.information").text().split(/\s\s+/);
|
||||
const novel = { novelName, novelCover, score, info };
|
||||
const novel = {novelName, novelCover, score};
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
|
||||
return novels;
|
||||
return novels;
|
||||
};
|
||||
|
||||
const scrapeSearchResults = async (searchTerm) => {
|
||||
const url =
|
||||
"https://myanimelist.net/manga.php?q=" +
|
||||
searchTerm +
|
||||
"&cat=manga&type=2";
|
||||
|
||||
const res = await fetch(url);
|
||||
const body = await res.text();
|
||||
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const novels = [];
|
||||
|
||||
$(".list")
|
||||
.find("tr")
|
||||
.each(function (res) {
|
||||
const novelName = $(this).find("a > strong").text();
|
||||
|
||||
if (novelName) {
|
||||
const novelId = $(this).find("img").attr("data-src").split("/");
|
||||
|
||||
const novelCover =
|
||||
"https://cdn.myanimelist.net/images/manga/" +
|
||||
novelId[7] +
|
||||
"/" +
|
||||
novelId[8].split(".")[0] +
|
||||
".jpg";
|
||||
|
||||
const score = $(this).find("td:nth-child(5)").text().trim();
|
||||
|
||||
const novel = { novelName, novelCover, score };
|
||||
|
||||
novels.push(novel);
|
||||
}
|
||||
});
|
||||
|
||||
return novels;
|
||||
};
|
||||
|
||||
export { scrapeTopNovels, scrapeSearchResults };
|
||||
export {scrapeTopNovels, scrapeSearchResults};
|
||||
|
||||
@@ -1,168 +1,161 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {StyleSheet, View, FlatList, ActivityIndicator} from 'react-native';
|
||||
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { useSelector } from "react-redux";
|
||||
import {ProgressBar} from 'react-native-paper';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import { Searchbar } from "../../../components/Searchbar/Searchbar";
|
||||
import EmptyView from "../../../components/EmptyView";
|
||||
import {Searchbar} from '../../../components/Searchbar/Searchbar';
|
||||
import EmptyView from '../../../components/EmptyView';
|
||||
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import GlobalSearchSourceItem from "./GlobalSearchSourceItem";
|
||||
import {ScreenContainer} from '../../../components/Common';
|
||||
import GlobalSearchSourceItem from './GlobalSearchSourceItem';
|
||||
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { useLibrary, useSettings, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { getSource } from "../../../sources/sources";
|
||||
import {showToast} from '../../../hooks/showToast';
|
||||
import {useLibrary, useSettings, useTheme} from '../../../hooks/reduxHooks';
|
||||
import {getSource} from '../../../sources/sources';
|
||||
|
||||
const GlobalSearch = ({ route, navigation }) => {
|
||||
const theme = useTheme();
|
||||
const GlobalSearch = ({route, navigation}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
let novelName = "";
|
||||
if (route.params) {
|
||||
novelName = route.params.novelName;
|
||||
let novelName = '';
|
||||
if (route.params) {
|
||||
novelName = route.params.novelName;
|
||||
}
|
||||
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector(state => state.sourceReducer);
|
||||
sources = sources.filter(source => filters.indexOf(source.lang) === -1);
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
source => pinned.indexOf(source.sourceId) !== -1,
|
||||
);
|
||||
const {searchAllSources = false} = useSettings();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchText, setSearchText] = useState(novelName);
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
const library = useLibrary();
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
novelName && onSubmitEditing();
|
||||
|
||||
return () => (mounted = false);
|
||||
}, []);
|
||||
|
||||
const clearSearchbar = () => setSearchText('');
|
||||
const onChangeText = text => setSearchText(text);
|
||||
|
||||
const onSubmitEditing = async () => {
|
||||
setSearchResults([]);
|
||||
setProgress(0);
|
||||
|
||||
let globalSearchSources = searchAllSources ? sources : pinnedSources;
|
||||
|
||||
for (let i = 0; i < globalSearchSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(globalSearchSources[i].sourceId);
|
||||
const data = await source.searchNovels(encodeURI(searchText));
|
||||
|
||||
setSearchResults(searchResults => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(globalSearchSources[i].sourceName + ': ' + error.message);
|
||||
setSearchResults(searchResults => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
setProgress(progress => progress + 1 / globalSearchSources.length);
|
||||
}
|
||||
};
|
||||
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
const renderItem = ({item}) => (
|
||||
<GlobalSearchSourceItem
|
||||
source={item}
|
||||
library={library}
|
||||
theme={theme}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
(source) => pinned.indexOf(source.sourceId) !== -1
|
||||
);
|
||||
const { searchAllSources = false } = useSettings();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchText, setSearchText] = useState(novelName);
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [progress, setProgress] = useState(0);
|
||||
|
||||
const library = useLibrary();
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
novelName && onSubmitEditing();
|
||||
|
||||
return () => (mounted = false);
|
||||
}, []);
|
||||
|
||||
const clearSearchbar = () => setSearchText("");
|
||||
const onChangeText = (text) => setSearchText(text);
|
||||
|
||||
const onSubmitEditing = async () => {
|
||||
setSearchResults([]);
|
||||
setProgress(0);
|
||||
|
||||
let globalSearchSources = searchAllSources ? sources : pinnedSources;
|
||||
|
||||
for (let i = 0; i < globalSearchSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(globalSearchSources[i].sourceId);
|
||||
const data = await source.searchNovels(encodeURI(searchText));
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(
|
||||
globalSearchSources[i].sourceName + ": " + error.message
|
||||
);
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: globalSearchSources[i].sourceId,
|
||||
sourceName: globalSearchSources[i].sourceName,
|
||||
lang: globalSearchSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
setProgress(
|
||||
(progress) => progress + 1 / globalSearchSources.length
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchSourceItem
|
||||
source={item}
|
||||
library={library}
|
||||
theme={theme}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Global Search"
|
||||
backAction="arrow-left"
|
||||
onBackAction={navigation.goBack}
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
clearSearchbar={clearSearchbar}
|
||||
/>
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Searchbar
|
||||
theme={theme}
|
||||
placeholder="Global Search"
|
||||
backAction="arrow-left"
|
||||
onBackAction={navigation.goBack}
|
||||
searchText={searchText}
|
||||
onChangeText={onChangeText}
|
||||
onSubmitEditing={onSubmitEditing}
|
||||
clearSearchbar={clearSearchbar}
|
||||
/>
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
)}
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
<>
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in ${
|
||||
searchAllSources
|
||||
? 'all sources'
|
||||
: pinned.length === 0
|
||||
? 'pinned sources\n(No sources pinned)'
|
||||
: 'pinned sources'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
<>
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in ${
|
||||
searchAllSources
|
||||
? "all sources"
|
||||
: pinned.length === 0
|
||||
? "pinned sources\n(No sources pinned)"
|
||||
: "pinned sources"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
);
|
||||
</>
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="large" color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearch;
|
||||
|
||||
@@ -1,70 +1,64 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, Image, Pressable } from "react-native";
|
||||
import React from 'react';
|
||||
import {StyleSheet, View, Text, Image, Pressable} from 'react-native';
|
||||
|
||||
const GlobalSearchNovelCover = ({
|
||||
novel,
|
||||
theme,
|
||||
onPress,
|
||||
inLibrary,
|
||||
onLongPress,
|
||||
novel,
|
||||
theme,
|
||||
onPress,
|
||||
inLibrary,
|
||||
onLongPress,
|
||||
}) => {
|
||||
const { novelName, novelCover } = novel;
|
||||
const {novelName, novelCover} = novel;
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, borderRadius: 6, overflow: "hidden" }}>
|
||||
<Pressable
|
||||
android_ripple={{ color: theme.rippleColor }}
|
||||
style={styles.novelContainer}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
novelCover && !novelCover.startsWith("/")
|
||||
? novelCover
|
||||
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
|
||||
}}
|
||||
style={[
|
||||
styles.novelCover,
|
||||
inLibrary && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
</>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View style={{flex: 1, borderRadius: 6, overflow: 'hidden'}}>
|
||||
<Pressable
|
||||
android_ripple={{color: theme.rippleColor}}
|
||||
style={styles.novelContainer}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
novelCover && !novelCover.startsWith('/')
|
||||
? novelCover
|
||||
: 'https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true',
|
||||
}}
|
||||
style={[styles.novelCover, inLibrary && {opacity: 0.5}]}
|
||||
progressiveRenderingEnabled={true}
|
||||
/>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[styles.title, {color: theme.textColorPrimary}]}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
</>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
novelContainer: {
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
novelCover: {
|
||||
height: 150,
|
||||
width: 115,
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 4,
|
||||
flexWrap: "wrap",
|
||||
width: 115,
|
||||
},
|
||||
novelContainer: {
|
||||
paddingHorizontal: 5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
novelCover: {
|
||||
height: 150,
|
||||
width: 115,
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'pt-sans-bold',
|
||||
fontSize: 14,
|
||||
padding: 4,
|
||||
flexWrap: 'wrap',
|
||||
width: 115,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,53 +1,51 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, FlatList, Text } from "react-native";
|
||||
import React from 'react';
|
||||
import {StyleSheet, FlatList, Text} from 'react-native';
|
||||
|
||||
import GlobalSearchNovelCover from "./GlobalSearchNovelCover";
|
||||
import GlobalSearchNovelCover from './GlobalSearchNovelCover';
|
||||
|
||||
const GlobalSearchNovelList = ({ data, theme, library, navigation }) => {
|
||||
const inLibrary = (sourceId, novelUrl) =>
|
||||
library.some(
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
const GlobalSearchNovelList = ({data, theme, library, navigation}) => {
|
||||
const inLibrary = (sourceId, novelUrl) =>
|
||||
library.some(obj => obj.novelUrl === novelUrl && obj.sourceId === sourceId);
|
||||
|
||||
const navigateToNovel = (item) => navigation.push("Novel", item);
|
||||
const navigateToNovel = item => navigation.push('Novel', item);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchNovelCover
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() => navigateToNovel(item)}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<GlobalSearchNovelCover
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() => navigateToNovel(item)}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
const ListEmptyComponent = (
|
||||
<Text style={[{ color: theme.textColorSecondary }, styles.emptyList]}>
|
||||
No results found
|
||||
</Text>
|
||||
);
|
||||
const ListEmptyComponent = (
|
||||
<Text style={[{color: theme.textColorSecondary}, styles.emptyList]}>
|
||||
No results found
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.listContainer}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<FlatList
|
||||
contentContainerStyle={styles.listContainer}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={item => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={ListEmptyComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchNovelList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listContainer: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
emptyList: {
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
listContainer: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
emptyList: {
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,36 +1,34 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
import React from 'react';
|
||||
import {StyleSheet, View, Text} from 'react-native';
|
||||
|
||||
import GlobalSearchNovelList from "./GlobalSearchNovelList";
|
||||
import GlobalSearchNovelList from './GlobalSearchNovelList';
|
||||
|
||||
const GlobalSearchSourceItem = ({ source, library, theme, navigation }) => {
|
||||
const { sourceName, lang, novels } = source;
|
||||
const GlobalSearchSourceItem = ({source, library, theme, navigation}) => {
|
||||
const {sourceName, lang, novels} = source;
|
||||
|
||||
return (
|
||||
<>
|
||||
<View style={styles.sourceContainer}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text style={{ color: theme.textColorSecondary, fontSize: 12 }}>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<GlobalSearchNovelList
|
||||
data={novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<View style={styles.sourceContainer}>
|
||||
<Text style={{color: theme.textColorPrimary}}>{sourceName}</Text>
|
||||
<Text style={{color: theme.textColorSecondary, fontSize: 12}}>
|
||||
{lang}
|
||||
</Text>
|
||||
</View>
|
||||
<GlobalSearchNovelList
|
||||
data={novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearchSourceItem;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
sourceContainer: {
|
||||
padding: 8,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
sourceContainer: {
|
||||
padding: 8,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,75 +1,70 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, FlatList, Text } from "react-native";
|
||||
import React from 'react';
|
||||
import {StyleSheet, View, FlatList, Text} from 'react-native';
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import { Appbar } from "../../../components/Appbar";
|
||||
import MigrationSourceCard from "./MigrationSourceCard";
|
||||
import {Appbar} from '../../../components/Appbar';
|
||||
import MigrationSourceCard from './MigrationSourceCard';
|
||||
|
||||
import { useLibrary, useTheme } from "../../../hooks/reduxHooks";
|
||||
import {useLibrary, useTheme} from '../../../hooks/reduxHooks';
|
||||
|
||||
const GlobalSearch = ({ navigation }) => {
|
||||
const theme = useTheme();
|
||||
const GlobalSearch = ({navigation}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const library = useLibrary();
|
||||
let { sources } = useSelector((state) => state.sourceReducer);
|
||||
const library = useLibrary();
|
||||
let {sources} = useSelector(state => state.sourceReducer);
|
||||
|
||||
const novelsPerSource = (sourceId) =>
|
||||
library.filter((novel) => novel.sourceId === sourceId).length;
|
||||
const novelsPerSource = sourceId =>
|
||||
library.filter(novel => novel.sourceId === sourceId).length;
|
||||
|
||||
sources = sources.filter(
|
||||
(source) =>
|
||||
library.filter((novel) => novel.sourceId === source.sourceId).length
|
||||
);
|
||||
sources = sources.filter(
|
||||
source =>
|
||||
library.filter(novel => novel.sourceId === source.sourceId).length,
|
||||
);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<MigrationSourceCard
|
||||
item={item}
|
||||
theme={theme}
|
||||
noOfNovels={novelsPerSource(item.sourceId)}
|
||||
buttonLabel="All"
|
||||
onPress={() => navigation.navigate("SourceNovels", item.sourceId)}
|
||||
/>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<MigrationSourceCard
|
||||
item={item}
|
||||
theme={theme}
|
||||
noOfNovels={novelsPerSource(item.sourceId)}
|
||||
buttonLabel="All"
|
||||
onPress={() => navigation.navigate('SourceNovels', item.sourceId)}
|
||||
/>
|
||||
);
|
||||
|
||||
const ListHeaderComponent = (
|
||||
<Text style={[{ color: theme.textColorSecondary }, styles.listHeader]}>
|
||||
Select a Source To Migrate From
|
||||
</Text>
|
||||
);
|
||||
const ListHeaderComponent = (
|
||||
<Text style={[{color: theme.textColorSecondary}, styles.listHeader]}>
|
||||
Select a Source To Migrate From
|
||||
</Text>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Appbar title="Select Source" onBackAction={navigation.goBack} />
|
||||
<FlatList
|
||||
data={sources}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
return (
|
||||
<View style={[styles.container, {backgroundColor: theme.colorPrimaryDark}]}>
|
||||
<Appbar title="Select Source" onBackAction={navigation.goBack} />
|
||||
<FlatList
|
||||
data={sources}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
ListHeaderComponent={ListHeaderComponent}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalSearch;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
listHeader: {
|
||||
padding: 20,
|
||||
paddingBottom: 10,
|
||||
textTransform: "uppercase",
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
listHeader: {
|
||||
padding: 20,
|
||||
paddingBottom: 10,
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,142 +1,138 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, FlatList, Text, View } from "react-native";
|
||||
import React, {useState} from 'react';
|
||||
import {StyleSheet, FlatList, Text, View} from 'react-native';
|
||||
|
||||
import { Portal, Modal, Button } from "react-native-paper";
|
||||
import {Portal, Modal, Button} from 'react-native-paper';
|
||||
|
||||
import GlobalSearchNovelCover from "../globalsearch/GlobalSearchNovelCover";
|
||||
import GlobalSearchNovelCover from '../globalsearch/GlobalSearchNovelCover';
|
||||
|
||||
import { migrateNovel } from "../../../database/queries/NovelQueries";
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import {migrateNovel} from '../../../database/queries/NovelQueries';
|
||||
import {showToast} from '../../../hooks/showToast';
|
||||
|
||||
const MigrationNovelList = ({ data, theme, library, navigation }) => {
|
||||
const [selectedNovel, setSelectedNovel] = useState(false);
|
||||
const MigrationNovelList = ({data, theme, library, navigation}) => {
|
||||
const [selectedNovel, setSelectedNovel] = useState(false);
|
||||
|
||||
const [migrateNovelDialog, setMigrateNovelDialog] = useState(false);
|
||||
const showMigrateNovelDialog = () => setMigrateNovelDialog(true);
|
||||
const hideMigrateNovelDialog = () => setMigrateNovelDialog(false);
|
||||
const [migrateNovelDialog, setMigrateNovelDialog] = useState(false);
|
||||
const showMigrateNovelDialog = () => setMigrateNovelDialog(true);
|
||||
const hideMigrateNovelDialog = () => setMigrateNovelDialog(false);
|
||||
|
||||
const inLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
(obj) => obj.novelUrl === novelUrl && obj.sourceId === sourceId
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<GlobalSearchNovelCover
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() =>
|
||||
showModal(item.sourceId, item.novelUrl, item.novelName)
|
||||
}
|
||||
onLongPress={() =>
|
||||
navigation.navigate("Novel", {
|
||||
...item,
|
||||
sourceId: item.sourceId,
|
||||
})
|
||||
}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
const inLibrary = (sourceId, novelUrl) => {
|
||||
return library.some(
|
||||
obj => obj.novelUrl === novelUrl && obj.sourceId === sourceId,
|
||||
);
|
||||
};
|
||||
|
||||
const showModal = (sourceId, novelUrl, novelName) => {
|
||||
if (inLibrary(sourceId, novelUrl)) {
|
||||
showToast("Novel already in library");
|
||||
} else {
|
||||
setSelectedNovel({ sourceId, novelUrl, novelName });
|
||||
showMigrateNovelDialog();
|
||||
const renderItem = ({item}) => (
|
||||
<GlobalSearchNovelCover
|
||||
novel={item}
|
||||
theme={theme}
|
||||
onPress={() => showModal(item.sourceId, item.novelUrl, item.novelName)}
|
||||
onLongPress={() =>
|
||||
navigation.navigate('Novel', {
|
||||
...item,
|
||||
sourceId: item.sourceId,
|
||||
})
|
||||
}
|
||||
inLibrary={inLibrary(item.sourceId, item.novelUrl)}
|
||||
/>
|
||||
);
|
||||
|
||||
const showModal = (sourceId, novelUrl, novelName) => {
|
||||
if (inLibrary(sourceId, novelUrl)) {
|
||||
showToast('Novel already in library');
|
||||
} else {
|
||||
setSelectedNovel({sourceId, novelUrl, novelName});
|
||||
showMigrateNovelDialog();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatListCont}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={item => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
No results found
|
||||
</Text>
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlatList
|
||||
contentContainerStyle={styles.flatListCont}
|
||||
horizontal={true}
|
||||
data={data}
|
||||
keyExtractor={(item) => item.novelUrl}
|
||||
renderItem={renderItem}
|
||||
ListEmptyComponent={
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
padding: 8,
|
||||
paddingVertical: 4,
|
||||
}}
|
||||
>
|
||||
No results found
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={migrateNovelDialog}
|
||||
onDismiss={hideMigrateNovelDialog}
|
||||
contentContainerStyle={{
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 18,
|
||||
}}
|
||||
>
|
||||
{`Migrate ${selectedNovel.novelName}?`}
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onPress={hideMigrateNovelDialog}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccent } }}
|
||||
onPress={async () => {
|
||||
hideMigrateNovelDialog();
|
||||
await migrateNovel(
|
||||
selectedNovel.sourceId,
|
||||
selectedNovel.novelUrl
|
||||
);
|
||||
showToast(
|
||||
`${selectedNovel.novelName} migrated to new source.`
|
||||
);
|
||||
}}
|
||||
>
|
||||
Migrate
|
||||
</Button>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={migrateNovelDialog}
|
||||
onDismiss={hideMigrateNovelDialog}
|
||||
contentContainerStyle={{
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 18,
|
||||
}}
|
||||
>
|
||||
{`Migrate ${selectedNovel.novelName}?`}
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{marginTop: 30}}
|
||||
labelStyle={{
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: 'none',
|
||||
}}
|
||||
onPress={hideMigrateNovelDialog}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
style={{marginTop: 30}}
|
||||
labelStyle={{
|
||||
color: theme.colorAccent,
|
||||
letterSpacing: 0,
|
||||
textTransform: 'none',
|
||||
}}
|
||||
theme={{colors: {primary: theme.colorAccent}}}
|
||||
onPress={async () => {
|
||||
hideMigrateNovelDialog();
|
||||
await migrateNovel(
|
||||
selectedNovel.sourceId,
|
||||
selectedNovel.novelUrl,
|
||||
);
|
||||
showToast(`${selectedNovel.novelName} migrated to new source.`);
|
||||
}}
|
||||
>
|
||||
Migrate
|
||||
</Button>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MigrationNovelList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flatListCont: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
flatListCont: {
|
||||
flexGrow: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,158 +1,147 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
} from "react-native";
|
||||
import { ProgressBar } from "react-native-paper";
|
||||
import { useLibrary, useSettings, useTheme } from "../../../hooks/reduxHooks";
|
||||
import { useSelector } from "react-redux";
|
||||
StyleSheet,
|
||||
View,
|
||||
Text,
|
||||
FlatList,
|
||||
ActivityIndicator,
|
||||
} from 'react-native';
|
||||
import {ProgressBar} from 'react-native-paper';
|
||||
import {useLibrary, useSettings, useTheme} from '../../../hooks/reduxHooks';
|
||||
import {useSelector} from 'react-redux';
|
||||
|
||||
import EmptyView from "../../../components/EmptyView";
|
||||
import MigrationNovelList from "./MigrationNovelList";
|
||||
import { Appbar } from "../../../components/Appbar";
|
||||
import EmptyView from '../../../components/EmptyView';
|
||||
import MigrationNovelList from './MigrationNovelList';
|
||||
import {Appbar} from '../../../components/Appbar';
|
||||
|
||||
import { showToast } from "../../../hooks/showToast";
|
||||
import { migrateNovel } from "../../../database/queries/NovelQueries";
|
||||
import { ScreenContainer } from "../../../components/Common";
|
||||
import { getSource } from "../../../sources/sources";
|
||||
import {showToast} from '../../../hooks/showToast';
|
||||
import {migrateNovel} from '../../../database/queries/NovelQueries';
|
||||
import {ScreenContainer} from '../../../components/Common';
|
||||
import {getSource} from '../../../sources/sources';
|
||||
|
||||
const MigrationNovels = ({ navigation, route }) => {
|
||||
const { sourceId, novelName } = route.params;
|
||||
const theme = useTheme();
|
||||
const MigrationNovels = ({navigation, route}) => {
|
||||
const {sourceId, novelName} = route.params;
|
||||
const theme = useTheme();
|
||||
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState("");
|
||||
const { searchAllSources = false } = useSettings();
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector((state) => state.sourceReducer);
|
||||
sources = sources.filter((source) => filters.indexOf(source.lang) === -1);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState('');
|
||||
const {searchAllSources = false} = useSettings();
|
||||
let {
|
||||
sources,
|
||||
pinned,
|
||||
filters = [],
|
||||
} = useSelector(state => state.sourceReducer);
|
||||
sources = sources.filter(source => filters.indexOf(source.lang) === -1);
|
||||
|
||||
const library = useLibrary();
|
||||
const library = useLibrary();
|
||||
|
||||
const pinnedSources = sources.filter(
|
||||
(source) =>
|
||||
pinned.indexOf(source.sourceId) !== -1 &&
|
||||
source.sourceId !== sourceId
|
||||
);
|
||||
const pinnedSources = sources.filter(
|
||||
source =>
|
||||
pinned.indexOf(source.sourceId) !== -1 && source.sourceId !== sourceId,
|
||||
);
|
||||
|
||||
const getSearchResults = async () => {
|
||||
let migrationSources = searchAllSources ? sources : pinnedSources;
|
||||
for (let i = 0; i < migrationSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
const getSearchResults = async () => {
|
||||
let migrationSources = searchAllSources ? sources : pinnedSources;
|
||||
for (let i = 0; i < migrationSources.length; i++) {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const source = getSource(migrationSources[i].sourceId);
|
||||
const data = await source.searchNovels(novelName);
|
||||
const source = getSource(migrationSources[i].sourceId);
|
||||
const data = await source.searchNovels(novelName);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
setSearchResults(searchResults => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: data,
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
showToast(error.message);
|
||||
|
||||
setSearchResults((searchResults) => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
setProgress((progress) => progress + 1 / migrationSources.length);
|
||||
}
|
||||
};
|
||||
setSearchResults(searchResults => [
|
||||
...searchResults,
|
||||
{
|
||||
sourceId: migrationSources[i].sourceId,
|
||||
sourceName: migrationSources[i].sourceName,
|
||||
sourceLanguage: migrationSources[i].lang,
|
||||
novels: [],
|
||||
},
|
||||
]);
|
||||
setLoading(false);
|
||||
}
|
||||
setProgress(progress => progress + 1 / migrationSources.length);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSearchResults();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
getSearchResults();
|
||||
}, []);
|
||||
|
||||
const renderItem = ({ item }) => (
|
||||
<>
|
||||
<View style={{ padding: 8, paddingVertical: 16 }}>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
{item.sourceName}
|
||||
</Text>
|
||||
<Text style={{ color: theme.textColorSecondary, fontSize: 12 }}>
|
||||
{item.sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<MigrationNovelList
|
||||
data={item.novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
const renderItem = ({item}) => (
|
||||
<>
|
||||
<View style={{padding: 8, paddingVertical: 16}}>
|
||||
<Text style={{color: theme.textColorPrimary}}>{item.sourceName}</Text>
|
||||
<Text style={{color: theme.textColorSecondary, fontSize: 12}}>
|
||||
{item.sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
<MigrationNovelList
|
||||
data={item.novels}
|
||||
theme={theme}
|
||||
library={library}
|
||||
navigation={navigation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Appbar
|
||||
title={novelName}
|
||||
onBackAction={() => navigation.goBack()}
|
||||
/>
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
return (
|
||||
<ScreenContainer theme={theme}>
|
||||
<Appbar title={novelName} onBackAction={() => navigation.goBack()} />
|
||||
{progress > 0 && (
|
||||
<ProgressBar color={theme.colorAccent} progress={progress} />
|
||||
)}
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={item => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
<>
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in your pinned sources ${
|
||||
pinned.length === 0 ? '(No sources pinned)' : ''
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
<FlatList
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1,
|
||||
padding: 4,
|
||||
}}
|
||||
data={searchResults}
|
||||
keyExtractor={(item) => item.sourceId.toString()}
|
||||
renderItem={renderItem}
|
||||
extraData={pinned}
|
||||
ListEmptyComponent={
|
||||
<>
|
||||
{!loading && (
|
||||
<EmptyView
|
||||
icon="__φ(..)"
|
||||
description={`Search a novel in your pinned sources ${
|
||||
pinned.length === 0
|
||||
? "(No sources pinned)"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator
|
||||
size="large"
|
||||
color={theme.colorAccent}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
);
|
||||
</>
|
||||
}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="large" color={theme.colorAccent} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</ScreenContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default MigrationNovels;
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
import React from "react";
|
||||
import { View, Text, Image, StyleSheet } from "react-native";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
import React from 'react';
|
||||
import {View, Text, Image, StyleSheet} from 'react-native';
|
||||
import {TouchableRipple} from 'react-native-paper';
|
||||
|
||||
const MigrationSourceCard = ({ item, theme, noOfNovels, onPress }) => {
|
||||
const { sourceName, icon, sourceLanguage } = item;
|
||||
const MigrationSourceCard = ({item, theme, noOfNovels, onPress}) => {
|
||||
const {sourceName, icon, sourceLanguage} = item;
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.cardContainer}
|
||||
onPress={onPress}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Image source={{ uri: icon }} style={styles.sourceIcon} />
|
||||
<View style={styles.sourceDetailsContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{sourceName} {` (${noOfNovels || 0})`}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.cardContainer}
|
||||
onPress={onPress}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<>
|
||||
<Image source={{uri: icon}} style={styles.sourceIcon} />
|
||||
<View style={styles.sourceDetailsContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
>
|
||||
{sourceName} {` (${noOfNovels || 0})`}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default MigrationSourceCard;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
cardContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
sourceIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
sourceDetailsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: "#C14033",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
cardContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
sourceIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
},
|
||||
sourceDetailsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginLeft: 16,
|
||||
},
|
||||
sourceStatus: {
|
||||
color: '#C14033',
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
},
|
||||
});
|
||||
|
||||
+640
-675
File diff suppressed because it is too large
Load Diff
@@ -1,129 +1,129 @@
|
||||
import React from "react";
|
||||
import { Dimensions, View, Text, StyleSheet } from "react-native";
|
||||
import React from 'react';
|
||||
import {Dimensions, View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import Slider from "@react-native-community/slider";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
import Slider from '@react-native-community/slider';
|
||||
import {getDeviceOrientation} from '../../../services/utils/helpers';
|
||||
|
||||
const ReaderSeekbar = ({
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
}) => {
|
||||
const onSlidingComplete = (value) => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
const onSlidingComplete = value => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
} else if (getDeviceOrientation() === "potrait") {
|
||||
return (
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
if (hide) {
|
||||
return null;
|
||||
} else if (getDeviceOrientation() === 'potrait') {
|
||||
return (
|
||||
<View style={styles.verticalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginLeft: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginRight: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ReaderSeekbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get("window").width / 2) + 40,
|
||||
bottom: 300,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
backgroundColor: "rgba(0,0,0,0.7)",
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
verticalSliderContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get('window').width / 2) + 40,
|
||||
bottom: 300,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{rotate: '90deg'}],
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
backgroundColor: 'rgba(0,0,0,0.7)',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,91 +1,85 @@
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import React, {useState} from 'react';
|
||||
import {View} from 'react-native';
|
||||
|
||||
import { Appbar } from "react-native-paper";
|
||||
import FadeView from "../../../components/Common/CrossFadeView";
|
||||
import { bookmarkChapterAction } from "../../../redux/novel/novel.actions";
|
||||
import {Appbar} from 'react-native-paper';
|
||||
import FadeView from '../../../components/Common/CrossFadeView';
|
||||
import {bookmarkChapterAction} from '../../../redux/novel/novel.actions';
|
||||
|
||||
const ChapterAppbar = ({
|
||||
navigation,
|
||||
bookmark,
|
||||
novelName,
|
||||
chapterId,
|
||||
chapterName,
|
||||
hide,
|
||||
dispatch,
|
||||
tts,
|
||||
textToSpeech,
|
||||
theme,
|
||||
textToSpeechPosition,
|
||||
pauseTts,
|
||||
navigation,
|
||||
bookmark,
|
||||
novelName,
|
||||
chapterId,
|
||||
chapterName,
|
||||
hide,
|
||||
dispatch,
|
||||
tts,
|
||||
textToSpeech,
|
||||
theme,
|
||||
textToSpeechPosition,
|
||||
pauseTts,
|
||||
}) => {
|
||||
const [bookmarked, setBookmarked] = useState(bookmark);
|
||||
const [bookmarked, setBookmarked] = useState(bookmark);
|
||||
|
||||
return (
|
||||
<FadeView
|
||||
style={{
|
||||
flex: 1,
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
return (
|
||||
<FadeView
|
||||
style={{
|
||||
flex: 1,
|
||||
position: 'absolute',
|
||||
width: '100%',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
}}
|
||||
active={hide}
|
||||
animationDuration={150}
|
||||
>
|
||||
<View style={{flex: 1, backgroundColor: `${theme.colorPrimary}E6`}}>
|
||||
<Appbar.Header style={{backgroundColor: 'transparent', elevation: 0}}>
|
||||
<Appbar.BackAction
|
||||
onPress={navigation.goBack}
|
||||
color={theme.textColorPrimary}
|
||||
size={26}
|
||||
style={{marginRight: 0}}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={novelName}
|
||||
titleStyle={{color: theme.textColorPrimary}}
|
||||
subtitle={chapterName}
|
||||
subtitleStyle={{color: theme.textColorSecondary}}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="volume-high"
|
||||
size={24}
|
||||
onPress={tts}
|
||||
color={
|
||||
textToSpeech === 'progress'
|
||||
? theme.colorAccent
|
||||
: theme.textColorPrimary
|
||||
}
|
||||
/>
|
||||
|
||||
{textToSpeechPosition.end > 0 && (
|
||||
<Appbar.Action
|
||||
icon={textToSpeech === 'paused' ? 'play' : 'pause'}
|
||||
size={24}
|
||||
onPress={pauseTts}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Appbar.Action
|
||||
icon={bookmarked ? 'bookmark' : 'bookmark-outline'}
|
||||
size={24}
|
||||
onPress={() => {
|
||||
dispatch(bookmarkChapterAction([{bookmark, chapterId}]));
|
||||
setBookmarked(!bookmarked);
|
||||
}}
|
||||
active={hide}
|
||||
animationDuration={150}
|
||||
>
|
||||
<View
|
||||
style={{ flex: 1, backgroundColor: `${theme.colorPrimary}E6` }}
|
||||
>
|
||||
<Appbar.Header
|
||||
style={{ backgroundColor: "transparent", elevation: 0 }}
|
||||
>
|
||||
<Appbar.BackAction
|
||||
onPress={navigation.goBack}
|
||||
color={theme.textColorPrimary}
|
||||
size={26}
|
||||
style={{ marginRight: 0 }}
|
||||
/>
|
||||
<Appbar.Content
|
||||
title={novelName}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
subtitle={chapterName}
|
||||
subtitleStyle={{ color: theme.textColorSecondary }}
|
||||
/>
|
||||
<Appbar.Action
|
||||
icon="volume-high"
|
||||
size={24}
|
||||
onPress={tts}
|
||||
color={
|
||||
textToSpeech === "progress"
|
||||
? theme.colorAccent
|
||||
: theme.textColorPrimary
|
||||
}
|
||||
/>
|
||||
|
||||
{textToSpeechPosition.end > 0 && (
|
||||
<Appbar.Action
|
||||
icon={textToSpeech === "paused" ? "play" : "pause"}
|
||||
size={24}
|
||||
onPress={pauseTts}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Appbar.Action
|
||||
icon={bookmarked ? "bookmark" : "bookmark-outline"}
|
||||
size={24}
|
||||
onPress={() => {
|
||||
dispatch(
|
||||
bookmarkChapterAction([{ bookmark, chapterId }])
|
||||
);
|
||||
setBookmarked(!bookmarked);
|
||||
}}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Appbar.Header>
|
||||
</View>
|
||||
</FadeView>
|
||||
);
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Appbar.Header>
|
||||
</View>
|
||||
</FadeView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChapterAppbar;
|
||||
|
||||
@@ -1,103 +1,103 @@
|
||||
import React from "react";
|
||||
import { Pressable, StyleSheet, View } from "react-native";
|
||||
import { IconButton } from "react-native-paper";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import FadeView from "../../../components/Common/CrossFadeView";
|
||||
import React from 'react';
|
||||
import {Pressable, StyleSheet, View} from 'react-native';
|
||||
import {IconButton} from 'react-native-paper';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import FadeView from '../../../components/Common/CrossFadeView';
|
||||
|
||||
const ChapterFooter = ({
|
||||
hide,
|
||||
theme,
|
||||
swipeGestures,
|
||||
readerSheetRef,
|
||||
scrollViewRef,
|
||||
navigateToNextChapter,
|
||||
navigateToPrevChapter,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
useWebViewForChapter,
|
||||
enableSwipeGestures,
|
||||
autoScroll,
|
||||
enableWebView,
|
||||
enableAutoScroll,
|
||||
hide,
|
||||
theme,
|
||||
swipeGestures,
|
||||
readerSheetRef,
|
||||
scrollViewRef,
|
||||
navigateToNextChapter,
|
||||
navigateToPrevChapter,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
useWebViewForChapter,
|
||||
enableSwipeGestures,
|
||||
autoScroll,
|
||||
enableWebView,
|
||||
enableAutoScroll,
|
||||
}) => {
|
||||
const rippleConfig = {
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
radius: 50,
|
||||
};
|
||||
const rippleConfig = {
|
||||
color: theme.rippleColor,
|
||||
borderless: true,
|
||||
radius: 50,
|
||||
};
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
return (
|
||||
<FadeView
|
||||
style={{
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
}}
|
||||
active={hide}
|
||||
animationDuration={150}
|
||||
return (
|
||||
<FadeView
|
||||
style={{
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
}}
|
||||
active={hide}
|
||||
animationDuration={150}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: `${theme.colorPrimary}E6`,
|
||||
flexDirection: 'row',
|
||||
paddingBottom: insets.bottom,
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToPrevChapter}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: `${theme.colorPrimary}E6`,
|
||||
flexDirection: "row",
|
||||
paddingBottom: insets.bottom,
|
||||
}}
|
||||
<IconButton
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!prevChapter}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableSwipeGestures}
|
||||
>
|
||||
<IconButton
|
||||
icon="gesture-swipe"
|
||||
disabled={!swipeGestures}
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
{!useWebViewForChapter && (
|
||||
<>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableAutoScroll}
|
||||
>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToPrevChapter}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-left"
|
||||
size={26}
|
||||
disabled={!prevChapter}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableSwipeGestures}
|
||||
>
|
||||
<IconButton
|
||||
icon="gesture-swipe"
|
||||
disabled={!swipeGestures}
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
{!useWebViewForChapter && (
|
||||
<>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableAutoScroll}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-triple-down"
|
||||
disabled={!autoScroll}
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => scrollViewRef.current.scrollTo({})}
|
||||
>
|
||||
<IconButton
|
||||
icon="format-vertical-align-top"
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
{/* <Pressable
|
||||
<IconButton
|
||||
icon="chevron-triple-down"
|
||||
disabled={!autoScroll}
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => scrollViewRef.current.scrollTo({})}
|
||||
>
|
||||
<IconButton
|
||||
icon="format-vertical-align-top"
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
{/* <Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={enableWebView}
|
||||
@@ -109,42 +109,42 @@ const ChapterFooter = ({
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable> */}
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => readerSheetRef.current.show()}
|
||||
>
|
||||
<IconButton
|
||||
icon="cog-outline"
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToNextChapter}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!nextChapter}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</FadeView>
|
||||
);
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={() => readerSheetRef.current.show()}
|
||||
>
|
||||
<IconButton
|
||||
icon="cog-outline"
|
||||
size={26}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
android_ripple={rippleConfig}
|
||||
style={styles.buttonStyles}
|
||||
onPress={navigateToNextChapter}
|
||||
>
|
||||
<IconButton
|
||||
icon="chevron-right"
|
||||
size={26}
|
||||
disabled={!nextChapter}
|
||||
color={theme.textColorPrimary}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</FadeView>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChapterFooter;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonStyles: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
buttonStyles: {
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 8,
|
||||
paddingBottom: 4,
|
||||
},
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,135 +1,135 @@
|
||||
import React from "react";
|
||||
import { Dimensions, View, Text, StyleSheet } from "react-native";
|
||||
import React from 'react';
|
||||
import {Dimensions, View, Text, StyleSheet} from 'react-native';
|
||||
|
||||
import Slider from "@react-native-community/slider";
|
||||
import { getDeviceOrientation } from "../../../services/utils/helpers";
|
||||
import FadeView from "../../../components/Common/CrossFadeView";
|
||||
import Slider from '@react-native-community/slider';
|
||||
import {getDeviceOrientation} from '../../../services/utils/helpers';
|
||||
import FadeView from '../../../components/Common/CrossFadeView';
|
||||
|
||||
const VerticalScrollbar = ({
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
theme,
|
||||
hide,
|
||||
contentSize,
|
||||
scrollPercentage,
|
||||
setLoading,
|
||||
scrollViewRef,
|
||||
}) => {
|
||||
const onSlidingComplete = (value) => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
const onSlidingComplete = value => {
|
||||
setLoading(true);
|
||||
scrollViewRef.current.scrollTo({
|
||||
x: 0,
|
||||
y: Math.round((value * contentSize) / 100),
|
||||
animated: false,
|
||||
});
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (hide) {
|
||||
return null;
|
||||
if (hide) {
|
||||
return null;
|
||||
} else {
|
||||
if (getDeviceOrientation() === 'potrait') {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.verticalSliderContainer,
|
||||
{backgroundColor: `${theme.colorPrimary}E6`},
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
transform: [{rotate: '-90deg'}],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
if (getDeviceOrientation() === "potrait") {
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.verticalSliderContainer,
|
||||
{ backgroundColor: `${theme.colorPrimary}E6` },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginRight: 16,
|
||||
transform: [{ rotate: "-90deg" }],
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View style={styles.horizontalSliderContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginLeft: 16,
|
||||
}}
|
||||
>
|
||||
{scrollPercentage}
|
||||
</Text>
|
||||
<Slider
|
||||
style={{
|
||||
flex: 1,
|
||||
height: 40,
|
||||
}}
|
||||
minimumValue={0}
|
||||
maximumValue={100}
|
||||
step={1}
|
||||
value={scrollPercentage}
|
||||
onSlidingComplete={onSlidingComplete}
|
||||
thumbTintColor={theme.colorAccent}
|
||||
minimumTrackTintColor={theme.colorAccent}
|
||||
maximumTrackTintColor={theme.textColorPrimary}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: '#FFFFFF',
|
||||
marginRight: 16,
|
||||
}}
|
||||
>
|
||||
100
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default VerticalScrollbar;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
verticalSliderContainer: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{ rotate: "90deg" }],
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get("window").width / 2) + 40,
|
||||
bottom: 300,
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: "absolute",
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
alignItems: "center",
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
verticalSliderContainer: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
transform: [{rotate: '90deg'}],
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
right: -(Dimensions.get('window').width / 2) + 40,
|
||||
bottom: 300,
|
||||
},
|
||||
horizontalSliderContainer: {
|
||||
position: 'absolute',
|
||||
zIndex: 2,
|
||||
bottom: 100,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
borderRadius: 50,
|
||||
marginHorizontal: 8,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React from "react";
|
||||
import WebView from "react-native-webview";
|
||||
import React from 'react';
|
||||
import WebView from 'react-native-webview';
|
||||
|
||||
export const WebViewReader = ({ style, css, text, onScroll }) => (
|
||||
<WebView
|
||||
style={style}
|
||||
originWhitelist={["*"]}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={onScroll}
|
||||
source={{
|
||||
html: `
|
||||
export const WebViewReader = ({style, css, text, onScroll}) => (
|
||||
<WebView
|
||||
style={style}
|
||||
originWhitelist={['*']}
|
||||
scalesPageToFit={true}
|
||||
showsVerticalScrollIndicator={false}
|
||||
onScroll={onScroll}
|
||||
source={{
|
||||
html: `
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
@@ -22,6 +22,6 @@ export const WebViewReader = ({ style, css, text, onScroll }) => (
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user