Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ed0e8b4c4 | |||
| 458cdb2c2a | |||
| 0a476ba6b4 | |||
| 6e85909f6f | |||
| 5642122a5e | |||
| 77481dfc06 | |||
| a870442022 | |||
| a9a176297e | |||
| f4c0a647fb | |||
| 06e7e45df0 | |||
| c538bf4956 | |||
| e1679a41f1 | |||
| 3c2f4a59b7 | |||
| 8c13b1ff12 | |||
| 45083e5fd1 | |||
| 3b37c1cb33 | |||
| 48187d7d54 | |||
| 3af0b1afbc | |||
| b9695e7f56 | |||
| 7c068aa7d5 | |||
| 8b182f1e31 | |||
| 5029513424 | |||
| f4c6a3b4a1 | |||
| 27f5e20107 | |||
| c249b52304 | |||
| c179e0e5bd | |||
| b78382cc03 | |||
| 9df3694c04 | |||
| dfd300c358 | |||
| 76c108597d | |||
| 23ff02674e | |||
| 55e2371f5d | |||
| 4eb225cdf3 | |||
| 3cb41c8bfb | |||
| 05511e073e | |||
| 085b9b9eb3 | |||
| 9290d59f77 | |||
| c0345ae9a9 | |||
| c12a7f378a | |||
| d3c2d130e1 | |||
| 134c72a683 | |||
| 4c326e35c0 |
Binary file not shown.
|
Before Width: | Height: | Size: 55 KiB |
@@ -6,28 +6,28 @@ import AppLoading from "expo-app-loading";
|
||||
|
||||
import Router from "./src/navigation/Router";
|
||||
|
||||
import { createTables, createIndexes } from "./src/services/db";
|
||||
|
||||
// Redux
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./src/redux/store/configureStore";
|
||||
import { persistor, store } from "./src/redux/store";
|
||||
import { PersistGate } from "redux-persist/lib/integration/react";
|
||||
import { createDB, deleteDb } from "./src/database/DBHelper";
|
||||
|
||||
const getFonts = () =>
|
||||
Font.loadAsync({
|
||||
"pt-sans-bold": require("./assets/fonts/PTSansNarrow-Bold.ttf"),
|
||||
"arbutus-slab": require("./assets/fonts/ArbutusSlab-Regular.ttf"),
|
||||
domine: require("./assets/fonts/Domine-VariableFont_wght.ttf"),
|
||||
lato: require("./assets/fonts/Lato-Regular.ttf"),
|
||||
"noto-sans": require("./assets/fonts/NotoSans-Regular.ttf"),
|
||||
"open-sans": require("./assets/fonts/OpenSans-Regular.ttf"),
|
||||
"pt-serif": require("./assets/fonts/PTSerif-Regular.ttf"),
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const [fontsLoaded, setFontsLoaded] = useState(false);
|
||||
|
||||
const createDb = () => {
|
||||
createTables();
|
||||
createIndexes();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
createDb();
|
||||
createDB();
|
||||
}, []);
|
||||
|
||||
if (fontsLoaded) {
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"expo": {
|
||||
"name": "LNReader",
|
||||
"slug": "LNReader",
|
||||
"version": "1.0.0",
|
||||
"scheme": "lnreader",
|
||||
"version": "1.0.15",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/icon.png",
|
||||
"splash": {
|
||||
@@ -11,7 +12,8 @@
|
||||
"backgroundColor": "#000000"
|
||||
},
|
||||
"updates": {
|
||||
"fallbackToCacheTimeout": 0
|
||||
"fallbackToCacheTimeout": 0,
|
||||
"enabled": false
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
Generated
+42
-389
@@ -1512,122 +1512,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jimp/bmp": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.1.tgz",
|
||||
"integrity": "sha512-iwyNYQeBawrdg/f24x3pQ5rEx+/GwjZcCXd3Kgc+ZUd+Ivia7sIqBsOnDaMZdKCBPlfW364ekexnlOqyVa0NWg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1",
|
||||
"bmp-js": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"@jimp/core": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.1.tgz",
|
||||
"integrity": "sha512-la7kQia31V6kQ4q1kI/uLimu8FXx7imWVajDGtwUG8fzePLWDFJyZl0fdIXVCL1JW2nBcRHidUot6jvlRDi2+g==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1",
|
||||
"any-base": "^1.1.0",
|
||||
"buffer": "^5.2.0",
|
||||
"exif-parser": "^0.1.12",
|
||||
"file-type": "^9.0.0",
|
||||
"load-bmfont": "^1.3.1",
|
||||
"mkdirp": "^0.5.1",
|
||||
"phin": "^2.9.1",
|
||||
"pixelmatch": "^4.0.2",
|
||||
"tinycolor2": "^1.4.1"
|
||||
}
|
||||
},
|
||||
"@jimp/custom": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.1.tgz",
|
||||
"integrity": "sha512-DNUAHNSiUI/j9hmbatD6WN/EBIyeq4AO0frl5ETtt51VN1SvE4t4v83ZA/V6ikxEf3hxLju4tQ5Pc3zmZkN/3A==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/core": "^0.16.1"
|
||||
}
|
||||
},
|
||||
"@jimp/gif": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.1.tgz",
|
||||
"integrity": "sha512-r/1+GzIW1D5zrP4tNrfW+3y4vqD935WBXSc8X/wm23QTY9aJO9Lw6PEdzpYCEY+SOklIFKaJYUAq/Nvgm/9ryw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1",
|
||||
"gifwrap": "^0.9.2",
|
||||
"omggif": "^1.0.9"
|
||||
}
|
||||
},
|
||||
"@jimp/jpeg": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.16.1.tgz",
|
||||
"integrity": "sha512-8352zrdlCCLFdZ/J+JjBslDvml+fS3Z8gttdml0We759PnnZGqrnPRhkOEOJbNUlE+dD4ckLeIe6NPxlS/7U+w==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1",
|
||||
"jpeg-js": "0.4.2"
|
||||
}
|
||||
},
|
||||
"@jimp/plugin-resize": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.1.tgz",
|
||||
"integrity": "sha512-u4JBLdRI7dargC04p2Ha24kofQBk3vhaf0q8FwSYgnCRwxfvh2RxvhJZk9H7Q91JZp6wgjz/SjvEAYjGCEgAwQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1"
|
||||
}
|
||||
},
|
||||
"@jimp/png": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.1.tgz",
|
||||
"integrity": "sha512-iyWoCxEBTW0OUWWn6SveD4LePW89kO7ZOy5sCfYeDM/oTPLpR8iMIGvZpZUz1b8kvzFr27vPst4E5rJhGjwsdw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/utils": "^0.16.1",
|
||||
"pngjs": "^3.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@jimp/tiff": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.1.tgz",
|
||||
"integrity": "sha512-3K3+xpJS79RmSkAvFMgqY5dhSB+/sxhwTFA9f4AVHUK0oKW+u6r52Z1L0tMXHnpbAdR9EJ+xaAl2D4x19XShkQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"utif": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"@jimp/types": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.1.tgz",
|
||||
"integrity": "sha512-g1w/+NfWqiVW4CaXSJyD28JQqZtm2eyKMWPhBBDCJN9nLCN12/Az0WFF3JUAktzdsEC2KRN2AqB1a2oMZBNgSQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@jimp/bmp": "^0.16.1",
|
||||
"@jimp/gif": "^0.16.1",
|
||||
"@jimp/jpeg": "^0.16.1",
|
||||
"@jimp/png": "^0.16.1",
|
||||
"@jimp/tiff": "^0.16.1",
|
||||
"timm": "^1.6.1"
|
||||
}
|
||||
},
|
||||
"@jimp/utils": {
|
||||
"version": "0.16.1",
|
||||
"resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.16.1.tgz",
|
||||
"integrity": "sha512-8fULQjB0x4LzUSiSYG6ZtQl355sZjxbv8r9PPAuYHzS9sGiSHJQavNqK/nKnpDsVkU88/vRGcE7t3nMU0dEnVw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"regenerator-runtime": "^0.13.3"
|
||||
}
|
||||
},
|
||||
"@react-native-async-storage/async-storage": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.13.2.tgz",
|
||||
@@ -2166,15 +2050,10 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"@types/lodash": {
|
||||
"version": "4.14.166",
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.166.tgz",
|
||||
"integrity": "sha512-A3YT/c1oTlyvvW/GQqG86EyqWNrT/tisOIh2mW3YCgcx71TNjiTZA3zYZWA5BCmtsOTXjhliy4c4yEkErw6njA=="
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "14.14.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz",
|
||||
"integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="
|
||||
"version": "14.14.35",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
|
||||
"integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag=="
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
@@ -2303,11 +2182,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz",
|
||||
"integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768="
|
||||
},
|
||||
"any-base": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz",
|
||||
"integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="
|
||||
},
|
||||
"anymatch": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
|
||||
@@ -2583,11 +2457,6 @@
|
||||
"resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.18.0.tgz",
|
||||
"integrity": "sha512-vE52okJvzsVWhcgUHOv+69OG3Mdg151xyn41aVQN/5W5S+S43qZhxECtYLAEHMSFWX6Mv5IZrzj3T5+JqXfj5Q=="
|
||||
},
|
||||
"bmp-js": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
||||
"integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
|
||||
},
|
||||
"bplist-creator": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.0.8.tgz",
|
||||
@@ -2660,15 +2529,6 @@
|
||||
"node-int64": "^0.4.0"
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
|
||||
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.1.13"
|
||||
}
|
||||
},
|
||||
"buffer-alloc": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
|
||||
@@ -2688,11 +2548,6 @@
|
||||
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
|
||||
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
|
||||
},
|
||||
"buffer-equal": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
|
||||
"integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs="
|
||||
},
|
||||
"buffer-fill": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
|
||||
@@ -3236,11 +3091,6 @@
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@@ -3408,11 +3258,6 @@
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"exif-parser": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz",
|
||||
"integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI="
|
||||
},
|
||||
"expand-brackets": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
|
||||
@@ -3857,11 +3702,6 @@
|
||||
"escape-string-regexp": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"file-type": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz",
|
||||
"integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw=="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
@@ -4042,15 +3882,6 @@
|
||||
"resolved": "https://registry.npmjs.org/getenv/-/getenv-0.7.0.tgz",
|
||||
"integrity": "sha1-ObkYOHB+IIb9HPbvh3fRyT4UZJ4="
|
||||
},
|
||||
"gifwrap": {
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.2.tgz",
|
||||
"integrity": "sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA==",
|
||||
"requires": {
|
||||
"image-q": "^1.1.1",
|
||||
"omggif": "^1.0.10"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
|
||||
@@ -4064,15 +3895,6 @@
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"requires": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"globals": {
|
||||
"version": "11.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
|
||||
@@ -4190,16 +4012,6 @@
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
|
||||
},
|
||||
"image-q": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/image-q/-/image-q-1.1.1.tgz",
|
||||
"integrity": "sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY="
|
||||
},
|
||||
"image-size": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz",
|
||||
@@ -4436,11 +4248,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||
},
|
||||
"is-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz",
|
||||
"integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ=="
|
||||
},
|
||||
"is-map": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz",
|
||||
@@ -4899,11 +4706,6 @@
|
||||
"resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.6.tgz",
|
||||
"integrity": "sha512-JNAkmPeB/GS2tCRqUzRPsTOHpGDah7xP18vGJfIjZC+W2sxEHbxgJxetIjIqhjQ3yYbYNEELkM/spKLtwoOSUQ=="
|
||||
},
|
||||
"jpeg-js": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.2.tgz",
|
||||
"integrity": "sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@@ -4985,21 +4787,6 @@
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
|
||||
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="
|
||||
},
|
||||
"load-bmfont": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz",
|
||||
"integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==",
|
||||
"requires": {
|
||||
"buffer-equal": "0.0.1",
|
||||
"mime": "^1.3.4",
|
||||
"parse-bmfont-ascii": "^1.0.3",
|
||||
"parse-bmfont-binary": "^1.0.5",
|
||||
"parse-bmfont-xml": "^1.1.4",
|
||||
"phin": "^2.9.1",
|
||||
"xhr": "^2.0.1",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
|
||||
@@ -5943,14 +5730,6 @@
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
|
||||
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
|
||||
},
|
||||
"min-document": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
|
||||
"requires": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
@@ -6079,27 +5858,6 @@
|
||||
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.12.0.tgz",
|
||||
"integrity": "sha512-HZ3XehqShTFj9gHauRJ3Bri9eiCTOII7/crtXzURtT14NdnOFs9Ia5E82W7z3izVBNx760tqwddxrBJVG52Y1Q=="
|
||||
},
|
||||
"node-vibrant": {
|
||||
"version": "3.1.6",
|
||||
"resolved": "https://registry.npmjs.org/node-vibrant/-/node-vibrant-3.1.6.tgz",
|
||||
"integrity": "sha512-Wlc/hQmBMOu6xon12ZJHS2N3M+I6J8DhrD3Yo6m5175v8sFkVIN+UjhKVRcO+fqvre89ASTpmiFEP3nPO13SwA==",
|
||||
"requires": {
|
||||
"@jimp/custom": "^0.16.1",
|
||||
"@jimp/plugin-resize": "^0.16.1",
|
||||
"@jimp/types": "^0.16.1",
|
||||
"@types/lodash": "^4.14.53",
|
||||
"@types/node": "^10.11.7",
|
||||
"lodash": "^4.17.20",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.17.50",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.50.tgz",
|
||||
"integrity": "sha512-vwX+/ija9xKc/z9VqMCdbf4WYcMTGsI0I/L/6shIF3qXURxZOhPQlPRHtjTpiNhAwn0paMJzlOQqw6mAGEQnTA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"noop-fn": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz",
|
||||
@@ -6242,11 +6000,6 @@
|
||||
"isobject": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"omggif": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz",
|
||||
"integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
@@ -6284,6 +6037,11 @@
|
||||
"is-wsl": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"opencollective-postinstall": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
|
||||
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q=="
|
||||
},
|
||||
"options": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
|
||||
@@ -6333,35 +6091,6 @@
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
|
||||
"integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"parse-bmfont-ascii": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz",
|
||||
"integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU="
|
||||
},
|
||||
"parse-bmfont-binary": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz",
|
||||
"integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY="
|
||||
},
|
||||
"parse-bmfont-xml": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz",
|
||||
"integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==",
|
||||
"requires": {
|
||||
"xml-parse-from-string": "^1.0.0",
|
||||
"xml2js": "^0.4.5"
|
||||
}
|
||||
},
|
||||
"parse-headers": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz",
|
||||
"integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA=="
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
|
||||
@@ -6411,11 +6140,6 @@
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
|
||||
},
|
||||
"phin": {
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz",
|
||||
"integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA=="
|
||||
},
|
||||
"pify": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
|
||||
@@ -6429,21 +6153,6 @@
|
||||
"node-modules-regexp": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"pixelmatch": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz",
|
||||
"integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=",
|
||||
"requires": {
|
||||
"pngjs": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pngjs": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
|
||||
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"pkg-dir": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
|
||||
@@ -6605,11 +6314,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||
@@ -6654,15 +6358,25 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.9.4",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz",
|
||||
"integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ=="
|
||||
"version": "6.10.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz",
|
||||
"integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"side-channel": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.0",
|
||||
"get-intrinsic": "^1.0.2",
|
||||
"object-inspect": "^1.9.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"query-string": {
|
||||
"version": "6.13.7",
|
||||
@@ -6674,11 +6388,6 @@
|
||||
"strict-uri-encode": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
@@ -6699,14 +6408,6 @@
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-color-extractor": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/react-color-extractor/-/react-color-extractor-1.1.2.tgz",
|
||||
"integrity": "sha512-AIWzlSuS8IudaXOFscJzNaY7NeGldoGy8k4spyXriXlXkGwUPk7IWsJDr0ibSYzOFVuTfKyhfiSnc1E5fDAfxw==",
|
||||
"requires": {
|
||||
"node-vibrant": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"react-devtools-core": {
|
||||
"version": "4.10.1",
|
||||
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.10.1.tgz",
|
||||
@@ -7120,12 +6821,13 @@
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-native-input-spinner": {
|
||||
"version": "1.7.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-input-spinner/-/react-native-input-spinner-1.7.7.tgz",
|
||||
"integrity": "sha512-6zYFU2uhChJ+JC/5yujlZu+r5PYQSYIXluUqTNR3jOCKai0dlCkWiz7d3K2rM7Q2SAIiw4bSBR6mWZIuhL+DEg==",
|
||||
"react-native-inappbrowser-reborn": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-inappbrowser-reborn/-/react-native-inappbrowser-reborn-3.5.1.tgz",
|
||||
"integrity": "sha512-pEM9DSvW3LYoI7x1sfr4GtTA1/BSzSoQvHeNRtbJwOpujgRBqnEQrapBwC/24l2pIRhKDWmXGfqc12478IXY3Q==",
|
||||
"requires": {
|
||||
"prop-types": "^15.7.2"
|
||||
"invariant": "^2.2.4",
|
||||
"opencollective-postinstall": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"react-native-iphone-x-helper": {
|
||||
@@ -7154,6 +6856,11 @@
|
||||
"react-native-iphone-x-helper": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"react-native-picker-scrollview": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-picker-scrollview/-/react-native-picker-scrollview-1.0.1.tgz",
|
||||
"integrity": "sha512-6N4IuAwLVDLoO3IUCW0QzWJrafXVi7WylofaPg4PNPfPRFOYAsGKMN5yEPw463tWtmbBxP6vZEIOfGl93d9Aeg=="
|
||||
},
|
||||
"react-native-reanimated": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-1.13.2.tgz",
|
||||
@@ -7301,6 +7008,11 @@
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
@@ -8115,21 +7827,11 @@
|
||||
"resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz",
|
||||
"integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM="
|
||||
},
|
||||
"timm": {
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz",
|
||||
"integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw=="
|
||||
},
|
||||
"tiny-queue": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tiny-queue/-/tiny-queue-0.2.1.tgz",
|
||||
"integrity": "sha1-JaZ/LG4lOyypQZd7XvdELvl6YEY="
|
||||
},
|
||||
"tinycolor2": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
|
||||
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="
|
||||
},
|
||||
"tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
@@ -8394,15 +8096,6 @@
|
||||
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
|
||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI="
|
||||
},
|
||||
"url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
"integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
|
||||
"requires": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
@@ -8425,14 +8118,6 @@
|
||||
"object-assign": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"utif": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz",
|
||||
"integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==",
|
||||
"requires": {
|
||||
"pako": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -8629,17 +8314,6 @@
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"xhr": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
|
||||
"integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==",
|
||||
"requires": {
|
||||
"global": "~4.4.0",
|
||||
"is-function": "^1.0.1",
|
||||
"parse-headers": "^2.0.0",
|
||||
"xtend": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"xml-js": {
|
||||
"version": "1.6.11",
|
||||
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
|
||||
@@ -8648,27 +8322,6 @@
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"xml-parse-from-string": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz",
|
||||
"integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
|
||||
"integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~11.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"xmlbuilder": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
|
||||
+54
-51
@@ -1,53 +1,56 @@
|
||||
{
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.2",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/netinfo": "^5.9.7",
|
||||
"@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": "~40.0.0",
|
||||
"expo-app-loading": "^1.0.1",
|
||||
"expo-font": "^8.4.0",
|
||||
"expo-linear-gradient": "^8.4.0",
|
||||
"expo-sharing": "~8.5.0",
|
||||
"expo-sqlite": "~8.5.0",
|
||||
"expo-status-bar": "~1.0.3",
|
||||
"expo-web-browser": "~8.6.0",
|
||||
"moment": "^2.29.1",
|
||||
"react": "16.13.1",
|
||||
"react-color-extractor": "^1.1.2",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz",
|
||||
"react-native-collapsible-header-views": "^1.1.2",
|
||||
"react-native-gesture-handler": "^1.8.0",
|
||||
"react-native-input-spinner": "^1.7.7",
|
||||
"react-native-offline": "^5.7.0",
|
||||
"react-native-paper": "^4.5.0",
|
||||
"react-native-reanimated": "^1.13.2",
|
||||
"react-native-safe-area-context": "3.1.9",
|
||||
"react-native-screens": "~2.15.0",
|
||||
"react-native-vector-icons": "^7.1.0",
|
||||
"react-native-web": "~0.13.12",
|
||||
"react-native-webview": "11.0.0",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0"
|
||||
},
|
||||
"private": true
|
||||
"main": "node_modules/expo/AppEntry.js",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"eject": "expo eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-native-async-storage/async-storage": "^1.13.2",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-native-community/netinfo": "^5.9.7",
|
||||
"@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": "~40.0.0",
|
||||
"expo-app-loading": "^1.0.1",
|
||||
"expo-font": "^8.4.0",
|
||||
"expo-linear-gradient": "^8.4.0",
|
||||
"expo-sharing": "~8.5.0",
|
||||
"expo-sqlite": "~8.5.0",
|
||||
"expo-status-bar": "~1.0.3",
|
||||
"expo-web-browser": "~8.6.0",
|
||||
"moment": "^2.29.1",
|
||||
"qs": "^6.10.1",
|
||||
"react": "16.13.1",
|
||||
"react-dom": "16.13.1",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.1.tar.gz",
|
||||
"react-native-collapsible-header-views": "^1.1.2",
|
||||
"react-native-gesture-handler": "^1.8.0",
|
||||
"react-native-inappbrowser-reborn": "^3.5.1",
|
||||
"react-native-offline": "^5.7.0",
|
||||
"react-native-paper": "^4.5.0",
|
||||
"react-native-picker-scrollview": "^1.0.1",
|
||||
"react-native-reanimated": "^1.13.2",
|
||||
"react-native-safe-area-context": "3.1.9",
|
||||
"react-native-screens": "~2.15.0",
|
||||
"react-native-vector-icons": "^7.1.0",
|
||||
"react-native-web": "~0.13.12",
|
||||
"react-native-webview": "11.0.0",
|
||||
"react-redux": "^7.2.2",
|
||||
"redux": "^4.0.5",
|
||||
"redux-devtools-extension": "^2.13.9",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rn-sliding-up-panel": "^2.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "~7.9.0",
|
||||
"@types/node": "^14.14.35"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ import Constants from "expo-constants";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const Appbar = ({ title, onBackAction }) => {
|
||||
export const Appbar = ({ title, onBackAction, style }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<MaterialAppbar.Header style={{ backgroundColor: theme.colorPrimary }}>
|
||||
<MaterialAppbar.Header
|
||||
style={[{ backgroundColor: theme.colorPrimary }, style]}
|
||||
>
|
||||
{onBackAction && (
|
||||
<MaterialAppbar.BackAction onPress={onBackAction} />
|
||||
)}
|
||||
@@ -34,7 +36,7 @@ export const SearchAppbar = ({
|
||||
getNovels,
|
||||
setLoading,
|
||||
screen,
|
||||
onFilter,
|
||||
openWebView,
|
||||
}) => {
|
||||
const searchRef = useRef(null);
|
||||
|
||||
@@ -110,17 +112,15 @@ export const SearchAppbar = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
disabled={typeof onFilter === "function" ? false : true}
|
||||
/**
|
||||
* TODO
|
||||
*/
|
||||
onPress={() => onFilter?.()}
|
||||
/>
|
||||
{openWebView && (
|
||||
<IconButton
|
||||
icon="earth"
|
||||
color={theme.textColorSecondary}
|
||||
style={{ marginRight: 0 }}
|
||||
size={23}
|
||||
onPress={openWebView}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
@@ -10,9 +10,7 @@ const EmptyView = ({ icon, description }) => {
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewIcon,
|
||||
{
|
||||
color: theme.textColorSecondary,
|
||||
},
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{icon}
|
||||
@@ -20,9 +18,7 @@ const EmptyView = ({ icon, description }) => {
|
||||
<Text
|
||||
style={[
|
||||
styles.emptyViewText,
|
||||
{
|
||||
color: theme.textColorSecondary,
|
||||
},
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{description}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { List, Divider as PaperDivider } from "react-native-paper";
|
||||
|
||||
const ListSection = ({ children }) => (
|
||||
<List.Section style={styles.listSection}>{children}</List.Section>
|
||||
);
|
||||
|
||||
const ListSubHeader = ({ children, theme }) => (
|
||||
<List.Subheader
|
||||
style={[styles.listSubHeader, { color: theme.colorAccentDark }]}
|
||||
>
|
||||
{children}
|
||||
</List.Subheader>
|
||||
);
|
||||
|
||||
const ListItem = ({ title, description, icon, onPress, theme, right }) => (
|
||||
<List.Item
|
||||
title={title}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
description={description}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
descriptionNumberOfLines={1}
|
||||
left={() =>
|
||||
icon && (
|
||||
<List.Icon
|
||||
color={theme.colorAccentDark}
|
||||
icon={icon}
|
||||
style={{ marginVertical: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
right={() =>
|
||||
right && (
|
||||
<List.Icon
|
||||
color="#47a84a"
|
||||
icon={right}
|
||||
style={{ marginVertical: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onPress={onPress}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
);
|
||||
|
||||
const Divider = ({ theme }) => (
|
||||
<PaperDivider
|
||||
style={{
|
||||
backgroundColor: theme.dividerColor,
|
||||
height: 1,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export { ListSection, ListSubHeader, ListItem, Divider };
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
listSection: {
|
||||
flex: 1,
|
||||
marginVertical: 0,
|
||||
},
|
||||
listSubHeader: {
|
||||
paddingBottom: 5,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,274 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground, Image } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const displayMode = useSelector(
|
||||
(state) => state.settingsReducer.displayMode
|
||||
);
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const itemsPerRow = useSelector(
|
||||
(state) => state.settingsReducer.itemsPerRow
|
||||
);
|
||||
|
||||
const height = {
|
||||
1: 550,
|
||||
2: 280,
|
||||
3: 180,
|
||||
4: 140,
|
||||
5: 100,
|
||||
6: 80,
|
||||
7: 70,
|
||||
8: 58,
|
||||
9: 55,
|
||||
10: 50,
|
||||
};
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View style={{ flex: 1 / itemsPerRow }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
{item.novelCover && (
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: height[itemsPerRow] }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
>
|
||||
<View style={{ flexDirection: "row", margin: 4 }}>
|
||||
{item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
flex: 0,
|
||||
backgroundColor: "#47a84a",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
alignSelf: "flex-start",
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
)}
|
||||
{item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
flex: 0,
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
alignSelf: "flex-start",
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
{displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
"transparent",
|
||||
"rgba(0,0,0,0.6)",
|
||||
]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color:
|
||||
"rgba(255,255,255,1)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
)}
|
||||
</ImageBackground>
|
||||
)}
|
||||
{displayMode === 1 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
) : (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.listView}
|
||||
onPress={onPress}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.extensionIcon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
fontSize: 15,
|
||||
paddingRight: 8,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{item.chaptersDownloaded && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
backgroundColor: "#47a84a",
|
||||
color: "#FFFFFF",
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersUnread && {
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersDownloaded}
|
||||
</Text>
|
||||
)}
|
||||
{item.chaptersUnread && (
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
backgroundColor: "#2979FF",
|
||||
color: "#FFFFFF",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
paddingTop: 2,
|
||||
paddingHorizontal: 4,
|
||||
fontSize: 12,
|
||||
},
|
||||
!item.chaptersDownloaded && {
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.chaptersUnread}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// logo: {
|
||||
// height: 180,
|
||||
// },
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
|
||||
linearGradient: {
|
||||
bottom: -1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
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 RadioButton = ({ value, label, theme }) => (
|
||||
<View style={styles.radioButtonContainer}>
|
||||
<MaterialRadioButton
|
||||
value={value}
|
||||
color={theme.colorAccentDark}
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
/>
|
||||
<Text
|
||||
style={[styles.radioButtonLabel, { color: theme.textColorPrimary }]}
|
||||
>
|
||||
{label}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
radioButtonContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingVertical: 8,
|
||||
},
|
||||
radioButtonLabel: {
|
||||
marginLeft: 16,
|
||||
fontSize: 16,
|
||||
},
|
||||
});
|
||||
@@ -1,164 +0,0 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, ImageBackground, Image } from "react-native";
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { TouchableRipple } from "react-native-paper";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const NovelCover = ({ item, onPress, libraryStatus }) => {
|
||||
const displayMode = useSelector(
|
||||
(state) => state.settingsReducer.displayMode
|
||||
);
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const itemsPerRow = useSelector(
|
||||
(state) => state.settingsReducer.itemsPerRow
|
||||
);
|
||||
|
||||
const height = {
|
||||
1: 550,
|
||||
2: 280,
|
||||
3: 180,
|
||||
4: 140,
|
||||
5: 100,
|
||||
6: 80,
|
||||
7: 70,
|
||||
8: 58,
|
||||
9: 55,
|
||||
10: 50,
|
||||
};
|
||||
|
||||
return displayMode !== 2 ? (
|
||||
<View style={{ flex: 1 / itemsPerRow }}>
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.opac}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
{item.novelCover && (
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={{ height: height[itemsPerRow] }}
|
||||
imageStyle={[
|
||||
{ borderRadius: 4 },
|
||||
libraryStatus && { opacity: 0.5 },
|
||||
]}
|
||||
progressiveRenderingEnabled={true}
|
||||
>
|
||||
{displayMode === 0 && (
|
||||
<View style={styles.titleContainer}>
|
||||
<LinearGradient
|
||||
colors={[
|
||||
"transparent",
|
||||
"rgba(0,0,0,0.6)",
|
||||
]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color:
|
||||
"rgba(255,255,255,1)",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</LinearGradient>
|
||||
</View>
|
||||
)}
|
||||
</ImageBackground>
|
||||
)}
|
||||
{displayMode === 1 && (
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.title,
|
||||
{
|
||||
color: theme.textColorPrimary,
|
||||
padding: 4,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
) : (
|
||||
<TouchableRipple
|
||||
borderless
|
||||
centered
|
||||
rippleColor={theme.rippleColor}
|
||||
style={styles.listView}
|
||||
onPress={onPress}
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.extensionIcon}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 16,
|
||||
fontSize: 15,
|
||||
paddingRight: 8,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCover;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
// logo: {
|
||||
// height: 180,
|
||||
// },
|
||||
titleContainer: {
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
borderRadius: 4,
|
||||
},
|
||||
title: {
|
||||
fontFamily: "pt-sans-bold",
|
||||
fontSize: 14,
|
||||
padding: 8,
|
||||
},
|
||||
|
||||
linearGradient: {
|
||||
bottom: -1,
|
||||
borderRadius: 4,
|
||||
},
|
||||
opac: {
|
||||
paddingHorizontal: 4.5,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 42,
|
||||
height: 42,
|
||||
borderRadius: 4,
|
||||
},
|
||||
listView: {
|
||||
flex: 1,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -1,41 +0,0 @@
|
||||
import React from "react";
|
||||
import { Checkbox } from "react-native-paper";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const DisplayCheckbox = ({ displayMode, onPress, value }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const label = {
|
||||
0: "Compact Grid",
|
||||
1: "Comfortable Grid",
|
||||
2: "List",
|
||||
};
|
||||
|
||||
return (
|
||||
<Checkbox.Item
|
||||
label={label[value]}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={displayMode === value ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ThemeCheckbox = ({ onPress, label, checked }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<Checkbox.Item
|
||||
label={label}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={checked ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
import {
|
||||
createNovelTableQuery,
|
||||
createUrlIndexQuery,
|
||||
createLibraryIndexQuery,
|
||||
} from "./tables/NovelTable";
|
||||
import {
|
||||
createChapterTableQuery,
|
||||
createNovelIdIndexQuery,
|
||||
createUnreadChaptersIndexQuery,
|
||||
} from "./tables/ChapterTable";
|
||||
import {
|
||||
createHistoryTableQuery,
|
||||
createChapterIdIndexQuery,
|
||||
} from "./tables/HistoryTable";
|
||||
import { createDownloadTableQuery } from "./tables/DownloadTable";
|
||||
import {
|
||||
createUpdatesTableQuery,
|
||||
updatesSeedDataQuery,
|
||||
} from "./tables/UpdateTable";
|
||||
|
||||
/**
|
||||
* Database Version = 2
|
||||
*/
|
||||
|
||||
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 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");
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,203 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
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)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const getChaptersQuery = (sort, filter) =>
|
||||
`SELECT * FROM chapters WHERE novelId = ? ${filter ?? ""} ${
|
||||
sort ?? "ORDER BY chapterId ASC"
|
||||
}`;
|
||||
|
||||
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)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const getChapterQuery = `SELECT * FROM downloads WHERE downloadChapterId = ?`;
|
||||
|
||||
export const getChapter = 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)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
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)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const isChapterDownloadedQuery = `SELECT * FROM downloads WHERE downloadChapterId=?`;
|
||||
|
||||
export const isChapterDownloaded = async (chapterId) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
isChapterDownloadedQuery,
|
||||
[chapterId],
|
||||
(txObj, res) => {
|
||||
if (res.rows.length !== 0) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const downloadChapterQuery = `INSERT INTO downloads (downloadChapterId, chapterName, chapterText, prevChapter, nextChapter) VALUES (?, ?, ?, ?, ?)`;
|
||||
|
||||
export const downloadChapter = async (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterId
|
||||
) => {
|
||||
const downloadUrl = `https://lnreader-extensions.vercel.app/api/${extensionId}/novel/${novelUrl}${chapterUrl}`;
|
||||
|
||||
// console.log(downloadUrl);
|
||||
|
||||
const response = await fetch(downloadUrl);
|
||||
const chapter = await response.json();
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`UPDATE chapters SET downloaded = 1 WHERE chapterId = ?`,
|
||||
[chapterId]
|
||||
);
|
||||
tx.executeSql(
|
||||
downloadChapterQuery,
|
||||
[
|
||||
chapterId,
|
||||
chapter.chapterName,
|
||||
chapter.chapterText,
|
||||
chapter.prevChapter,
|
||||
chapter.nextChapter,
|
||||
],
|
||||
(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=?`;
|
||||
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
updateIsDownloadedQuery,
|
||||
[chapterId],
|
||||
(tx, res) => console.log(`Chaptermarked not downapdeod deleted`),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
tx.executeSql(
|
||||
deleteChapterQuery,
|
||||
[chapterId],
|
||||
(tx, res) => console.log(`Chapter deleted`),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
// export const deleteAllChapters = async (novelId) => {
|
||||
// const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE novelId=?`;
|
||||
// const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId=?`;
|
||||
|
||||
// db.transaction((tx) => {
|
||||
// tx.executeSql(
|
||||
// updateIsDownloadedQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chaptermarked not downapdeod deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// tx.executeSql(
|
||||
// deleteChapterQuery,
|
||||
// [chapterId],
|
||||
// (tx, res) => console.log(`Chapter deleted`),
|
||||
// (txObj, error) => console.log("Error ", error)
|
||||
// );
|
||||
// });
|
||||
// };
|
||||
|
||||
const getLastReadChapterQuery = `
|
||||
SELECT chapters.*
|
||||
FROM history
|
||||
JOIN chapters
|
||||
ON history.historyChapterId = chapters.chapterId
|
||||
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)
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const getHistoryQuery = `
|
||||
SELECT history.*, chapters.*, novels.*
|
||||
FROM history
|
||||
JOIN chapters
|
||||
ON history.historyChapterId = chapters.chapterId
|
||||
JOIN novels
|
||||
ON history.historyNovelId = novels.novelId
|
||||
ORDER BY history.historyTimeRead DESC`;
|
||||
|
||||
export const getHistory = async () => {
|
||||
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,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteHistory = async (novelId) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql("DELETE FROM history WHERE historyNovelId = ?", [
|
||||
novelId,
|
||||
]);
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteAllHistory = () => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql("DELETE FROM history; VACCUM;");
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const getLibraryQuery = `
|
||||
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
|
||||
FROM novels
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
|
||||
FROM chapters
|
||||
WHERE chapters.read = 0
|
||||
GROUP BY chapters.novelId
|
||||
) AS C
|
||||
ON novels.novelId = C.novelId
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
|
||||
FROM chapters
|
||||
WHERE chapters.downloaded = 1
|
||||
GROUP BY chapters.novelId
|
||||
) AS D
|
||||
ON novels.novelId = D.novelId
|
||||
WHERE novels.followed = 1
|
||||
GROUP BY novels.novelId
|
||||
`;
|
||||
|
||||
export const getLibrary = () => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
getLibraryQuery,
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => {
|
||||
// console.log(_array);
|
||||
resolve(_array);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// const searchLibraryQuery = ``;
|
||||
|
||||
export const searchLibrary = (searchText) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`
|
||||
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
|
||||
FROM novels
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
|
||||
FROM chapters
|
||||
WHERE chapters.read = 0
|
||||
GROUP BY chapters.novelId
|
||||
) AS C
|
||||
ON novels.novelId = C.novelId
|
||||
LEFT JOIN (
|
||||
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
|
||||
FROM chapters
|
||||
WHERE chapters.downloaded = 1
|
||||
GROUP BY chapters.novelId
|
||||
) AS D
|
||||
ON novels.novelId = D.novelId
|
||||
WHERE novels.followed = 1 AND novelName LIKE '%${searchText}%'
|
||||
GROUP BY novels.novelId
|
||||
`,
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => resolve(_array),
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
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 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)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const checkNovelInCacheQuery = `SELECT * FROM novels WHERE novelUrl=? LIMIT 1`;
|
||||
|
||||
export const checkNovelInCache = (novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
checkNovelInCacheQuery,
|
||||
[novelUrl],
|
||||
(txObj, res) => {
|
||||
if (res.rows.length !== 0) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
export const getNovel = async (novelUrl) => {
|
||||
return new Promise((resolve, reject) =>
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
`SELECT * FROM novels WHERE novelUrl = ?`,
|
||||
[novelUrl],
|
||||
(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) => {
|
||||
console.log("Cleared Novel Cache");
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
import * as SQLite from "expo-sqlite";
|
||||
const db = SQLite.openDatabase("lnreader.db");
|
||||
|
||||
const getUpdatesQuery = `
|
||||
SELECT chapters.chapterId, chapters.chapterUrl, chapters.chapterName, novels.novelUrl, novels.novelId, novels.novelCover, novels.novelName, novels.sourceId, updates.updateTime, updates.updateId
|
||||
FROM updates
|
||||
JOIN chapters
|
||||
ON updates.chapterId = chapters.chapterId
|
||||
JOIN novels
|
||||
ON updates.novelId = novels.novelId
|
||||
ORDER BY updates.updateTime DESC`;
|
||||
|
||||
export const getUpdates = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
getUpdatesQuery,
|
||||
null,
|
||||
(txObj, { rows: { _array } }) => {
|
||||
resolve(_array);
|
||||
},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteNovelUpdates = (novelId) => {
|
||||
db.transaction((tx) => {
|
||||
tx.executeSql(
|
||||
"DELETE FROM updates WHERE novelId = ?",
|
||||
[novelId],
|
||||
(txObj, res) => {},
|
||||
(txObj, error) => console.log("Error ", error)
|
||||
);
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
export const createChapterTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS chapters(
|
||||
chapterId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chapterUrl TEXT,
|
||||
novelId INTEGER NOT NULL,
|
||||
chapterName TEXT NOT NULL,
|
||||
releaseDate TEXT,
|
||||
bookmark BOOLEAN NOT NULL DEFAULT 0,
|
||||
\`read\` INTEGER NOT NULL DEFAULT 0,
|
||||
downloaded INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE(novelId, chapterUrl),
|
||||
FOREIGN KEY (novelId) REFERENCES novels(novelId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
|
||||
export const createNovelIdIndexQuery = `CREATE INDEX IF NOT EXISTS chapterNovelIdIndex ON chapters(novelId)`;
|
||||
|
||||
export const createUnreadChaptersIndexQuery = `CREATE INDEX IF NOT EXISTS chapterUnreadByNovelIndex ON chapters(novelId, \`read\`)`;
|
||||
@@ -0,0 +1,11 @@
|
||||
export const createDownloadTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS downloads(
|
||||
downloadId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
downloadChapterId INTEGER NOT NULL,
|
||||
chapterName TEXT,
|
||||
chapterText TEXT,
|
||||
nextChapter TEXT,
|
||||
prevChapter TEXT,
|
||||
FOREIGN KEY (downloadChapterId) REFERENCES chapters(chapterId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
@@ -0,0 +1,11 @@
|
||||
export const createHistoryTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS history(
|
||||
historyId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
historyChapterId INTEGER NOT NULL,
|
||||
historyNovelId INTEGER NOT NULL UNIQUE,
|
||||
historyTimeRead DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (historyChapterId) REFERENCES chapters(chapterId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
|
||||
export const createChapterIdIndexQuery = `CREATE INDEX IF NOT EXISTS historyChapterIdIndex ON history(historyChapterId)`;
|
||||
@@ -0,0 +1,21 @@
|
||||
export const createNovelTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS novels(
|
||||
novelId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
novelUrl TEXT NOT NULL,
|
||||
sourceUrl TEXT UNIQUE NOT NULL,
|
||||
sourceId INTEGER NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
novelName TEXT NOT NULL,
|
||||
novelCover TEXT NOT NULL,
|
||||
novelSummary TEXT,
|
||||
author TEXT,
|
||||
artist TEXT,
|
||||
status TEXT,
|
||||
genre TEXT,
|
||||
followed INTEGER DEFAULT 0,
|
||||
unread INTEGER DEFAULT 1
|
||||
)`;
|
||||
|
||||
export const createUrlIndexQuery = `CREATE INDEX IF NOT EXISTS novelUrlIndex ON novels(novelUrl)`;
|
||||
|
||||
export const createLibraryIndexQuery = `CREATE INDEX IF NOT EXISTS novelFollowedIndex ON novels(followed)`;
|
||||
@@ -0,0 +1,11 @@
|
||||
export const createUpdatesTableQuery = `
|
||||
CREATE TABLE IF NOT EXISTS updates(
|
||||
updateId INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chapterId INTEGER NOT NULL,
|
||||
novelId INTEGER NOT NULL,
|
||||
updateTime DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(novelId, chapterId),
|
||||
FOREIGN KEY (chapterId) REFERENCES chapters(chapterId),
|
||||
FOREIGN KEY (novelId) REFERENCES novels(novelId)
|
||||
ON DELETE CASCADE
|
||||
)`;
|
||||
+16
-31
@@ -10,19 +10,19 @@ import { setStatusBarStyle } from "expo-status-bar";
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
|
||||
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 About from "../screens/More/About";
|
||||
import Settings from "../screens/More/Settings";
|
||||
import Trackers from "../screens/More/Trackers";
|
||||
|
||||
import NovelItem from "../screens/Novel/Novel";
|
||||
import ChapterItem from "../screens/Chapter/Chapter";
|
||||
|
||||
// Extensions
|
||||
import BoxNovel from "../screens/Extensions/boxnovel/BoxNovel";
|
||||
import ReadLightNovel from "../screens/Extensions/readlightnovel/ReadLightNovel";
|
||||
import Extension from "../screens/Extension/Extension";
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
|
||||
@@ -33,28 +33,12 @@ const stackNavigatorConfig = {
|
||||
...TransitionPresets.RevealFromBottomAndroid,
|
||||
};
|
||||
|
||||
const MoreStack = () => {
|
||||
const SettingsStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="More" component={More} />
|
||||
<Stack.Screen name="About" component={About} />
|
||||
<Stack.Screen name="Settings" component={Settings} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const BoxNovelStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="BoxNovel" component={BoxNovel} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
|
||||
const ReadLightNovelStack = () => {
|
||||
return (
|
||||
<Stack.Navigator screenOptions={stackNavigatorConfig}>
|
||||
<Stack.Screen name="ReadLightNovel" component={ReadLightNovel} />
|
||||
<Stack.Screen name="Trackers" component={Trackers} />
|
||||
</Stack.Navigator>
|
||||
);
|
||||
};
|
||||
@@ -83,7 +67,7 @@ const BottomNavigator = () => {
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{/*
|
||||
|
||||
<Tab.Screen
|
||||
name="Updates"
|
||||
component={Updates}
|
||||
@@ -96,7 +80,7 @@ const BottomNavigator = () => {
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/> */}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="History"
|
||||
component={History}
|
||||
@@ -125,7 +109,7 @@ const BottomNavigator = () => {
|
||||
/>
|
||||
<Tab.Screen
|
||||
name="More"
|
||||
component={MoreStack}
|
||||
component={More}
|
||||
options={{
|
||||
tabBarIcon: ({ color }) => (
|
||||
<MaterialCommunityIcons
|
||||
@@ -141,9 +125,9 @@ const BottomNavigator = () => {
|
||||
};
|
||||
|
||||
const Router = () => {
|
||||
const themeCode = useSelector((state) => state.themeReducer.themeCode);
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
useEffect(() => setStatusBarStyle(themeCode === 1 ? "dark" : "light"), []);
|
||||
useEffect(() => setStatusBarStyle(theme.id === 1 ? "dark" : "light"), []);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
@@ -164,11 +148,12 @@ const Router = () => {
|
||||
headerTintColor: "white",
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen name="BoxNovelStack" component={BoxNovelStack} />
|
||||
<Stack.Screen
|
||||
name="ReadLightNovelStack"
|
||||
component={ReadLightNovelStack}
|
||||
/>
|
||||
<Stack.Screen name="SettingsStack" component={SettingsStack} />
|
||||
<Stack.Screen name="Extension" component={Extension} />
|
||||
{/* <Stack.Screen
|
||||
name="ExtensionStack"
|
||||
component={ExtensionStack}
|
||||
/> */}
|
||||
</Stack.Navigator>
|
||||
</Provider>
|
||||
);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { GET_EXTENSIONS } from "./types";
|
||||
import { fetchExtensionList } from "../../services/api";
|
||||
|
||||
export const getExtensions = () => async (dispatch) => {
|
||||
const res = await fetchExtensionList();
|
||||
|
||||
dispatch({
|
||||
type: GET_EXTENSIONS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import {
|
||||
UPDATE_NOVEL_HISTORY,
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
} from "./types";
|
||||
import {
|
||||
getHistoryFromDb,
|
||||
insertIntoHistory,
|
||||
deleteChapterHistory,
|
||||
} from "../../services/db";
|
||||
|
||||
export const getHistory = () => async (dispatch) => {
|
||||
dispatch({ type: LOAD_HISTORY });
|
||||
|
||||
const history = await getHistoryFromDb();
|
||||
|
||||
dispatch({ type: GET_HISTORY, payload: history });
|
||||
};
|
||||
|
||||
export const updateNovelHistory = (chapterUrl, chapterName, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
await insertIntoHistory(chapterUrl, chapterName, novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL_HISTORY,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteChapterFromHistory = (novelUrl) => async (dispatch) => {
|
||||
await deleteChapterHistory(novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: CLEAR_NOVEL_HISTORY,
|
||||
payload: { novelUrl },
|
||||
});
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import { GET_LIBRARY_NOVELS } from "./types";
|
||||
import { getLibraryFromDb, searchInLibrary } from "../../services/db";
|
||||
|
||||
export const getLibraryNovels = () => async (dispatch) => {
|
||||
const res = await getLibraryFromDb();
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const searchLibraryNovels = (searchText) => async (dispatch) => {
|
||||
const res = await searchInLibrary(searchText);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
@@ -1,168 +0,0 @@
|
||||
import { ToastAndroid } from "react-native";
|
||||
|
||||
import {
|
||||
LOADING_NOVEL,
|
||||
GET_NOVEL,
|
||||
GET_CHAPTERS,
|
||||
FETCHING_NOVEL,
|
||||
SET_NOVEL,
|
||||
UPDATE_IN_LIBRARY,
|
||||
CHAPTER_READ,
|
||||
GET_CHAPTER,
|
||||
CHAPTER_LOADING,
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
} from "./types";
|
||||
import {
|
||||
checkNovelInDb,
|
||||
getChaptersFromDb,
|
||||
getNovelInfoFromDb,
|
||||
insertChaptersInDb,
|
||||
insertNovelInfoInDb,
|
||||
toggleFavourite,
|
||||
downloadChapterFromSource,
|
||||
deleteChapterFromDb,
|
||||
chapterRead,
|
||||
isChapterDownloaded,
|
||||
getChapterFromDb,
|
||||
} from "../../services/db";
|
||||
import {
|
||||
fetchChapterFromSource,
|
||||
fetchChaptersFromSource,
|
||||
fetchNovelFromSource,
|
||||
} from "../../services/api";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
};
|
||||
|
||||
export const getNovel = (inLibrary, extensionId, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: LOADING_NOVEL });
|
||||
|
||||
if (inLibrary === 1) {
|
||||
const chapters = await getChaptersFromDb(novelUrl, "", "");
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const inCache = await checkNovelInDb(novelUrl);
|
||||
|
||||
if (inCache) {
|
||||
const novel = await getNovelInfoFromDb(novelUrl);
|
||||
const chapters = await getChaptersFromDb(novelUrl, "", "");
|
||||
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
} else {
|
||||
const novel = await fetchNovelFromSource(extensionId, novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: { novel, chapters: novel.novelChapters },
|
||||
});
|
||||
|
||||
insertNovelInfoInDb(novel);
|
||||
insertChaptersInDb(novelUrl, novel.novelChapters);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sortAndFilterChapters = (novelUrl, filter, sort) => async (
|
||||
dispatch
|
||||
) => {
|
||||
const chapters = await getChaptersFromDb(novelUrl, filter, sort);
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
};
|
||||
|
||||
export const insertNovelInLibrary = (inLibrary, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
toggleFavourite(inLibrary, novelUrl);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_IN_LIBRARY,
|
||||
payload: !inLibrary,
|
||||
});
|
||||
|
||||
ToastAndroid.show(
|
||||
!inLibrary ? "Added to library" : "Removed from library",
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
};
|
||||
|
||||
export const getChapter = (extensionId, chapterUrl, novelUrl) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: CHAPTER_LOADING });
|
||||
const isDownloaded = await isChapterDownloaded(chapterUrl, novelUrl);
|
||||
let chapter;
|
||||
|
||||
if (isDownloaded) {
|
||||
chapter = await getChapterFromDb(chapterUrl, novelUrl);
|
||||
} else {
|
||||
chapter = await fetchChapterFromSource(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl
|
||||
);
|
||||
}
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const updateChapterRead = (chapterUrl, novelUrl) => async (dispatch) => {
|
||||
await chapterRead(chapterUrl, novelUrl);
|
||||
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterUrl, novelUrl } });
|
||||
};
|
||||
|
||||
export const downloadChapter = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName
|
||||
) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: {
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
},
|
||||
});
|
||||
|
||||
await downloadChapterFromSource(extensionId, novelUrl, chapterUrl);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: { extensionId, novelUrl, chapterUrl },
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteChapter = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName
|
||||
) => async (dispatch) => {
|
||||
await deleteChapterFromDb(novelUrl, chapterUrl);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: { extensionId, novelUrl, chapterUrl },
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
import { SWITCH_THEME } from "./types";
|
||||
|
||||
/**
|
||||
* 0 -> Amoled Dark Theme
|
||||
* 1 -> Light Theme
|
||||
* 2 -> Dark Theme
|
||||
* 3 -> Midnight Dusk Theme
|
||||
*/
|
||||
|
||||
export const switchTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SWITCH_THEME,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { ToastAndroid } from "react-native";
|
||||
|
||||
import {
|
||||
CHAPTER_READ,
|
||||
GET_CHAPTER,
|
||||
CHAPTER_LOADING,
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
} from "./chapter.types";
|
||||
|
||||
import { fetchChapter } from "../../source/Source";
|
||||
|
||||
import {
|
||||
markChapterRead,
|
||||
isChapterDownloaded,
|
||||
getChapter,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
|
||||
export const getChapterAction = (
|
||||
extensionId,
|
||||
chapterUrl,
|
||||
novelUrl,
|
||||
novelId
|
||||
) => async (dispatch) => {
|
||||
dispatch({ type: CHAPTER_LOADING });
|
||||
|
||||
let chapter = await getChapter(novelId);
|
||||
|
||||
if (!chapter) {
|
||||
chapter = await fetchChapter(extensionId, novelUrl, chapterUrl);
|
||||
}
|
||||
console.log(isDownloaded);
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const markChapterReadAction = (chapterId) => async (dispatch) => {
|
||||
await markChapterRead(chapterId);
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterId } });
|
||||
};
|
||||
|
||||
export const downloadChapterAction = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterId
|
||||
) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: {
|
||||
chapterId,
|
||||
},
|
||||
});
|
||||
|
||||
await downloadChapter(extensionId, novelUrl, chapterUrl, chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: { chapterId },
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteChapterAction = (chapterId, chapterName) => async (
|
||||
dispatch
|
||||
) => {
|
||||
await deleteChapter(chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: { chapterId },
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import {
|
||||
CHAPTER_READ,
|
||||
GET_CHAPTER,
|
||||
CHAPTER_LOADING,
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
} from "./chapter.types";
|
||||
|
||||
const initialState = {
|
||||
chapter: null,
|
||||
downloading: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const chapterReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case CHAPTER_READ:
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload.chapterId
|
||||
? { ...chapter, read: 1 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
case CHAPTER_LOADING:
|
||||
return {
|
||||
...state,
|
||||
chapterLoading: true,
|
||||
};
|
||||
case GET_CHAPTER:
|
||||
return { ...state, chapter: payload, chapterLoading: false };
|
||||
case CHAPTER_DOWNLOADING:
|
||||
return {
|
||||
...state,
|
||||
downloading: payload.chapterId,
|
||||
};
|
||||
case CHAPTER_DOWNLOADED:
|
||||
return {
|
||||
...state,
|
||||
downloading: null,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload.chapterId
|
||||
? { ...chapter, downloaded: 1 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
case CHAPTER_DELETED:
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload.chapterId
|
||||
? { ...chapter, downloaded: 0 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default chapterReducer;
|
||||
@@ -0,0 +1,6 @@
|
||||
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_LOADING = "CHAPTER_LOADING";
|
||||
export const GET_CHAPTER = "GET_CHAPTER";
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
UPDATE_NOVEL_HISTORY,
|
||||
CLEAR_NOVEL_HISTORY,
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
} from "./history.types";
|
||||
import {
|
||||
getHistory,
|
||||
insertHistory,
|
||||
deleteHistory,
|
||||
} from "../../database/queries/HistoryQueries";
|
||||
|
||||
export const getHistoryAction = () => async (dispatch) => {
|
||||
dispatch({ type: LOAD_HISTORY });
|
||||
|
||||
const history = await getHistory();
|
||||
|
||||
dispatch({ type: GET_HISTORY, payload: history });
|
||||
};
|
||||
|
||||
export const insertHistoryAction = (novelId, chapterId) => async (dispatch) => {
|
||||
await insertHistory(novelId, chapterId);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL_HISTORY,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteHistoryAction = (novelId) => async (dispatch) => {
|
||||
await deleteHistory(novelId);
|
||||
|
||||
dispatch({
|
||||
type: CLEAR_NOVEL_HISTORY,
|
||||
payload: { novelId },
|
||||
});
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
GET_HISTORY,
|
||||
LOAD_HISTORY,
|
||||
UPDATE_NOVEL_HISTORY,
|
||||
} from "../actions/types";
|
||||
} from "./history.types";
|
||||
|
||||
const initialState = {
|
||||
history: [],
|
||||
@@ -24,7 +24,7 @@ const historyReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
history: state.history.filter(
|
||||
(item) => item.novelUrl !== payload.novelUrl
|
||||
(item) => item.novelId !== payload.novelId
|
||||
),
|
||||
};
|
||||
// return {
|
||||
@@ -0,0 +1,4 @@
|
||||
export const LOAD_HISTORY = "LOAD_HISTORY";
|
||||
export const GET_HISTORY = "GET_HISTORY";
|
||||
export const UPDATE_NOVEL_HISTORY = "UPDATE_NOVEL_HISTORY";
|
||||
export const CLEAR_NOVEL_HISTORY = "CLEAR_NOVEL_HISTORY";
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
} from "./library.types";
|
||||
import {
|
||||
getLibrary,
|
||||
searchLibrary,
|
||||
} from "../../database/queries/LibraryQueries";
|
||||
|
||||
export const getLibraryAction = () => async (dispatch) => {
|
||||
const res = await getLibrary();
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_NOVELS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
export const searchLibraryAction = (searchText) => async (dispatch) => {
|
||||
const res = await searchLibrary(searchText);
|
||||
|
||||
dispatch({
|
||||
type: GET_LIBRARY_SEARCH_RESULTS,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
FOLLOW_NOVEL,
|
||||
UNFOLLOW_NOVEL,
|
||||
UPDATE_IN_LIBRARY,
|
||||
} from "../novel/novel.types";
|
||||
import {
|
||||
GET_LIBRARY_NOVELS,
|
||||
GET_LIBRARY_SEARCH_RESULTS,
|
||||
} from "./library.types";
|
||||
|
||||
const initialState = {
|
||||
novels: [],
|
||||
searchResults: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const libraryReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case GET_LIBRARY_NOVELS:
|
||||
return { ...state, novels: payload, loading: false };
|
||||
case GET_LIBRARY_SEARCH_RESULTS:
|
||||
return { ...state, novels: payload, loading: false };
|
||||
// case FOLLOW_NOVEL:
|
||||
// return {
|
||||
// ...state,
|
||||
// novels: [...state.novels, { ...payload, followed: 1 }],
|
||||
// };
|
||||
// case UNFOLLOW_NOVEL:
|
||||
// return {
|
||||
// ...state,
|
||||
// novels: state.novels.filter(
|
||||
// (novel) => novel.novelId !== payload
|
||||
// ),
|
||||
// };
|
||||
case UPDATE_IN_LIBRARY:
|
||||
return {
|
||||
...state,
|
||||
novels: [...state.novels, { novelUrl: payload.novelUrl }],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default libraryReducer;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
|
||||
export const GET_LIBRARY_SEARCH_RESULTS = "GET_LIBRARY_SEARCH_RESULTS";
|
||||
@@ -0,0 +1,240 @@
|
||||
import { ToastAndroid } from "react-native";
|
||||
|
||||
import {
|
||||
LOADING_NOVEL,
|
||||
GET_NOVEL,
|
||||
GET_CHAPTERS,
|
||||
FETCHING_NOVEL,
|
||||
SET_NOVEL,
|
||||
UPDATE_IN_LIBRARY,
|
||||
CHAPTER_READ,
|
||||
GET_CHAPTER,
|
||||
CHAPTER_LOADING,
|
||||
CHAPTER_DOWNLOADING,
|
||||
CHAPTER_DOWNLOADED,
|
||||
CHAPTER_DELETED,
|
||||
UPDATE_NOVEL,
|
||||
} from "./novel.types";
|
||||
|
||||
import { updateNovel } from "../../services/updates";
|
||||
import { fetchChapter, fetchNovel } from "../../source/Source";
|
||||
import {
|
||||
followNovel,
|
||||
insertNovel,
|
||||
getNovel,
|
||||
} from "../../database/queries/NovelQueries";
|
||||
import {
|
||||
getChapters,
|
||||
insertChapters,
|
||||
markChapterRead,
|
||||
isChapterDownloaded,
|
||||
getChapter,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
} from "../../database/queries/ChapterQueries";
|
||||
import { deleteNovelUpdates } from "../../database/queries/UpdateQueries";
|
||||
|
||||
export const setNovel = (novel) => async (dispatch) => {
|
||||
dispatch({ type: SET_NOVEL, payload: novel });
|
||||
};
|
||||
|
||||
export const getNovelAction = (
|
||||
followed,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
sort,
|
||||
filter
|
||||
) => async (dispatch) => {
|
||||
dispatch({ type: LOADING_NOVEL });
|
||||
|
||||
if (followed === 1) {
|
||||
const chapters = await getChapters(novelId, sort, filter);
|
||||
|
||||
dispatch({
|
||||
type: GET_CHAPTERS,
|
||||
payload: chapters,
|
||||
});
|
||||
} else {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
const novel = await getNovel(novelUrl);
|
||||
|
||||
if (novel) {
|
||||
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(novelUrl);
|
||||
novel.chapters = await getChapters(novel.novelId);
|
||||
|
||||
dispatch({
|
||||
type: GET_NOVEL,
|
||||
payload: novel,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const sortAndFilterChapters = (novelUrl, filter, sort) => async (
|
||||
dispatch
|
||||
) => {
|
||||
// const chapters = await getChaptersFromDb(novelUrl, filter, sort);
|
||||
// dispatch({
|
||||
// type: GET_CHAPTERS,
|
||||
// payload: chapters,
|
||||
// });
|
||||
};
|
||||
|
||||
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 },
|
||||
});
|
||||
|
||||
ToastAndroid.show(
|
||||
!novel.followed ? "Added to library" : "Removed from library",
|
||||
ToastAndroid.SHORT
|
||||
);
|
||||
};
|
||||
|
||||
export const getChapterAction = (
|
||||
extensionId,
|
||||
chapterUrl,
|
||||
novelUrl,
|
||||
novelId
|
||||
) => async (dispatch) => {
|
||||
dispatch({ type: CHAPTER_LOADING });
|
||||
|
||||
const isDownloaded = await isChapterDownloaded(novelId);
|
||||
let chapter;
|
||||
|
||||
if (isDownloaded) {
|
||||
chapter = await getChapter(novelId);
|
||||
} else {
|
||||
chapter = await fetchChapter(extensionId, novelUrl, chapterUrl);
|
||||
}
|
||||
dispatch({ type: GET_CHAPTER, payload: chapter });
|
||||
};
|
||||
|
||||
export const markChapterReadAction = (chapterId) => async (dispatch) => {
|
||||
await markChapterRead(chapterId);
|
||||
dispatch({ type: CHAPTER_READ, payload: { chapterId } });
|
||||
};
|
||||
|
||||
export const downloadChapterAction = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapterUrl,
|
||||
chapterName,
|
||||
chapterId
|
||||
) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
await downloadChapter(extensionId, novelUrl, chapterUrl, chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Downloaded ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const downloadAllChaptersAction = (
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapters
|
||||
) => async (dispatch) => {
|
||||
await chapters.map((chapter, index) => {
|
||||
setTimeout(async () => {
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADING,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
|
||||
if (!chapter.downloaded) {
|
||||
await downloadChapter(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterId
|
||||
);
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DOWNLOADED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
}, 1000 * index);
|
||||
});
|
||||
|
||||
ToastAndroid.show(`All chapters downloaded`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteChapterAction = (chapterId, chapterName) => async (
|
||||
dispatch
|
||||
) => {
|
||||
await deleteChapter(chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: chapterId,
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted ${chapterName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const deleteAllChaptersAction = (chapters) => async (dispatch) => {
|
||||
await chapters.map((chapter) => {
|
||||
deleteChapter(chapter.chapterId);
|
||||
|
||||
dispatch({
|
||||
type: CHAPTER_DELETED,
|
||||
payload: chapter.chapterId,
|
||||
});
|
||||
});
|
||||
|
||||
ToastAndroid.show(`Deleted all chapters`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
export const updateNovelAction = (extensionId, novelUrl, novelId) => async (
|
||||
dispatch
|
||||
) => {
|
||||
dispatch({ type: FETCHING_NOVEL });
|
||||
|
||||
await updateNovel(extensionId, novelUrl, novelId);
|
||||
|
||||
let novel = await getNovel(novelUrl);
|
||||
let chapters = await getChapters(novel.novelId);
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_NOVEL,
|
||||
payload: { novel, chapters },
|
||||
});
|
||||
};
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
UPDATE_IN_LIBRARY,
|
||||
CHAPTER_READ,
|
||||
CHAPTER_LOADING,
|
||||
} from "../actions/types";
|
||||
UPDATE_NOVEL,
|
||||
UPDATE_LAST_READ,
|
||||
} from "./novel.types";
|
||||
|
||||
const initialState = {
|
||||
novel: null,
|
||||
@@ -30,13 +32,13 @@ const novelReducer = (state = initialState, action) => {
|
||||
case LOADING_NOVEL:
|
||||
return { ...state, loading: true };
|
||||
case FETCHING_NOVEL:
|
||||
return { ...state, loading: true, fetching: true };
|
||||
return { ...state, fetching: true };
|
||||
case SET_NOVEL:
|
||||
return { ...state, novel: payload };
|
||||
case GET_NOVEL:
|
||||
return {
|
||||
...state,
|
||||
novel: payload.novel,
|
||||
novel: payload,
|
||||
chapters: payload.chapters,
|
||||
loading: false,
|
||||
fetching: false,
|
||||
@@ -51,15 +53,21 @@ const novelReducer = (state = initialState, action) => {
|
||||
case UPDATE_IN_LIBRARY:
|
||||
return {
|
||||
...state,
|
||||
novel: { ...state.novel, libraryStatus: payload },
|
||||
novel: { ...state.novel, followed: payload.followed },
|
||||
};
|
||||
case UPDATE_NOVEL:
|
||||
return {
|
||||
...state,
|
||||
novel: payload.novel,
|
||||
chapters: payload.chapters,
|
||||
loading: false,
|
||||
fetching: false,
|
||||
};
|
||||
|
||||
case CHAPTER_READ:
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterUrl === payload.chapterUrl &&
|
||||
chapter.novelUrl === payload.novelUrl
|
||||
chapter.chapterId === payload.chapterId
|
||||
? { ...chapter, read: 1 }
|
||||
: chapter
|
||||
),
|
||||
@@ -74,19 +82,18 @@ const novelReducer = (state = initialState, action) => {
|
||||
case CHAPTER_DOWNLOADING:
|
||||
return {
|
||||
...state,
|
||||
downloading: [...state.downloading, payload],
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 3 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
case CHAPTER_DOWNLOADED:
|
||||
return {
|
||||
...state,
|
||||
downloading: state.downloading.filter(
|
||||
(chapter) =>
|
||||
chapter.chapterUrl === payload.chapterUrl &&
|
||||
chapter.novelUrl === payload.novelUrl
|
||||
),
|
||||
// downloading: state.downloading.filter((id) => id === payload),
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterUrl === payload.chapterUrl &&
|
||||
chapter.novelUrl === payload.novelUrl
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 1 }
|
||||
: chapter
|
||||
),
|
||||
@@ -95,12 +102,17 @@ const novelReducer = (state = initialState, action) => {
|
||||
return {
|
||||
...state,
|
||||
chapters: state.chapters.map((chapter) =>
|
||||
chapter.chapterUrl === payload.chapterUrl &&
|
||||
chapter.novelUrl === payload.novelUrl
|
||||
chapter.chapterId === payload
|
||||
? { ...chapter, downloaded: 0 }
|
||||
: chapter
|
||||
),
|
||||
};
|
||||
|
||||
case UPDATE_LAST_READ:
|
||||
return {
|
||||
...state,
|
||||
novel: { ...state.novel, lastRead: payload },
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -1,47 +1,19 @@
|
||||
export const SWITCH_THEME = "SWITCH_THEME";
|
||||
|
||||
export const GET_LIBRARY_NOVELS = "GET_LIBRARY_NOVELS";
|
||||
|
||||
export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
|
||||
|
||||
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
|
||||
|
||||
export const GET_EXTENSIONS = "GET_EXTENSIONS";
|
||||
|
||||
export const LOADING_NOVEL = "LOADING_NOVEL";
|
||||
|
||||
export const FETCHING_NOVEL = "FETCHING_NOVEL";
|
||||
|
||||
export const LOADING_CHAPTERS = "LOADING_CHAPTERS";
|
||||
|
||||
export const SET_NOVEL = "SET_NOVEL";
|
||||
|
||||
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 LOAD_HISTORY = "LOAD_HISTORY";
|
||||
|
||||
export const UPDATE_NOVEL_HISTORY = "UPDATE_NOVEL_HISTORY";
|
||||
|
||||
export const CLEAR_NOVEL_HISTORY = "CLEAR_NOVEL_HISTORY";
|
||||
|
||||
export const GET_HISTORY = "GET_HISTORY";
|
||||
|
||||
export const CHAPTER_READ = "CHAPTER_READ";
|
||||
|
||||
export const CHAPTER_LOADING = "CHAPTER_LOADING";
|
||||
export const GET_CHAPTER = "GET_CHAPTER";
|
||||
|
||||
export const CHAPTER_LOADING = "CHAPTER_LOADING";
|
||||
|
||||
export const UPDATE_READER_THEME = "UPDATE_READER_THEME";
|
||||
|
||||
export const UPDATE_READER_TEXT_SIZE = "UPDATE_READER_TEXT_SIZE";
|
||||
export const FOLLOW_NOVEL = "FOLLOW_NOVEL";
|
||||
export const UNFOLLOW_NOVEL = "UNFOLLOW_NOVEL";
|
||||
@@ -1,17 +0,0 @@
|
||||
const initialState = {
|
||||
novels: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const libraryReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case "GET_LIBRARY_NOVELS":
|
||||
return { novels: payload, loading: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default libraryReducer;
|
||||
@@ -3,7 +3,10 @@ import {
|
||||
SET_ITEMS_PER_ROW,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "./types";
|
||||
UPDATE_READER_TEXT_ALIGN,
|
||||
UPDATE_READER_PADDING,
|
||||
SET_READER_FONT,
|
||||
} from "./settings.types";
|
||||
|
||||
export const setDisplayMode = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
@@ -13,7 +16,6 @@ export const setDisplayMode = (val) => (dispatch) => {
|
||||
};
|
||||
|
||||
export const setItemsPerRow = (val) => (dispatch) => {
|
||||
console.log("called" + val);
|
||||
dispatch({
|
||||
type: SET_ITEMS_PER_ROW,
|
||||
payload: val,
|
||||
@@ -33,3 +35,24 @@ export const updateReaderTextSize = (val) => (dispatch) => {
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderTextAlign = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_TEXT_ALIGN,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateReaderPadding = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: UPDATE_READER_PADDING,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
|
||||
export const setReaderFont = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SET_READER_FONT,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,12 @@
|
||||
import {
|
||||
SET_DISPLAY_MODE,
|
||||
SET_ITEMS_PER_ROW,
|
||||
SET_READER_FONT,
|
||||
UPDATE_READER_PADDING,
|
||||
UPDATE_READER_TEXT_ALIGN,
|
||||
UPDATE_READER_TEXT_SIZE,
|
||||
UPDATE_READER_THEME,
|
||||
} from "../actions/types";
|
||||
} from "./settings.types";
|
||||
|
||||
const initialState = {
|
||||
displayMode: 0,
|
||||
@@ -11,6 +14,9 @@ const initialState = {
|
||||
reader: {
|
||||
theme: 1,
|
||||
textSize: 16,
|
||||
textAlign: "left",
|
||||
padding: 5,
|
||||
fontFamily: null,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -48,6 +54,21 @@ const settingsReducer = (state = initialState, action) => {
|
||||
return { ...state, reader: { ...state.reader, theme: payload } };
|
||||
case UPDATE_READER_TEXT_SIZE:
|
||||
return { ...state, reader: { ...state.reader, textSize: payload } };
|
||||
case UPDATE_READER_TEXT_ALIGN:
|
||||
return {
|
||||
...state,
|
||||
reader: { ...state.reader, textAlign: payload },
|
||||
};
|
||||
case UPDATE_READER_PADDING:
|
||||
return {
|
||||
...state,
|
||||
reader: { ...state.reader, padding: payload },
|
||||
};
|
||||
case SET_READER_FONT:
|
||||
return {
|
||||
...state,
|
||||
reader: { ...state.reader, fontFamily: payload },
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const SET_DISPLAY_MODE = "SET_DISPLAY_MODE";
|
||||
export const SET_ITEMS_PER_ROW = "SET_ITEMS_PER_ROW";
|
||||
export const UPDATE_READER_THEME = "UPDATE_READER_THEME";
|
||||
export const UPDATE_READER_TEXT_SIZE = "UPDATE_READER_TEXT_SIZE";
|
||||
export const UPDATE_READER_TEXT_ALIGN = "UPDATE_READER_TEXT_ALIGN";
|
||||
export const UPDATE_READER_PADDING = "UPDATE_READER_PADDING";
|
||||
export const SET_READER_FONT = "SET_READER_FONT";
|
||||
@@ -0,0 +1,11 @@
|
||||
import { GET_SOURCES } from "./source.types";
|
||||
import { fetchSources } from "../../source/Source";
|
||||
|
||||
export const getSourcesAction = () => async (dispatch) => {
|
||||
const res = await fetchSources();
|
||||
|
||||
dispatch({
|
||||
type: GET_SOURCES,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GET_EXTENSIONS } from "../actions/types";
|
||||
import { GET_SOURCES } from "./source.types";
|
||||
|
||||
const initialState = {
|
||||
extensions: [],
|
||||
@@ -9,7 +9,7 @@ const libraryReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case GET_EXTENSIONS:
|
||||
case GET_SOURCES:
|
||||
return { extensions: payload, loading: false };
|
||||
default:
|
||||
return state;
|
||||
@@ -0,0 +1 @@
|
||||
export const GET_SOURCES = "GET_SOURCES";
|
||||
@@ -3,12 +3,14 @@ import { persistStore, persistReducer } from "redux-persist";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import thunk from "redux-thunk";
|
||||
|
||||
import themeReducer from "../reducers/theme";
|
||||
import settingsReducer from "../reducers/settings";
|
||||
import libraryReducer from "../reducers/library";
|
||||
import extensionReducer from "../reducers/extension";
|
||||
import novelReducer from "../reducers/novel";
|
||||
import historyReducer from "../reducers/history";
|
||||
import themeReducer from "./theme/theme.reducer";
|
||||
import settingsReducer from "./settings/settings.reducer";
|
||||
import libraryReducer from "./library/library.reducer";
|
||||
import extensionReducer from "./source/source.reducers";
|
||||
import novelReducer from "./novel/novel.reducer";
|
||||
import historyReducer from "./history/history.reducer";
|
||||
import updatesReducer from "./updates/updates.reducer";
|
||||
import trackerReducer from "./tracker/tracker.reducer";
|
||||
|
||||
const persistConfig = {
|
||||
key: "root",
|
||||
@@ -25,6 +27,8 @@ const persistedReducer = persistReducer(
|
||||
extensionReducer,
|
||||
novelReducer,
|
||||
historyReducer,
|
||||
updatesReducer,
|
||||
trackerReducer,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { SWITCH_THEME } from "./theme.types";
|
||||
|
||||
export const switchTheme = (val) => (dispatch) => {
|
||||
dispatch({
|
||||
type: SWITCH_THEME,
|
||||
payload: val,
|
||||
});
|
||||
};
|
||||
@@ -13,7 +13,6 @@ const themes = {
|
||||
};
|
||||
|
||||
const initialState = {
|
||||
themeCode: 0,
|
||||
theme: themes[0],
|
||||
};
|
||||
|
||||
@@ -22,7 +21,7 @@ const themeReducer = (state = initialState, action) => {
|
||||
|
||||
switch (type) {
|
||||
case "SWITCH_THEME":
|
||||
return { ...state, themeCode: payload, theme: themes[payload] };
|
||||
return { ...state, theme: themes[payload] };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const SWITCH_THEME = "SWITCH_THEME";
|
||||
@@ -0,0 +1,57 @@
|
||||
import { findListItem, updateItem } from "../../trackers/MyAnimeList";
|
||||
import {
|
||||
REMOVE_TRACKER,
|
||||
SET_TRACKER,
|
||||
TRACK_NOVEL,
|
||||
UNTRACK_NOVEL,
|
||||
UPDATE_TRACKER,
|
||||
UPDATE_CHAPTERS_READ,
|
||||
} from "./tracker.types";
|
||||
|
||||
import { updateMalChaptersRead } from "../../trackers/MyAnimeList";
|
||||
|
||||
export const setTracker = (res) => async (dispatch) => {
|
||||
dispatch({
|
||||
type: SET_TRACKER,
|
||||
payload: res,
|
||||
});
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
dispatch({ type: TRACK_NOVEL, payload: trackObj });
|
||||
};
|
||||
|
||||
export const updateTracker = (malId, accessToken, body) => async (dispatch) => {
|
||||
let res = await updateItem(malId, accessToken, body);
|
||||
|
||||
dispatch({ type: UPDATE_TRACKER, payload: { malId, ...res } });
|
||||
};
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_CHAPTERS_READ,
|
||||
payload: { malId, chaptersRead: res },
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
REMOVE_TRACKER,
|
||||
SET_TRACKER,
|
||||
TRACK_NOVEL,
|
||||
UNTRACK_NOVEL,
|
||||
UPDATE_CHAPTERS_READ,
|
||||
UPDATE_TRACKER,
|
||||
} from "./tracker.types";
|
||||
|
||||
const initialState = {
|
||||
tracker: null,
|
||||
trackedNovels: [],
|
||||
};
|
||||
|
||||
const trackerReducer = (state = initialState, 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;
|
||||
}
|
||||
};
|
||||
|
||||
export default trackerReducer;
|
||||
@@ -0,0 +1,9 @@
|
||||
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 UPDATE_TRACKER = "UPDATE_TRACKER";
|
||||
|
||||
export const UPDATE_CHAPTERS_READ = "UPDATE_CHAPTERS_READ";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { GET_UPDATES } from "./updates.types";
|
||||
|
||||
import { getUpdates } from "../../database/queries/UpdateQueries";
|
||||
|
||||
export const getUpdatesAction = () => async (dispatch) => {
|
||||
const updates = await getUpdates();
|
||||
|
||||
dispatch({ type: GET_UPDATES, payload: updates });
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { LOAD_UPDATES, GET_UPDATES } from "./updates.types";
|
||||
|
||||
const initialState = {
|
||||
updates: [],
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const updateReducer = (state = initialState, action) => {
|
||||
const { type, payload } = action;
|
||||
|
||||
switch (type) {
|
||||
case LOAD_UPDATES:
|
||||
return { ...state, loading: true };
|
||||
case GET_UPDATES:
|
||||
return { ...state, updates: payload, loading: false };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default updateReducer;
|
||||
@@ -0,0 +1,2 @@
|
||||
export const GET_UPDATES = "GET_UPDATES";
|
||||
export const LOAD_UPDATES = "LOAD_UPDATES";
|
||||
@@ -1,16 +1,16 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import { getExtensions } from "../../redux/actions/extension";
|
||||
import { getSourcesAction } from "../../redux/source/source.actions";
|
||||
|
||||
import ExtensionCard from "./components/ExtensionCard";
|
||||
|
||||
const Browse = ({ theme, extensions, loading, getExtensions }) => {
|
||||
const Browse = ({ theme, extensions, loading, getSourcesAction }) => {
|
||||
useEffect(() => {
|
||||
getExtensions();
|
||||
}, []);
|
||||
getSourcesAction();
|
||||
}, [getSourcesAction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -46,11 +46,10 @@ const mapStateToProps = (state) => ({
|
||||
loading: state.extensionReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getExtensions })(Browse);
|
||||
export default connect(mapStateToProps, { getSourcesAction })(Browse);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useNavigation } from "@react-navigation/native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const ExtensionCard = ({ item }) => {
|
||||
const { sourceName, sourceCover, sourceLanguage } = item;
|
||||
const { sourceName, sourceCover, sourceLanguage, status } = item;
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
@@ -15,15 +15,19 @@ const ExtensionCard = ({ item }) => {
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.extensionCard}
|
||||
onPress={() => navigation.navigate(sourceName + "Stack")}
|
||||
onPress={() =>
|
||||
navigation.navigate("Extension", {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceUrl: item.sourceUrl,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{
|
||||
uri: sourceCover,
|
||||
}}
|
||||
source={{ uri: sourceCover }}
|
||||
style={styles.extensionIcon}
|
||||
resizeMode="contain"
|
||||
/>
|
||||
@@ -37,14 +41,25 @@ const ExtensionCard = ({ item }) => {
|
||||
>
|
||||
{sourceName}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
{sourceLanguage}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: "#D63B2F",
|
||||
fontSize: 12,
|
||||
marginLeft: 5,
|
||||
}}
|
||||
>
|
||||
{status === 0 && "(Not Working)"}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View>
|
||||
<Button
|
||||
@@ -54,7 +69,11 @@ const ExtensionCard = ({ item }) => {
|
||||
uppercase={false}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() =>
|
||||
navigation.navigate(sourceName + "Stack")
|
||||
navigation.navigate("Extension", {
|
||||
sourceId: item.sourceId,
|
||||
sourceName: item.sourceName,
|
||||
sourceUrl: item.sourceUrl,
|
||||
})
|
||||
}
|
||||
>
|
||||
Browse
|
||||
@@ -75,7 +94,6 @@ const styles = StyleSheet.create({
|
||||
marginVertical: 4,
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 4,
|
||||
},
|
||||
extensionIcon: {
|
||||
width: 40,
|
||||
|
||||
@@ -6,29 +6,53 @@ import { CollapsibleHeaderScrollView } from "react-native-collapsible-header-vie
|
||||
|
||||
import BottomSheet from "./components/BottomSheet";
|
||||
|
||||
import { updateNovelHistory } from "../../redux/actions/history";
|
||||
import { getChapter, updateChapterRead } from "../../redux/actions/novel";
|
||||
import { insertHistoryAction } from "../../redux/history/history.actions";
|
||||
import {
|
||||
getChapterAction,
|
||||
markChapterReadAction,
|
||||
} from "../../redux/novel/novel.actions";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { connect, useSelector, useDispatch } from "react-redux";
|
||||
import { parseChapterNumber } from "../../services/updates";
|
||||
import { updateChaptersRead } from "../../redux/tracker/tracker.actions";
|
||||
|
||||
const ChapterItem = ({
|
||||
route,
|
||||
navigation,
|
||||
theme,
|
||||
updateNovelHistory,
|
||||
updateChapterRead,
|
||||
getChapter,
|
||||
insertHistoryAction,
|
||||
markChapterReadAction,
|
||||
getChapterAction,
|
||||
chapter,
|
||||
loading,
|
||||
reader,
|
||||
}) => {
|
||||
const { extensionId, chapterUrl, novelUrl, chapterName } = route.params;
|
||||
const {
|
||||
chapterId,
|
||||
extensionId,
|
||||
chapterUrl,
|
||||
novelUrl,
|
||||
novelId,
|
||||
} = route.params;
|
||||
|
||||
let _panel = useRef(null);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const trackedNovels = useSelector(
|
||||
(state) => state.trackerReducer.trackedNovels
|
||||
);
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
|
||||
let isTracked = false;
|
||||
|
||||
if (!loading) {
|
||||
isTracked = trackedNovels.find((obj) => obj.novelId === novelId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getChapter(extensionId, chapterUrl, novelUrl);
|
||||
updateNovelHistory(chapterUrl, chapterName, novelUrl);
|
||||
getChapterAction(extensionId, chapterUrl, novelUrl, chapterId);
|
||||
insertHistoryAction(novelId, chapterId);
|
||||
}, []);
|
||||
|
||||
const readerBackground = (val) => {
|
||||
@@ -47,10 +71,14 @@ const ChapterItem = ({
|
||||
return textColor;
|
||||
};
|
||||
|
||||
const chapterNumber = !loading && parseChapterNumber(chapter.chapterName);
|
||||
|
||||
const readerLineHeight = (val) => {
|
||||
const lineHeight = {
|
||||
12: 20,
|
||||
14: 22,
|
||||
16: 25,
|
||||
18: 26,
|
||||
20: 28,
|
||||
};
|
||||
return lineHeight[val];
|
||||
@@ -68,6 +96,8 @@ const ChapterItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
// !loading && parseChapterNumber(chapter.chapterName);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<CollapsibleHeaderScrollView
|
||||
@@ -96,10 +126,12 @@ const ChapterItem = ({
|
||||
size={26}
|
||||
disabled={!chapter.prevChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
navigation.replace("ChapterItem", {
|
||||
chapterUrl: chapter.prevChapter,
|
||||
chapterId: chapterId - 1,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
@@ -110,10 +142,12 @@ const ChapterItem = ({
|
||||
size={26}
|
||||
disabled={!chapter.nextChapter}
|
||||
onPress={() => {
|
||||
navigation.push("ChapterItem", {
|
||||
navigation.replace("ChapterItem", {
|
||||
chapterUrl: chapter.nextChapter,
|
||||
extensionId,
|
||||
novelUrl,
|
||||
novelId,
|
||||
chapterId: chapterId + 1,
|
||||
chapterName: chapter.chapterName,
|
||||
});
|
||||
}}
|
||||
@@ -138,7 +172,21 @@ const ChapterItem = ({
|
||||
]}
|
||||
onScroll={({ nativeEvent }) => {
|
||||
if (isCloseToBottom(nativeEvent)) {
|
||||
updateChapterRead(chapterUrl, novelUrl);
|
||||
markChapterReadAction(chapterId);
|
||||
if (
|
||||
isTracked &&
|
||||
chapterNumber &&
|
||||
chapterNumber >
|
||||
isTracked.my_list_status.num_chapters_read
|
||||
) {
|
||||
dispatch(
|
||||
updateChaptersRead(
|
||||
isTracked.id,
|
||||
tracker.access_token,
|
||||
chapterNumber
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -157,6 +205,11 @@ const ChapterItem = ({
|
||||
fontSize: reader.textSize,
|
||||
color: readerTextColor(reader.theme),
|
||||
lineHeight: readerLineHeight(reader.textSize),
|
||||
textAlign: reader.textAlign,
|
||||
paddingHorizontal: `${reader.padding}%`,
|
||||
},
|
||||
reader.fontFamily && {
|
||||
fontFamily: reader.fontFamily,
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -179,15 +232,14 @@ const mapStateToProps = (state) => ({
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
updateNovelHistory,
|
||||
updateChapterRead,
|
||||
getChapter,
|
||||
insertHistoryAction,
|
||||
markChapterReadAction,
|
||||
getChapterAction,
|
||||
})(ChapterItem);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flexGrow: 1,
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 10,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import React from "react";
|
||||
|
||||
import { Animated, StyleSheet, View, Text, Dimensions } from "react-native";
|
||||
import { ToggleButton } from "react-native-paper";
|
||||
import { ToggleButton, IconButton, Chip } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
import { fonts } from "../../../utils/constants";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
setReaderFont,
|
||||
updateReaderPadding,
|
||||
updateReaderTextAlign,
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
} from "../../../redux/actions/settings";
|
||||
import BottomSheetHandle from "../../../components/common/BottomSheetHandle";
|
||||
} from "../../../redux/settings/settings.actions";
|
||||
import BottomSheetHandle from "../../../components/BottomSheetHandle";
|
||||
|
||||
const ChapterBottomSheet = ({
|
||||
bottomSheetRef,
|
||||
@@ -19,13 +22,16 @@ const ChapterBottomSheet = ({
|
||||
reader,
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
updateReaderTextAlign,
|
||||
updateReaderPadding,
|
||||
setReaderFont,
|
||||
}) => {
|
||||
return (
|
||||
<Bottomsheet
|
||||
animatedValue={new Animated.Value(0)}
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 200, bottom: 0 }}
|
||||
snappingPoints={[0, 200]}
|
||||
draggableRange={{ top: 400, bottom: 0 }}
|
||||
snappingPoints={[0, 400]}
|
||||
showBackdrop={false}
|
||||
>
|
||||
<View style={styles.contentContainer}>
|
||||
@@ -33,7 +39,7 @@ const ChapterBottomSheet = ({
|
||||
<View style={styles.readerSettingsContainer}>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
@@ -47,53 +53,199 @@ const ChapterBottomSheet = ({
|
||||
value={reader.textSize}
|
||||
minimumValue={12}
|
||||
maximumValue={20}
|
||||
step={4}
|
||||
step={2}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
onValueChange={(value) => updateReaderTextSize(value)}
|
||||
/>
|
||||
<Text
|
||||
<View
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontWeight: "bold",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
Reader Theme
|
||||
</Text>
|
||||
<ToggleButton.Row
|
||||
onValueChange={(value) => updateReaderTheme(value)}
|
||||
value={reader.theme}
|
||||
style={{ marginTop: 10 }}
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Reader Theme
|
||||
</Text>
|
||||
<ToggleButton.Row
|
||||
onValueChange={(value) =>
|
||||
updateReaderTheme(value)
|
||||
}
|
||||
value={reader.theme}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#FFFFFF"
|
||||
value={1}
|
||||
style={{
|
||||
backgroundColor: "#000000",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#000000"
|
||||
value={2}
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#000000"
|
||||
value={3}
|
||||
style={{
|
||||
backgroundColor: "#F4ECD8",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
</ToggleButton.Row>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Text Align
|
||||
</Text>
|
||||
<ToggleButton.Row
|
||||
onValueChange={(value) =>
|
||||
updateReaderTextAlign(value ?? "left")
|
||||
}
|
||||
value={reader.textAlign}
|
||||
style={{ marginTop: 10 }}
|
||||
>
|
||||
<ToggleButton
|
||||
icon="format-align-left"
|
||||
color="#000000"
|
||||
value="left"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-align-justify"
|
||||
color="#000000"
|
||||
value="justify"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-align-right"
|
||||
color="#000000"
|
||||
value="right"
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
</ToggleButton.Row>
|
||||
</View>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#FFFFFF"
|
||||
value={1}
|
||||
style={{
|
||||
backgroundColor: "#000000",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
Padding
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
icon="minus"
|
||||
color={theme.colorAccentDark}
|
||||
size={26}
|
||||
disabled={reader.padding <= 0 ? true : false}
|
||||
onPress={() =>
|
||||
updateReaderPadding(reader.padding - 1)
|
||||
}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#000000"
|
||||
value={2}
|
||||
<Text
|
||||
style={{
|
||||
backgroundColor: "#FFFFFF",
|
||||
marginHorizontal: 10,
|
||||
color: "#FFFFFF",
|
||||
paddingHorizontal: 50,
|
||||
}}
|
||||
>
|
||||
{`${reader.padding}%`}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="plus"
|
||||
color={theme.colorAccentDark}
|
||||
size={26}
|
||||
disabled={reader.padding >= 10 ? true : false}
|
||||
onPress={() =>
|
||||
updateReaderPadding(reader.padding + 1)
|
||||
}
|
||||
/>
|
||||
<ToggleButton
|
||||
icon="format-text"
|
||||
color="#000000"
|
||||
value={3}
|
||||
style={{
|
||||
backgroundColor: "#F4ECD8",
|
||||
marginHorizontal: 10,
|
||||
}}
|
||||
/>
|
||||
</ToggleButton.Row>
|
||||
</View>
|
||||
<Text
|
||||
style={{
|
||||
color: "#FFFFFF",
|
||||
fontWeight: "bold",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
Font Style
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
{fonts.map((font, index) => (
|
||||
<Chip
|
||||
key={index}
|
||||
onPress={() => setReaderFont(font.fontFamily)}
|
||||
textStyle={{ fontFamily: font.fontFamily }}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
marginHorizontal: 5,
|
||||
marginVertical: 5,
|
||||
}}
|
||||
selected={
|
||||
reader.fontFamily === font.fontFamily
|
||||
? true
|
||||
: false
|
||||
}
|
||||
>
|
||||
{font.name}
|
||||
</Chip>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
@@ -108,6 +260,9 @@ const mapStateToProps = (state) => ({
|
||||
export default connect(mapStateToProps, {
|
||||
updateReaderTextSize,
|
||||
updateReaderTheme,
|
||||
updateReaderTextAlign,
|
||||
updateReaderPadding,
|
||||
setReaderFont,
|
||||
})(ChapterBottomSheet);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
+35
-18
@@ -1,14 +1,17 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
import { Provider } from "react-native-paper";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
|
||||
import NovelCover from "../../../components/common/NovelCover";
|
||||
import { SearchAppbar } from "../../../components/common/Appbar";
|
||||
import NovelCover from "../../components/NovelCover";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import { SearchAppbar } from "../../components/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import EmptyView from "../../../components/common/EmptyView";
|
||||
|
||||
const ReadLightNovel = ({ navigation }) => {
|
||||
const Extension = ({ navigation, route }) => {
|
||||
const { sourceId, sourceName, sourceUrl } = route.params;
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const library = useSelector((state) => state.libraryReducer.novels);
|
||||
const itemsPerRow = useSelector(
|
||||
@@ -16,13 +19,28 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
);
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const [novels, setNovels] = useState();
|
||||
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const getSourceUrl = () => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/novels/1/?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/novels/`;
|
||||
}
|
||||
};
|
||||
|
||||
const getSearchUrl = () => {
|
||||
if (sourceId === 1) {
|
||||
return `https://lnreader-extensions.vercel.app/api/1/search/?s=${searchText}&?o=rating`;
|
||||
} else {
|
||||
return `https://lnreader-extensions.vercel.app/api/${sourceId}/search/?s=${searchText}`;
|
||||
}
|
||||
};
|
||||
|
||||
const getNovels = () => {
|
||||
fetch(`https://lnreader-extensions.herokuapp.com/api/2/novels/`)
|
||||
fetch(getSourceUrl())
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
@@ -32,9 +50,7 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
|
||||
const getSearchResults = (searchText) => {
|
||||
setLoading(true);
|
||||
fetch(
|
||||
`https://lnreader-extensions.herokuapp.com/api/2/search/?s=${searchText}`
|
||||
)
|
||||
fetch(getSearchUrl())
|
||||
.then((response) => response.json())
|
||||
.then((json) => {
|
||||
setNovels(json);
|
||||
@@ -60,12 +76,13 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
>
|
||||
<SearchAppbar
|
||||
screen="Extension"
|
||||
placeholder="Search ReadLightNovel"
|
||||
placeholder={`Search ${sourceName}`}
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
getSearchResults={getSearchResults}
|
||||
getNovels={getNovels}
|
||||
setLoading={setLoading}
|
||||
openWebView={() => WebBrowser.openBrowserAsync(sourceUrl)}
|
||||
/>
|
||||
|
||||
{loading ? (
|
||||
@@ -87,17 +104,17 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
<NovelCover
|
||||
item={item}
|
||||
onPress={() =>
|
||||
navigation.navigate("NovelItem", item)
|
||||
}
|
||||
libraryStatus={
|
||||
checkIFInLibrary(item.novelUrl)
|
||||
? true
|
||||
: false
|
||||
navigation.navigate("NovelItem", {
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelUrl: item.novelUrl,
|
||||
sourceId: item.extensionId,
|
||||
})
|
||||
}
|
||||
libraryStatus={checkIFInLibrary(item.novelUrl)}
|
||||
/>
|
||||
)}
|
||||
ListEmptyComponent={
|
||||
searchText !== "" &&
|
||||
novels.length === 0 && (
|
||||
<EmptyView
|
||||
icon="(; ̄Д ̄)"
|
||||
@@ -112,7 +129,7 @@ const ReadLightNovel = ({ navigation }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ReadLightNovel;
|
||||
export default Extension;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
@@ -3,35 +3,35 @@ import { useFocusEffect } from "@react-navigation/native";
|
||||
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import HistoryCard from "./components/HistoryCard";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import EmptyView from "../../components/common/EmptyView";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
import {
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
} from "../../redux/actions/history";
|
||||
getHistoryAction,
|
||||
deleteHistoryAction,
|
||||
} from "../../redux/history/history.actions";
|
||||
|
||||
const History = ({
|
||||
navigation,
|
||||
theme,
|
||||
history,
|
||||
loading,
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
getHistoryAction,
|
||||
deleteHistoryAction,
|
||||
}) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getHistory();
|
||||
}, [])
|
||||
getHistoryAction();
|
||||
}, [getHistoryAction])
|
||||
);
|
||||
|
||||
const renderHistoryCard = ({ item }) => (
|
||||
<HistoryCard
|
||||
item={item}
|
||||
deleteHistory={deleteChapterFromHistory}
|
||||
deleteHistoryAction={deleteHistoryAction}
|
||||
navigation={navigation}
|
||||
/>
|
||||
);
|
||||
@@ -79,8 +79,8 @@ const mapStateToProps = (state) => ({
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getHistory,
|
||||
deleteChapterFromHistory,
|
||||
getHistoryAction,
|
||||
deleteHistoryAction,
|
||||
})(History);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -7,7 +7,7 @@ import moment from "moment";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
const HistoryCard = ({ item, deleteHistoryAction, navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
@@ -49,7 +49,7 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
numberOfLines={1}
|
||||
>
|
||||
{`${item.chapterName} - ${moment(
|
||||
item.lastRead
|
||||
item.historyTimeRead
|
||||
).calendar()}`}
|
||||
</Text>
|
||||
</View>
|
||||
@@ -66,7 +66,7 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
style={{
|
||||
marginRight: 0,
|
||||
}}
|
||||
onPress={() => deleteHistory(item.novelUrl)}
|
||||
onPress={() => deleteHistoryAction(item.novelId)}
|
||||
/>
|
||||
<IconButton
|
||||
icon="play"
|
||||
@@ -75,11 +75,11 @@ const HistoryCard = ({ item, deleteHistory, navigation }) => {
|
||||
style={{ marginRight: 10 }}
|
||||
onPress={() =>
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterId: item.historyChapterId,
|
||||
chapterUrl: item.chapterUrl,
|
||||
extensionId: item.extensionId,
|
||||
extensionId: item.sourceId,
|
||||
novelUrl: item.novelUrl,
|
||||
novelName: item.novelName,
|
||||
novelCover: item.novelCover,
|
||||
novelId: item.novelId,
|
||||
chapterName: item.chapterName,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import React, { useState, useCallback, useEffect } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -11,23 +11,24 @@ import { FlatList } from "react-native-gesture-handler";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import NovelCover from "../../components/common/NovelCover";
|
||||
import EmptyView from "../../components/common/EmptyView";
|
||||
import NovelCover from "../../components/NovelCover";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
|
||||
import {
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
} from "../../redux/actions/library";
|
||||
import { SearchAppbar } from "../../components/common/Appbar";
|
||||
import { setNovel } from "../../redux/actions/novel";
|
||||
getLibraryAction,
|
||||
searchLibraryAction,
|
||||
} from "../../redux/library/library.actions";
|
||||
import { SearchAppbar } from "../../components/Appbar";
|
||||
import { setNovel } from "../../redux/novel/novel.actions";
|
||||
import { updateAllNovels } from "../../services/updates";
|
||||
|
||||
const LibraryScreen = ({
|
||||
navigation,
|
||||
theme,
|
||||
novels,
|
||||
loading,
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
getLibraryAction,
|
||||
searchLibraryAction,
|
||||
setNovel,
|
||||
itemsPerRow,
|
||||
}) => {
|
||||
@@ -38,8 +39,8 @@ const LibraryScreen = ({
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
setSearchText("");
|
||||
getLibraryNovels();
|
||||
}, [])
|
||||
getLibraryAction();
|
||||
}, [getLibraryAction])
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -48,7 +49,7 @@ const LibraryScreen = ({
|
||||
const onRefresh = () => {
|
||||
ToastAndroid.show("Updating library", ToastAndroid.SHORT);
|
||||
setRefreshing(true);
|
||||
getLibraryNovels();
|
||||
updateAllNovels();
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
@@ -68,8 +69,8 @@ const LibraryScreen = ({
|
||||
<SearchAppbar
|
||||
screen="Library"
|
||||
placeholder="Search Library"
|
||||
getNovels={getLibraryNovels}
|
||||
getSearchResults={searchLibraryNovels}
|
||||
getNovels={getLibraryAction}
|
||||
getSearchResults={searchLibraryAction}
|
||||
searchText={searchText}
|
||||
setSearchText={setSearchText}
|
||||
/>
|
||||
@@ -134,8 +135,8 @@ const mapStateToProps = (state) => ({
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getLibraryNovels,
|
||||
searchLibraryNovels,
|
||||
getLibraryAction,
|
||||
searchLibraryAction,
|
||||
setNovel,
|
||||
})(LibraryScreen);
|
||||
|
||||
|
||||
+55
-61
@@ -1,10 +1,11 @@
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import * as Linking from "expo-linking";
|
||||
import { List, Divider } from "react-native-paper";
|
||||
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
import { Divider, ListItem, ListSection } from "../../components/List";
|
||||
|
||||
const AboutScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
@@ -12,65 +13,58 @@ const AboutScreen = ({ navigation }) => {
|
||||
return (
|
||||
<>
|
||||
<Appbar title="About" onBackAction={() => navigation.goBack()} />
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
marginTop: 0,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Version"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="Stable 1.0.9"
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Build Time"
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
description="21-03-21 11:10 AM"
|
||||
/>
|
||||
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="What's new"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader/commits/main"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<Divider />
|
||||
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
title="Github"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
descriptionStyle={{ color: theme.textColorSecondary }}
|
||||
title="Extensions"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
descriptionNumberOfLines={1}
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
)
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
/>
|
||||
</List.Section>
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Version"
|
||||
description="Stable 1.0.15"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Build Time"
|
||||
description="30-04-21 11:00 AM"
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="What's new"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader/commits/main"
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<Divider theme={theme} />
|
||||
<ListItem
|
||||
title="Discord"
|
||||
description="https://discord.gg/QdcWN4MD63"
|
||||
onPress={() =>
|
||||
Linking.openURL("https://discord.gg/QdcWN4MD63")
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Github"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader"
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Extensions"
|
||||
description="https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
onPress={() =>
|
||||
Linking.openURL(
|
||||
"https://github.com/rajarsheechatterjee/lnreader-extensions"
|
||||
)
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
</ListSection>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+58
-37
@@ -1,49 +1,70 @@
|
||||
import React from "react";
|
||||
|
||||
import { StyleSheet } from "react-native";
|
||||
import { List } from "react-native-paper";
|
||||
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
|
||||
import { StyleSheet, View, Image } from "react-native";
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import { ListItem, ListSection } from "../../components/List";
|
||||
|
||||
const MoreScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="More" />
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
}}
|
||||
>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="Settings"
|
||||
left={() => (
|
||||
<List.Icon
|
||||
color={theme.colorAccentDark}
|
||||
icon="cog-outline"
|
||||
<Appbar title="More" style={{ elevation: 0 }} />
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<View style={{ overflow: "hidden", paddingBottom: 4 }}>
|
||||
<View
|
||||
style={{
|
||||
paddingTop: 20,
|
||||
paddingBottom: 30,
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colorPrimary,
|
||||
elevation: 4,
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={require("../../../assets/logo.png")}
|
||||
style={{
|
||||
height: 60,
|
||||
width: 60,
|
||||
tintColor: theme.textColorPrimary,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
onPress={() => navigation.navigate("Settings")}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
title="About"
|
||||
left={() => (
|
||||
<List.Icon
|
||||
color={theme.colorAccentDark}
|
||||
icon="information-outline"
|
||||
/>
|
||||
)}
|
||||
onPress={() => navigation.navigate("About")}
|
||||
/>
|
||||
</List.Section>
|
||||
</View>
|
||||
</View>
|
||||
<ListSection>
|
||||
<ListItem
|
||||
title="Settings"
|
||||
icon="cog-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("SettingsStack", {
|
||||
screen: "Settings",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="Tracking"
|
||||
icon="sync"
|
||||
onPress={() =>
|
||||
navigation.navigate("SettingsStack", {
|
||||
screen: "Trackers",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
<ListItem
|
||||
title="About"
|
||||
icon="information-outline"
|
||||
onPress={() =>
|
||||
navigation.navigate("SettingsStack", {
|
||||
screen: "About",
|
||||
})
|
||||
}
|
||||
theme={theme}
|
||||
/>
|
||||
</ListSection>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+80
-217
@@ -1,24 +1,25 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text } from "react-native";
|
||||
import { setStatusBarStyle } from "expo-status-bar";
|
||||
import { List, Modal, Portal } from "react-native-paper";
|
||||
import InputSpinner from "react-native-input-spinner";
|
||||
|
||||
import { View } from "react-native";
|
||||
import { connect } from "react-redux";
|
||||
import { switchTheme } from "../../redux/actions/theme";
|
||||
import { setDisplayMode, setItemsPerRow } from "../../redux/actions/settings";
|
||||
|
||||
import { Appbar } from "../../components/common/Appbar";
|
||||
import { switchTheme } from "../../redux/theme/theme.actions";
|
||||
import {
|
||||
DisplayCheckbox,
|
||||
ThemeCheckbox,
|
||||
} from "../../components/settings/Checkbox";
|
||||
setDisplayMode,
|
||||
setItemsPerRow,
|
||||
} from "../../redux/settings/settings.actions";
|
||||
import { deleteAllHistory } from "../../database/queries/HistoryQueries";
|
||||
import { deleteNovelCache } from "../../database/queries/NovelQueries";
|
||||
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
import {
|
||||
deleteHistory,
|
||||
deleteNovelsNotInLibrary,
|
||||
deleteDatabase,
|
||||
} from "../../services/db";
|
||||
Divider,
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../components/List";
|
||||
import DisplayModeModal from "./components/DisplayModeModal";
|
||||
import GridSizeModal from "./components/GridSizeModal";
|
||||
import ThemeModal from "./components/ThemeModal";
|
||||
|
||||
const SettingsScreen = ({
|
||||
navigation,
|
||||
@@ -30,242 +31,104 @@ const SettingsScreen = ({
|
||||
setDisplayMode,
|
||||
setItemsPerRow,
|
||||
}) => {
|
||||
const display = {
|
||||
0: "Compact Grid",
|
||||
1: "Comfortable Grid",
|
||||
2: "List",
|
||||
const displayModeLabel = (displayMode) => {
|
||||
const label = {
|
||||
0: "Compact Grid",
|
||||
1: "Comfortable Grid",
|
||||
2: "List",
|
||||
};
|
||||
|
||||
return label[displayMode];
|
||||
};
|
||||
|
||||
const themes = [
|
||||
{ themeCode: 0, label: "AMOLED Dark Theme", statusBar: "light" },
|
||||
{ themeCode: 1, label: "Light Theme", statusBar: "dark" },
|
||||
{ themeCode: 2, label: "Dark Theme", statusBar: "light" },
|
||||
{ themeCode: 3, label: "Midnight Dusk Theme", statusBar: "light" },
|
||||
];
|
||||
|
||||
const desciptionStyles = {
|
||||
color: theme.textColorSecondary,
|
||||
};
|
||||
|
||||
const titleStyles = { color: theme.textColorPrimary };
|
||||
|
||||
/**
|
||||
* Display Mode Modal
|
||||
*/
|
||||
// Display Mode Modal
|
||||
const [displayModalVisible, setDisplayModalVisible] = useState(false);
|
||||
const showDisplayModal = () => setDisplayModalVisible(true);
|
||||
const hideDisplayModal = () => setDisplayModalVisible(false);
|
||||
|
||||
/**
|
||||
* Change Theme Modal
|
||||
*/
|
||||
// Theme Modal
|
||||
const [themeModalVisible, setthemeModalVisible] = useState(false);
|
||||
const showthemeModal = () => setthemeModalVisible(true);
|
||||
const hidethemeModal = () => setthemeModalVisible(false);
|
||||
|
||||
const [itemsModalVisible, setitemsModalVisible] = useState(false);
|
||||
const showitemsModal = () => setitemsModalVisible(true);
|
||||
const hideitemsModal = () => setitemsModalVisible(false);
|
||||
// Grid Size Modal
|
||||
const [gridSizeModalVisible, setGridSizeModalVisible] = useState(false);
|
||||
const showGridSizeModal = () => setGridSizeModalVisible(true);
|
||||
const hideGridSizeModal = () => setGridSizeModalVisible(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar title="Settings" onBackAction={() => navigation.goBack()} />
|
||||
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
|
||||
<List.Section
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
}}
|
||||
>
|
||||
<List.Subheader
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
paddingBottom: 5,
|
||||
}}
|
||||
>
|
||||
Data
|
||||
</List.Subheader>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ListSection>
|
||||
<ListSubHeader theme={theme}>Data</ListSubHeader>
|
||||
<ListItem
|
||||
title="Clear database"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete history for novels not in your library"
|
||||
onPress={() => deleteNovelsNotInLibrary()}
|
||||
rippleColor={theme.rippleColor}
|
||||
onPress={() => deleteNovelCache()}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
<ListItem
|
||||
title="Clear history"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete reading history for all novels"
|
||||
onPress={() => deleteHistory()}
|
||||
rippleColor={theme.rippleColor}
|
||||
onPress={() => deleteAllHistory()}
|
||||
theme={theme}
|
||||
/>
|
||||
<List.Subheader
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
paddingBottom: 5,
|
||||
}}
|
||||
>
|
||||
Display
|
||||
</List.Subheader>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
<Divider theme={theme} />
|
||||
<ListSubHeader theme={theme}>Display</ListSubHeader>
|
||||
<ListItem
|
||||
title="Display Mode"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description={display[displayMode]}
|
||||
description={displayModeLabel(displayMode)}
|
||||
onPress={showDisplayModal}
|
||||
rippleColor={theme.rippleColor}
|
||||
theme={theme}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={displayModalVisible}
|
||||
onDismiss={hideDisplayModal}
|
||||
contentContainerStyle={{
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
>
|
||||
<DisplayCheckbox
|
||||
value={1}
|
||||
displayMode={displayMode}
|
||||
onPress={() => setDisplayMode(1)}
|
||||
/>
|
||||
<DisplayCheckbox
|
||||
value={0}
|
||||
displayMode={displayMode}
|
||||
onPress={() => setDisplayMode(0)}
|
||||
/>
|
||||
<DisplayCheckbox
|
||||
value={2}
|
||||
displayMode={displayMode}
|
||||
onPress={() => setDisplayMode(2)}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
<ListItem
|
||||
title="Items per row in library"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description={itemsPerRow}
|
||||
onPress={showitemsModal}
|
||||
rippleColor={theme.rippleColor}
|
||||
description={`${itemsPerRow} items per row`}
|
||||
onPress={showGridSizeModal}
|
||||
theme={theme}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={itemsModalVisible}
|
||||
onDismiss={hideitemsModal}
|
||||
contentContainerStyle={{
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 18,
|
||||
marginBottom: 10,
|
||||
}}
|
||||
>
|
||||
Items per row
|
||||
</Text>
|
||||
<InputSpinner
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
elevation: 0,
|
||||
}}
|
||||
max={10}
|
||||
min={1}
|
||||
skin="paper"
|
||||
background={theme.colorPrimaryDark}
|
||||
colorMax={theme.colorAccentDark}
|
||||
colorLeft={theme.colorAccentDark}
|
||||
colorRight={theme.colorAccentDark}
|
||||
buttonTextColor={theme.textColorPrimary}
|
||||
colorPress={"white"}
|
||||
colorMin={theme.colorAccentDark}
|
||||
value={itemsPerRow}
|
||||
onChange={(num) => setItemsPerRow(num)}
|
||||
inputStyle={{
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</Portal>
|
||||
|
||||
{/* <List.Subheader
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
paddingBottom: 5,
|
||||
}}
|
||||
>
|
||||
Devloper options
|
||||
</List.Subheader>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
title="Delete database"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description="Delete entire database"
|
||||
rippleColor={theme.rippleColor}
|
||||
onPress={() => deleteDatabase()}
|
||||
/> */}
|
||||
<List.Subheader
|
||||
style={{
|
||||
color: theme.colorAccentDark,
|
||||
paddingBottom: 5,
|
||||
}}
|
||||
>
|
||||
Theme
|
||||
</List.Subheader>
|
||||
<List.Item
|
||||
titleStyle={titleStyles}
|
||||
<Divider theme={theme} />
|
||||
<ListSubHeader theme={theme}>Theme</ListSubHeader>
|
||||
<ListItem
|
||||
title="Theme"
|
||||
descriptionStyle={desciptionStyles}
|
||||
description={themes[themeCode].label}
|
||||
description={theme.name}
|
||||
onPress={showthemeModal}
|
||||
rippleColor={theme.rippleColor}
|
||||
theme={theme}
|
||||
/>
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={themeModalVisible}
|
||||
onDismiss={hidethemeModal}
|
||||
contentContainerStyle={{
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
}}
|
||||
>
|
||||
{themes.map((item, index) => (
|
||||
<ThemeCheckbox
|
||||
key={index}
|
||||
label={themes[item.themeCode].label}
|
||||
checked={
|
||||
themeCode === item.themeCode
|
||||
? true
|
||||
: false
|
||||
}
|
||||
onPress={() => {
|
||||
switchTheme(item.themeCode);
|
||||
setStatusBarStyle(item.statusBar);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Modal>
|
||||
</Portal>
|
||||
</List.Section>
|
||||
</ListSection>
|
||||
<DisplayModeModal
|
||||
displayMode={displayMode}
|
||||
displayModalVisible={displayModalVisible}
|
||||
hideDisplayModal={hideDisplayModal}
|
||||
setDisplayMode={setDisplayMode}
|
||||
theme={theme}
|
||||
/>
|
||||
<GridSizeModal
|
||||
itemsPerRow={itemsPerRow}
|
||||
gridSizeModalVisible={gridSizeModalVisible}
|
||||
hideGridSizeModal={hideGridSizeModal}
|
||||
setItemsPerRow={setItemsPerRow}
|
||||
theme={theme}
|
||||
/>
|
||||
<ThemeModal
|
||||
themeModalVisible={themeModalVisible}
|
||||
hidethemeModal={hidethemeModal}
|
||||
switchTheme={switchTheme}
|
||||
theme={theme}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
themeCode: state.themeReducer.themeCode,
|
||||
theme: state.themeReducer.theme,
|
||||
displayMode: state.settingsReducer.displayMode,
|
||||
itemsPerRow: state.settingsReducer.itemsPerRow,
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Modal, Portal, Text, Button, Provider } from "react-native-paper";
|
||||
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import {
|
||||
myAnimeListConfig,
|
||||
getAccessToken,
|
||||
malTokenWatcher,
|
||||
} from "../../trackers/MyAnimeList";
|
||||
import { Appbar } from "../../components/Appbar";
|
||||
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import {
|
||||
Divider,
|
||||
ListItem,
|
||||
ListSection,
|
||||
ListSubHeader,
|
||||
} from "../../components/List";
|
||||
import { removeTracker, setTracker } from "../../redux/tracker/tracker.actions";
|
||||
|
||||
const TrackerScreen = ({ navigation }) => {
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// Tracker Modal
|
||||
const [visible, setVisible] = useState(false);
|
||||
const showModal = () => setVisible(true);
|
||||
const hideModal = () => setVisible(false);
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<Appbar title="Tacking" onBackAction={() => navigation.goBack()} />
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
paddingVertical: 8,
|
||||
}}
|
||||
>
|
||||
<ListSection>
|
||||
<ListSubHeader theme={theme}>Services</ListSubHeader>
|
||||
<ListItem
|
||||
title="MyAnimeList"
|
||||
onPress={async () => {
|
||||
if (tracker) {
|
||||
showModal();
|
||||
} else {
|
||||
await WebBrowser.openAuthSessionAsync(
|
||||
myAnimeListConfig.authUrl,
|
||||
myAnimeListConfig.redirectUri
|
||||
).then((res) => {
|
||||
console.log(res.type);
|
||||
if (res.type === "success") {
|
||||
const { url } = res;
|
||||
|
||||
const codeExtractor = new RegExp(
|
||||
/=([^&]+)/
|
||||
);
|
||||
let code = url.match(codeExtractor);
|
||||
|
||||
console.log(code);
|
||||
|
||||
if (code) {
|
||||
code = code[1];
|
||||
getAccessToken(
|
||||
code,
|
||||
myAnimeListConfig.codeChallenge
|
||||
).then((objects) => {
|
||||
console.log(objects);
|
||||
dispatch(setTracker(objects));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
right={tracker && "check"}
|
||||
theme={theme}
|
||||
/>
|
||||
{tracker && tracker.expires_in < new Date(Date.now()) && (
|
||||
<>
|
||||
<Divider theme={theme} />
|
||||
<ListSubHeader theme={theme}>
|
||||
Settings
|
||||
</ListSubHeader>
|
||||
<ListItem
|
||||
title="Revalidate MyAnimeList"
|
||||
onPress={() => {
|
||||
const res = malTokenWatcher(tracker);
|
||||
dispatch(setTracker(res));
|
||||
}}
|
||||
theme={theme}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</ListSection>
|
||||
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={visible}
|
||||
onDismiss={hideModal}
|
||||
contentContainerStyle={{
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorPrimary,
|
||||
fontSize: 20,
|
||||
}}
|
||||
>
|
||||
Log out from MyAnimeList?
|
||||
</Text>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onPress={hideModal}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
style={{ marginTop: 30 }}
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onPress={() => {
|
||||
dispatch(removeTracker());
|
||||
hideModal();
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</Button>
|
||||
</View>
|
||||
</Modal>
|
||||
</Portal>
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackerScreen;
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Portal, Modal, Checkbox } from "react-native-paper";
|
||||
|
||||
const DisplayModeModal = ({
|
||||
displayMode,
|
||||
displayModalVisible,
|
||||
setDisplayMode,
|
||||
hideDisplayModal,
|
||||
theme,
|
||||
}) => {
|
||||
const displayModes = [
|
||||
{ displayMode: 0, label: "Compact Grid" },
|
||||
{ displayMode: 1, label: "Comfortable Grid" },
|
||||
{ displayMode: 2, label: "List" },
|
||||
];
|
||||
|
||||
const renderCheckboxes = () => {
|
||||
return displayModes.map((mode) => (
|
||||
<Checkbox.Item
|
||||
key={mode.displayMode}
|
||||
label={mode.label}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={
|
||||
displayMode === mode.displayMode ? "checked" : "unchecked"
|
||||
}
|
||||
mode="ios"
|
||||
uncheckedColor={theme.textColorSecondary}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => setDisplayMode(mode.displayMode)}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={displayModalVisible}
|
||||
onDismiss={hideDisplayModal}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
{renderCheckboxes()}
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default DisplayModeModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import React from "react";
|
||||
import { Text, StyleSheet } from "react-native";
|
||||
import { Portal, Modal } from "react-native-paper";
|
||||
import Slider from "@react-native-community/slider";
|
||||
import { RadioButton, RadioButtonGroup } from "../../../components/RadioButton";
|
||||
|
||||
const GridSizeModal = ({
|
||||
itemsPerRow,
|
||||
gridSizeModalVisible,
|
||||
hideGridSizeModal,
|
||||
setItemsPerRow,
|
||||
theme,
|
||||
}) => {
|
||||
const gridSizes = {
|
||||
1: "XS",
|
||||
2: "S",
|
||||
3: "M",
|
||||
4: "L",
|
||||
5: "XL",
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={gridSizeModalVisible}
|
||||
onDismiss={hideGridSizeModal}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.modalHeader,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
Grid size
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
styles.modalDescription,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{`${itemsPerRow} per row`}
|
||||
</Text>
|
||||
<Slider
|
||||
style={styles.slider}
|
||||
value={itemsPerRow}
|
||||
minimumValue={1}
|
||||
maximumValue={5}
|
||||
step={1}
|
||||
minimumTrackTintColor={theme.colorAccentDark}
|
||||
maximumTrackTintColor="#000000"
|
||||
thumbTintColor={theme.colorAccentDark}
|
||||
onValueChange={(value) => setItemsPerRow(value)}
|
||||
/>
|
||||
{/* <RadioButtonGroup
|
||||
onValueChange={(value) => setItemsPerRow(value)}
|
||||
value={itemsPerRow}
|
||||
>
|
||||
{Object.keys(gridSizes).map((item) => (
|
||||
<RadioButton
|
||||
key={item}
|
||||
value={item}
|
||||
label={gridSizes[item]}
|
||||
theme={theme}
|
||||
/>
|
||||
))}
|
||||
</RadioButtonGroup> */}
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default GridSizeModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
modalHeader: {
|
||||
fontSize: 18,
|
||||
marginBottom: 10,
|
||||
},
|
||||
modalDescription: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
slider: {
|
||||
width: "100%",
|
||||
height: 40,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Portal, Modal, Checkbox } from "react-native-paper";
|
||||
|
||||
import { setStatusBarStyle } from "expo-status-bar";
|
||||
|
||||
const ThemeModal = ({
|
||||
themeModalVisible,
|
||||
hidethemeModal,
|
||||
switchTheme,
|
||||
theme,
|
||||
}) => {
|
||||
const themes = [
|
||||
{ id: 0, name: "AMOLED Dark", statusBar: "light" },
|
||||
{ id: 1, name: "Light", statusBar: "dark" },
|
||||
{ id: 2, name: "Dark", statusBar: "light" },
|
||||
{ id: 3, name: "Midnight Dusk", statusBar: "light" },
|
||||
];
|
||||
|
||||
const renderThemeCheckboxes = () =>
|
||||
themes.map((item) => (
|
||||
<Checkbox.Item
|
||||
key={item.id}
|
||||
label={item.name}
|
||||
labelStyle={{ color: theme.textColorPrimary }}
|
||||
status={theme.id === item.id ? "checked" : "unchecked"}
|
||||
mode="ios"
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => {
|
||||
switchTheme(item.id);
|
||||
setStatusBarStyle(item.statusBar);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<Modal
|
||||
visible={themeModalVisible}
|
||||
onDismiss={hidethemeModal}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
{renderThemeCheckboxes()}
|
||||
</Modal>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeModal;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
});
|
||||
+55
-71
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
StyleSheet,
|
||||
View,
|
||||
@@ -10,17 +10,16 @@ import { Provider, Portal } from "react-native-paper";
|
||||
|
||||
import ChapterCard from "./components/ChapterCard";
|
||||
import NovelInfoHeader from "./components/NovelHeader";
|
||||
import { BottomSheet } from "./components/BottomSheet";
|
||||
|
||||
import { downloadOrDeleteChapter } from "../../services/db";
|
||||
import ChaptersSettingsSheet from "./components/ChaptersSettingsSheet";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import {
|
||||
getNovel,
|
||||
insertNovelInLibrary,
|
||||
getNovelAction,
|
||||
sortAndFilterChapters,
|
||||
} from "../../redux/actions/novel";
|
||||
updateNovelAction,
|
||||
} from "../../redux/novel/novel.actions";
|
||||
import TrackSheet from "./components/Tracker/TrackSheet";
|
||||
|
||||
const Novel = ({
|
||||
route,
|
||||
@@ -28,67 +27,40 @@ const Novel = ({
|
||||
novel,
|
||||
chapters,
|
||||
loading,
|
||||
getNovel,
|
||||
getNovelAction,
|
||||
fetching,
|
||||
insertNovelInLibrary,
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
}) => {
|
||||
const {
|
||||
extensionId,
|
||||
sourceId,
|
||||
novelUrl,
|
||||
novelName,
|
||||
novelCover,
|
||||
libraryStatus,
|
||||
followed,
|
||||
novelId,
|
||||
} = route.params;
|
||||
|
||||
// const [downloading, setDownloading] = useState({
|
||||
// downloading: false,
|
||||
// chapterUrl: "",
|
||||
// });
|
||||
let chaptersSettingsSheetRef = useRef(null);
|
||||
let trackerSheetRef = useRef(null);
|
||||
|
||||
let _panel = useRef(null); // Bottomsheet ref
|
||||
|
||||
/**
|
||||
* Download chapter from source
|
||||
*
|
||||
* If downloaded then delete from db
|
||||
*/
|
||||
|
||||
// const downloadChapter = (
|
||||
// downloadStatus,
|
||||
// extensionId,
|
||||
// novelUrl,
|
||||
// chapterUrl,
|
||||
// chapterName
|
||||
// ) => {
|
||||
// setDownloading({ downloading: true, chapterUrl: chapterUrl });
|
||||
// downloadOrDeleteChapter(
|
||||
// downloadStatus,
|
||||
// extensionId,
|
||||
// novelUrl,
|
||||
// chapterUrl
|
||||
// ).then((res) => {
|
||||
// setDownloading({ downloading: false });
|
||||
// ToastAndroid.show(
|
||||
// !downloadStatus
|
||||
// ? `Downloaded ${chapterName}`
|
||||
// : `Deleted ${chapterName}`,
|
||||
// ToastAndroid.SHORT
|
||||
// );
|
||||
// });
|
||||
// };
|
||||
const [sort, setSort] = useState("ORDER BY chapterId ASC");
|
||||
const [filter, setFilter] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
getNovel(libraryStatus, extensionId, novelUrl);
|
||||
}, []);
|
||||
getNovelAction(followed, sourceId, novelUrl, novelId, sort, filter);
|
||||
}, [getNovelAction, sort, filter]);
|
||||
|
||||
const onRefresh = async () => {
|
||||
await updateNovelAction(sourceId, novelUrl, novelId);
|
||||
ToastAndroid.show(`Updated ${novelName}`, ToastAndroid.SHORT);
|
||||
};
|
||||
|
||||
const renderChapterCard = ({ item }) => (
|
||||
<ChapterCard
|
||||
novelUrl={novelUrl}
|
||||
extensionId={extensionId}
|
||||
extensionId={sourceId}
|
||||
chapter={item}
|
||||
// downloadChapter={downloadChapter}
|
||||
// downloading={downloading}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -102,7 +74,7 @@ const Novel = ({
|
||||
>
|
||||
<FlatList
|
||||
data={!loading && chapters}
|
||||
keyExtractor={(item) => item.chapterUrl}
|
||||
keyExtractor={(item) => item.chapterId.toString()}
|
||||
removeClippedSubviews={true}
|
||||
maxToRenderPerBatch={5}
|
||||
windowSize={15}
|
||||
@@ -110,31 +82,44 @@ const Novel = ({
|
||||
renderItem={renderChapterCard}
|
||||
ListHeaderComponent={
|
||||
<NovelInfoHeader
|
||||
theme={theme}
|
||||
item={{ novelName, novelCover }}
|
||||
novel={novel}
|
||||
noOfChapters={chapters.length}
|
||||
insertNovelInLibrary={insertNovelInLibrary}
|
||||
loading={loading}
|
||||
bottomSheetRef={_panel}
|
||||
novelName={novelName}
|
||||
novelCover={novelCover}
|
||||
novel={novel}
|
||||
chapters={chapters}
|
||||
trackerSheetRef={trackerSheetRef}
|
||||
chaptersSettingsSheetRef={chaptersSettingsSheetRef}
|
||||
theme={theme}
|
||||
/>
|
||||
}
|
||||
refreshControl={
|
||||
<RefreshControl
|
||||
refreshing={fetching}
|
||||
// onRefresh={onRefresh}
|
||||
onRefresh={onRefresh}
|
||||
colors={[theme.textColorPrimary]}
|
||||
progressBackgroundColor={theme.colorPrimary}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Portal>
|
||||
<BottomSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={_panel}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
/>
|
||||
</Portal>
|
||||
{!loading && (
|
||||
<Portal>
|
||||
<ChaptersSettingsSheet
|
||||
novelUrl={novelUrl}
|
||||
bottomSheetRef={chaptersSettingsSheetRef}
|
||||
sortAndFilterChapters={sortAndFilterChapters}
|
||||
sort={sort}
|
||||
filter={filter}
|
||||
setSort={setSort}
|
||||
setFilter={setFilter}
|
||||
/>
|
||||
<TrackSheet
|
||||
bottomSheetRef={trackerSheetRef}
|
||||
novelId={novel.novelId}
|
||||
novelName={novel.novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
</Portal>
|
||||
)}
|
||||
</View>
|
||||
</Provider>
|
||||
);
|
||||
@@ -148,13 +133,12 @@ const mapStateToProps = (state) => ({
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, {
|
||||
getNovel,
|
||||
insertNovelInLibrary,
|
||||
getNovelAction,
|
||||
|
||||
sortAndFilterChapters,
|
||||
updateNovelAction,
|
||||
})(Novel);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
container: { flex: 1 },
|
||||
});
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, ActivityIndicator, StyleSheet } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { TouchableRipple, IconButton, Menu } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
|
||||
import { deleteChapter, downloadChapter } from "../../../redux/actions/novel";
|
||||
import {
|
||||
deleteChapterAction,
|
||||
downloadChapterAction,
|
||||
} from "../../../redux/novel/novel.actions";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const ChapterCard = ({
|
||||
@@ -11,28 +14,88 @@ const ChapterCard = ({
|
||||
chapter,
|
||||
extensionId,
|
||||
theme,
|
||||
downloading,
|
||||
downloadChapter,
|
||||
deleteChapter,
|
||||
// downloading,
|
||||
downloadChapterAction,
|
||||
deleteChapterAction,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
|
||||
const isDownloading = () =>
|
||||
downloading.some(
|
||||
(obj) =>
|
||||
obj.chapterUrl === chapter.chapterUrl &&
|
||||
obj.novelUrl === novelUrl
|
||||
);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const openMenu = () => setVisible(true);
|
||||
const closeMenu = () => setVisible(false);
|
||||
|
||||
const displayDownloadButton = () => {
|
||||
if (chapter.downloaded === 3) {
|
||||
return (
|
||||
<ActivityIndicator
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
style={{ margin: 3.5, padding: 5 }}
|
||||
/>
|
||||
);
|
||||
} else if (chapter.downloaded === 1) {
|
||||
return (
|
||||
<Menu
|
||||
visible={visible}
|
||||
onDismiss={closeMenu}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="check-circle"
|
||||
animated
|
||||
color={theme.textColorPrimary}
|
||||
size={25}
|
||||
onPress={openMenu}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
>
|
||||
<Menu.Item
|
||||
onPress={() =>
|
||||
deleteChapterAction(
|
||||
chapter.chapterId,
|
||||
chapter.chapterName
|
||||
)
|
||||
}
|
||||
title="Delete"
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<IconButton
|
||||
icon="arrow-down-circle-outline"
|
||||
animated
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
onPress={() => {
|
||||
downloadChapterAction(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName,
|
||||
chapter.chapterId
|
||||
);
|
||||
// console.log(downloading);
|
||||
}}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TouchableRipple
|
||||
style={styles.chapterCardContainer}
|
||||
onPress={() =>
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterId: chapter.chapterId,
|
||||
chapterUrl: chapter.chapterUrl,
|
||||
extensionId,
|
||||
novelUrl: novelUrl,
|
||||
chapterName: chapter.chapterName,
|
||||
novelId: chapter.novelId,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
@@ -70,48 +133,7 @@ const ChapterCard = ({
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<View>
|
||||
{isDownloading() && !chapter.downloaded ? (
|
||||
<ActivityIndicator
|
||||
color={theme.textColorHintDark}
|
||||
size={25}
|
||||
style={{ margin: 3.5, padding: 5 }}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
icon={
|
||||
chapter.downloaded
|
||||
? "check-circle"
|
||||
: "arrow-down-circle-outline"
|
||||
}
|
||||
animated
|
||||
color={
|
||||
chapter.downloaded
|
||||
? theme.textColorPrimary
|
||||
: theme.textColorHintDark
|
||||
}
|
||||
size={25}
|
||||
onPress={() => {
|
||||
if (!chapter.downloaded) {
|
||||
downloadChapter(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName
|
||||
);
|
||||
} else {
|
||||
deleteChapter(
|
||||
extensionId,
|
||||
novelUrl,
|
||||
chapter.chapterUrl,
|
||||
chapter.chapterName
|
||||
);
|
||||
}
|
||||
}}
|
||||
style={{ margin: 2 }}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View>{displayDownloadButton()}</View>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
@@ -119,12 +141,13 @@ const ChapterCard = ({
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
downloading: state.novelReducer.downloading,
|
||||
// downloading: state.novelReducer.downloading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { downloadChapter, deleteChapter })(
|
||||
ChapterCard
|
||||
);
|
||||
export default connect(mapStateToProps, {
|
||||
downloadChapterAction,
|
||||
deleteChapterAction,
|
||||
})(ChapterCard);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
chapterCardContainer: {
|
||||
|
||||
+22
-32
@@ -1,33 +1,27 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, View, Text } from "react-native";
|
||||
import { StyleSheet, View, Text, Animated } from "react-native";
|
||||
import { List, RadioButton, Checkbox } from "react-native-paper";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { useSelector } from "react-redux";
|
||||
|
||||
export const BottomSheet = ({
|
||||
const ChaptersSettingsSheet = ({
|
||||
bottomSheetRef,
|
||||
sortAndFilterChapters,
|
||||
sort,
|
||||
filter,
|
||||
setSort,
|
||||
setFilter,
|
||||
novelUrl,
|
||||
}) => {
|
||||
const checkBoxStyle = { flexDirection: "row", alignItems: "center" };
|
||||
|
||||
const [chapterFlags, setChapterFlags] = useState({ sort: "", filter: "" });
|
||||
|
||||
const sortChapters = (sort) => {
|
||||
setChapterFlags({ ...chapterFlags, sort: sort });
|
||||
sortAndFilterChapters(novelUrl, chapterFlags.filter, sort);
|
||||
};
|
||||
|
||||
const filterChapters = (filter) => {
|
||||
setChapterFlags({ ...chapterFlags, filter: filter });
|
||||
sortAndFilterChapters(novelUrl, filter, chapterFlags.sort);
|
||||
};
|
||||
|
||||
const theme = useSelector((state) => state.themeReducer.theme);
|
||||
|
||||
const [animatedValue] = useState(new Animated.Value(0));
|
||||
|
||||
return (
|
||||
<Bottomsheet
|
||||
animatedValue={animatedValue}
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 370, bottom: 0 }}
|
||||
snappingPoints={[0, 370]}
|
||||
@@ -51,8 +45,8 @@ export const BottomSheet = ({
|
||||
</List.Subheader>
|
||||
<View>
|
||||
<RadioButton.Group
|
||||
onValueChange={(newValue) => sortChapters(newValue)}
|
||||
value={chapterFlags.sort}
|
||||
onValueChange={(newValue) => setSort(newValue)}
|
||||
value={sort}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
@@ -88,7 +82,7 @@ export const BottomSheet = ({
|
||||
<RadioButton.Item
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
value=""
|
||||
value="ORDER BY chapterId ASC"
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
@@ -113,12 +107,10 @@ export const BottomSheet = ({
|
||||
</List.Subheader>
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "" ? "checked" : "unchecked"
|
||||
}
|
||||
status={filter === "" ? "checked" : "unchecked"}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("")}
|
||||
onPress={() => setFilter("")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show all
|
||||
@@ -127,13 +119,11 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND `read`=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
filter === "AND `read`=1" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=1")}
|
||||
onPress={() => setFilter("AND `read`=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show read chapters
|
||||
@@ -142,13 +132,11 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND `read`=0"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
filter === "AND `read`=0" ? "checked" : "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND `read`=0")}
|
||||
onPress={() => setFilter("AND `read`=0")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show unread chapters
|
||||
@@ -157,13 +145,13 @@ export const BottomSheet = ({
|
||||
<View style={checkBoxStyle}>
|
||||
<Checkbox.Item
|
||||
status={
|
||||
chapterFlags.filter === "AND downloaded=1"
|
||||
filter === "AND downloaded=1"
|
||||
? "checked"
|
||||
: "unchecked"
|
||||
}
|
||||
uncheckedColor={theme.textColorHintDark}
|
||||
color={theme.colorAccentDark}
|
||||
onPress={() => filterChapters("AND downloaded=1")}
|
||||
onPress={() => setFilter("AND downloaded=1")}
|
||||
/>
|
||||
<Text style={{ color: theme.textColorPrimary }}>
|
||||
Show downloaded chapters
|
||||
@@ -174,6 +162,8 @@ export const BottomSheet = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ChaptersSettingsSheet;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
flex: 1,
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Chip } from "react-native-paper";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
import { followNovelAction } from "../../../../redux/novel/novel.actions";
|
||||
|
||||
const FollowChip = ({ followed, novel, theme }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const getIconName = () => (followed ? "heart" : "heart-outline");
|
||||
|
||||
const getChipLabel = () => (followed ? "In Library" : "Add to library");
|
||||
|
||||
return (
|
||||
<Chip
|
||||
mode="outlined"
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={getIconName()}
|
||||
size={21}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)}
|
||||
onPress={() => dispatch(followNovelAction(novel))}
|
||||
style={[
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
styles.toggleFavourite,
|
||||
]}
|
||||
textStyle={{
|
||||
fontWeight: "bold",
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{getChipLabel()}
|
||||
</Chip>
|
||||
);
|
||||
};
|
||||
|
||||
export default FollowChip;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
toggleFavourite: {
|
||||
height: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
paddingLeft: 4,
|
||||
borderWidth: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { Text, StyleSheet } from "react-native";
|
||||
|
||||
export const GenreChip = ({ children, theme }) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.genreChip,
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
borderColor: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
);
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
genreChip: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
paddingVertical: 2,
|
||||
paddingHorizontal: 10,
|
||||
marginHorizontal: 2,
|
||||
fontSize: 13,
|
||||
borderWidth: 1,
|
||||
borderRadius: 50,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import { View, ImageBackground, StyleSheet } from "react-native";
|
||||
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
|
||||
const NovelCoverImage = ({ children, source, theme }) => {
|
||||
return (
|
||||
<ImageBackground source={source} style={styles.background}>
|
||||
<LinearGradient
|
||||
colors={["rgba(0,0,0,0.2)", theme.colorPrimaryDark]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
{children}
|
||||
</LinearGradient>
|
||||
</ImageBackground>
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelCoverImage;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
background: { height: 285 },
|
||||
linearGradient: { height: 286 },
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import React, { useState } from "react";
|
||||
import { View, Text, StyleSheet } from "react-native";
|
||||
|
||||
const NovelSummary = ({ summary, followed, theme }) => {
|
||||
const [more, showMore] = useState(!followed);
|
||||
|
||||
return (
|
||||
summary !== "" && (
|
||||
<View style={styles.summaryContainer}>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary },
|
||||
styles.summaryHeader,
|
||||
]}
|
||||
>
|
||||
About
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
lineHeight: 20,
|
||||
}}
|
||||
numberOfLines={more ? 200 : 2}
|
||||
onPress={() => showMore(!more)}
|
||||
ellipsizeMode="clip"
|
||||
>
|
||||
{summary}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
},
|
||||
styles.showMoreButton,
|
||||
]}
|
||||
onPress={() => showMore(!more)}
|
||||
>
|
||||
{more ? "Less" : "More"}
|
||||
</Text>
|
||||
</View>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default NovelSummary;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
summaryContainer: {
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
summaryHeader: {
|
||||
marginTop: 5,
|
||||
paddingVertical: 5,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
showMoreButton: {
|
||||
fontWeight: "bold",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 18,
|
||||
paddingLeft: 6,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Button } from "react-native-paper";
|
||||
|
||||
const ReadButton = ({ navigation, novel, chapters, theme }) => {
|
||||
const lastRead = chapters.find((obj) => obj.read === 0);
|
||||
|
||||
return (
|
||||
chapters.length > 0 &&
|
||||
(lastRead ? (
|
||||
<Button
|
||||
color="white"
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
onPress={() => {
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterId: lastRead.chapterId,
|
||||
chapterUrl: lastRead.chapterUrl,
|
||||
novelUrl: novel.novelUrl,
|
||||
novelId: lastRead.novelId,
|
||||
extensionId: novel.sourceId,
|
||||
chapterName: lastRead.chapterName,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{novel.unread ? `Start reading ` : `Continue reading `}
|
||||
{lastRead.chapterName}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
color={theme.textColorHintDark}
|
||||
style={[
|
||||
styles.startButton,
|
||||
{ backgroundColor: theme.colorDisabled },
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
>
|
||||
All chapters read
|
||||
</Button>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
export default ReadButton;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
startButton: {
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { StyleSheet } from "react-native";
|
||||
import { Chip } from "react-native-paper";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
|
||||
const TrackerChip = ({ isTracked, trackerSheetRef, theme }) => {
|
||||
const getIconName = () => (isTracked ? "check" : "sync");
|
||||
|
||||
const getChipLabel = () => (isTracked ? "Tracked" : "Tracking");
|
||||
|
||||
return (
|
||||
<Chip
|
||||
mode="outlined"
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={getIconName(isTracked)}
|
||||
size={21}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)}
|
||||
onPress={() => trackerSheetRef.current.show()}
|
||||
style={[
|
||||
styles.toggleFavourite,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
textStyle={{
|
||||
fontWeight: "bold",
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{getChipLabel()}
|
||||
</Chip>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackerChip;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
toggleFavourite: {
|
||||
height: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
paddingLeft: 4,
|
||||
marginLeft: 0,
|
||||
borderWidth: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
});
|
||||
@@ -1,157 +1,125 @@
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
ImageBackground,
|
||||
StyleSheet,
|
||||
Image,
|
||||
FlatList,
|
||||
Share,
|
||||
} from "react-native";
|
||||
import { TouchableRipple, IconButton, Button, Chip } from "react-native-paper";
|
||||
import { View, Text, StyleSheet, Image, FlatList, Share } from "react-native";
|
||||
import { TouchableRipple, IconButton, Button, Menu } from "react-native-paper";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import * as WebBrowser from "expo-web-browser";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
|
||||
import { LinearGradient } from "expo-linear-gradient";
|
||||
import { MaterialCommunityIcons } from "@expo/vector-icons";
|
||||
import {
|
||||
downloadAllChaptersAction,
|
||||
deleteAllChaptersAction,
|
||||
} from "../../../redux/novel/novel.actions";
|
||||
import { GenreChip } from "./Info/GenreChip";
|
||||
import NovelCoverImage from "./Info/NovelCoverImage";
|
||||
import FollowChip from "./Info/FollowChip";
|
||||
import TrackerChip from "./Info/TrackerChip";
|
||||
import NovelSummary from "./Info/NovelSummary";
|
||||
import ReadButton from "./Info/ReadButton";
|
||||
|
||||
const NovelInfoHeader = ({
|
||||
item,
|
||||
novel,
|
||||
noOfChapters,
|
||||
insertNovelInLibrary,
|
||||
novelName,
|
||||
novelCover,
|
||||
loading,
|
||||
bottomSheetRef,
|
||||
novel,
|
||||
chapters,
|
||||
theme,
|
||||
chaptersSettingsSheetRef,
|
||||
trackerSheetRef,
|
||||
}) => {
|
||||
const navigation = useNavigation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [more, setMore] = useState(
|
||||
!loading && novel.libraryStatus !== 1 ? true : false
|
||||
const [downloadMenu, showDownloadMenu] = useState(false);
|
||||
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
const trackedNovels = useSelector(
|
||||
(state) => state.trackerReducer.trackedNovels
|
||||
);
|
||||
|
||||
const renderGenreChip = ({ item }) => (
|
||||
<Text
|
||||
style={[
|
||||
styles.genre,
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
borderColor: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorPrimary,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item}
|
||||
</Text>
|
||||
);
|
||||
let isTracked = false;
|
||||
|
||||
if (!loading) {
|
||||
isTracked = trackedNovels.find((obj) => obj.novelId === novel.novelId);
|
||||
}
|
||||
|
||||
const getGenres = ({ item }) => <GenreChip theme={theme}>{item}</GenreChip>;
|
||||
|
||||
return (
|
||||
<View style={{ flexGrow: 1 }}>
|
||||
<ImageBackground
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.background}
|
||||
>
|
||||
<LinearGradient
|
||||
colors={["rgba(0,0,0,0.2)", theme.colorPrimaryDark]}
|
||||
style={styles.linearGradient}
|
||||
>
|
||||
<View style={styles.detailsContainer}>
|
||||
<View>
|
||||
<Image
|
||||
source={{ uri: item.novelCover }}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.nameContainer}>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.name,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
{item.novelName}
|
||||
</Text>
|
||||
{!loading && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{novel["Author(s)"].replace(",", ", ")}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel["Status"]}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel.source}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<NovelCoverImage source={{ uri: novelCover }} theme={theme}>
|
||||
<View style={styles.detailsContainer}>
|
||||
<View>
|
||||
<Image
|
||||
source={{ uri: novelCover }}
|
||||
style={styles.logo}
|
||||
/>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
</ImageBackground>
|
||||
<View style={styles.nameContainer}>
|
||||
<Text
|
||||
numberOfLines={2}
|
||||
style={[
|
||||
styles.name,
|
||||
{ color: theme.textColorPrimary },
|
||||
]}
|
||||
>
|
||||
{novelName}
|
||||
</Text>
|
||||
{!loading && (
|
||||
<>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
numberOfLines={2}
|
||||
>
|
||||
{novel.author.replace(",", ", ")}
|
||||
</Text>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
marginVertical: 3,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel.status}
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
fontSize: 14,
|
||||
}}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{novel.source}
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</NovelCoverImage>
|
||||
{!loading && (
|
||||
<>
|
||||
<View style={styles.buttonsContainer}>
|
||||
<Chip
|
||||
mode="outlined"
|
||||
icon={() => (
|
||||
<MaterialCommunityIcons
|
||||
name={
|
||||
novel.libraryStatus
|
||||
? "heart"
|
||||
: "heart-outline"
|
||||
}
|
||||
size={21}
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)}
|
||||
onPress={() =>
|
||||
insertNovelInLibrary(
|
||||
novel.libraryStatus,
|
||||
novel.novelUrl
|
||||
)
|
||||
}
|
||||
style={[
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
styles.toggleFavourite,
|
||||
]}
|
||||
textStyle={{
|
||||
fontWeight: "bold",
|
||||
color: theme.textColorPrimary,
|
||||
}}
|
||||
>
|
||||
{novel.libraryStatus
|
||||
? "In Library"
|
||||
: "Add to library"}
|
||||
</Chip>
|
||||
|
||||
<FollowChip
|
||||
theme={theme}
|
||||
followed={novel.followed}
|
||||
novel={novel}
|
||||
/>
|
||||
{tracker && (
|
||||
<TrackerChip
|
||||
theme={theme}
|
||||
isTracked={isTracked}
|
||||
trackerSheetRef={trackerSheetRef}
|
||||
/>
|
||||
)}
|
||||
<IconButton
|
||||
onPress={() =>
|
||||
WebBrowser.openBrowserAsync(
|
||||
novel.sourceUrl && novel.sourceUrl
|
||||
)
|
||||
WebBrowser.openBrowserAsync(novel.sourceUrl)
|
||||
}
|
||||
icon="earth"
|
||||
color={theme.colorAccentDark}
|
||||
@@ -159,57 +127,54 @@ const NovelInfoHeader = ({
|
||||
/>
|
||||
<IconButton
|
||||
onPress={() =>
|
||||
Share.share({
|
||||
message: novel.sourceUrl,
|
||||
})
|
||||
Share.share({ message: novel.sourceUrl })
|
||||
}
|
||||
icon="share-variant"
|
||||
color={theme.colorAccentDark}
|
||||
size={21}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{novel.novelSummary !== "" && (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
}}
|
||||
<Menu
|
||||
visible={downloadMenu}
|
||||
onDismiss={() => showDownloadMenu(false)}
|
||||
anchor={
|
||||
<IconButton
|
||||
icon="download"
|
||||
color={theme.colorAccentDark}
|
||||
size={21}
|
||||
onPress={() => showDownloadMenu(true)}
|
||||
/>
|
||||
}
|
||||
contentStyle={{ backgroundColor: theme.menuColor }}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
{ color: theme.textColorPrimary },
|
||||
styles.summaryHeader,
|
||||
]}
|
||||
>
|
||||
About
|
||||
</Text>
|
||||
<Text
|
||||
style={{
|
||||
color: theme.textColorSecondary,
|
||||
lineHeight: 20,
|
||||
}}
|
||||
numberOfLines={more ? 100 : 2}
|
||||
onPress={() => setMore(!more)}
|
||||
ellipsizeMode="clip"
|
||||
>
|
||||
{novel.novelSummary}
|
||||
</Text>
|
||||
<Text
|
||||
style={[
|
||||
{
|
||||
color: theme.colorAccentDark,
|
||||
backgroundColor: theme.colorPrimaryDark,
|
||||
},
|
||||
styles.moreBtn,
|
||||
]}
|
||||
onPress={() => setMore(!more)}
|
||||
>
|
||||
{more ? "Less " : "More "}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<Menu.Item
|
||||
title="Download all"
|
||||
style={{ backgroundColor: theme.menuColor }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
onPress={() =>
|
||||
dispatch(
|
||||
downloadAllChaptersAction(
|
||||
novel.sourceId,
|
||||
novel.novelUrl,
|
||||
chapters
|
||||
)
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Menu.Item
|
||||
title="Delete downloads"
|
||||
style={{ backgroundColor: theme.menuColor }}
|
||||
titleStyle={{ color: theme.textColorPrimary }}
|
||||
onPress={() =>
|
||||
dispatch(deleteAllChaptersAction(chapters))
|
||||
}
|
||||
/>
|
||||
</Menu>
|
||||
</View>
|
||||
<NovelSummary
|
||||
summary={novel.novelSummary}
|
||||
followed={novel.followed}
|
||||
theme={theme}
|
||||
/>
|
||||
<FlatList
|
||||
style={
|
||||
novel.novelSummary === ""
|
||||
@@ -218,38 +183,23 @@ const NovelInfoHeader = ({
|
||||
}
|
||||
contentContainerStyle={styles.genreContainer}
|
||||
horizontal
|
||||
data={novel["Genre(s)"] && novel["Genre(s)"].split(",")}
|
||||
data={novel.genre.split(",")}
|
||||
keyExtractor={(item) => item}
|
||||
renderItem={renderGenreChip}
|
||||
renderItem={getGenres}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
/>
|
||||
|
||||
<Button
|
||||
color="white"
|
||||
style={[
|
||||
{ backgroundColor: theme.colorAccentDark },
|
||||
styles.startButton,
|
||||
]}
|
||||
uppercase={false}
|
||||
labelStyle={{ letterSpacing: 0 }}
|
||||
onPress={() =>
|
||||
navigation.navigate("ChapterItem", {
|
||||
chapterUrl: novel.lastRead,
|
||||
novelUrl: novel.novelUrl,
|
||||
extensionId: novel.extensionId,
|
||||
chapterName: novel.lastReadName,
|
||||
})
|
||||
}
|
||||
>
|
||||
{novel.unread === 1
|
||||
? `Start reading ${novel.lastReadName}`
|
||||
: `Continue reading ${novel.lastReadName}`}
|
||||
</Button>
|
||||
|
||||
<ReadButton
|
||||
novel={novel}
|
||||
chapters={chapters}
|
||||
navigation={navigation}
|
||||
theme={theme}
|
||||
/>
|
||||
<TouchableRipple
|
||||
style={styles.bottomsheet}
|
||||
onPress={() =>
|
||||
bottomSheetRef.current.show({ velocity: -1.5 })
|
||||
chaptersSettingsSheetRef.current.show({
|
||||
velocity: -1.5,
|
||||
})
|
||||
}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
@@ -260,7 +210,7 @@ const NovelInfoHeader = ({
|
||||
styles.chapters,
|
||||
]}
|
||||
>
|
||||
{noOfChapters + " Chapters"}
|
||||
{`${chapters.length} Chapters`}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="filter-variant"
|
||||
@@ -283,12 +233,6 @@ const styles = StyleSheet.create({
|
||||
marginHorizontal: 16,
|
||||
paddingTop: 8,
|
||||
},
|
||||
background: {
|
||||
height: 285,
|
||||
},
|
||||
linearGradient: {
|
||||
height: 286,
|
||||
},
|
||||
detailsContainer: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
@@ -301,16 +245,6 @@ const styles = StyleSheet.create({
|
||||
margin: 3.2,
|
||||
borderRadius: 6,
|
||||
},
|
||||
genre: {
|
||||
borderRadius: 50,
|
||||
borderWidth: 1,
|
||||
paddingHorizontal: 10,
|
||||
marginHorizontal: 2,
|
||||
fontSize: 13,
|
||||
paddingVertical: 2,
|
||||
justifyContent: "center",
|
||||
flex: 1,
|
||||
},
|
||||
name: {
|
||||
fontWeight: "bold",
|
||||
fontSize: 18,
|
||||
@@ -319,16 +253,7 @@ const styles = StyleSheet.create({
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
toggleFavourite: {
|
||||
height: 32,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: 16,
|
||||
marginRight: 4,
|
||||
paddingHorizontal: 4,
|
||||
borderWidth: 0,
|
||||
elevation: 0,
|
||||
},
|
||||
|
||||
chapters: {
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 4,
|
||||
@@ -341,24 +266,7 @@ const styles = StyleSheet.create({
|
||||
alignItems: "center",
|
||||
paddingRight: 12,
|
||||
},
|
||||
summaryHeader: {
|
||||
marginTop: 5,
|
||||
paddingVertical: 5,
|
||||
fontSize: 15,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
moreBtn: {
|
||||
fontWeight: "bold",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 16,
|
||||
paddingLeft: 6,
|
||||
},
|
||||
startButton: {
|
||||
marginTop: 8,
|
||||
marginBottom: 16,
|
||||
marginHorizontal: 16,
|
||||
},
|
||||
|
||||
genreContainer: {
|
||||
paddingHorizontal: 15,
|
||||
paddingBottom: 15,
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View, Text, Image } from "react-native";
|
||||
import { TouchableRipple, IconButton } from "react-native-paper";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { untrackNovel } from "../../../../redux/tracker/tracker.actions";
|
||||
|
||||
export const AddMalTrackingCard = ({ theme, setTrackSearchDialog }) => (
|
||||
<View
|
||||
style={[
|
||||
styles.addCardContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
source={require("../../../../../assets/mal.png")}
|
||||
style={styles.trackerIcon}
|
||||
/>
|
||||
<TouchableRipple
|
||||
style={styles.rippleContainer}
|
||||
borderless
|
||||
onPress={() => setTrackSearchDialog(true)}
|
||||
rippleColor={theme.colorAccentDark}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
textAlignVertical: "center",
|
||||
color: theme.colorAccentDark,
|
||||
}}
|
||||
>
|
||||
Add Tracking
|
||||
</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
);
|
||||
|
||||
export const MalTrackItemCard = ({
|
||||
trackItem,
|
||||
setTrackStatusDialog,
|
||||
setTrackChaptersDialog,
|
||||
setTrackScoreDialog,
|
||||
getStatus,
|
||||
theme,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
styles.cardContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.titleContainer,
|
||||
{ borderBottomColor: theme.dividerColor },
|
||||
]}
|
||||
>
|
||||
<Image
|
||||
source={require("../../../../../assets/mal.png")}
|
||||
style={[styles.trackerIcon, { borderBottomLeftRadius: 0 }]}
|
||||
/>
|
||||
<View style={styles.listItemContainer}>
|
||||
<Text
|
||||
style={[
|
||||
styles.listItem,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{trackItem.title}
|
||||
</Text>
|
||||
<IconButton
|
||||
icon="close"
|
||||
color={theme.textColorSecondary}
|
||||
size={21}
|
||||
onPress={() => dispatch(untrackNovel(trackItem.id))}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<View style={{ height: 50, flexDirection: "row" }}>
|
||||
<TouchableRipple
|
||||
style={[
|
||||
styles.listItemLeft,
|
||||
{ borderRightColor: theme.dividerColor },
|
||||
]}
|
||||
borderless
|
||||
onPress={() => setTrackStatusDialog(true)}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.listItem,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{getStatus(trackItem.my_list_status.status)}
|
||||
</Text>
|
||||
</TouchableRipple>
|
||||
<TouchableRipple
|
||||
style={{ flex: 1 }}
|
||||
borderless
|
||||
onPress={() => setTrackChaptersDialog(true)}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.listItem,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{`${trackItem.my_list_status.num_chapters_read}/${
|
||||
trackItem.num_chapters !== 0
|
||||
? trackItem.num_chapters
|
||||
: "-"
|
||||
}`}
|
||||
</Text>
|
||||
</TouchableRipple>
|
||||
<TouchableRipple
|
||||
style={[
|
||||
styles.listItemRight,
|
||||
{ borderLeftColor: theme.dividerColor },
|
||||
]}
|
||||
borderless
|
||||
onPress={() => setTrackScoreDialog(true)}
|
||||
rippleColor={theme.rippleColor}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.listItem,
|
||||
{ color: theme.textColorSecondary },
|
||||
]}
|
||||
>
|
||||
{trackItem.my_list_status.score === 0
|
||||
? "-"
|
||||
: trackItem.my_list_status.score}
|
||||
</Text>
|
||||
</TouchableRipple>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
trackerIcon: {
|
||||
height: 50,
|
||||
width: 50,
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
rippleContainer: {
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
borderTopRightRadius: 4,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
addCardContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
margin: 8,
|
||||
borderRadius: 4,
|
||||
elevation: 2,
|
||||
},
|
||||
cardContainer: {
|
||||
margin: 8,
|
||||
elevation: 2,
|
||||
borderRadius: 4,
|
||||
},
|
||||
titleContainer: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderBottomWidth: 1,
|
||||
},
|
||||
listItemContainer: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
borderTopRightRadius: 4,
|
||||
},
|
||||
listItem: {
|
||||
flex: 1,
|
||||
textAlign: "center",
|
||||
textAlignVertical: "center",
|
||||
},
|
||||
listItemLeft: {
|
||||
flex: 1,
|
||||
borderRightWidth: 1,
|
||||
borderBottomLeftRadius: 4,
|
||||
},
|
||||
listItemRight: {
|
||||
flex: 1,
|
||||
borderLeftWidth: 1,
|
||||
borderBottomRightRadius: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import React, { useState } from "react";
|
||||
import { StyleSheet, Text } from "react-native";
|
||||
import { Modal, TextInput } from "react-native-paper";
|
||||
import {
|
||||
RadioButton,
|
||||
RadioButtonGroup,
|
||||
} from "../../../../components/RadioButton";
|
||||
|
||||
const SetTrackChaptersDialog = ({
|
||||
trackItem,
|
||||
trackChaptersDialog,
|
||||
setTrackChaptersDialog,
|
||||
updateTrackChapters,
|
||||
theme,
|
||||
}) => {
|
||||
const [trackChapters, setTrackChapters] = useState(
|
||||
trackItem.my_list_status.num_chapters_read
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={trackChaptersDialog}
|
||||
onDismiss={() => setTrackChaptersDialog(false)}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.dialogTitle, { color: theme.textColorPrimary }]}
|
||||
>
|
||||
Chapters
|
||||
</Text>
|
||||
<TextInput
|
||||
value={trackChapters.toString()}
|
||||
onChangeText={(text) => setTrackChapters(text ? +text : "")}
|
||||
onSubmitEditing={() => updateTrackChapters(trackChapters)}
|
||||
mode="outlined"
|
||||
keyboardType="numeric"
|
||||
theme={{
|
||||
colors: {
|
||||
primary: theme.colorAccentDark,
|
||||
placeholder: theme.textColorHintDark,
|
||||
text: theme.textColorPrimary,
|
||||
background: "transparent",
|
||||
},
|
||||
}}
|
||||
underlineColor={theme.textColorHintDark}
|
||||
dense
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetTrackChaptersDialog;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
margin: 30,
|
||||
padding: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Text } from "react-native";
|
||||
import { Modal } from "react-native-paper";
|
||||
import {
|
||||
RadioButton,
|
||||
RadioButtonGroup,
|
||||
} from "../../../../components/RadioButton";
|
||||
|
||||
const SetTrackScoreDialog = ({
|
||||
trackItem,
|
||||
trackScoreDialog,
|
||||
setTrackScoreDialog,
|
||||
updateTrackScore,
|
||||
theme,
|
||||
}) => {
|
||||
const getScoreLabel = (score) => {
|
||||
const myanimeListScores = {
|
||||
0: "No Score",
|
||||
1: "Apalling",
|
||||
2: "Horrible",
|
||||
3: "Very Bad",
|
||||
4: "Bad",
|
||||
5: "Average",
|
||||
6: "Fine",
|
||||
7: "Good",
|
||||
8: "Very Good",
|
||||
9: "Great",
|
||||
10: "Masterpiece",
|
||||
};
|
||||
|
||||
return `(${score}) ${myanimeListScores[score]}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={trackScoreDialog}
|
||||
onDismiss={() => setTrackScoreDialog(false)}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
theme={{ colors: { backdrop: "rgba(0,0,0,0.25)" } }}
|
||||
>
|
||||
<Text
|
||||
style={[styles.dialogTitle, { color: theme.textColorPrimary }]}
|
||||
>
|
||||
Score
|
||||
</Text>
|
||||
<RadioButtonGroup
|
||||
onValueChange={updateTrackScore}
|
||||
value={trackItem.my_list_status.score}
|
||||
>
|
||||
{[...Array(11).keys()].map((item, index) => (
|
||||
<RadioButton
|
||||
key={index}
|
||||
value={item}
|
||||
label={getScoreLabel(item)}
|
||||
theme={theme}
|
||||
/>
|
||||
))}
|
||||
</RadioButtonGroup>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetTrackScoreDialog;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
margin: 30,
|
||||
padding: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, Text } from "react-native";
|
||||
import { Modal } from "react-native-paper";
|
||||
import {
|
||||
RadioButton,
|
||||
RadioButtonGroup,
|
||||
} from "../../../../components/RadioButton";
|
||||
|
||||
const SetTrackStatusDialog = ({
|
||||
trackItem,
|
||||
trackStatusDialog,
|
||||
setTrackStatusDialog,
|
||||
updateTrackStatus,
|
||||
theme,
|
||||
}) => {
|
||||
const myAnimeListStatus = {
|
||||
reading: "Reading",
|
||||
completed: "Completed",
|
||||
on_hold: "On Hold",
|
||||
dropped: "Dropped",
|
||||
plan_to_read: "Plan to read",
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={trackStatusDialog}
|
||||
onDismiss={() => setTrackStatusDialog(false)}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
<Text
|
||||
style={[styles.dialogTitle, { color: theme.textColorPrimary }]}
|
||||
>
|
||||
Status
|
||||
</Text>
|
||||
<RadioButtonGroup
|
||||
onValueChange={updateTrackStatus}
|
||||
value={trackItem.my_list_status.status}
|
||||
>
|
||||
{Object.keys(myAnimeListStatus).map((key, index) => (
|
||||
<RadioButton
|
||||
key={index}
|
||||
value={key}
|
||||
label={myAnimeListStatus[key]}
|
||||
theme={theme}
|
||||
/>
|
||||
))}
|
||||
</RadioButtonGroup>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetTrackStatusDialog;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
margin: 30,
|
||||
padding: 20,
|
||||
borderRadius: 8,
|
||||
},
|
||||
dialogTitle: {
|
||||
fontSize: 18,
|
||||
marginBottom: 16,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { StyleSheet, Text, View, Image, ActivityIndicator } from "react-native";
|
||||
import { Button, Modal, TextInput, TouchableRipple } from "react-native-paper";
|
||||
import { searchNovels } from "../../../../trackers/MyAnimeList";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { ScrollView } from "react-native-gesture-handler";
|
||||
import { trackNovel } from "../../../../redux/tracker/tracker.actions";
|
||||
|
||||
const TrackSearchDialog = ({
|
||||
trackSearchDialog,
|
||||
setTrackSearchDialog,
|
||||
novelId,
|
||||
novelName,
|
||||
theme,
|
||||
}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [searchText, setSearchText] = useState(novelName);
|
||||
|
||||
const [selectedNovel, setSelectedNovel] = useState();
|
||||
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const getSearchresults = async () => {
|
||||
const res = await searchNovels(searchText, tracker.access_token);
|
||||
setSearchResults(res);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
trackSearchDialog && getSearchresults();
|
||||
}, [trackSearchDialog]);
|
||||
|
||||
const renderSearchResultCard = (item) => (
|
||||
<TouchableRipple
|
||||
style={[
|
||||
{ flexDirection: "row", borderRadius: 4, margin: 8 },
|
||||
selectedNovel &&
|
||||
selectedNovel.id === item.node.id && {
|
||||
backgroundColor: theme.rippleColor,
|
||||
},
|
||||
]}
|
||||
key={item.node.id}
|
||||
onPress={() => setSelectedNovel(item.node)}
|
||||
rippleColor={theme.rippleColor}
|
||||
borderless
|
||||
>
|
||||
<>
|
||||
<Image
|
||||
source={{ uri: item.node.main_picture.large }}
|
||||
style={{ height: 150, width: 100, borderRadius: 4 }}
|
||||
/>
|
||||
<Text
|
||||
style={{
|
||||
flex: 1,
|
||||
color: theme.textColorPrimary,
|
||||
marginLeft: 20,
|
||||
fontSize: 16,
|
||||
flexWrap: "wrap",
|
||||
padding: 8,
|
||||
paddingLeft: 0,
|
||||
}}
|
||||
>
|
||||
{item.node.title}
|
||||
</Text>
|
||||
</>
|
||||
</TouchableRipple>
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={trackSearchDialog}
|
||||
onDismiss={() => setTrackSearchDialog(false)}
|
||||
contentContainerStyle={[
|
||||
styles.containerStyle,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
theme={{ colors: { backdrop: "rgba(0,0,0,0.25)" } }}
|
||||
>
|
||||
<TextInput
|
||||
value={searchText}
|
||||
onChangeText={(text) => setSearchText(text)}
|
||||
onSubmitEditing={getSearchresults}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: theme.colorAccentDark,
|
||||
text: theme.textColorPrimary,
|
||||
background: "transparent",
|
||||
},
|
||||
}}
|
||||
underlineColor={theme.textColorHintDark}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
color={theme.textColorSecondary}
|
||||
icon="close"
|
||||
onPress={() => setSearchText("")}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ScrollView
|
||||
style={{ flexGrow: 1, maxHeight: 500, marginVertical: 8 }}
|
||||
>
|
||||
{loading ? (
|
||||
<ActivityIndicator
|
||||
color={theme.colorAccentDark}
|
||||
size={45}
|
||||
style={{ margin: 16 }}
|
||||
/>
|
||||
) : (
|
||||
searchResults &&
|
||||
searchResults.data &&
|
||||
searchResults.data.map((result) =>
|
||||
renderSearchResultCard(result)
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 30,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
onPress={() => setSelectedNovel(null)}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
<View style={{ flexDirection: "row" }}>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
onPress={() => setTrackSearchDialog(false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
labelStyle={{
|
||||
color: theme.colorAccentDark,
|
||||
letterSpacing: 0,
|
||||
textTransform: "none",
|
||||
}}
|
||||
theme={{ colors: { primary: theme.colorAccentDark } }}
|
||||
onPress={() => {
|
||||
if (selectedNovel) {
|
||||
dispatch(
|
||||
trackNovel(
|
||||
{ ...selectedNovel, novelId },
|
||||
tracker.access_token
|
||||
)
|
||||
);
|
||||
}
|
||||
setTrackSearchDialog(false);
|
||||
}}
|
||||
>
|
||||
OK
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackSearchDialog;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
containerStyle: {
|
||||
padding: 20,
|
||||
margin: 20,
|
||||
borderRadius: 6,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { StyleSheet, View, ToastAndroid } from "react-native";
|
||||
import { Portal } from "react-native-paper";
|
||||
import Bottomsheet from "rn-sliding-up-panel";
|
||||
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { updateTracker } from "../../../../redux/tracker/tracker.actions";
|
||||
|
||||
import TrackSearchDialog from "./TrackSearchDialog";
|
||||
import SetTrackStatusDialog from "./SetTrackStatusDialog";
|
||||
import SetTrackScoreDialog from "./SetTrackScoreDialog";
|
||||
import SetTrackChaptersDialog from "./SetTrackChaptersDialog";
|
||||
import { AddMalTrackingCard, MalTrackItemCard } from "./MyAnimeListCards";
|
||||
|
||||
const TrackSheet = ({ bottomSheetRef, novelId, novelName, theme }) => {
|
||||
const tracker = useSelector((state) => state.trackerReducer.tracker);
|
||||
const trackedNovels = useSelector(
|
||||
(state) => state.trackerReducer.trackedNovels
|
||||
);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [trackItem, setTrackItem] = useState();
|
||||
|
||||
useEffect(() => {
|
||||
setTrackItem(trackedNovels.find((obj) => obj.novelId === novelId));
|
||||
}, [trackedNovels, novelId]);
|
||||
|
||||
const [trackSearchDialog, setTrackSearchDialog] = useState(false);
|
||||
const [trackStatusDialog, setTrackStatusDialog] = useState(false);
|
||||
const [trackChaptersDialog, setTrackChaptersDialog] = useState(false);
|
||||
const [trackScoreDialog, setTrackScoreDialog] = useState(false);
|
||||
|
||||
const getStatus = (status) => {
|
||||
const myAnimeListStatus = {
|
||||
reading: "Reading",
|
||||
completed: "Completed",
|
||||
on_hold: "On Hold",
|
||||
dropped: "Dropped",
|
||||
plan_to_read: "Plan to read",
|
||||
};
|
||||
|
||||
return myAnimeListStatus[status];
|
||||
};
|
||||
|
||||
const updateTrackChapters = (newChapters) => {
|
||||
if (newChapters !== "") {
|
||||
setTrackItem({
|
||||
...trackItem,
|
||||
my_list_status: {
|
||||
...trackItem.my_list_status,
|
||||
num_chapters_read: newChapters,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(
|
||||
updateTracker(trackItem.id, tracker.access_token, {
|
||||
...trackItem.my_list_status,
|
||||
num_chapters_read: newChapters,
|
||||
})
|
||||
);
|
||||
|
||||
setTrackChaptersDialog(false);
|
||||
} else {
|
||||
ToastAndroid.show("Enter a valid number", ToastAndroid.SHORT);
|
||||
}
|
||||
};
|
||||
|
||||
const updateTrackStatus = (newStatus) => {
|
||||
setTrackItem({
|
||||
...trackItem,
|
||||
my_list_status: {
|
||||
...trackItem.my_list_status,
|
||||
status: newStatus,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(
|
||||
updateTracker(trackItem.id, tracker.access_token, {
|
||||
...trackItem.my_list_status,
|
||||
status: newStatus,
|
||||
})
|
||||
);
|
||||
|
||||
setTrackStatusDialog(false);
|
||||
};
|
||||
|
||||
const updateTrackScore = (newScore) => {
|
||||
setTrackItem({
|
||||
...trackItem,
|
||||
my_list_status: {
|
||||
...trackItem.my_list_status,
|
||||
score: newScore,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(
|
||||
updateTracker(trackItem.id, tracker.access_token, {
|
||||
...trackItem.my_list_status,
|
||||
score: newScore,
|
||||
})
|
||||
);
|
||||
|
||||
setTrackScoreDialog(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Bottomsheet
|
||||
ref={bottomSheetRef}
|
||||
draggableRange={{ top: 120, bottom: 0 }}
|
||||
snappingPoints={[0, 120]}
|
||||
backdropOpacity={0.25}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.contentContainer,
|
||||
{ backgroundColor: theme.colorPrimary },
|
||||
]}
|
||||
>
|
||||
{!trackItem ? (
|
||||
<AddMalTrackingCard
|
||||
theme={theme}
|
||||
setTrackSearchDialog={setTrackSearchDialog}
|
||||
/>
|
||||
) : (
|
||||
<MalTrackItemCard
|
||||
trackItem={trackItem}
|
||||
setTrackStatusDialog={setTrackStatusDialog}
|
||||
setTrackChaptersDialog={setTrackChaptersDialog}
|
||||
setTrackScoreDialog={setTrackScoreDialog}
|
||||
theme={theme}
|
||||
getStatus={getStatus}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
</Bottomsheet>
|
||||
<Portal>
|
||||
{trackItem ? (
|
||||
<>
|
||||
<SetTrackStatusDialog
|
||||
trackItem={trackItem}
|
||||
trackStatusDialog={trackStatusDialog}
|
||||
setTrackStatusDialog={setTrackStatusDialog}
|
||||
updateTrackStatus={updateTrackStatus}
|
||||
theme={theme}
|
||||
/>
|
||||
<SetTrackChaptersDialog
|
||||
trackItem={trackItem}
|
||||
trackChaptersDialog={trackChaptersDialog}
|
||||
setTrackChaptersDialog={setTrackChaptersDialog}
|
||||
updateTrackChapters={updateTrackChapters}
|
||||
theme={theme}
|
||||
/>
|
||||
<SetTrackScoreDialog
|
||||
trackItem={trackItem}
|
||||
trackScoreDialog={trackScoreDialog}
|
||||
setTrackScoreDialog={setTrackScoreDialog}
|
||||
updateTrackScore={updateTrackScore}
|
||||
theme={theme}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<TrackSearchDialog
|
||||
trackSearchDialog={trackSearchDialog}
|
||||
setTrackSearchDialog={setTrackSearchDialog}
|
||||
novelId={novelId}
|
||||
novelName={novelName}
|
||||
theme={theme}
|
||||
/>
|
||||
)}
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TrackSheet;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: { flex: 1 },
|
||||
});
|
||||
@@ -1,3 +1,67 @@
|
||||
/**
|
||||
* Updates Screen
|
||||
*/
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import { useFocusEffect } from "@react-navigation/native";
|
||||
import { StyleSheet, View, FlatList, ActivityIndicator } from "react-native";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import EmptyView from "../../components/EmptyView";
|
||||
import { getUpdatesAction } from "../../redux/updates/updates.actions";
|
||||
import { Appbar } from "./components/Appbar";
|
||||
import UpdateCard from "./components/UpdateCard";
|
||||
|
||||
const Updates = ({ theme, updates, loading, getUpdatesAction }) => {
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getUpdatesAction();
|
||||
}, [getUpdatesAction])
|
||||
);
|
||||
|
||||
const renderUpdateCard = ({ item }) => <UpdateCard item={item} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Appbar />
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
{ backgroundColor: theme.colorPrimaryDark },
|
||||
]}
|
||||
>
|
||||
<FlatList
|
||||
contentContainerStyle={{ flexGrow: 1, paddingVertical: 8 }}
|
||||
data={updates}
|
||||
keyExtractor={(item) => item.updateId.toString()}
|
||||
renderItem={renderUpdateCard}
|
||||
ListFooterComponent={
|
||||
loading && (
|
||||
<ActivityIndicator
|
||||
size="small"
|
||||
color={theme.colorAccentDark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ListEmptyComponent={
|
||||
!loading && (
|
||||
<EmptyView
|
||||
icon="(˘・_・˘)"
|
||||
description="No recent updates"
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
theme: state.themeReducer.theme,
|
||||
updates: state.updatesReducer.updates,
|
||||
loading: state.updatesReducer.loading,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, { getUpdatesAction })(Updates);
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user