book cover fixed
This commit is contained in:
parent
9400ff5535
commit
d73f54eeb4
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
|
||||
Loading…
Reference in New Issue
Block a user