fix: stabilize reader navigation and release 2.33

This commit is contained in:
Курнат Андрей
2026-07-17 21:51:40 +03:00
parent c880395341
commit 2a1a53cf34
9 changed files with 164 additions and 79 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ android {
applicationId = "com.aletheia.app" applicationId = "com.aletheia.app"
minSdk = 24 minSdk = 24
targetSdk = 36 targetSdk = 36
versionCode = 42 versionCode = 44
versionName = "2.31" versionName = "2.33"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
+1
View File
@@ -27,6 +27,7 @@
<activity <activity
android:name=".ui.reader.ReaderActivity" android:name=".ui.reader.ReaderActivity"
android:configChanges="orientation|screenSize|smallestScreenSize"
android:exported="false" android:exported="false"
android:theme="@style/Theme.Aletheia" android:theme="@style/Theme.Aletheia"
android:windowSoftInputMode="adjustResize" /> android:windowSoftInputMode="adjustResize" />
@@ -21,6 +21,7 @@
this.preparedDocuments = new WeakSet(); this.preparedDocuments = new WeakSet();
this.resizeTimer = null; this.resizeTimer = null;
this.resizeObserver = null; this.resizeObserver = null;
this.pendingResizeTarget = undefined;
} }
async load(arrayBuffer, payload) { async load(arrayBuffer, payload) {
@@ -139,7 +140,8 @@
getPreferences: this.host.getPreferences, getPreferences: this.host.getPreferences,
next: this.host.next, next: this.host.next,
previous: this.host.previous, previous: this.host.previous,
toggleControls: this.host.toggleControls toggleControls: this.host.toggleControls,
nativeTapZones: true
}); });
if (!this.preparedDocuments.has(contents.document)) { if (!this.preparedDocuments.has(contents.document)) {
this.preparedDocuments.add(contents.document); this.preparedDocuments.add(contents.document);
@@ -253,10 +255,14 @@
if (!global.ResizeObserver || this.resizeObserver) return; if (!global.ResizeObserver || this.resizeObserver) return;
const self = this; const self = this;
this.resizeObserver = new ResizeObserver(function () { this.resizeObserver = new ResizeObserver(function () {
if (self.pendingResizeTarget === undefined) {
self.pendingResizeTarget = self.currentCfi || null;
}
clearTimeout(self.resizeTimer); clearTimeout(self.resizeTimer);
self.resizeTimer = setTimeout(async function () { self.resizeTimer = setTimeout(async function () {
if (!self.rendition || self.destroyed) return; if (!self.rendition || self.destroyed) return;
const target = self.currentCfi; const target = self.pendingResizeTarget;
self.pendingResizeTarget = undefined;
try { try {
self.rendition.resize(); self.rendition.resize();
if (target) await self.rendition.display(target); if (target) await self.rendition.display(target);
@@ -594,6 +600,7 @@
destroy() { destroy() {
this.destroyed = true; this.destroyed = true;
clearTimeout(this.resizeTimer); clearTimeout(this.resizeTimer);
this.pendingResizeTarget = undefined;
if (this.resizeObserver) this.resizeObserver.disconnect(); if (this.resizeObserver) this.resizeObserver.disconnect();
this.resizeObserver = null; this.resizeObserver = null;
this.clearSearch(false); this.clearSearch(false);
+12 -4
View File
@@ -146,6 +146,7 @@
this.searchMarks = []; this.searchMarks = [];
this.resizeObserver = null; this.resizeObserver = null;
this.resizeTimer = null; this.resizeTimer = null;
this.pendingResizeLocator = undefined;
this.scrollFrame = 0; this.scrollFrame = 0;
this.selectionTimer = null; this.selectionTimer = null;
this.detachGestures = null; this.detachGestures = null;
@@ -169,7 +170,8 @@
getPreferences: this.host.getPreferences, getPreferences: this.host.getPreferences,
next: this.host.next, next: this.host.next,
previous: this.host.previous, previous: this.host.previous,
toggleControls: this.host.toggleControls toggleControls: this.host.toggleControls,
nativeTapZones: true
}); });
await Internal.waitForPreferredFont(document, this.host.getPreferences()); await Internal.waitForPreferredFont(document, this.host.getPreferences());
await this._layout(false); await this._layout(false);
@@ -487,9 +489,9 @@
return wrapper; return wrapper;
} }
async _layout(preserve) { async _layout(preserve, preservedLocator) {
const preferences = this.host.getPreferences(); const preferences = this.host.getPreferences();
const locator = preserve ? this.currentLocator() : null; const locator = preserve ? (preservedLocator || this.currentLocator()) : null;
const progress = this.currentProgress; const progress = this.currentProgress;
const width = Math.max(1, this.mount.clientWidth); const width = Math.max(1, this.mount.clientWidth);
const height = Math.max(1, this.mount.clientHeight); const height = Math.max(1, this.mount.clientHeight);
@@ -534,9 +536,14 @@
if (!global.ResizeObserver || this.resizeObserver) return; if (!global.ResizeObserver || this.resizeObserver) return;
const self = this; const self = this;
this.resizeObserver = new ResizeObserver(function () { this.resizeObserver = new ResizeObserver(function () {
if (self.pendingResizeLocator === undefined) {
self.pendingResizeLocator = self.currentLocator();
}
clearTimeout(self.resizeTimer); clearTimeout(self.resizeTimer);
self.resizeTimer = setTimeout(function () { self.resizeTimer = setTimeout(function () {
if (!self.destroyed) self._layout(true).catch(function (error) { const locator = self.pendingResizeLocator;
self.pendingResizeLocator = undefined;
if (!self.destroyed) self._layout(true, locator).catch(function (error) {
Internal.reportError(error, 'fb2.resize', true); Internal.reportError(error, 'fb2.resize', true);
}); });
}, 120); }, 120);
@@ -1051,6 +1058,7 @@
destroy() { destroy() {
this.destroyed = true; this.destroyed = true;
clearTimeout(this.resizeTimer); clearTimeout(this.resizeTimer);
this.pendingResizeLocator = undefined;
clearTimeout(this.selectionTimer); clearTimeout(this.selectionTimer);
if (this.scrollFrame) cancelAnimationFrame(this.scrollFrame); if (this.scrollFrame) cancelAnimationFrame(this.scrollFrame);
if (this.resizeObserver) this.resizeObserver.disconnect(); if (this.resizeObserver) this.resizeObserver.disconnect();
+37 -11
View File
@@ -3,6 +3,17 @@
const Internal = global.ReaderV2Internal = global.ReaderV2Internal || {}; const Internal = global.ReaderV2Internal = global.ReaderV2Internal || {};
const attachedDocuments = new WeakMap(); const attachedDocuments = new WeakMap();
const LEFT_ZONE_END = 1 / 3;
const RIGHT_ZONE_START = 2 / 3;
let lastHandledTouchAt = 0;
function viewportWidth(doc) {
const viewWidth = Number(doc && doc.defaultView && doc.defaultView.innerWidth);
if (Number.isFinite(viewWidth) && viewWidth > 0) return viewWidth;
const documentWidth = Number(doc && doc.documentElement && doc.documentElement.clientWidth);
if (Number.isFinite(documentWidth) && documentWidth > 0) return documentWidth;
return Math.max(1, Number(global.innerWidth) || 1);
}
function selectionText(doc) { function selectionText(doc) {
try { try {
@@ -39,7 +50,7 @@
startAt = Date.now(); startAt = Date.now();
startTarget = event.target; startTarget = event.target;
moved = false; moved = false;
const width = doc.documentElement.clientWidth || global.innerWidth; const width = viewportWidth(doc);
brightnessCandidate = Boolean(preferences().brightnessGesture && startX <= width * .18); brightnessCandidate = Boolean(preferences().brightnessGesture && startX <= width * .18);
} }
@@ -55,16 +66,18 @@
} }
function doTap(x, width) { function doTap(x, width) {
if (options.nativeTapZones) return false;
const current = preferences(); const current = preferences();
const mode = current.pageTurnMode || 'tapSwipe'; const mode = current.pageTurnMode || 'tapSwipe';
const normalized = x / Math.max(1, width); const normalized = x / Math.max(1, width);
if (normalized >= .28 && normalized <= .72) { if (normalized >= LEFT_ZONE_END && normalized <= RIGHT_ZONE_START) {
options.toggleControls(); options.toggleControls();
return; return true;
} }
if (mode !== 'tapSwipe' && mode !== 'tap') return; if (mode !== 'tapSwipe' && mode !== 'tap') return false;
if (normalized < .28) current.invertZones ? options.next() : options.previous(); if (normalized < LEFT_ZONE_END) current.invertZones ? options.next() : options.previous();
else current.invertZones ? options.previous() : options.next(); else current.invertZones ? options.previous() : options.next();
return true;
} }
function onTouchEnd(event) { function onTouchEnd(event) {
@@ -76,14 +89,14 @@
const horizontal = Math.abs(dx) > Math.abs(dy) * 1.25; const horizontal = Math.abs(dx) > Math.abs(dy) * 1.25;
const current = preferences(); const current = preferences();
const mode = current.pageTurnMode || 'tapSwipe'; const mode = current.pageTurnMode || 'tapSwipe';
const viewportWidth = doc.documentElement.clientWidth || global.innerWidth; const width = viewportWidth(doc);
const viewportHeight = doc.documentElement.clientHeight || global.innerHeight; const viewportHeight = doc.documentElement.clientHeight || global.innerHeight;
if (interactiveTarget(startTarget) || selectionText(doc)) return; if (interactiveTarget(startTarget) || selectionText(doc)) return;
if ( if (
current.brightnessGesture && current.brightnessGesture &&
startX <= viewportWidth * .18 && startX <= width * .18 &&
elapsed < 1200 && elapsed < 1200 &&
Math.abs(dy) >= 28 && Math.abs(dy) >= 28 &&
Math.abs(dy) > Math.abs(dx) * 1.35 Math.abs(dy) > Math.abs(dx) * 1.35
@@ -92,24 +105,37 @@
delta: Internal.clamp((-dy / Math.max(1, viewportHeight)) * 100, -100, 100) delta: Internal.clamp((-dy / Math.max(1, viewportHeight)) * 100, -100, 100)
}); });
consumedAt = Date.now(); consumedAt = Date.now();
lastHandledTouchAt = consumedAt;
event.preventDefault();
return; return;
} }
if (elapsed < 650 && horizontal && Math.abs(dx) >= 48 && (mode === 'tapSwipe' || mode === 'swipe')) { if (elapsed < 650 && horizontal && Math.abs(dx) >= 48 && (mode === 'tapSwipe' || mode === 'swipe')) {
dx < 0 ? options.next() : options.previous(); dx < 0 ? options.next() : options.previous();
consumedAt = Date.now(); consumedAt = Date.now();
lastHandledTouchAt = consumedAt;
event.preventDefault();
return; return;
} }
if (elapsed < 300 && !moved && Math.abs(dx) < 12 && Math.abs(dy) < 12) { if (elapsed < 300 && !moved && Math.abs(dx) < 12 && Math.abs(dy) < 12) {
doTap(touch.clientX, doc.documentElement.clientWidth || global.innerWidth); if (doTap(touch.clientX, width)) {
consumedAt = Date.now(); consumedAt = Date.now();
lastHandledTouchAt = consumedAt;
event.preventDefault();
}
} }
} }
function onClick(event) { function onClick(event) {
if (Date.now() - consumedAt < 500 || interactiveTarget(event.target) || selectionText(doc)) return; const now = Date.now();
doTap(event.clientX, doc.documentElement.clientWidth || global.innerWidth); if (
now - consumedAt < 700 ||
now - lastHandledTouchAt < 700 ||
interactiveTarget(event.target) ||
selectionText(doc)
) return;
doTap(event.clientX, viewportWidth(doc));
} }
function onKeyDown(event) { function onKeyDown(event) {
@@ -127,7 +153,7 @@
doc.addEventListener('touchstart', onTouchStart, { passive: true }); doc.addEventListener('touchstart', onTouchStart, { passive: true });
doc.addEventListener('touchmove', onTouchMove, { passive: false }); doc.addEventListener('touchmove', onTouchMove, { passive: false });
doc.addEventListener('touchend', onTouchEnd, { passive: true }); doc.addEventListener('touchend', onTouchEnd, { passive: false });
doc.addEventListener('click', onClick, false); doc.addEventListener('click', onClick, false);
doc.addEventListener('keydown', onKeyDown, false); doc.addEventListener('keydown', onKeyDown, false);
@@ -5,6 +5,7 @@ import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@@ -24,9 +25,9 @@ import com.aletheia.app.ui.books.BookshelfFragment
import com.aletheia.app.ui.home.HomeFragment import com.aletheia.app.ui.home.HomeFragment
import com.aletheia.app.ui.qbooks.QBooksLibraryFragment import com.aletheia.app.ui.qbooks.QBooksLibraryFragment
import com.aletheia.app.ui.reader.ReaderActivity import com.aletheia.app.ui.reader.ReaderActivity
import com.aletheia.app.ui.reader.ReaderHubFragment
import com.aletheia.app.ui.settings.SettingsFragment import com.aletheia.app.ui.settings.SettingsFragment
import com.aletheia.app.util.setBookCover import com.aletheia.app.util.setBookCover
import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -57,7 +58,10 @@ class MainActivity : AppCompatActivity() {
if (savedInstanceState == null) { if (savedInstanceState == null) {
selectOrOpenTab(resolveInitialTab(intent?.getStringExtra(EXTRA_OPEN_TAB))) selectOrOpenTab(resolveInitialTab(intent?.getStringExtra(EXTRA_OPEN_TAB)))
} else if (supportFragmentManager.fragments.none { !it.isHidden }) { } else if (
supportFragmentManager.fragments.none { !it.isHidden } ||
supportFragmentManager.findFragmentByTag(LEGACY_TAG_READER)?.isHidden == false
) {
selectOrOpenTab(R.id.nav_home) selectOrOpenTab(R.id.nav_home)
} }
} }
@@ -86,7 +90,10 @@ class MainActivity : AppCompatActivity() {
private fun openTab(itemId: Int): Boolean = when (itemId) { private fun openTab(itemId: Int): Boolean = when (itemId) {
R.id.nav_home -> showTab(TAG_HOME) { HomeFragment.newInstance() } R.id.nav_home -> showTab(TAG_HOME) { HomeFragment.newInstance() }
R.id.nav_search -> showTab(TAG_SEARCH) { QBooksLibraryFragment.newInstance() } R.id.nav_search -> showTab(TAG_SEARCH) { QBooksLibraryFragment.newInstance() }
R.id.nav_reader -> showTab(TAG_READER) { ReaderHubFragment.newInstance() } R.id.nav_reader -> {
openLastReadBook()
false
}
R.id.nav_my_books -> showTab(TAG_MY_BOOKS) { BookshelfFragment.newInstance() } R.id.nav_my_books -> showTab(TAG_MY_BOOKS) { BookshelfFragment.newInstance() }
R.id.nav_profile -> showTab(TAG_PROFILE) { SettingsFragment.newInstance() } R.id.nav_profile -> showTab(TAG_PROFILE) { SettingsFragment.newInstance() }
else -> false else -> false
@@ -130,9 +137,7 @@ class MainActivity : AppCompatActivity() {
private fun refreshMiniReader() { private fun refreshMiniReader() {
lifecycleScope.launch { lifecycleScope.launch {
val book = withContext(Dispatchers.IO) { val book = findLastReadableBook()
app.bookRepository.getAllBooks().maxByOrNull { item -> item.lastRead }
}
miniReaderBook = book miniReaderBook = book
val visibility = if (book == null) View.GONE else View.VISIBLE val visibility = if (book == null) View.GONE else View.VISIBLE
binding.miniReaderPanel.visibility = visibility binding.miniReaderPanel.visibility = visibility
@@ -150,6 +155,36 @@ class MainActivity : AppCompatActivity() {
private fun openMiniReader() { private fun openMiniReader() {
val book = miniReaderBook ?: return val book = miniReaderBook ?: return
openReader(book)
}
private fun openLastReadBook() {
if (supportFragmentManager.fragments.none { fragment -> !fragment.isHidden }) {
showTab(TAG_HOME) { HomeFragment.newInstance() }
}
lifecycleScope.launch {
val book = findLastReadableBook()
if (book == null) {
Toast.makeText(
this@MainActivity,
R.string.reader_no_book_selected,
Toast.LENGTH_SHORT
).show()
return@launch
}
miniReaderBook = book
openReader(book)
}
}
private suspend fun findLastReadableBook(): Book? = withContext(Dispatchers.IO) {
app.bookRepository.getAllBooks()
.asSequence()
.filter { book -> File(book.filePath).isFile }
.maxByOrNull { book -> book.lastRead }
}
private fun openReader(book: Book) {
startActivity( startActivity(
Intent(this, ReaderActivity::class.java) Intent(this, ReaderActivity::class.java)
.putExtra(ReaderActivity.EXTRA_BOOK_ID, book.id) .putExtra(ReaderActivity.EXTRA_BOOK_ID, book.id)
@@ -203,7 +238,7 @@ class MainActivity : AppCompatActivity() {
const val EXTRA_OPEN_TAB = "open_tab" const val EXTRA_OPEN_TAB = "open_tab"
private const val TAG_HOME = "home" private const val TAG_HOME = "home"
private const val TAG_SEARCH = "search" private const val TAG_SEARCH = "search"
private const val TAG_READER = "reader" private const val LEGACY_TAG_READER = "reader"
private const val TAG_MY_BOOKS = "my-books" private const val TAG_MY_BOOKS = "my-books"
private const val TAG_PROFILE = "profile" private const val TAG_PROFILE = "profile"
} }
@@ -5,6 +5,7 @@ import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
@@ -14,7 +15,6 @@ import android.graphics.drawable.StateListDrawable
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.SystemClock
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Gravity import android.view.Gravity
@@ -23,6 +23,7 @@ import android.view.View
import android.view.ViewConfiguration import android.view.ViewConfiguration
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.webkit.WebView
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
@@ -99,11 +100,9 @@ class ReaderActivity : AppCompatActivity() {
private var readerTouchDownX = 0f private var readerTouchDownX = 0f
private var readerTouchDownY = 0f private var readerTouchDownY = 0f
private var readerTouchDownAt = 0L private var readerTouchDownAt = 0L
private var lastNativeControlsToggleAt = 0L
private val readerTapSlop by lazy(LazyThreadSafetyMode.NONE) { private val readerTapSlop by lazy(LazyThreadSafetyMode.NONE) {
ViewConfiguration.get(this).scaledTouchSlop.toFloat() ViewConfiguration.get(this).scaledTouchSlop.toFloat()
} }
private var pageHeaderBasePadding = Insets() private var pageHeaderBasePadding = Insets()
private var controlsBasePadding = Insets() private var controlsBasePadding = Insets()
private var settingsBasePadding = Insets() private var settingsBasePadding = Insets()
@@ -159,6 +158,12 @@ class ReaderActivity : AppCompatActivity() {
applySystemUi() applySystemUi()
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
ViewCompat.requestApplyInsets(binding.root)
applySystemUi()
}
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(STATE_FINISHING, finishingReader) outState.putBoolean(STATE_FINISHING, finishingReader)
outState.putString(STATE_PREFERENCES, preferences.toStateJson()) outState.putString(STATE_PREFERENCES, preferences.toStateJson())
@@ -196,11 +201,11 @@ class ReaderActivity : AppCompatActivity() {
if (bookReady && preferences.volumeButtons && !preferences.verticalScroll) { if (bookReady && preferences.volumeButtons && !preferences.verticalScroll) {
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN -> {
webController.next() webController.previous()
return true return true
} }
KeyEvent.KEYCODE_VOLUME_UP -> { KeyEvent.KEYCODE_VOLUME_UP -> {
webController.previous() webController.next()
return true return true
} }
} }
@@ -546,8 +551,8 @@ class ReaderActivity : AppCompatActivity() {
currentPage = book.currentPage.coerceAtLeast(1), currentPage = book.currentPage.coerceAtLeast(1),
totalPages = book.totalPages.coerceAtLeast(1) totalPages = book.totalPages.coerceAtLeast(1)
) )
position = app.readerStateCoordinator.latest(book.id)?.toReaderPosition() position = restoredPosition
?: restoredPosition ?: app.readerStateCoordinator.latest(book.id)?.toReaderPosition()
?: databasePosition ?: databasePosition
restoredPosition = null restoredPosition = null
publishPosition() publishPosition()
@@ -589,10 +594,7 @@ class ReaderActivity : AppCompatActivity() {
if (finishingReader) return if (finishingReader) return
when (event) { when (event) {
ReaderEvent.ShellReady -> binding.readerLoadingText.text = "Открываю книгу…" ReaderEvent.ShellReady -> binding.readerLoadingText.text = "Открываю книгу…"
ReaderEvent.ToggleControls -> { ReaderEvent.ToggleControls -> toggleControls()
val sinceNativeToggle = SystemClock.uptimeMillis() - lastNativeControlsToggleAt
if (sinceNativeToggle > NATIVE_TAP_DUPLICATE_WINDOW_MS) toggleControls()
}
ReaderEvent.Navigation -> Unit ReaderEvent.Navigation -> Unit
is ReaderEvent.BookReady -> { is ReaderEvent.BookReady -> {
bookReady = true bookReady = true
@@ -669,6 +671,14 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
private fun toggleControls() {
if (binding.readerSettingsOverlay.isVisible || binding.readerContentsOverlay.isVisible ||
binding.readerSelectionOverlay.isVisible || binding.noteEditorOverlay.isVisible
) return
binding.readerControlsOverlay.isVisible = !binding.readerControlsOverlay.isVisible
renderChromeVisibility()
}
private fun observeNativeReaderTap(event: MotionEvent) { private fun observeNativeReaderTap(event: MotionEvent) {
when (event.actionMasked) { when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
@@ -694,36 +704,44 @@ class ReaderActivity : AppCompatActivity() {
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
val elapsed = event.eventTime - readerTouchDownAt val elapsed = event.eventTime - readerTouchDownAt
val normalizedX = event.x / binding.readerWebView.width.coerceAtLeast(1)
val modalVisible = binding.readerLoadingOverlay.isVisible || val modalVisible = binding.readerLoadingOverlay.isVisible ||
binding.readerSettingsOverlay.isVisible || binding.readerSettingsOverlay.isVisible ||
binding.readerContentsOverlay.isVisible || binding.readerContentsOverlay.isVisible ||
binding.readerSelectionOverlay.isVisible || binding.readerSelectionOverlay.isVisible ||
binding.noteEditorOverlay.isVisible binding.noteEditorOverlay.isVisible
val shouldToggle = readerTouchActive && val hitType = binding.readerWebView.hitTestResult.type
val interactiveTarget = hitType == WebView.HitTestResult.SRC_ANCHOR_TYPE ||
hitType == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE ||
hitType == WebView.HitTestResult.EDIT_TEXT_TYPE ||
hitType == WebView.HitTestResult.EMAIL_TYPE ||
hitType == WebView.HitTestResult.PHONE_TYPE ||
hitType == WebView.HitTestResult.GEO_TYPE
val shouldHandle = readerTouchActive &&
!readerTouchMoved && !readerTouchMoved &&
elapsed in 0..NATIVE_TAP_MAX_DURATION_MS && elapsed in 0..NATIVE_TAP_MAX_DURATION_MS &&
normalizedX in READER_CENTER_ZONE_START..READER_CENTER_ZONE_END &&
bookReady && bookReady &&
!finishingReader && !finishingReader &&
currentSelection == null && currentSelection == null &&
!modalVisible !modalVisible &&
!interactiveTarget
readerTouchActive = false readerTouchActive = false
readerTouchMoved = false readerTouchMoved = false
if (shouldToggle) { if (!shouldHandle) return
lastNativeControlsToggleAt = SystemClock.uptimeMillis()
toggleControls()
}
}
}
}
private fun toggleControls() { val normalizedX = event.x / binding.readerWebView.width.coerceAtLeast(1)
if (binding.readerSettingsOverlay.isVisible || binding.readerContentsOverlay.isVisible || when {
binding.readerSelectionOverlay.isVisible || binding.noteEditorOverlay.isVisible normalizedX in READER_CENTER_ZONE_START..READER_CENTER_ZONE_END -> toggleControls()
) return preferences.pageTurnMode != ReaderPreferences.PAGE_TURN_TAP &&
binding.readerControlsOverlay.isVisible = !binding.readerControlsOverlay.isVisible preferences.pageTurnMode != ReaderPreferences.PAGE_TURN_TAP_SWIPE -> Unit
renderChromeVisibility() normalizedX < READER_CENTER_ZONE_START -> {
if (preferences.invertZones) webController.next() else webController.previous()
}
else -> {
if (preferences.invertZones) webController.previous() else webController.next()
}
}
}
}
} }
private fun renderChromeVisibility() { private fun renderChromeVisibility() {
@@ -1523,17 +1541,7 @@ class ReaderActivity : AppCompatActivity() {
} }
private fun handleBack() { private fun handleBack() {
when { finishReader()
binding.noteEditorOverlay.isVisible -> closeNoteEditor()
binding.readerSelectionOverlay.isVisible -> closeSelection()
binding.readerContentsOverlay.isVisible -> closeContents()
binding.readerSettingsOverlay.isVisible -> closeSettings()
binding.readerControlsOverlay.isVisible -> {
binding.readerControlsOverlay.isVisible = false
renderChromeVisibility()
}
else -> finishReader()
}
} }
private fun openExternalLink(url: String) { private fun openExternalLink(url: String) {
@@ -1719,9 +1727,8 @@ class ReaderActivity : AppCompatActivity() {
private const val PROGRESS_SAVE_DEBOUNCE_MS = 1_500L private const val PROGRESS_SAVE_DEBOUNCE_MS = 1_500L
private const val SETTINGS_SAVE_DEBOUNCE_MS = 180L private const val SETTINGS_SAVE_DEBOUNCE_MS = 180L
private const val NATIVE_TAP_MAX_DURATION_MS = 350L private const val NATIVE_TAP_MAX_DURATION_MS = 350L
private const val NATIVE_TAP_DUPLICATE_WINDOW_MS = 450L private const val READER_CENTER_ZONE_START = 1f / 3f
private const val READER_CENTER_ZONE_START = 0.28f private const val READER_CENTER_ZONE_END = 2f / 3f
private const val READER_CENTER_ZONE_END = 0.72f
private val FATAL_ERROR_STAGES = setOf( private val FATAL_ERROR_STAGES = setOf(
"book.load", "book.load",
"api.loadBook", "api.loadBook",
+12 -12
View File
@@ -629,13 +629,13 @@
android:focusable="true"> android:focusable="true">
<ImageView <ImageView
android:layout_width="60dp" android:layout_width="44dp"
android:layout_height="32dp" android:layout_height="44dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/bg_reader_v2_control" android:background="@drawable/bg_reader_v2_control"
android:contentDescription="@null" android:contentDescription="@null"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:padding="17dp" android:padding="6dp"
android:src="@drawable/ic_reader_v2_minus" android:src="@drawable/ic_reader_v2_minus"
android:tag="reader_v2_control" /> android:tag="reader_v2_control" />
</FrameLayout> </FrameLayout>
@@ -650,13 +650,13 @@
android:focusable="true"> android:focusable="true">
<ImageView <ImageView
android:layout_width="60dp" android:layout_width="44dp"
android:layout_height="32dp" android:layout_height="44dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/bg_reader_v2_control" android:background="@drawable/bg_reader_v2_control"
android:contentDescription="@null" android:contentDescription="@null"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:padding="13dp" android:padding="6dp"
android:src="@drawable/ic_reader_v2_plus" android:src="@drawable/ic_reader_v2_plus"
android:tag="reader_v2_control" /> android:tag="reader_v2_control" />
</FrameLayout> </FrameLayout>
@@ -832,13 +832,13 @@
android:focusable="true"> android:focusable="true">
<ImageView <ImageView
android:layout_width="60dp" android:layout_width="44dp"
android:layout_height="32dp" android:layout_height="44dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/bg_reader_v2_control" android:background="@drawable/bg_reader_v2_control"
android:contentDescription="@null" android:contentDescription="@null"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:padding="17dp" android:padding="6dp"
android:src="@drawable/ic_reader_v2_minus" android:src="@drawable/ic_reader_v2_minus"
android:tag="reader_v2_control" /> android:tag="reader_v2_control" />
</FrameLayout> </FrameLayout>
@@ -853,13 +853,13 @@
android:focusable="true"> android:focusable="true">
<ImageView <ImageView
android:layout_width="60dp" android:layout_width="44dp"
android:layout_height="32dp" android:layout_height="44dp"
android:layout_gravity="center" android:layout_gravity="center"
android:background="@drawable/bg_reader_v2_control" android:background="@drawable/bg_reader_v2_control"
android:contentDescription="@null" android:contentDescription="@null"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:padding="13dp" android:padding="6dp"
android:src="@drawable/ic_reader_v2_plus" android:src="@drawable/ic_reader_v2_plus"
android:tag="reader_v2_control" /> android:tag="reader_v2_control" />
</FrameLayout> </FrameLayout>
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="reader_no_book_selected">Книга для чтения не выбрана</string>
<string name="app_name">Алетея</string> <string name="app_name">Алетея</string>
<string name="tab_library">Мои книги</string> <string name="tab_library">Мои книги</string>