Added screen position saving in the epub reader

This commit is contained in:
sNice/Python
2026-03-22 18:33:20 -05:00
parent 1e7538184d
commit 895f129b81
8 changed files with 46 additions and 37 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-03-22T22:43:40.813806872Z"> <DropdownSelection timestamp="2026-03-22T23:16:51.147475690Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=42011JEKB06640" /> <DeviceId pluginId="PhysicalDevice" identifier="serial=42011JEKB06640" />
+1
View File
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
-1
View File
@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
+1
View File
@@ -42,6 +42,7 @@ android {
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
Binary file not shown.
-21
View File
@@ -1,21 +0,0 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "net.pythonsden.ao3_",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-debug.apk"
}
],
"elementType": "File",
"minSdkVersionForDexing": 24
}
@@ -72,6 +72,9 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavType import androidx.navigation.NavType
@@ -165,7 +168,8 @@ fun MainScreen() {
if (lastRoute == "reader/{filePath}") { if (lastRoute == "reader/{filePath}") {
val lastEpubPath = prefs.getString("last_epub_path", null) val lastEpubPath = prefs.getString("last_epub_path", null)
if (lastEpubPath != null) { if (lastEpubPath != null) {
navController.navigate("reader/$lastEpubPath") val encoded = URLEncoder.encode(lastEpubPath, StandardCharsets.UTF_8.toString())
navController.navigate("reader/$encoded")
} }
} else if (!isOfflineMode || lastRoute == Screen.Downloads.route) { } else if (!isOfflineMode || lastRoute == Screen.Downloads.route) {
navController.navigate(lastRoute) { navController.navigate(lastRoute) {
@@ -678,6 +682,7 @@ fun DownloadsScreen(navController: NavController, modifier: Modifier = Modifier)
@Composable @Composable
fun EpubReaderScreen(file: File, navController: NavController) { fun EpubReaderScreen(file: File, navController: NavController) {
val context = LocalContext.current val context = LocalContext.current
val lifecycleOwner = LocalLifecycleOwner.current
val prefs = remember { context.getSharedPreferences("ao3_prefs", Context.MODE_PRIVATE) } val prefs = remember { context.getSharedPreferences("ao3_prefs", Context.MODE_PRIVATE) }
val scrollKey = "scroll_${file.absolutePath}" val scrollKey = "scroll_${file.absolutePath}"
@@ -685,18 +690,30 @@ fun EpubReaderScreen(file: File, navController: NavController) {
var title by remember { mutableStateOf("Reading...") } var title by remember { mutableStateOf("Reading...") }
var webViewRef by remember { mutableStateOf<WebView?>(null) } var webViewRef by remember { mutableStateOf<WebView?>(null) }
// Save scroll position // Helper function to save scroll
DisposableEffect(file.absolutePath) { fun saveScroll(wv: WebView?) {
onDispose { wv?.let {
webViewRef?.let { wv -> val scrollY = it.scrollY
val scrollY = wv.scrollY if (scrollY > 0) {
if (scrollY > 0) { prefs.edit().putInt(scrollKey, scrollY).apply()
prefs.edit().putInt(scrollKey, scrollY).apply()
}
} }
} }
} }
// Save scroll position on pause or dispose
DisposableEffect(lifecycleOwner, file.absolutePath, webViewRef) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_PAUSE) {
saveScroll(webViewRef)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
saveScroll(webViewRef)
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
LaunchedEffect(file) { LaunchedEffect(file) {
try { try {
val book: Book = EpubReader().readEpub(FileInputStream(file)) val book: Book = EpubReader().readEpub(FileInputStream(file))
@@ -759,17 +776,27 @@ fun EpubReaderScreen(file: File, navController: NavController) {
super.onPageFinished(view, url) super.onPageFinished(view, url)
val lastScroll = prefs.getInt(scrollKey, 0) val lastScroll = prefs.getInt(scrollKey, 0)
if (lastScroll > 0) { if (lastScroll > 0) {
view?.post { // Try scrolling after several delays to ensure layout is done
view.scrollTo(0, lastScroll) view?.postDelayed({ view.scrollTo(0, lastScroll) }, 100)
} view?.postDelayed({ view.scrollTo(0, lastScroll) }, 300)
view?.postDelayed({ view.scrollTo(0, lastScroll) }, 600)
} }
} }
} }
setOnScrollChangeListener { _, _, _, scrollY, _ ->
if (scrollY > 0) {
prefs.edit().putInt(scrollKey, scrollY).apply()
}
}
webViewRef = this webViewRef = this
} }
}, },
update = { webView -> update = { webView ->
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null) // Avoid reloading if content is the same
if (webView.tag != htmlContent && htmlContent.isNotBlank()) {
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "UTF-8", null)
webView.tag = htmlContent
}
} }
) )
} }
+2
View File
@@ -5,6 +5,7 @@ junit = "4.13.2"
junitVersion = "1.1.5" junitVersion = "1.1.5"
espressoCore = "3.5.1" espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.6.1" lifecycleRuntimeKtx = "2.6.1"
lifecycleRuntimeCompose = "2.6.1"
activityCompose = "1.8.0" activityCompose = "1.8.0"
kotlin = "2.2.10" kotlin = "2.2.10"
composeBom = "2024.09.00" composeBom = "2024.09.00"
@@ -17,6 +18,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeCompose" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }