mirror of
https://github.com/readest/readest
synced 2026-04-21 13:37:44 +00:00
fix(eink): remove scroll animation in eink mode and optimize eink detection (#3822)
* fix(eink): remove scroll animation in eink mode * fix(android): fix startup ANR on e-ink devices from getprop subprocesses
This commit is contained in:
parent
3df75a67f9
commit
ab7da981da
6 changed files with 51 additions and 25 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
|
@ -27,6 +27,7 @@ dependencies = [
|
|||
"rand 0.8.5",
|
||||
"read-progress-stream",
|
||||
"reqwest 0.12.28",
|
||||
"rsproperties",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
|
@ -5080,6 +5081,12 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "pretty-hex"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a65843dfefbafd3c879c683306959a6de478443ffe9c9adf02f5976432402d7"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
|
|
@ -5835,6 +5842,20 @@ dependencies = [
|
|||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsproperties"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98d55abde44982a00d67a84809166ea8f5c143abc86818f555485f19766cfe4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pretty-hex",
|
||||
"rustix 1.1.4",
|
||||
"thiserror 2.0.18",
|
||||
"zerocopy",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.21.3"
|
||||
|
|
|
|||
|
|
@ -78,3 +78,6 @@ tauri-plugin-single-instance = "2"
|
|||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-window-state = "2"
|
||||
discord-rich-presence = "1.0.0"
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
rsproperties = "0.3"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Known e-ink device manufacturers and brands (case-insensitive matching)
|
||||
const EINK_MANUFACTURERS: &[&str] = &[
|
||||
|
|
@ -45,28 +45,21 @@ const EINK_MODELS: &[&str] = &[
|
|||
"max lumi",
|
||||
];
|
||||
|
||||
/// Get Android system property using getprop command
|
||||
fn get_system_property(prop: &str) -> Option<String> {
|
||||
Command::new("getprop")
|
||||
.arg(prop)
|
||||
.output()
|
||||
rsproperties::get::<String>(prop)
|
||||
.ok()
|
||||
.and_then(|output| {
|
||||
if output.status.success() {
|
||||
let value = String::from_utf8_lossy(&output.stdout).trim().to_string();
|
||||
if value.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(value)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Check if the current Android device is an e-ink device
|
||||
/// Check if the current Android device is an e-ink device.
|
||||
///
|
||||
/// The result is cached on first call so subsequent calls are free.
|
||||
pub fn is_eink_device() -> bool {
|
||||
static IS_EINK: OnceLock<bool> = OnceLock::new();
|
||||
*IS_EINK.get_or_init(detect_eink_device)
|
||||
}
|
||||
|
||||
fn detect_eink_device() -> bool {
|
||||
// Get device manufacturer and model
|
||||
let manufacturer = get_system_property("ro.product.manufacturer")
|
||||
.or_else(|| get_system_property("ro.product.brand"))
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ const TOCView: React.FC<{
|
|||
bookKey: string;
|
||||
toc: TOCItem[];
|
||||
}> = ({ bookKey, toc }) => {
|
||||
const { getView, getProgress } = useReaderStore();
|
||||
const { getView, getViewSettings, getProgress } = useReaderStore();
|
||||
const { sideBarBookKey, isSideBarVisible } = useSidebarStore();
|
||||
const progress = getProgress(bookKey);
|
||||
const isEink = !!getViewSettings(bookKey)?.isEink;
|
||||
|
||||
const [expandedItems, setExpandedItems] = useState<Set<string>>(() =>
|
||||
computeExpandedSet(toc, progress?.sectionHref),
|
||||
|
|
@ -131,14 +132,18 @@ const TOCView: React.FC<{
|
|||
const timer = setTimeout(() => {
|
||||
const idx = flatItems.findIndex((f) => f.item.href === activeHref);
|
||||
if (idx !== -1) {
|
||||
// Eink displays ghost previous frames during smooth JS scroll
|
||||
// animations; force an instant jump to avoid the artifact. A CSS-only
|
||||
// fix is impossible because scrollTo({ behavior: 'smooth' }) overrides
|
||||
// CSS scroll-behavior and is not a CSS transition.
|
||||
const distance = Math.abs(idx - visibleCenterRef.current);
|
||||
const behavior = distance > 16 ? 'auto' : 'smooth';
|
||||
const behavior = isEink || distance > 16 ? 'auto' : 'smooth';
|
||||
virtuosoRef.current?.scrollToIndex({ index: idx, align: 'center', behavior });
|
||||
}
|
||||
pendingScrollRef.current = false;
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}, [flatItems, activeHref, isSideBarVisible]);
|
||||
}, [flatItems, activeHref, isSideBarVisible, isEink]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className='toc-list rounded' role='tree'>
|
||||
|
|
|
|||
|
|
@ -20,7 +20,11 @@ const useScrollToItem = (
|
|||
const isVisible = rect.top >= 0 && rect.bottom <= window.innerHeight;
|
||||
|
||||
if (!isVisible) {
|
||||
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
// Eink displays ghost previous frames during smooth JS scroll animations;
|
||||
// force an instant jump. scrollIntoView({ behavior: 'smooth' }) overrides
|
||||
// CSS scroll-behavior, so a CSS-only fix via useEinkMode is impossible.
|
||||
const isEink = document.documentElement.getAttribute('data-eink') === 'true';
|
||||
element.scrollIntoView({ behavior: isEink ? 'auto' : 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
if (isCurrent) {
|
||||
|
|
|
|||
|
|
@ -39,13 +39,13 @@ export function getDefaultViewSettings(ctx: Context): ViewSettings {
|
|||
...DEFAULT_BOOK_STYLE,
|
||||
...DEFAULT_BOOK_FONT,
|
||||
...DEFAULT_BOOK_LANGUAGE,
|
||||
...(ctx.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(ctx.isEink ? DEFAULT_EINK_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
...DEFAULT_VIEW_CONFIG,
|
||||
...DEFAULT_TTS_CONFIG,
|
||||
...DEFAULT_SCREEN_CONFIG,
|
||||
...DEFAULT_ANNOTATOR_CONFIG,
|
||||
...(ctx.isMobile ? DEFAULT_MOBILE_VIEW_SETTINGS : {}),
|
||||
...(ctx.isEink ? DEFAULT_EINK_VIEW_SETTINGS : {}),
|
||||
...(isCJKEnv() ? DEFAULT_CJK_VIEW_SETTINGS : {}),
|
||||
...{ ...DEFAULT_TRANSLATOR_CONFIG, translateTargetLang: getTargetLang() },
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue