diff --git a/android/app/src/main/assets/css/index.css b/android/app/src/main/assets/css/index.css index 82ca1cec..d6c28298 100644 --- a/android/app/src/main/assets/css/index.css +++ b/android/app/src/main/assets/css/index.css @@ -97,7 +97,23 @@ img { top: 50%; transform: translateY(-50%); right: 5vw; - transition: 150ms; +} + +#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 { @@ -105,10 +121,20 @@ img { 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; @@ -118,7 +144,7 @@ img { fill: var(--theme-onSurface); } -#ScrollBar { +#ToolWrapper > #ScrollBar { margin-top: 8px; width: 2.4rem; height: 45vh; @@ -130,35 +156,35 @@ img { user-select: none; } -.scrollbar-item { +#ScrollBar > .scrollbar-item { display: flex; justify-content: center; align-items: center; color: var(--theme-onSurface); } -.scrollbar-item.scrollbar-text { +#ScrollBar > .scrollbar-item.scrollbar-text { height: 12.5%; } -.scrollbar-item#scrollbar-slider { +#ScrollBar > .scrollbar-item#scrollbar-slider { height: 75%; align-items: unset; } -#scrollbar-track { +#ScrollBar #scrollbar-track { width: 0.1rem; height: 100%; background-color: var(--theme-outline); } -#scrollbar-progress { - width: 0.1rem; +#ScrollBar #scrollbar-progress { + width: 100%; height: 0; background-color: var(--theme-primary); } -#scrollbar-thumb-wrapper { +#ScrollBar #scrollbar-thumb-wrapper { position: relative; top: 100%; height: 2rem; @@ -169,7 +195,62 @@ img { transform: translate(-0.95rem, -1rem); } -#scrollbar-thumb { +#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; @@ -186,10 +267,8 @@ img { #reader-footer { display: flex; - flex: 1; - justify-content: space-between; - min-height: 2rem; - padding: 0.5rem 2rem 0 2rem; + flex: 3; + padding: 0.2rem 2rem; background-color: var(--readerSettings-theme); color: var(--readerSettings-textColor); font-size: 1rem; @@ -198,7 +277,13 @@ img { } .reader-footer-item { - display: flex; + flex: 1; +} +.reader-footer-item:first-child { + text-align: left; +} +.reader-footer-item:last-child { + text-align: right; } tts.highlight { diff --git a/android/app/src/main/assets/js/index.js b/android/app/src/main/assets/js/index.js index 3925eb0e..cf12deb2 100644 --- a/android/app/src/main/assets/js/index.js +++ b/android/app/src/main/assets/js/index.js @@ -36,6 +36,7 @@ class Reader { ), ); this.chapter = document.querySelector('chapter'); + this.rawHTML = this.chapter.innerHTML; this.chapterHeight = this.chapter.scrollHeight + this.paddingTop; this.layoutHeight = window.innerHeight; this.pluginId = this.chapter.getAttribute('data-plugin-id'); @@ -54,11 +55,13 @@ class Reader { this.time.innerText = new Date().toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', + hour12: false, }); this.timeInterval = setInterval(() => { this.time.innerText = new Date().toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', + hour12: false, }); }, 60000); this.updateBatteryLevel(batteryLevel); @@ -110,6 +113,13 @@ class Reader { updateGeneralSettings = settings => { this.showScrollPercentage = settings.showScrollPercentage; this.showBatteryAndTime = settings.showBatteryAndTime; + this.verticalSeekbar = settings.verticalSeekbar; + this.bionicReading = settings.bionicReading; + if (this.bionicReading) { + this.chapter.innerHTML = textVide.textVide(this.rawHTML); + } else { + this.chapter.innerHTML = this.rawHTML; + } if (settings.swipeGestures) { swipeHandler.enable(); } else { @@ -132,6 +142,15 @@ class Reader { this.time.classList.add('hidden'); } } + if (!this.verticalSeekbar) { + toolWrapper.$.classList.add('horizontal'); + } else { + toolWrapper.$.classList.remove('horizontal'); + } + if (scrollHandler) { + scrollHandler.refresh(); + scrollHandler.update(); + } }; updateBatteryLevel = level => { this.battery.innerText = Math.ceil(level * 100) + '%'; @@ -194,7 +213,11 @@ class ScrollHandler { this.thumb.ontouchend = () => (this.lock = false); this.thumb.ontouchmove = e => { const ratio = - (e.changedTouches[0].clientY - this.sliderOffsetY) / this.sliderHeight; + ((this.reader.verticalSeekbar + ? e.changedTouches[0].clientY + : e.changedTouches[0].clientX) - + this.sliderOffsetY) / + this.sliderHeight; this.update(ratio < 0 ? 0 : ratio); }; } @@ -208,7 +231,13 @@ class ScrollHandler { } const percentage = parseInt(ratio * 100); if (this.toolWrapper.visible) { - this.progress.style.height = percentage + '%'; + if (this.reader.verticalSeekbar) { + this.progress.style.height = percentage + '%'; + this.progress.style.width = '100%'; + } else { + this.progress.style.height = '100%'; + this.progress.style.width = percentage + '%'; + } this.percentage.innerText = percentage; } if (this.lock) { @@ -222,8 +251,13 @@ class ScrollHandler { } }; refresh = () => { - this.sliderHeight = this.slider.clientHeight; - this.sliderOffsetY = this.slider.getBoundingClientRect().top; + if (this.reader.verticalSeekbar) { + this.sliderHeight = this.slider.clientHeight; + this.sliderOffsetY = this.slider.getBoundingClientRect().top; + } else { + this.sliderHeight = this.slider.clientWidth; + this.sliderOffsetY = this.slider.getBoundingClientRect().left; + } }; onShow = () => { this.reader.refresh(); @@ -243,12 +277,26 @@ class SwipeHandler { this.initialY = e.changedTouches[0].screenY; }; + /** + * @param {TouchEvent} e + */ touchEndHandler = e => { let diffX = e.changedTouches[0].screenX - this.initialX; let diffY = e.changedTouches[0].screenY - this.initialY; - if (Math.abs(diffX) > Math.abs(diffY) && Math.abs(diffX) > 10) { - e.preventDefault(); - reader.post({ type: diffX < 0 ? 'next' : 'prev' }); + if ( + e.target.id?.startsWith('scrollbar') || + e.target.id === 'Image-Modal-img' + ) { + return; + } + if (Math.abs(diffX) > Math.abs(diffY) * 2 && Math.abs(diffX) > 180) { + if (diffX < 0 && this.initialX >= window.innerWidth / 2) { + e.preventDefault(); + reader.post({ type: 'next' }); + } else if (diffX > 0 && this.initialX <= window.innerWidth / 2) { + e.preventDefault(); + reader.post({ type: 'prev' }); + } } }; diff --git a/android/app/src/main/assets/js/text-vibe.js b/android/app/src/main/assets/js/text-vibe.js new file mode 100644 index 00000000..4ab954c7 --- /dev/null +++ b/android/app/src/main/assets/js/text-vibe.js @@ -0,0 +1,111 @@ +var textVide = (function (L) { + 'use strict'; + const f = t => t == null || t === '', + h = (t, n) => + Object.keys(t).reduce((e, s) => (n(e[s]) && delete e[s], e), t), + I = (t, n) => ({ ...n, ...h(t, f) }), + _ = ['', ''], + y = t => + I(t, { + sep: _, + fixationPoint: 1, + ignoreHtmlTag: !0, + ignoreHtmlEntity: !0, + }), + u = [ + [0, 4, 12, 17, 24, 29, 35, 42, 48], + [1, 2, 7, 10, 13, 14, 19, 22, 25, 28, 31, 34, 37, 40, 43, 46, 49], + [ + 1, 2, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, 37, + 39, 41, 43, 45, 47, 49, + ], + [ + 0, 2, 4, 5, 6, 8, 9, 11, 14, 15, 17, 18, 20, 0, 21, 23, 24, 26, 27, 29, + 30, 32, 33, 35, 36, 38, 39, 41, 42, 44, 45, 47, 48, + ], + [ + 0, 2, 3, 5, 6, 7, 8, 10, 11, 12, 14, 15, 17, 19, 20, 21, 23, 24, 25, 26, + 28, 29, 30, 32, 33, 34, 35, 37, 38, 39, 41, 42, 43, 44, 46, 47, 48, + ], + ], + A = (t, n) => { + const { length: i } = t, + e = u[n - 1] ?? u[0], + s = e.findIndex(c => i <= c); + let o = i - s; + return s === -1 && (o = i - e.length), Math.max(o, 0); + }, + R = (t, n) => + typeof n === 'string' ? `${n}${t}${n}` : `${n[0]}${t}${n[1]}`, + m = t => + Array.from(t).map(n => { + const i = n.index, + [e] = n, + { length: s } = e; + return [i, i + s - 1]; + }), + H = /()|(<[^>]*>)/g, + N = t => { + const n = t.matchAll(H), + e = m(n).reverse(); + return s => { + const o = s.index, + c = e.find(([r]) => o > r); + if (!c) { + return !1; + } + const [, a] = c; + return o < a; + }; + }, + O = /(&[\w#]+;)/g, + M = t => { + const n = t.matchAll(O), + e = m(n).reverse(); + return s => { + const o = s.index, + c = e.find(([r]) => o > r); + if (!c) { + return !1; + } + const [, a] = c; + return o < a; + }; + }, + x = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu, + F = (t, n = {}) => { + if (!(t != null && t.length)) { + return ''; + } + const { + fixationPoint: i, + sep: e, + ignoreHtmlTag: s, + ignoreHtmlEntity: o, + } = y(n), + c = Array.from(t.matchAll(x)); + let a = '', + l = 0, + r; + s && (r = N(t)); + let d; + o && (d = M(t)); + for (const g of c) { + if ((r == null ? void 0 : r(g)) || (d == null ? void 0 : d(g))) { + continue; + } + const [D] = g, + T = g.index, + E = T + A(D, i), + U = t.slice(l, T); + (a += U), T !== E && (a += R(t.slice(T, E), e)), (l = E); + } + const G = t.slice(l); + return a + G; + }; + return ( + (L.textVide = F), + Object.defineProperty(L, Symbol.toStringTag, { value: 'Module' }), + L + ); +})({}); diff --git a/src/database/queries/LibraryQueries.ts b/src/database/queries/LibraryQueries.ts index 0f49704f..37949ef4 100644 --- a/src/database/queries/LibraryQueries.ts +++ b/src/database/queries/LibraryQueries.ts @@ -41,7 +41,7 @@ export const getLibraryNovelsFromDb = ( let getLibraryNovelsQuery = 'SELECT * FROM Novel WHERE inLibrary = 1'; if (onlyOngoingNovels) { - getLibraryNovelsQuery += " AND status NOT LIKE 'Completed'"; + getLibraryNovelsQuery += " AND status = 'Ongoing'"; } return new Promise(resolve => diff --git a/src/hooks/persisted/useSettings.ts b/src/hooks/persisted/useSettings.ts index 2ae2a80d..c92c9cfb 100644 --- a/src/hooks/persisted/useSettings.ts +++ b/src/hooks/persisted/useSettings.ts @@ -81,6 +81,7 @@ export interface ChapterGeneralSettings { autoScrollOffset: number | null; verticalSeekbar: boolean; removeExtraParagraphSpacing: boolean; + bionicReading: boolean; } export interface ReaderTheme { @@ -158,6 +159,7 @@ export const initialChapterGeneralSettings: ChapterGeneralSettings = { autoScrollOffset: null, verticalSeekbar: true, removeExtraParagraphSpacing: false, + bionicReading: false, }; export const initialChapterReaderSettings: ChapterReaderSettings = { diff --git a/src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx b/src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx index 55a2d1ef..3ab56645 100644 --- a/src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx +++ b/src/screens/reader/components/ReaderBottomSheet/ReaderBottomSheet.tsx @@ -33,12 +33,13 @@ const GeneralTab: React.FC = () => { const { fullScreenMode, autoScroll, - // verticalSeekbar, + verticalSeekbar, showBatteryAndTime, showScrollPercentage, useVolumeButtons, swipeGestures, removeExtraParagraphSpacing, + bionicReading, setChapterGeneralSettings, } = useChapterGeneralSettings(); @@ -58,14 +59,14 @@ const GeneralTab: React.FC = () => { value={autoScroll} theme={theme} /> - {/* setChapterGeneralSettings({ verticalSeekbar: !verticalSeekbar }) } value={verticalSeekbar} theme={theme} - /> */} + /> @@ -110,6 +111,14 @@ const GeneralTab: React.FC = () => { value={useVolumeButtons} theme={theme} /> + + setChapterGeneralSettings({ bionicReading: !bionicReading }) + } + value={bionicReading} + theme={theme} + /> ); }; diff --git a/src/screens/reader/components/WebViewReader.tsx b/src/screens/reader/components/WebViewReader.tsx index 22c6ac3d..47aa2ffd 100644 --- a/src/screens/reader/components/WebViewReader.tsx +++ b/src/screens/reader/components/WebViewReader.tsx @@ -75,7 +75,13 @@ const WebViewReader: FC = props => { initialChapterReaderSettings, [], ); - const { showScrollPercentage, swipeGestures, showBatteryAndTime } = useMemo( + const { + showScrollPercentage, + swipeGestures, + showBatteryAndTime, + verticalSeekbar, + bionicReading, + } = useMemo( () => getMMKVObject(CHAPTER_GENERAL_SETTINGS) || initialChapterGeneralSettings, @@ -223,6 +229,8 @@ const WebViewReader: FC = props => { showScrollPercentage: ${showScrollPercentage}, swipeGestures: ${swipeGestures}, showBatteryAndTime: ${showBatteryAndTime}, + verticalSeekbar: ${verticalSeekbar}, + bionicReading: ${bionicReading}, } var batteryLevel = ${batteryLevel}; var autoSaveInterval = 2222; @@ -269,6 +277,7 @@ const WebViewReader: FC = props => { ` } +