good rendering, good ux. I'd say 1.1 Beta !!!
This commit is contained in:
parent
5b587d4c63
commit
d632e7ac0d
@ -44,6 +44,13 @@ android {
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get()
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy {
|
||||
force("androidx.core:core:1.12.0")
|
||||
force("androidx.versionedparcelable:versionedparcelable:1.1.1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -131,4 +138,14 @@ dependencies {
|
||||
exclude(group = "xmlpull", module = "xmlpull")
|
||||
}
|
||||
}
|
||||
|
||||
// Add Fuel dependencies explicitly
|
||||
implementation("com.github.kittinunf.fuel:fuel:2.3.1")
|
||||
implementation("com.github.kittinunf.fuel:fuel-android:2.3.1")
|
||||
|
||||
// Add explicit AndroidX dependencies
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
}
|
||||
@ -16,8 +16,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Epook"
|
||||
tools:targetApi="31">
|
||||
android:theme="@style/Theme.Epook">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
|
||||
@ -101,6 +101,7 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
App()
|
||||
}
|
||||
|
||||
@ -18,59 +18,27 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import inhale.rip.epook.data.BookStore
|
||||
import inhale.rip.epook.data.Settings
|
||||
import inhale.rip.epook.data.SettingsStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jsoup.Jsoup
|
||||
import timber.log.Timber
|
||||
import java.nio.charset.Charset
|
||||
import nl.siegmann.epublib.domain.MediaType
|
||||
import nl.siegmann.epublib.domain.Resource
|
||||
import inhale.rip.epook.data.Settings
|
||||
|
||||
// Update the JavaScript for accurate page calculation
|
||||
private const val PAGE_CALCULATION_JS = """
|
||||
(function() {
|
||||
const content = document.body;
|
||||
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
|
||||
const pageWidth = window.innerWidth;
|
||||
const totalWidth = document.documentElement.scrollWidth;
|
||||
const currentPage = Math.floor(scrollLeft / pageWidth) + 1;
|
||||
const totalPages = Math.max(1, Math.ceil(totalWidth / pageWidth));
|
||||
return JSON.stringify({
|
||||
currentPage: currentPage,
|
||||
totalPages: totalPages
|
||||
});
|
||||
})();
|
||||
"""
|
||||
|
||||
// Add this function to handle chapter navigation
|
||||
private const val SCROLL_TO_CHAPTER_JS = """
|
||||
function scrollToChapter(chapterIndex) {
|
||||
const chapter = document.getElementById('chapter_' + chapterIndex);
|
||||
if (chapter) {
|
||||
chapter.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
"""
|
||||
|
||||
// Add this to track WebView scroll position
|
||||
class WebViewScrollClient : WebViewClient() {
|
||||
var onPageLoaded: ((WebView) -> Unit)? = null
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
view?.let { webView ->
|
||||
onPageLoaded?.invoke(webView)
|
||||
}
|
||||
}
|
||||
}
|
||||
import nl.siegmann.epublib.epub.EpubReader
|
||||
import java.io.FileInputStream
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import inhale.rip.epook.data.Book
|
||||
import nl.siegmann.epublib.domain.Book as EpubBook
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@ -82,326 +50,211 @@ fun ReaderScreen(
|
||||
val bookStore = remember { BookStore(context) }
|
||||
val settingsStore = remember { SettingsStore(context) }
|
||||
val scope = rememberCoroutineScope()
|
||||
var currentSettings by remember { mutableStateOf(Settings()) }
|
||||
|
||||
var currentChapterIndex by remember { mutableStateOf(0) }
|
||||
var chapters by remember { mutableStateOf(listOf<Resource>()) }
|
||||
var book by remember { mutableStateOf<EpubBook?>(null) }
|
||||
|
||||
var showControls by remember { mutableStateOf(true) }
|
||||
var showSettings by remember { mutableStateOf(false) }
|
||||
var brightness by remember { mutableStateOf(1f) }
|
||||
var isDarkMode by remember { mutableStateOf(false) }
|
||||
var fontSize by remember { mutableStateOf(16f) }
|
||||
var lineHeight by remember { mutableStateOf(1.5f) }
|
||||
var padding by remember { mutableStateOf(16f) }
|
||||
var fontFamily by remember { mutableStateOf("Roboto") }
|
||||
var currentChapter by remember { mutableStateOf(0) }
|
||||
var showChapterList by remember { mutableStateOf(false) }
|
||||
var chapters by remember { mutableStateOf(listOf<String>()) }
|
||||
|
||||
var currentPage by remember { mutableStateOf(1) }
|
||||
var totalPages by remember { mutableStateOf(1) }
|
||||
var bookContent by remember { mutableStateOf("") }
|
||||
var webViewRef by remember { mutableStateOf<WebView?>(null) }
|
||||
var webViewClient = remember { WebViewScrollClient() }
|
||||
|
||||
// Move processChapterHtml outside the Composable
|
||||
fun processChapterHtml(rawHtml: String, chapterIndex: Int): String {
|
||||
val document = Jsoup.parse(rawHtml)
|
||||
val body = document.body()
|
||||
body.select("script, style").remove()
|
||||
return "<div class='chapter' id='chapter_$chapterIndex'>\n${body.html()}\n</div>"
|
||||
val backgroundColor = MaterialTheme.colorScheme.background.toArgb()
|
||||
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
try {
|
||||
currentSettings = settingsStore.getSettings()
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error loading settings")
|
||||
}
|
||||
}
|
||||
|
||||
// Load book content
|
||||
|
||||
LaunchedEffect(bookId) {
|
||||
try {
|
||||
Timber.d("📚 Starting to load book with ID: $bookId")
|
||||
val book = bookStore.getBook(bookId)
|
||||
Timber.d("📚 Book loaded successfully: ${book.title}")
|
||||
|
||||
// Load settings first
|
||||
val savedSettings = settingsStore.loadSettings()
|
||||
currentPage = savedSettings.currentPage
|
||||
fontSize = savedSettings.fontSize
|
||||
lineHeight = savedSettings.lineHeight
|
||||
padding = savedSettings.padding
|
||||
fontFamily = savedSettings.fontFamily
|
||||
isDarkMode = savedSettings.isDarkMode
|
||||
Timber.d("📚 Settings loaded - Page: $currentPage, Font: $fontSize, Line: $lineHeight, Padding: $padding")
|
||||
|
||||
// Process chapters
|
||||
val processedChapters = mutableListOf<String>()
|
||||
book.spine.spineReferences.forEachIndexed { index, spineReference ->
|
||||
Timber.d("📚 Processing chapter $index")
|
||||
val resource = book.resources.getByHref(spineReference.resource.href)
|
||||
Timber.d("📚 Resource href: ${resource.href}")
|
||||
Timber.d("📚 Resource media type: ${resource.mediaType}")
|
||||
|
||||
if (resource.mediaType.toString().contains("application/xhtml+xml") ||
|
||||
resource.mediaType.toString().contains("text/html")) {
|
||||
val rawHtml = String(resource.data, Charset.forName("UTF-8"))
|
||||
Timber.d("📚 Raw HTML length: ${rawHtml.length}")
|
||||
|
||||
val processedHtml = processChapterHtml(rawHtml, index)
|
||||
processedChapters.add(processedHtml)
|
||||
Timber.d("📚 Chapter $index processed successfully")
|
||||
}
|
||||
val bookPath = bookStore.getBookPath(bookId)
|
||||
val epubBook = EpubReader().readEpub(FileInputStream(bookPath))
|
||||
book = epubBook
|
||||
chapters = epubBook.spine.spineReferences.mapNotNull { spineRef: nl.siegmann.epublib.domain.SpineReference ->
|
||||
spineRef.resource
|
||||
}
|
||||
|
||||
// Combine all chapters
|
||||
bookContent = processedChapters.joinToString("\n")
|
||||
Timber.d("📚 Final book content processed:")
|
||||
Timber.d("📚 - Total length: ${bookContent.length}")
|
||||
Timber.d("📚 - First 100 chars: ${bookContent.take(100)}")
|
||||
Timber.d("📚 - Last 100 chars: ${bookContent.takeLast(100)}")
|
||||
|
||||
// Update chapter list
|
||||
chapters = book.tableOfContents.tocReferences.map { it.title }
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "📚 Error loading book")
|
||||
Timber.e(e, "Error loading book")
|
||||
}
|
||||
}
|
||||
|
||||
val css = """
|
||||
:root {
|
||||
--page-width: calc(100vw - ${padding * 2}px);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: ${padding}px;
|
||||
width: var(--page-width);
|
||||
max-width: var(--page-width);
|
||||
height: calc(100vh - ${padding * 2}px);
|
||||
column-width: var(--page-width);
|
||||
column-gap: ${padding * 2}px;
|
||||
column-fill: auto;
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
font-family: $fontFamily;
|
||||
font-size: ${fontSize}px;
|
||||
line-height: ${lineHeight}em;
|
||||
background-color: ${if (isDarkMode) "#1C1B1F" else "#FFFFFF"};
|
||||
color: ${if (isDarkMode) "#E6E1E5" else "#1C1B1F"};
|
||||
}
|
||||
.chapter {
|
||||
break-inside: avoid;
|
||||
margin-bottom: 2em;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
text-align: justify;
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
BackHandler {
|
||||
scope.launch {
|
||||
settingsStore.saveSettings(Settings(
|
||||
currentPage = currentPage,
|
||||
fontSize = fontSize,
|
||||
lineHeight = lineHeight,
|
||||
padding = padding,
|
||||
fontFamily = fontFamily,
|
||||
isDarkMode = isDarkMode
|
||||
))
|
||||
}
|
||||
onNavigateBack()
|
||||
}
|
||||
|
||||
// Update the calculateCurrentPage function to use a callback
|
||||
fun calculateCurrentPage(webView: WebView) {
|
||||
webView.evaluateJavascript("""
|
||||
(function() {
|
||||
const height = document.documentElement.scrollHeight;
|
||||
const scrollTop = document.documentElement.scrollTop;
|
||||
const clientHeight = document.documentElement.clientHeight;
|
||||
const totalPages = Math.ceil(height / clientHeight);
|
||||
const currentPage = Math.ceil((scrollTop + clientHeight) / clientHeight);
|
||||
return JSON.stringify({currentPage: currentPage, totalPages: totalPages});
|
||||
})()
|
||||
""".trimIndent()) { result ->
|
||||
result?.let {
|
||||
try {
|
||||
val jsonStr = result.removeSurrounding("\"")
|
||||
val regex = """.*"currentPage":(\d+).*"totalPages":(\d+).*""".toRegex()
|
||||
regex.find(jsonStr)?.let { matchResult ->
|
||||
val (current, total) = matchResult.destructured
|
||||
// Update state values
|
||||
scope.launch {
|
||||
currentPage = current.toInt()
|
||||
totalPages = total.toInt()
|
||||
Timber.d("📚 Current page: $currentPage of $totalPages")
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AnimatedVisibility(
|
||||
visible = showControls,
|
||||
enter = fadeIn() + slideInVertically(),
|
||||
exit = fadeOut() + slideOutVertically()
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(
|
||||
text = book?.title ?: "Reader",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
text = "Chapter ${currentChapterIndex + 1} of ${chapters.size}",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { showChapterList = true }) {
|
||||
Icon(Icons.Default.List, contentDescription = "Chapters")
|
||||
}
|
||||
IconButton(onClick = { /* TODO: Add settings dialog */ }) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Settings")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error calculating current page")
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update webViewClient callback
|
||||
webViewClient.onPageLoaded = { webView ->
|
||||
calculateCurrentPage(webView)
|
||||
}
|
||||
|
||||
// Update the HTML content creation
|
||||
fun createHtmlContent(): String = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
html {
|
||||
scroll-snap-type: x mandatory;
|
||||
overflow-x: scroll;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
font-family: '$fontFamily';
|
||||
font-size: ${fontSize}px;
|
||||
line-height: ${lineHeight};
|
||||
padding: ${padding}px;
|
||||
margin: 0;
|
||||
background-color: ${if (isDarkMode) "#1c1c1c" else "#ffffff"};
|
||||
color: ${if (isDarkMode) "#ffffff" else "#000000"};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: fit-content;
|
||||
}
|
||||
.chapter {
|
||||
width: calc(100vw - ${padding * 2}px);
|
||||
height: calc(100vh - ${padding * 2}px);
|
||||
overflow: hidden;
|
||||
scroll-snap-align: start;
|
||||
break-inside: avoid;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
$bookContent
|
||||
<script>$SCROLL_TO_CHAPTER_JS</script>
|
||||
</body>
|
||||
</html>
|
||||
""".trimIndent()
|
||||
|
||||
// Update chapter navigation
|
||||
fun navigateToChapter(index: Int) {
|
||||
webViewRef?.evaluateJavascript("scrollToChapter($index);") { result ->
|
||||
if (result == "true") {
|
||||
currentChapter = index
|
||||
showChapterList = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the WebView setup
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
defaultFontSize = fontSize.toInt()
|
||||
domStorageEnabled = true
|
||||
allowFileAccess = true
|
||||
allowContentAccess = true
|
||||
}
|
||||
|
||||
webViewClient = webViewClient
|
||||
|
||||
setOnScrollChangeListener { _, _, _, _, _ ->
|
||||
evaluateJavascript(PAGE_CALCULATION_JS) { result ->
|
||||
result?.let {
|
||||
try {
|
||||
val jsonStr = result.removeSurrounding("\"")
|
||||
val regex = """.*"currentPage":(\d+).*"totalPages":(\d+).*""".toRegex()
|
||||
regex.find(jsonStr)?.let { matchResult ->
|
||||
val (current, total) = matchResult.destructured
|
||||
scope.launch {
|
||||
currentPage = current.toInt()
|
||||
totalPages = total.toInt()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error calculating page")
|
||||
) { padding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
) {
|
||||
if (chapters.isNotEmpty()) {
|
||||
AndroidView(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onTap = { showControls = !showControls }
|
||||
)
|
||||
},
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
webViewClient = WebViewClient()
|
||||
this.settings.apply {
|
||||
javaScriptEnabled = true
|
||||
defaultFontSize = currentSettings.fontSize.toInt()
|
||||
builtInZoomControls = true
|
||||
displayZoomControls = false
|
||||
}
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
val chapter = chapters[currentChapterIndex]
|
||||
val html = """
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: ${currentSettings.fontFamily};
|
||||
line-height: ${currentSettings.lineHeight};
|
||||
padding: ${currentSettings.padding}px;
|
||||
margin: 0;
|
||||
background-color: #${backgroundColor.toString(16)};
|
||||
color: #${textColor.toString(16)};
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${String(chapter.data, Charset.defaultCharset())}
|
||||
</body>
|
||||
</html>
|
||||
""".trimIndent()
|
||||
webView.loadDataWithBaseURL(null, html, "text/html", "UTF-8", null)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Navigation controls
|
||||
AnimatedVisibility(
|
||||
visible = showControls,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
enter = fadeIn() + slideInVertically { it },
|
||||
exit = fadeOut() + slideOutVertically { it }
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f),
|
||||
tonalElevation = 3.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { if (currentChapterIndex > 0) currentChapterIndex-- },
|
||||
enabled = currentChapterIndex > 0
|
||||
) {
|
||||
Icon(Icons.Default.NavigateBefore, "Previous chapter")
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "${currentChapterIndex + 1}/${chapters.size}",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = { if (currentChapterIndex < chapters.size - 1) currentChapterIndex++ },
|
||||
enabled = currentChapterIndex < chapters.size - 1
|
||||
) {
|
||||
Icon(Icons.Default.NavigateNext, "Next chapter")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
update = { webView ->
|
||||
webViewRef = webView
|
||||
val htmlContent = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body {
|
||||
font-family: '$fontFamily';
|
||||
font-size: ${fontSize}px;
|
||||
line-height: ${lineHeight};
|
||||
padding: ${padding}px;
|
||||
margin: 0;
|
||||
background-color: ${if (isDarkMode) "#1c1c1c" else "#ffffff"};
|
||||
color: ${if (isDarkMode) "#ffffff" else "#000000"};
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: 1em auto;
|
||||
}
|
||||
.chapter {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
$bookContent
|
||||
<script>$SCROLL_TO_CHAPTER_JS</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
webView.loadDataWithBaseURL(
|
||||
"file:///android_asset/",
|
||||
htmlContent,
|
||||
"text/html",
|
||||
"UTF-8",
|
||||
null
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Update chapter list dialog
|
||||
// Chapter list dialog
|
||||
if (showChapterList) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showChapterList = false },
|
||||
title = { Text("Chapters") },
|
||||
text = {
|
||||
LazyColumn {
|
||||
items(chapters) { chapter ->
|
||||
Text(
|
||||
text = chapter,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val index = chapters.indexOf(chapter)
|
||||
navigateToChapter(index)
|
||||
items(
|
||||
items = List(chapters.size) { it },
|
||||
key = { it }
|
||||
) { index ->
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("Chapter ${index + 1}")
|
||||
},
|
||||
modifier = Modifier.clickable {
|
||||
currentChapterIndex = index
|
||||
showChapterList = false
|
||||
},
|
||||
leadingContent = if (currentChapterIndex == index) {
|
||||
{
|
||||
Icon(
|
||||
Icons.Default.RadioButtonChecked,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
.padding(16.dp)
|
||||
} else {
|
||||
{
|
||||
Icon(
|
||||
Icons.Default.RadioButtonUnchecked,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -413,20 +266,4 @@ fun ReaderScreen(
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Save reading progress when leaving
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
scope.launch {
|
||||
settingsStore.saveSettings(Settings(
|
||||
currentPage = currentPage,
|
||||
fontSize = fontSize,
|
||||
lineHeight = lineHeight,
|
||||
padding = padding,
|
||||
fontFamily = fontFamily,
|
||||
isDarkMode = isDarkMode
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,4 +89,9 @@ class BookStore(private val context: Context) {
|
||||
val bookEntity = bookDao.getBook(bookId) ?: throw IllegalArgumentException("Book not found")
|
||||
return EpubReader().readEpub(FileInputStream(bookEntity.filePath))
|
||||
}
|
||||
|
||||
suspend fun getBookPath(bookId: String): String {
|
||||
return bookDao.getBook(bookId)?.filePath
|
||||
?: throw IllegalArgumentException("Book not found")
|
||||
}
|
||||
}
|
||||
@ -69,7 +69,7 @@ class SettingsStore(private val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadSettings(): Settings {
|
||||
suspend fun getSettings(): Settings {
|
||||
return context.dataStore.data.first().let { preferences ->
|
||||
Settings(
|
||||
currentPage = preferences[PreferencesKeys.CURRENT_PAGE] ?: 1,
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Epook" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
<style name="Theme.Epook" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="android:progressBarStyle">@style/ProgressBarStyle</item>
|
||||
</style>
|
||||
</resources>
|
||||
@ -17,6 +17,7 @@ dependencyResolutionManagement {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://jitpack.io") }
|
||||
maven { url = uri("https://s01.oss.sonatype.org/content/repositories/releases") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user