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
@@ -12,6 +12,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.MenuBook
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.collectAsState 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.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.ui.input.pointer.pointerInput 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) @OptIn(ExperimentalMaterial3Api::class)
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@@ -53,7 +59,6 @@ class MainActivity : ComponentActivity() {
setContent { setContent {
val navController = rememberNavController() val navController = rememberNavController()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
EpookTheme { EpookTheme {
NavHost( NavHost(
@@ -64,8 +69,7 @@ class MainActivity : ComponentActivity() {
MainScreen( MainScreen(
navController = navController, navController = navController,
bookStore = bookStore, bookStore = bookStore,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState
scope = scope
) )
} }
@@ -90,16 +94,18 @@ class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun MainScreen( fun MainScreen(
navController: NavController, navController: NavController,
bookStore: BookStore, bookStore: BookStore,
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
scope: CoroutineScope modifier: Modifier = Modifier
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope()
val books by bookStore.getAllBooks().collectAsState(initial = emptyList()) 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 { scope.launch {
try { try {
val inputStream = context.contentResolver.openInputStream(uri) val inputStream = context.contentResolver.openInputStream(uri)
@@ -134,15 +140,45 @@ private fun MainScreen(
contract = ActivityResultContracts.GetContent() contract = ActivityResultContracts.GetContent()
) { uri -> ) { uri ->
if (uri != null) { 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( Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }, snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = { floatingActionButton = {
FloatingActionButton( FloatingActionButton(
onClick = { launcher.launch("application/epub+zip") } onClick = { launcher.launch("application/epub+zip") },
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
) { ) {
Icon( Icon(
imageVector = Icons.Default.Add, imageVector = Icons.Default.Add,
@@ -151,24 +187,60 @@ private fun MainScreen(
} }
} }
) { padding -> ) { padding ->
if (books.isEmpty()) {
EmptyLibraryMessage(
modifier = Modifier
.fillMaxSize()
.padding(padding)
)
} else {
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 160.dp), columns = GridCells.Adaptive(minSize = 120.dp),
contentPadding = padding, contentPadding = PaddingValues(12.dp),
modifier = Modifier.fillMaxSize() horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier
.fillMaxSize()
.padding(padding)
) { ) {
items(books) { book -> items(books) { book ->
BookCard( BookCard(
book = book, book = book,
onClick = { navController.navigate("reader/${book.id}") }, onClick = { navController.navigate("reader/${book.id}") },
onLongClick = { onLongClick = { bookToDelete = book }
scope.launch {
bookStore.deleteBook(book.id)
}
}
) )
} }
} }
} }
}
}
@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) @OptIn(ExperimentalMaterial3Api::class)
@@ -185,7 +257,7 @@ private fun BookCard(
ElevatedCard( ElevatedCard(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(0.7f) .aspectRatio(0.75f)
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onTap = { onClick() }, onTap = { onClick() },
@@ -193,42 +265,45 @@ private fun BookCard(
) )
}, },
elevation = CardDefaults.elevatedCardElevation( elevation = CardDefaults.elevatedCardElevation(
defaultElevation = 4.dp, defaultElevation = 2.dp,
pressedElevation = if (isPressed) 8.dp else 4.dp pressedElevation = if (isPressed) 8.dp else 2.dp
),
colors = CardDefaults.elevatedCardColors(
containerColor = MaterialTheme.colorScheme.surface
) )
) { ) {
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
// Book cover
AsyncImage( AsyncImage(
model = book.coverPath ?: R.drawable.ic_launcher_background, model = book.coverPath ?: R.drawable.ic_book_24dp,
contentDescription = book.title, contentDescription = book.title,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop contentScale = ContentScale.Crop,
) )
Surface( // Simple overlay for title
Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomCenter), .background(
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f) Brush.verticalGradient(
) { colors = listOf(
Column( Color.Transparent,
modifier = Modifier.padding(12.dp) Color.Black.copy(alpha = 0.6f)
)
)
)
.align(Alignment.BottomCenter)
.padding(8.dp)
) { ) {
Text( Text(
text = book.title, text = book.title,
style = MaterialTheme.typography.titleMedium,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
Text(
text = book.author,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
maxLines = 1, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f) color = Color.White
) )
} }
} }
} }
}
} }
@@ -223,6 +223,27 @@ class BookStore(private val context: Context) {
// Extract EPUB contents // Extract EPUB contents
val extractedDirectory = File(bookDirectory, "extracted") val extractedDirectory = File(bookDirectory, "extracted")
extractedDirectory.mkdirs() 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) extractEpub(epubBook, extractedDirectory)
// Save book metadata // Save book metadata
@@ -232,7 +253,7 @@ class BookStore(private val context: Context) {
author = epubBook.metadata.authors.firstOrNull()?.toString() ?: "Unknown", author = epubBook.metadata.authors.firstOrNull()?.toString() ?: "Unknown",
path = epubFile.absolutePath, path = epubFile.absolutePath,
extractedPath = extractedDirectory.absolutePath, extractedPath = extractedDirectory.absolutePath,
coverPath = null // Handle cover separately if needed coverPath = coverPath
) )
bookDao.insert(book.toEntity()) bookDao.insert(book.toEntity())