book cover fixed

This commit is contained in:
inhale-dir 2024-12-13 15:01:09 +01:00
parent 9400ff5535
commit d73f54eeb4
2 changed files with 145 additions and 49 deletions

View File

@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.MenuBook
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.collectAsState
@ -41,6 +42,11 @@ import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.ui.input.pointer.pointerInput
import kotlinx.coroutines.flow.map
import androidx.compose.foundation.background
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.geometry.Offset
@OptIn(ExperimentalMaterial3Api::class)
class MainActivity : ComponentActivity() {
@ -53,7 +59,6 @@ class MainActivity : ComponentActivity() {
setContent {
val navController = rememberNavController()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
EpookTheme {
NavHost(
@ -64,8 +69,7 @@ class MainActivity : ComponentActivity() {
MainScreen(
navController = navController,
bookStore = bookStore,
snackbarHostState = snackbarHostState,
scope = scope
snackbarHostState = snackbarHostState
)
}
@ -90,16 +94,18 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun MainScreen(
fun MainScreen(
navController: NavController,
bookStore: BookStore,
snackbarHostState: SnackbarHostState,
scope: CoroutineScope
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()
val books by bookStore.getAllBooks().collectAsState(initial = emptyList())
var bookToDelete by remember { mutableStateOf<Book?>(null) }
fun importBook(uri: Uri, scope: CoroutineScope) {
fun importBook(uri: Uri) {
scope.launch {
try {
val inputStream = context.contentResolver.openInputStream(uri)
@ -134,15 +140,45 @@ private fun MainScreen(
contract = ActivityResultContracts.GetContent()
) { uri ->
if (uri != null) {
importBook(uri, scope)
importBook(uri)
}
}
// Delete confirmation dialog
bookToDelete?.let { book ->
AlertDialog(
onDismissRequest = { bookToDelete = null },
title = { Text("Delete Book") },
text = {
Text("Are you sure you want to delete \"${book.title}\"?")
},
confirmButton = {
TextButton(
onClick = {
scope.launch {
bookStore.deleteBook(book.id)
bookToDelete = null
}
}
) {
Text("Delete", color = MaterialTheme.colorScheme.error)
}
},
dismissButton = {
TextButton(onClick = { bookToDelete = null }) {
Text("Cancel")
}
}
)
}
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = {
FloatingActionButton(
onClick = { launcher.launch("application/epub+zip") }
onClick = { launcher.launch("application/epub+zip") },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
) {
Icon(
imageVector = Icons.Default.Add,
@ -151,26 +187,62 @@ private fun MainScreen(
}
}
) { padding ->
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp),
contentPadding = padding,
modifier = Modifier.fillMaxSize()
) {
items(books) { book ->
BookCard(
book = book,
onClick = { navController.navigate("reader/${book.id}") },
onLongClick = {
scope.launch {
bookStore.deleteBook(book.id)
}
}
)
if (books.isEmpty()) {
EmptyLibraryMessage(
modifier = Modifier
.fillMaxSize()
.padding(padding)
)
} else {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 120.dp),
contentPadding = PaddingValues(12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxSize()
.padding(padding)
) {
items(books) { book ->
BookCard(
book = book,
onClick = { navController.navigate("reader/${book.id}") },
onLongClick = { bookToDelete = book }
)
}
}
}
}
}
@Composable
private fun EmptyLibraryMessage(modifier: Modifier = Modifier) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Default.MenuBook,
contentDescription = null,
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary.copy(alpha = 0.6f)
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Your library is empty",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "Tap + to add your first book",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun BookCard(
@ -185,7 +257,7 @@ private fun BookCard(
ElevatedCard(
modifier = modifier
.fillMaxWidth()
.aspectRatio(0.7f)
.aspectRatio(0.75f)
.pointerInput(Unit) {
detectTapGestures(
onTap = { onClick() },
@ -193,41 +265,44 @@ private fun BookCard(
)
},
elevation = CardDefaults.elevatedCardElevation(
defaultElevation = 4.dp,
pressedElevation = if (isPressed) 8.dp else 4.dp
defaultElevation = 2.dp,
pressedElevation = if (isPressed) 8.dp else 2.dp
),
colors = CardDefaults.elevatedCardColors(
containerColor = MaterialTheme.colorScheme.surface
)
) {
Box(modifier = Modifier.fillMaxSize()) {
// Book cover
AsyncImage(
model = book.coverPath ?: R.drawable.ic_launcher_background,
model = book.coverPath ?: R.drawable.ic_book_24dp,
contentDescription = book.title,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
contentScale = ContentScale.Crop,
)
Surface(
// Simple overlay for title
Box(
modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter),
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.6f)
)
)
)
.align(Alignment.BottomCenter)
.padding(8.dp)
) {
Column(
modifier = Modifier.padding(12.dp)
) {
Text(
text = book.title,
style = MaterialTheme.typography.titleMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = book.author,
style = MaterialTheme.typography.bodyMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
}
Text(
text = book.title,
style = MaterialTheme.typography.bodyMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
color = Color.White
)
}
}
}

View File

@ -223,6 +223,27 @@ class BookStore(private val context: Context) {
// Extract EPUB contents
val extractedDirectory = File(bookDirectory, "extracted")
extractedDirectory.mkdirs()
// Extract and save cover image
var coverPath: String? = null
// Try to get cover from the book's resources
epubBook.resources.all
.firstOrNull { resource ->
resource.mediaType?.toString()?.contains("image") == true &&
(resource.href.contains("cover") || resource.id?.contains("cover") == true)
}?.let { coverResource ->
val coverFile = File(bookDirectory, "cover.jpg")
coverFile.writeBytes(coverResource.data)
coverPath = "file://${coverFile.absolutePath}"
Timber.d("Saved cover image from resources to: $coverPath")
} ?: epubBook.coverImage?.let { cover ->
val coverFile = File(bookDirectory, "cover.jpg")
coverFile.writeBytes(cover.data)
coverPath = "file://${coverFile.absolutePath}"
Timber.d("Saved direct cover image to: $coverPath")
}
extractEpub(epubBook, extractedDirectory)
// Save book metadata
@ -232,7 +253,7 @@ class BookStore(private val context: Context) {
author = epubBook.metadata.authors.firstOrNull()?.toString() ?: "Unknown",
path = epubFile.absolutePath,
extractedPath = extractedDirectory.absolutePath,
coverPath = null // Handle cover separately if needed
coverPath = coverPath
)
bookDao.insert(book.toEntity())