parent
dd7998c600
commit
57a7225545
12 changed files with 148 additions and 121 deletions
|
|
@ -9,8 +9,6 @@ package ua.acclorite.book_story.data.model.file
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
import com.anggrayudi.storage.file.DocumentFileCompat
|
|
||||||
import com.anggrayudi.storage.file.getAbsolutePath
|
|
||||||
|
|
||||||
object CachedFileCompat {
|
object CachedFileCompat {
|
||||||
fun fromUri(context: Context, uri: Uri, builder: CachedFileBuilder? = null): CachedFile {
|
fun fromUri(context: Context, uri: Uri, builder: CachedFileBuilder? = null): CachedFile {
|
||||||
|
|
@ -32,61 +30,6 @@ object CachedFileCompat {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromFullPath(
|
|
||||||
context: Context,
|
|
||||||
path: String,
|
|
||||||
builder: CachedFileBuilder? = null
|
|
||||||
): CachedFile? {
|
|
||||||
val uri = try {
|
|
||||||
val storageId = DocumentFileCompat.getStorageId(context, path)
|
|
||||||
if (storageId.isBlank()) throw NullPointerException("Could not get storageId.")
|
|
||||||
|
|
||||||
val basePath = DocumentFileCompat.getBasePath(context, path)
|
|
||||||
if (basePath.isBlank()) throw NullPointerException("Could not get basePath.")
|
|
||||||
|
|
||||||
val parentUri = context.contentResolver.persistedUriPermissions.find {
|
|
||||||
try {
|
|
||||||
val persistedUri = DocumentFileCompat.fromUri(context, it.uri)
|
|
||||||
val persistedUriPath = persistedUri?.getAbsolutePath(context)
|
|
||||||
|
|
||||||
return@find !(persistedUri == null ||
|
|
||||||
!persistedUri.canRead() ||
|
|
||||||
persistedUriPath.isNullOrBlank() ||
|
|
||||||
!path.startsWith(persistedUriPath, ignoreCase = true))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return@find false
|
|
||||||
}
|
|
||||||
}?.uri
|
|
||||||
if (parentUri == null) throw NullPointerException("Could not get parentUri.")
|
|
||||||
|
|
||||||
DocumentsContract.buildDocumentUriUsingTree(parentUri, "$storageId:$basePath")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
val cachedFile = CachedFile(
|
|
||||||
context = context,
|
|
||||||
uri = when {
|
|
||||||
DocumentsContract.isDocumentUri(context, uri) -> uri
|
|
||||||
|
|
||||||
DocumentsContract.isTreeUri(uri) -> {
|
|
||||||
DocumentsContract.buildDocumentUriUsingTree(
|
|
||||||
uri,
|
|
||||||
DocumentsContract.getTreeDocumentId(uri)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> uri
|
|
||||||
},
|
|
||||||
builder = builder
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!cachedFile.canAccess()) return null
|
|
||||||
return cachedFile
|
|
||||||
}
|
|
||||||
|
|
||||||
fun build(
|
fun build(
|
||||||
name: String? = null,
|
name: String? = null,
|
||||||
path: String? = null,
|
path: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@ package ua.acclorite.book_story.data.repository
|
||||||
import ua.acclorite.book_story.core.CoverImage
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
import ua.acclorite.book_story.data.local.room.BookDao
|
import ua.acclorite.book_story.data.local.room.BookDao
|
||||||
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
import ua.acclorite.book_story.data.mapper.book.BookMapper
|
||||||
|
import ua.acclorite.book_story.data.mapper.file.FileMapper
|
||||||
import ua.acclorite.book_story.data.parser.FileParser
|
import ua.acclorite.book_story.data.parser.FileParser
|
||||||
import ua.acclorite.book_story.data.parser.TextParser
|
import ua.acclorite.book_story.data.parser.TextParser
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.domain.model.reader.ReaderText
|
import ua.acclorite.book_story.domain.model.reader.ReaderText
|
||||||
import ua.acclorite.book_story.domain.repository.BookRepository
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
|
@ -22,6 +24,7 @@ import javax.inject.Singleton
|
||||||
class BookRepositoryImpl @Inject constructor(
|
class BookRepositoryImpl @Inject constructor(
|
||||||
private val database: BookDao,
|
private val database: BookDao,
|
||||||
private val bookMapper: BookMapper,
|
private val bookMapper: BookMapper,
|
||||||
|
private val fileMapper: FileMapper,
|
||||||
private val fileParser: FileParser,
|
private val fileParser: FileParser,
|
||||||
private val textParser: TextParser,
|
private val textParser: TextParser,
|
||||||
private val fileProvider: FileProvider
|
private val fileProvider: FileProvider
|
||||||
|
|
@ -44,6 +47,12 @@ class BookRepositoryImpl @Inject constructor(
|
||||||
.mapCatching { textParser.parse(it) }
|
.mapCatching { textParser.parse(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getFileFromBook(bookId: Int): Result<File> {
|
||||||
|
return getBook(bookId)
|
||||||
|
.mapCatching { fileProvider.getFileFromBook(it).getOrThrow() }
|
||||||
|
.mapCatching { fileMapper.toFile(it) }
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun addBook(book: Book): Result<Unit> = runCatching {
|
override suspend fun addBook(book: Book): Result<Unit> = runCatching {
|
||||||
database.insertBook(bookMapper.toBookEntity(book))
|
database.insertBook(bookMapper.toBookEntity(book))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,23 @@ class FileProviderImpl @Inject constructor(
|
||||||
) : FileProvider {
|
) : FileProvider {
|
||||||
|
|
||||||
override fun getFileFromBook(book: Book): Result<CachedFile> = runCatching {
|
override fun getFileFromBook(book: Book): Result<CachedFile> = runCatching {
|
||||||
CachedFileCompat.fromFullPath(
|
application.contentResolver.persistedUriPermissions.forEach { storage ->
|
||||||
context = application,
|
val storageFile = CachedFileCompat.fromUri(
|
||||||
path = book.filePath,
|
application,
|
||||||
builder = CachedFileCompat.build(
|
storage.uri
|
||||||
name = book.filePath.substringAfterLast(java.io.File.separator),
|
|
||||||
path = book.filePath,
|
|
||||||
isDirectory = false
|
|
||||||
)
|
)
|
||||||
).let { file ->
|
|
||||||
if (file == null || !file.canAccess()) {
|
|
||||||
throw NoSuchElementException("Could not load CachedFile.")
|
|
||||||
}
|
|
||||||
|
|
||||||
file
|
if (!storageFile.isDirectory) return@forEach
|
||||||
|
if (!book.filePath.startsWith(storageFile.path, ignoreCase = true)) return@forEach
|
||||||
|
|
||||||
|
storageFile.walk().forEach { file ->
|
||||||
|
if (book.filePath.equals(file.path, ignoreCase = true)) {
|
||||||
|
return@runCatching file
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw NoSuchElementException("Could not find file from book.")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStorageFiles(): Result<List<CachedFile>> = runCatching {
|
override fun getStorageFiles(): Result<List<CachedFile>> = runCatching {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
package ua.acclorite.book_story.domain.repository
|
package ua.acclorite.book_story.domain.repository
|
||||||
|
|
||||||
import ua.acclorite.book_story.core.CoverImage
|
import ua.acclorite.book_story.core.CoverImage
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.domain.model.reader.ReaderText
|
import ua.acclorite.book_story.domain.model.reader.ReaderText
|
||||||
|
|
||||||
|
|
@ -23,6 +24,10 @@ interface BookRepository {
|
||||||
bookId: Int
|
bookId: Int
|
||||||
): Result<List<ReaderText>>
|
): Result<List<ReaderText>>
|
||||||
|
|
||||||
|
suspend fun getFileFromBook(
|
||||||
|
bookId: Int
|
||||||
|
): Result<File>
|
||||||
|
|
||||||
suspend fun addBook(
|
suspend fun addBook(
|
||||||
book: Book
|
book: Book
|
||||||
): Result<Unit>
|
): Result<Unit>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Book's Story — free and open-source Material You eBook reader.
|
||||||
|
* Copyright (C) 2024-2025 Acclorite
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ua.acclorite.book_story.domain.use_case.book
|
||||||
|
|
||||||
|
import ua.acclorite.book_story.core.log.logE
|
||||||
|
import ua.acclorite.book_story.core.log.logI
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
|
import ua.acclorite.book_story.domain.repository.BookRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetFileFromBookUseCase @Inject constructor(
|
||||||
|
private val bookRepository: BookRepository
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(bookId: Int): File? {
|
||||||
|
logI("Getting file from book [$bookId].")
|
||||||
|
|
||||||
|
return bookRepository.getFileFromBook(bookId).fold(
|
||||||
|
onSuccess = {
|
||||||
|
logI("Successfully got file from book [$bookId].")
|
||||||
|
it
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
logE("Could not get file from book [$bookId] with error: ${it.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -24,6 +24,7 @@ import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.domain.use_case.book.CanResetCoverImageUseCase
|
import ua.acclorite.book_story.domain.use_case.book.CanResetCoverImageUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase
|
import ua.acclorite.book_story.domain.use_case.book.DeleteBookUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
|
import ua.acclorite.book_story.domain.use_case.book.GetBookUseCase
|
||||||
|
import ua.acclorite.book_story.domain.use_case.book.GetFileFromBookUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.ResetCoverImageUseCase
|
import ua.acclorite.book_story.domain.use_case.book.ResetCoverImageUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase
|
import ua.acclorite.book_story.domain.use_case.book.UpdateBookUseCase
|
||||||
import ua.acclorite.book_story.domain.use_case.book.UpdateCoverImageUseCase
|
import ua.acclorite.book_story.domain.use_case.book.UpdateCoverImageUseCase
|
||||||
|
|
@ -38,6 +39,7 @@ class BookInfoModel @Inject constructor(
|
||||||
private val updateCoverImageUseCase: UpdateCoverImageUseCase,
|
private val updateCoverImageUseCase: UpdateCoverImageUseCase,
|
||||||
private val updateBookUseCase: UpdateBookUseCase,
|
private val updateBookUseCase: UpdateBookUseCase,
|
||||||
private val getBookUseCase: GetBookUseCase,
|
private val getBookUseCase: GetBookUseCase,
|
||||||
|
private val getFileFromBookUseCase: GetFileFromBookUseCase,
|
||||||
private val deleteBookUseCase: DeleteBookUseCase,
|
private val deleteBookUseCase: DeleteBookUseCase,
|
||||||
private val canResetCoverImageUseCase: CanResetCoverImageUseCase,
|
private val canResetCoverImageUseCase: CanResetCoverImageUseCase,
|
||||||
private val resetCoverImageUseCase: ResetCoverImageUseCase
|
private val resetCoverImageUseCase: ResetCoverImageUseCase
|
||||||
|
|
@ -302,6 +304,13 @@ class BookInfoModel @Inject constructor(
|
||||||
event.context.getString(R.string.path_changed)
|
event.context.getString(R.string.path_changed)
|
||||||
.showToast(context = event.context)
|
.showToast(context = event.context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val file = getFileFromBookUseCase(_state.value.book.id)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
file = file
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -408,6 +417,13 @@ class BookInfoModel @Inject constructor(
|
||||||
onEvent(BookInfoEvent.OnShowPathDialog)
|
onEvent(BookInfoEvent.OnShowPathDialog)
|
||||||
}
|
}
|
||||||
onEvent(BookInfoEvent.OnCheckCoverReset)
|
onEvent(BookInfoEvent.OnCheckCoverReset)
|
||||||
|
|
||||||
|
val file = getFileFromBookUseCase(bookId)
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
file = file
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ data class BookInfoScreen(val bookId: Int) : Screen, Parcelable {
|
||||||
if (state.value.book.id == bookId) {
|
if (state.value.book.id == bookId) {
|
||||||
BookInfoContent(
|
BookInfoContent(
|
||||||
book = state.value.book,
|
book = state.value.book,
|
||||||
|
file = state.value.file,
|
||||||
categories = settingsState.value.categories,
|
categories = settingsState.value.categories,
|
||||||
bottomSheet = state.value.bottomSheet,
|
bottomSheet = state.value.bottomSheet,
|
||||||
dialog = state.value.dialog,
|
dialog = state.value.dialog,
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,13 @@ package ua.acclorite.book_story.presentation.book_info
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import ua.acclorite.book_story.core.BottomSheet
|
import ua.acclorite.book_story.core.BottomSheet
|
||||||
import ua.acclorite.book_story.core.Dialog
|
import ua.acclorite.book_story.core.Dialog
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class BookInfoState(
|
data class BookInfoState(
|
||||||
val book: Book = Book.default,
|
val book: Book = Book.default,
|
||||||
|
val file: File? = null,
|
||||||
|
|
||||||
val canResetCover: Boolean = false,
|
val canResetCover: Boolean = false,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import ua.acclorite.book_story.core.BottomSheet
|
import ua.acclorite.book_story.core.BottomSheet
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
|
import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
|
||||||
|
|
@ -16,6 +17,7 @@ import ua.acclorite.book_story.presentation.book_info.BookInfoScreen
|
||||||
fun BookInfoBottomSheet(
|
fun BookInfoBottomSheet(
|
||||||
bottomSheet: BottomSheet?,
|
bottomSheet: BottomSheet?,
|
||||||
book: Book,
|
book: Book,
|
||||||
|
file: File?,
|
||||||
canResetCover: Boolean,
|
canResetCover: Boolean,
|
||||||
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
|
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
|
||||||
changeCover: (BookInfoEvent.OnChangeCover) -> Unit,
|
changeCover: (BookInfoEvent.OnChangeCover) -> Unit,
|
||||||
|
|
@ -40,6 +42,7 @@ fun BookInfoBottomSheet(
|
||||||
BookInfoScreen.DETAILS_BOTTOM_SHEET -> {
|
BookInfoScreen.DETAILS_BOTTOM_SHEET -> {
|
||||||
BookInfoDetailsBottomSheet(
|
BookInfoDetailsBottomSheet(
|
||||||
book = book,
|
book = book,
|
||||||
|
file = file,
|
||||||
showPathDialog = showPathDialog,
|
showPathDialog = showPathDialog,
|
||||||
dismissBottomSheet = dismissBottomSheet
|
dismissBottomSheet = dismissBottomSheet
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import ua.acclorite.book_story.core.BottomSheet
|
import ua.acclorite.book_story.core.BottomSheet
|
||||||
import ua.acclorite.book_story.core.Dialog
|
import ua.acclorite.book_story.core.Dialog
|
||||||
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.domain.model.library.Category
|
import ua.acclorite.book_story.domain.model.library.Category
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
|
|
@ -17,6 +18,7 @@ import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoContent(
|
fun BookInfoContent(
|
||||||
book: Book,
|
book: Book,
|
||||||
|
file: File?,
|
||||||
categories: List<Category>,
|
categories: List<Category>,
|
||||||
bottomSheet: BottomSheet?,
|
bottomSheet: BottomSheet?,
|
||||||
dialog: Dialog?,
|
dialog: Dialog?,
|
||||||
|
|
@ -64,6 +66,7 @@ fun BookInfoContent(
|
||||||
BookInfoBottomSheet(
|
BookInfoBottomSheet(
|
||||||
bottomSheet = bottomSheet,
|
bottomSheet = bottomSheet,
|
||||||
book = book,
|
book = book,
|
||||||
|
file = file,
|
||||||
showPathDialog = showPathDialog,
|
showPathDialog = showPathDialog,
|
||||||
canResetCover = canResetCover,
|
canResetCover = canResetCover,
|
||||||
changeCover = changeCover,
|
changeCover = changeCover,
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,11 @@ import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.core.data.ExtensionsData
|
import ua.acclorite.book_story.core.data.ExtensionsData
|
||||||
import ua.acclorite.book_story.data.model.file.CachedFileCompat
|
import ua.acclorite.book_story.domain.model.file.File
|
||||||
import ua.acclorite.book_story.domain.model.library.Book
|
import ua.acclorite.book_story.domain.model.library.Book
|
||||||
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
import ua.acclorite.book_story.presentation.book_info.BookInfoEvent
|
||||||
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
|
import ua.acclorite.book_story.ui.common.components.common.LazyColumnWithScrollbar
|
||||||
|
|
@ -32,20 +31,16 @@ import java.util.Locale
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDetailsBottomSheet(
|
fun BookInfoDetailsBottomSheet(
|
||||||
book: Book,
|
book: Book,
|
||||||
|
file: File?,
|
||||||
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
|
showPathDialog: (BookInfoEvent.OnShowPathDialog) -> Unit,
|
||||||
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit
|
dismissBottomSheet: (BookInfoEvent.OnDismissBottomSheet) -> Unit
|
||||||
) {
|
) {
|
||||||
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
|
val pattern = remember { SimpleDateFormat("HH:mm dd MMM yyyy", Locale.getDefault()) }
|
||||||
val lastOpened = remember(book.lastOpened) { pattern.format(Date(book.lastOpened ?: 0)) }
|
val lastOpened = remember(book.lastOpened) { pattern.format(Date(book.lastOpened ?: 0)) }
|
||||||
|
|
||||||
val context = LocalContext.current
|
val fileSize = remember(file) {
|
||||||
val cachedFile = remember(book.filePath) {
|
if (file != null) {
|
||||||
CachedFileCompat.fromFullPath(context, book.filePath)
|
val sizeBytes = file.size
|
||||||
}
|
|
||||||
|
|
||||||
val fileSize = remember(cachedFile) {
|
|
||||||
if (cachedFile != null && cachedFile.canAccess()) {
|
|
||||||
val sizeBytes = cachedFile.size
|
|
||||||
val sizeKB = sizeBytes / 1024f
|
val sizeKB = sizeBytes / 1024f
|
||||||
val sizeMB = sizeKB / 1024f
|
val sizeMB = sizeKB / 1024f
|
||||||
when {
|
when {
|
||||||
|
|
@ -53,17 +48,25 @@ fun BookInfoDetailsBottomSheet(
|
||||||
sizeKB > 0f -> "%.2f KB".format(sizeKB)
|
sizeKB > 0f -> "%.2f KB".format(sizeKB)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
} else {
|
} else ""
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileExists = remember(cachedFile) {
|
val fileExists = remember(file) {
|
||||||
cachedFile.let {
|
file.let { file ->
|
||||||
it != null && it.canAccess() && !it.isDirectory && ExtensionsData.fileExtensions
|
if (file == null) return@let false
|
||||||
.any { ext ->
|
if (file.isDirectory) return@let false
|
||||||
it.name.endsWith(ext, ignoreCase = true)
|
if (
|
||||||
|
ExtensionsData.fileExtensions.none { ext ->
|
||||||
|
file.name.endsWith(
|
||||||
|
ext,
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
) {
|
||||||
|
return@let false
|
||||||
|
}
|
||||||
|
|
||||||
|
return@let true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ package ua.acclorite.book_story.ui.book_info
|
||||||
|
|
||||||
import androidx.compose.foundation.focusable
|
import androidx.compose.foundation.focusable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.EditNote
|
import androidx.compose.material.icons.filled.EditNote
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -24,6 +24,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import ua.acclorite.book_story.R
|
import ua.acclorite.book_story.R
|
||||||
import ua.acclorite.book_story.ui.common.components.common.IconButton
|
import ua.acclorite.book_story.ui.common.components.common.IconButton
|
||||||
import ua.acclorite.book_story.ui.common.components.common.StyledText
|
import ua.acclorite.book_story.ui.common.components.common.StyledText
|
||||||
|
import ua.acclorite.book_story.ui.theme.ExpandingTransition
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookInfoDetailsBottomSheetItem(
|
fun BookInfoDetailsBottomSheetItem(
|
||||||
|
|
@ -34,46 +35,52 @@ fun BookInfoDetailsBottomSheetItem(
|
||||||
errorMessage: String? = null,
|
errorMessage: String? = null,
|
||||||
onEdit: () -> Unit = {}
|
onEdit: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
OutlinedTextField(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.weight(1f)
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
.focusable(false),
|
verticalAlignment = Alignment.CenterVertically
|
||||||
value = text,
|
) {
|
||||||
onValueChange = {},
|
OutlinedTextField(
|
||||||
readOnly = true,
|
modifier = Modifier
|
||||||
isError = showError,
|
.weight(1f)
|
||||||
supportingText = if (!showError || errorMessage.isNullOrBlank()) null else {
|
.focusable(false),
|
||||||
{
|
value = text,
|
||||||
StyledText(
|
onValueChange = {},
|
||||||
text = errorMessage,
|
readOnly = true,
|
||||||
style = LocalTextStyle.current.copy(
|
isError = showError,
|
||||||
color = MaterialTheme.colorScheme.error
|
label = {
|
||||||
)
|
StyledText(label)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
label = {
|
|
||||||
StyledText(label)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (editable) {
|
if (editable) {
|
||||||
IconButton(
|
IconButton(
|
||||||
icon = Icons.Default.EditNote,
|
icon = Icons.Default.EditNote,
|
||||||
contentDescription = R.string.edit_content_desc,
|
contentDescription = R.string.edit_content_desc,
|
||||||
disableOnClick = false,
|
disableOnClick = false,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = MaterialTheme.colorScheme.primary,
|
||||||
modifier = Modifier.size(24.dp)
|
modifier = Modifier.size(24.dp)
|
||||||
) {
|
) {
|
||||||
onEdit()
|
onEdit()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExpandingTransition(showError && !errorMessage.isNullOrBlank()) {
|
||||||
|
StyledText(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
.padding(top = 4.dp),
|
||||||
|
text = errorMessage!!,
|
||||||
|
style = MaterialTheme.typography.bodySmall.copy(
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue