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 => {
`
}