diff --git a/android/app/src/main/assets/css/index.js b/android/app/src/main/assets/css/index.js new file mode 100644 index 000000000..7cc906b42 --- /dev/null +++ b/android/app/src/main/assets/css/index.js @@ -0,0 +1,455 @@ +export default ` +html { + scroll-behavior: smooth; + overflow-x: hidden; + padding-top: var(--StatusBar-currentHeight); + word-wrap: break-word; + padding: 0; +} + +::selection { + color: var(--theme-onSecondary); + background-color: var(--theme-secondary); +} + +::-moz-selection { + color: var(--theme-onSecondary); + background-color: var(--theme-secondary); +} + +body { + margin-left: 0; + margin-right: 0; + padding: 0; + padding-bottom: 40px; + + font-size: var(--readerSettings-textSize); + background-color: var(--readerSettings-theme); + color: var(--readerSettings-textColor); + text-align: var(--readerSettings-textAlign); + line-height: var(--readerSettings-lineHeight); + font-family: var(--readerSettings-fontFamily); +} + +chapter { + width: 100%; + display: block; +} + +chapter > * { + max-width: 100vw; + padding-left: var(--readerSettings-padding); + padding-right: var(--readerSettings-padding); +} + +hr { + margin-top: 20px; + margin-bottom: 20px; +} + +a { + color: var(--theme-primary); +} + +img { + display: block; + width: auto; + height: auto; + max-width: 100%; +} + +table { + background-color: var(--theme-onPrimary); + border-collapse: collapse; + color: var(--theme-primary); +} + +th { + font-weight: bold; +} + +td { + padding: 10px; + text-align: center; +} + +table, +th, +td { + border: 1px solid var(--theme-outline); +} + +.hidden { + visibility: hidden; +} + +.nextButton, +.infoText { + margin-left: var(--readerSettings-padding); + margin-right: var(--readerSettings-padding); + width: calc(100% - var(--readerSettings-padding) * 2); + border-radius: 50px; + border-width: 1; + color: var(--theme-onPrimary); + background-color: var(--theme-primary); + font-family: var(--readerSettings-fontFamily); + font-size: 16px; + border-width: 0; + user-select: none; +} + +.nextButton { + min-height: 40px; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding: 0 16px; +} + +.infoText { + background-color: transparent; + text-align: center; + border: none; + margin: 0px; + color: inherit; + padding-top: 16px; + padding-bottom: 16px; +} + +.chapterCtn { + min-height: var(--chapterCtn-height); + margin-bottom: auto; +} + +.d-none { + display: none; +} + +#ToolWrapper { + position: fixed; + top: 50%; + transform: translateY(-50%); + right: 5vw; +} + +#ToolWrapper.horizontal { + top: unset; + right: unset; + left: 50%; + transform: translateX(-50%); + bottom: 120px; + display: flex; + flex-direction: row-reverse; + align-items: center; +} + +@media only screen and (min-width: 500px) { + #ToolWrapper.horizontal { + bottom: 80px; + } +} + +#ToolWrapper.hidden { + opacity: 0; + right: 0; +} + +#ToolWrapper.hidden.horizontal { + opacity: 0; + bottom: 0; + right: unset; +} + +#TTS-Controller { + display: block; +} + +#ToolWrapper.horizontal > #TTS-Controller { + margin-left: 4px; +} + +#TTS-Controller button { + background: var(--theme-surface-0-9); + border: 0; +} + +#TTS-Controller svg { + fill: var(--theme-onSurface); +} + +#ToolWrapper > #ScrollBar { + margin-top: 8px; + width: 2.4rem; + height: 45vh; + min-height: 200px; + border-radius: 1.2rem; + background-color: var(--theme-surface-0-9); + touch-action: none; + font-size: 14px; + user-select: none; +} + +#ScrollBar > .scrollbar-item { + display: flex; + justify-content: center; + align-items: center; + color: var(--theme-onSurface); +} + +#ScrollBar > .scrollbar-item.scrollbar-text { + height: 12.5%; +} + +#ScrollBar > .scrollbar-item#scrollbar-slider { + height: 75%; + align-items: unset; +} + +#ScrollBar #scrollbar-track { + width: 0.1rem; + height: 100%; + background-color: var(--theme-outline); +} + +#ScrollBar #scrollbar-progress { + width: 100%; + height: 0; + background-color: var(--theme-primary); +} + +#ScrollBar #scrollbar-thumb-wrapper { + position: relative; + top: 100%; + height: 2rem; + width: 2rem; + display: flex; + align-items: center; + justify-content: center; + transform: translate(-0.95rem, -1rem); +} + +#ScrollBar #scrollbar-thumb { + top: 100%; + height: 1rem; + width: 1rem; + border-radius: 100%; + background-color: var(--theme-primary); +} + +#ToolWrapper.horizontal > #ScrollBar { + display: flex; + width: 80vw; + height: 2.4rem; + min-height: unset; + align-items: center; + margin-top: unset; +} + +#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item { + display: flex; + justify-content: center; + color: var(--theme-onSurface); +} + +#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item.scrollbar-text { + width: 12.5%; +} + +#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item#scrollbar-slider { + width: 75%; + align-items: center; +} + +#ToolWrapper.horizontal > #ScrollBar #scrollbar-track { + height: 0.1rem; + width: 100%; + background-color: var(--theme-outline); +} + +#ToolWrapper.horizontal > #ScrollBar #scrollbar-progress { + height: 100%; + width: 0; + background-color: var(--theme-primary); +} + +#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb-wrapper { + position: relative; + left: 100%; + height: 2rem; + width: 2rem; + display: flex; + align-items: center; + justify-content: center; + transform: translate(-0.95rem, -1rem); +} + +#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb { + top: 100%; + height: 1rem; + width: 1rem; + border-radius: 100%; + background-color: var(--theme-primary); +} + +#reader-footer-wrapper { + position: fixed; + bottom: 0; + left: 0; + width: 100%; +} + +#reader-footer { + display: flex; + flex: 3; + padding: 0.2rem 2rem; + background-color: var(--readerSettings-theme); + color: var(--readerSettings-textColor); + font-size: 1rem; + text-align: center; + user-select: none; +} + +.reader-footer-item { + flex: 1; +} + +.reader-footer-item:first-child { + text-align: left; +} + +.reader-footer-item:last-child { + text-align: right; +} + +.highlight { + color: var(--theme-onSecondary); + background-color: var(--theme-secondary); +} + +.contextMenu { + position: fixed; + background: var(--theme-surface-0-9); + height: 0; + top: var(--top); + left: var(--left); + overflow: hidden; + -webkit-backdrop-filter: blur(1px); + backdrop-filter: blur(1px); + -webkit-animation: menuAnimation 0.4s 0s both; + animation: menuAnimation 0.4s 0s both; + transform-origin: left; + list-style: none; + margin: 4px; + padding: 0; + display: flex; + flex-direction: column; + z-index: 999999999; +} + +.contextMenu-item { + padding: 4px; +} + +.contextMenu-button { + color: var(--theme-onSurface); + background: 0; + border: 0; + border-radius: 4px; + white-space: nowrap; + padding: 6px 24px 6px 7px; + text-align: left; + display: flex; + align-items: center; + font-size: 14px; + width: 100%; + -webkit-animation: menuItemAnimation 0.2s 0s both; + animation: menuItemAnimation 0.2s 0s both; + font-family: var(--readerSettings-fontFamily); + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: var(--theme-rippleColor); +} + +.contextMenu-button svg { + fill: var(--theme-onSurface); +} + +.contextMenu-button span { + margin-left: 4px; +} + +@-webkit-keyframes menuAnimation { + 0% { + opacity: 0; + transform: scale(0.5); + } + + 100% { + height: var(--height); + opacity: 1; + border-radius: 8px; + transform: scale(1); + } +} + +@keyframes menuAnimation { + 0% { + opacity: 0; + transform: scale(0.5); + } + + 100% { + height: var(--height); + opacity: 1; + border-radius: 8px; + transform: scale(1); + } +} + +@-webkit-keyframes menuItemAnimation { + 0% { + opacity: 0; + transform: translateX(-10px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes menuItemAnimation { + 0% { + opacity: 0; + transform: translateX(-10px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +#Image-Modal { + position: fixed; + left: 0; + top: 0; + user-select: none; +} + +#Image-Modal.show { + display: flex; + height: 100%; + width: 100%; + background-color: var(--theme-surface-0-9); + align-items: center; + justify-content: center; + overflow: scroll; +} + +#Image img { + display: block; + width: 100%; +} + +`; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 64221bafb..946f0e135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "lnreader", "version": "2.0.0", "dependencies": { - "@cd-z/react-native-epub-creator": "^2.0.4", + "@cd-z/react-native-epub-creator": "^3.0.0", "@gorhom/bottom-sheet": "^4.4.5", "@react-native-community/slider": "^4.4.2", "@react-native-cookies/cookies": "^6.2.1", @@ -43,6 +43,7 @@ "react-native-device-info": "^8.4.7", "react-native-drawer-layout": "^3.2.2", "react-native-error-boundary": "^1.1.16", + "react-native-file-access": "^3.1.0", "react-native-gesture-handler": "~2.12.0", "react-native-lottie-splash-screen": "^1.0.1", "react-native-mmkv": "^2.11.0", @@ -2043,18 +2044,20 @@ } }, "node_modules/@cd-z/epub-constructor": { - "version": "2.2.5", - "license": "MIT", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cd-z/epub-constructor/-/epub-constructor-3.0.1.tgz", + "integrity": "sha512-gI8eZ2+YnkflO48p+IjRVjFMJXv/86MYNdL6cxZUNd+GPEv3XBebg/hrHKXvTGTcBA3502CnwBf0hWoQyx3OoQ==", "dependencies": { - "node-html-parser": "^4.1.4" + "sanitize-html": "^2.13.0" } }, "node_modules/@cd-z/react-native-epub-creator": { - "version": "2.0.4", - "license": "MIT", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cd-z/react-native-epub-creator/-/react-native-epub-creator-3.0.0.tgz", + "integrity": "sha512-vpI3viNxkSJWuSCuotFKfe1Ulg7Z+VPBTSt4GkGSGwHwpZxkNnh/UOZqyHBTtn3yvjaq2PK3GABDZlxlDhiQvA==", "dependencies": { - "@cd-z/epub-constructor": "^2.1.0", - "expo-file-system": "^15.2.2", + "@cd-z/epub-constructor": "^3.0.0", + "react-native-file-access": "^3.1.0", "react-native-saf-x": "^2.2.1", "react-native-zip-archive": "^6.0.3" }, @@ -10212,13 +10215,6 @@ "node": ">= 0.4" } }, - "node_modules/he": { - "version": "1.2.0", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/hermes-estree": { "version": "0.12.0", "license": "MIT" @@ -13462,72 +13458,6 @@ "node": ">= 6.13.0" } }, - "node_modules/node-html-parser": { - "version": "4.1.5", - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "he": "1.2.0" - } - }, - "node_modules/node-html-parser/node_modules/css-select": { - "version": "4.3.0", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domhandler": { - "version": "4.3.1", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils": { - "version": "2.8.0", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils/node_modules/dom-serializer": { - "version": "1.4.1", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/node-html-parser/node_modules/css-select/node_modules/domutils/node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/node-int64": { "version": "0.4.0", "license": "MIT" @@ -14866,6 +14796,15 @@ } } }, + "node_modules/react-native-file-access": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.1.0.tgz", + "integrity": "sha512-wOpfKpJ8s4Csfjcvf7H4L1EtmejM07HQpndzMRWAianLC50EsPc78iV8TQaw5yI7j18rh9fWMqpevz8f5a1rsA==", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-gesture-handler": { "version": "2.12.1", "license": "MIT", diff --git a/package.json b/package.json index 409f69844..0738370bf 100644 --- a/package.json +++ b/package.json @@ -9,13 +9,14 @@ "start": "react-native start", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "prettier": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\" ./scripts", - "buildRelease": "node scripts/setEnvFile.cjs Release && cd android && ./gradlew clean && ./gradlew assembleRelease", + "buildRelease": "node scripts/convertCssToJs.cjs && node scripts/setEnvFile.cjs Release && cd android && ./gradlew clean && ./gradlew assembleRelease", "open": "open ./android/app/build/outputs/apk/release/", "strings": "node scripts/stringTypes.cjs", - "prepare": "husky install" + "prepare": "husky install", + "cssToJs": "node scripts/convertCssToJs.cjs" }, "dependencies": { - "@cd-z/react-native-epub-creator": "^2.0.4", + "@cd-z/react-native-epub-creator": "^3.0.0", "@gorhom/bottom-sheet": "^4.4.5", "@react-native-community/slider": "^4.4.2", "@react-native-cookies/cookies": "^6.2.1", @@ -50,6 +51,7 @@ "react-native-device-info": "^8.4.7", "react-native-drawer-layout": "^3.2.2", "react-native-error-boundary": "^1.1.16", + "react-native-file-access": "^3.1.0", "react-native-gesture-handler": "~2.12.0", "react-native-lottie-splash-screen": "^1.0.1", "react-native-mmkv": "^2.11.0", diff --git a/scripts/convertCssToJs.cjs b/scripts/convertCssToJs.cjs new file mode 100644 index 000000000..8ae58d357 --- /dev/null +++ b/scripts/convertCssToJs.cjs @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); + +const cssFilePath = path.resolve( + __dirname + '/../android/app/src/main/assets/css/', + 'index.css', +); +const jsFilePath = path.resolve( + __dirname + '/../android/app/src/main/assets/css/', + 'index.js', +); + +fs.readFile(cssFilePath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading CSS file:', err); + return; + } + + const jsContent = `export default \`\n${data}\n\`;`; + + fs.writeFile(jsFilePath, jsContent, 'utf8', err => { + if (err) { + console.error('Error writing JS file:', err); + return; + } + + console.info('CSS file successfully converted to JS module.'); + }); +}); diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 3356aa0db..5be6512b3 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -8,6 +8,7 @@ import { useTheme } from '@hooks/persisted'; interface ButtonProps extends Partial { title?: string; + children?: string; } const Button: React.FC = props => { diff --git a/src/components/List/List.tsx b/src/components/List/List.tsx index 2607c7b6b..703b4ea5b 100644 --- a/src/components/List/List.tsx +++ b/src/components/List/List.tsx @@ -87,13 +87,21 @@ const InfoItem = ({ title, icon = 'information-outline', theme, + paddingHorizontal = 16, }: { title: string; icon?: string; theme: ThemeColors; + paddingHorizontal?: number; }) => ( - - + + + {title} @@ -159,10 +167,13 @@ const styles = StyleSheet.create({ }, infoCtn: { paddingVertical: 12, - paddingHorizontal: 16, + // paddingHorizontal: 16, + display: 'flex', + flexDirection: 'row', }, infoMsg: { - marginTop: 12, + marginLeft: 6, + verticalAlign: 'middle', fontSize: 12, }, iconCtn: { diff --git a/src/screens/novel/NovelScreen.tsx b/src/screens/novel/NovelScreen.tsx index 4937dc824..842a7d7df 100644 --- a/src/screens/novel/NovelScreen.tsx +++ b/src/screens/novel/NovelScreen.tsx @@ -89,6 +89,7 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { refreshChapters, deleteChapters, } = useNovel(path, pluginId); + const theme = useTheme(); const { top: topInset, bottom: bottomInset } = useSafeAreaInsets(); @@ -381,7 +382,9 @@ const Novel = ({ route, navigation }: NovelScreenProps) => { {selected.length === 0 ? ( = ({ const openFolderPicker = async () => { try { - const resultUri = await openDocumentTree(true); + const resultUri = await FileManager.pickFolder(); if (resultUri) { - setUri(resultUri.uri); + setUri(resultUri); } } catch (error: any) { showToast(error.message); @@ -76,6 +76,7 @@ const ChooseEpubLocationModal: React.FC = ({ {getString('novelScreen.convertToEpubModal.chooseLocation')} + = ({ mode="outlined" theme={{ colors: { ...theme } }} underlineColor={theme.outline} + editable={false} dense right={ = ({ value={useAppTheme.value} onPress={useAppTheme.toggle} theme={theme} + style={styles.switch} /> = ({ value={useCustomJS.value} onPress={useCustomJS.toggle} theme={theme} + style={styles.switch} /> +