bug fixes after changes
This commit is contained in:
parent
6e795bd7d1
commit
3295816550
29
README.md
29
README.md
@ -36,7 +36,7 @@ Epook is a sleek, modern EPUB reader built for Android using Jetpack Compose. It
|
||||
- Reading progress persistence
|
||||
- Organized book collection view
|
||||
|
||||
### 🔧 Technical Features
|
||||
### <EFBFBD><EFBFBD><EFBFBD><EFBFBD> Technical Features
|
||||
- CSS stylesheet handling
|
||||
- HTML content processing
|
||||
- Efficient file management
|
||||
@ -77,3 +77,30 @@ This project is licensed under the MIT License - see the LICENSE file for detail
|
||||
- [epublib](https://github.com/psiegman/epublib) for EPUB processing
|
||||
- [Jsoup](https://jsoup.org/) for HTML parsing
|
||||
- [Material Design 3](https://m3.material.io/) for design guidelines
|
||||
|
||||
## 🎨 App Icon Design
|
||||
|
||||
### Primary Design
|
||||
A minimalist, modern book icon featuring:
|
||||
- A stylized open book in Material Design style
|
||||
- Primary color: Deep Purple (#6750A4) with white pages
|
||||
- Subtle gradient background from lighter to darker purple
|
||||
- Rounded corners following Material Design 3 guidelines
|
||||
- Clean, simple lines with minimal detail
|
||||
|
||||
### Specifications
|
||||
- Size: 512x512px (Play Store master icon)
|
||||
- Adaptive Icon Layers:
|
||||
- Foreground: Book icon in white/light purple
|
||||
- Background: Gradient from #6750A4 to #4F378B
|
||||
- Safe zone: 384x384px centered
|
||||
- Corner radius: 100dp (Material 3 spec)
|
||||
|
||||
### Alternative Sizes
|
||||
- 48x48dp (mdpi)
|
||||
- 72x72dp (hdpi)
|
||||
- 96x96dp (xhdpi)
|
||||
- 144x144dp (xxhdpi)
|
||||
- 192x192dp (xxxhdpi)
|
||||
|
||||
### Design Elements
|
||||
|
||||
@ -50,6 +50,7 @@ import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebResourceError
|
||||
import android.webkit.WebSettings
|
||||
import android.view.View
|
||||
|
||||
// Move the Chapter data class outside the composable
|
||||
private data class Chapter(
|
||||
@ -58,6 +59,9 @@ private data class Chapter(
|
||||
val resource: Resource
|
||||
)
|
||||
|
||||
// First, add a constant for the swipe area height
|
||||
private const val SWIPE_AREA_HEIGHT = 80 // in dp
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ReaderScreen(
|
||||
@ -149,130 +153,154 @@ fun ReaderScreen(
|
||||
}
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = if (showControls) 80.dp else 0.dp)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(
|
||||
onTap = { showControls = !showControls }
|
||||
)
|
||||
}
|
||||
.pointerInput(Unit) {
|
||||
var initialX = 0f
|
||||
detectHorizontalDragGestures(
|
||||
onDragStart = { offset ->
|
||||
initialX = offset.x
|
||||
},
|
||||
onDragEnd = {
|
||||
// Implement drag threshold for swipe
|
||||
val dragThreshold = size.width * 0.2f // 20% of screen width
|
||||
val dragDistance = initialX - currentX
|
||||
|
||||
when {
|
||||
dragDistance > dragThreshold && currentChapterIndex < chapters.size - 1 -> {
|
||||
// Swiped left - next chapter
|
||||
currentChapterIndex++
|
||||
}
|
||||
dragDistance < -dragThreshold && currentChapterIndex > 0 -> {
|
||||
// Swiped right - previous chapter
|
||||
currentChapterIndex--
|
||||
}
|
||||
}
|
||||
}
|
||||
) { change, _ ->
|
||||
currentX = change.position.x
|
||||
change.consume()
|
||||
}
|
||||
}
|
||||
) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
error: WebResourceError?
|
||||
) {
|
||||
Timber.e("WebView error loading ${request?.url}: ${error?.description}")
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
val url = request.url.toString()
|
||||
Timber.d("Loading resource: $url")
|
||||
|
||||
try {
|
||||
if (url.endsWith(".css") && extractedPath != null) {
|
||||
val possiblePaths = listOf(
|
||||
url.substringAfterLast("/"),
|
||||
"Styles/${url.substringAfterLast("/")}",
|
||||
url.substringAfter("/extracted/")
|
||||
)
|
||||
|
||||
for (cssPath in possiblePaths) {
|
||||
val cssFile = File(extractedPath!!, cssPath)
|
||||
if (cssFile.exists()) {
|
||||
Timber.d("Found CSS file at: ${cssFile.absolutePath}")
|
||||
return WebResourceResponse(
|
||||
"text/css",
|
||||
"UTF-8",
|
||||
cssFile.inputStream()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error intercepting resource request")
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
) {
|
||||
// WebView without swipe gesture
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onReceivedError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
error: WebResourceError?
|
||||
) {
|
||||
Timber.e("WebView error loading ${request?.url}: ${error?.description}")
|
||||
super.onReceivedError(view, request, error)
|
||||
}
|
||||
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
builtInZoomControls = true
|
||||
displayZoomControls = false
|
||||
allowFileAccess = true
|
||||
@Suppress("DEPRECATION")
|
||||
allowFileAccessFromFileURLs = true
|
||||
@Suppress("DEPRECATION")
|
||||
allowUniversalAccessFromFileURLs = true
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
domStorageEnabled = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
}
|
||||
}.also { webView = it }
|
||||
},
|
||||
update = { view ->
|
||||
val chapter = chapters.getOrNull(currentChapterIndex)?.resource
|
||||
chapter?.let { resource ->
|
||||
scope.launch {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest
|
||||
): WebResourceResponse? {
|
||||
val url = request.url.toString()
|
||||
Timber.d("Loading resource: $url")
|
||||
|
||||
try {
|
||||
extractedPath = bookStore.getBook(bookId)?.extractedPath
|
||||
?: throw IllegalStateException("Book not properly extracted")
|
||||
|
||||
val baseUrl = "file://$extractedPath/"
|
||||
val fullUrl = baseUrl + resource.href.replace("../", "")
|
||||
|
||||
Timber.d("Loading chapter from: $fullUrl")
|
||||
Timber.d("Base URL: $baseUrl")
|
||||
|
||||
view.loadUrl(fullUrl)
|
||||
if (url.endsWith(".css") && extractedPath != null) {
|
||||
val possiblePaths = listOf(
|
||||
url.substringAfterLast("/"),
|
||||
"Styles/${url.substringAfterLast("/")}",
|
||||
url.substringAfter("/extracted/")
|
||||
)
|
||||
|
||||
for (cssPath in possiblePaths) {
|
||||
val cssFile = File(extractedPath!!, cssPath)
|
||||
if (cssFile.exists()) {
|
||||
Timber.d("Found CSS file at: ${cssFile.absolutePath}")
|
||||
return WebResourceResponse(
|
||||
"text/css",
|
||||
"UTF-8",
|
||||
cssFile.inputStream()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error loading chapter")
|
||||
Timber.e(e, "Error intercepting resource request")
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
}
|
||||
|
||||
settings.apply {
|
||||
javaScriptEnabled = true
|
||||
builtInZoomControls = true
|
||||
displayZoomControls = false
|
||||
allowFileAccess = true
|
||||
@Suppress("DEPRECATION")
|
||||
allowFileAccessFromFileURLs = true
|
||||
@Suppress("DEPRECATION")
|
||||
allowUniversalAccessFromFileURLs = true
|
||||
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
domStorageEnabled = true
|
||||
cacheMode = WebSettings.LOAD_DEFAULT
|
||||
|
||||
// Add these specific scrolling optimizations
|
||||
@Suppress("DEPRECATION")
|
||||
setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
|
||||
// Disable features that might cause stuttering
|
||||
loadsImagesAutomatically = true
|
||||
blockNetworkImage = true // Since we're reading locally
|
||||
blockNetworkLoads = true // Since we're reading locally
|
||||
|
||||
// Enable hardware acceleration
|
||||
setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||
}
|
||||
|
||||
// Set better scrolling properties
|
||||
overScrollMode = View.OVER_SCROLL_NEVER
|
||||
isVerticalScrollBarEnabled = false // Hide scrollbar for smoother scrolling
|
||||
|
||||
// Set scroll sensitivity
|
||||
@Suppress("DEPRECATION")
|
||||
setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY)
|
||||
|
||||
// Enable smooth scrolling
|
||||
isScrollContainer = true
|
||||
|
||||
// Set better touch handling
|
||||
isNestedScrollingEnabled = true
|
||||
|
||||
}.also { webView = it }
|
||||
},
|
||||
update = { view ->
|
||||
val chapter = chapters.getOrNull(currentChapterIndex)?.resource
|
||||
chapter?.let { resource ->
|
||||
scope.launch {
|
||||
try {
|
||||
extractedPath = bookStore.getBook(bookId)?.extractedPath
|
||||
?: throw IllegalStateException("Book not properly extracted")
|
||||
|
||||
val baseUrl = "file://$extractedPath/"
|
||||
val fullUrl = baseUrl + resource.href.replace("../", "")
|
||||
|
||||
Timber.d("Loading chapter from: $fullUrl")
|
||||
Timber.d("Base URL: $baseUrl")
|
||||
|
||||
view.loadUrl(fullUrl)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Error loading chapter")
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(bottom = if (showControls) SWIPE_AREA_HEIGHT.dp else 0.dp)
|
||||
)
|
||||
|
||||
// Add swipe area at the bottom
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(SWIPE_AREA_HEIGHT.dp)
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(MaterialTheme.colorScheme.surface.copy(alpha = 0.1f))
|
||||
.pointerInput(Unit) {
|
||||
detectHorizontalDragGestures { _, dragAmount ->
|
||||
currentX += dragAmount
|
||||
when {
|
||||
currentX > 100 -> {
|
||||
if (currentChapterIndex > 0) {
|
||||
currentChapterIndex--
|
||||
}
|
||||
currentX = 0f
|
||||
}
|
||||
currentX < -100 -> {
|
||||
if (currentChapterIndex < chapters.size - 1) {
|
||||
currentChapterIndex++
|
||||
}
|
||||
currentX = 0f
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Controls overlay
|
||||
AnimatedVisibility(
|
||||
@ -340,30 +368,6 @@ fun ReaderScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Back button overlay
|
||||
AnimatedVisibility(
|
||||
visible = showControls,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.statusBarsPadding()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onNavigateBack,
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f),
|
||||
contentColor = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chapter list dialog
|
||||
|
||||
Loading…
Reference in New Issue
Block a user