[新增] 简单用Compose重写了书籍信息编辑界面
This commit is contained in:
@@ -1,192 +1,47 @@
|
||||
package io.legado.app.ui.book.info.edit
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.ArrayAdapter
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.VMBaseActivity
|
||||
import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.databinding.ActivityBookInfoEditBinding
|
||||
import io.legado.app.help.book.BookHelp
|
||||
import io.legado.app.help.book.addType
|
||||
import io.legado.app.help.book.isAudio
|
||||
import io.legado.app.help.book.isImage
|
||||
import io.legado.app.help.book.isLocal
|
||||
import io.legado.app.help.book.removeType
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.legado.app.base.AppTheme
|
||||
import io.legado.app.base.BaseComposeActivity
|
||||
import io.legado.app.ui.book.changecover.ChangeCoverDialog
|
||||
import io.legado.app.utils.FileUtils
|
||||
import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.SelectImageContract
|
||||
import io.legado.app.utils.externalFiles
|
||||
import io.legado.app.utils.inputStream
|
||||
import io.legado.app.utils.launch
|
||||
import io.legado.app.utils.readUri
|
||||
import io.legado.app.utils.setOnApplyWindowInsetsListenerCompat
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import splitties.init.appCtx
|
||||
import splitties.views.bottomPadding
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class BookInfoEditActivity :
|
||||
VMBaseActivity<ActivityBookInfoEditBinding, BookInfoEditViewModel>(),
|
||||
ChangeCoverDialog.CallBack {
|
||||
class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
|
||||
|
||||
private val selectCover = registerForActivityResult(SelectImageContract()) {
|
||||
it.uri?.let { uri ->
|
||||
coverChangeTo(uri)
|
||||
}
|
||||
}
|
||||
private val viewModel by viewModels<BookInfoEditViewModel>()
|
||||
|
||||
override val binding by viewBinding(ActivityBookInfoEditBinding::inflate)
|
||||
override val viewModel by viewModels<BookInfoEditViewModel>()
|
||||
|
||||
// 从资源获取书籍类型数组
|
||||
private val bookTypes = arrayOf("文本", "音频", "图片")
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
viewModel.bookData.observe(this) { upView(it) }
|
||||
if (viewModel.bookData.value == null) {
|
||||
intent.getStringExtra("bookUrl")?.let {
|
||||
viewModel.loadBook(it)
|
||||
}
|
||||
}
|
||||
initView()
|
||||
initEvent()
|
||||
}
|
||||
|
||||
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.book_info_edit, menu)
|
||||
return super.onCompatCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_save -> saveData()
|
||||
}
|
||||
return super.onCompatOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
binding.root.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->
|
||||
val typeMask = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()
|
||||
val insets = windowInsets.getInsets(typeMask)
|
||||
view.bottomPadding = insets.bottom
|
||||
windowInsets
|
||||
}
|
||||
initAutoCompleteTextView()
|
||||
}
|
||||
|
||||
private fun initAutoCompleteTextView() = binding.run {
|
||||
// 创建适配器
|
||||
val adapter = ArrayAdapter(
|
||||
this@BookInfoEditActivity,
|
||||
android.R.layout.simple_dropdown_item_1line, bookTypes
|
||||
)
|
||||
actvType.setAdapter(adapter)
|
||||
actvType.setOnClickListener {
|
||||
actvType.showDropDown()
|
||||
}
|
||||
actvType.setOnFocusChangeListener { _, hasFocus ->
|
||||
if (hasFocus) {
|
||||
actvType.showDropDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initEvent() = binding.run {
|
||||
tvChangeCover.setOnClickListener {
|
||||
viewModel.bookData.value?.let {
|
||||
showDialogFragment(
|
||||
ChangeCoverDialog(it.name, it.author)
|
||||
@Composable
|
||||
override fun Content() {
|
||||
setContent {
|
||||
AppTheme {
|
||||
BookInfoEditScreen(
|
||||
viewModel = viewModel,
|
||||
onBack = { finish() },
|
||||
onSave = ::saveData
|
||||
)
|
||||
}
|
||||
}
|
||||
tvSelectCover.setOnClickListener {
|
||||
selectCover.launch()
|
||||
}
|
||||
tvRefreshCover.setOnClickListener {
|
||||
viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString()
|
||||
upCover()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
intent.getStringExtra("bookUrl")?.let {
|
||||
viewModel.loadBook(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun upView(book: Book) = binding.run {
|
||||
tieBookName.setText(book.name)
|
||||
tieBookAuthor.setText(book.author)
|
||||
val selectedType = when {
|
||||
book.isImage -> 2
|
||||
book.isAudio -> 1
|
||||
else -> 0
|
||||
}
|
||||
actvType.setText(bookTypes.getOrNull(selectedType) ?: bookTypes[0], false)
|
||||
tieCoverUrl.setText(book.getDisplayCover())
|
||||
tieBookIntro.setText(book.getDisplayIntro())
|
||||
upCover()
|
||||
}
|
||||
|
||||
private fun upCover() {
|
||||
viewModel.book?.let {
|
||||
binding.ivCover.load(it.getDisplayCover(), it.name, it.author, false, it.origin)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveData() = binding.run {
|
||||
val book = viewModel.book ?: return@run
|
||||
val oldBook = book.copy()
|
||||
book.name = tieBookName.text?.toString() ?: ""
|
||||
book.author = tieBookAuthor.text?.toString() ?: ""
|
||||
val local = if (book.isLocal) BookType.local else 0
|
||||
val selectedType = actvType.text.toString()
|
||||
val bookType = when (selectedType) {
|
||||
bookTypes[2] -> BookType.image or local
|
||||
bookTypes[1] -> BookType.audio or local
|
||||
else -> BookType.text or local
|
||||
}
|
||||
book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text)
|
||||
book.addType(bookType)
|
||||
val customCoverUrl = tieCoverUrl.text?.toString()
|
||||
book.customCoverUrl = if (customCoverUrl == book.coverUrl) null else customCoverUrl
|
||||
val customIntro = tieBookIntro.text?.toString()
|
||||
book.customIntro = if (customIntro == book.intro) null else customIntro
|
||||
BookHelp.updateCacheFolder(oldBook, book)
|
||||
viewModel.saveBook(book) {
|
||||
private fun saveData() {
|
||||
viewModel.saveBook {
|
||||
setResult(RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun coverChangeTo(coverUrl: String) {
|
||||
viewModel.book?.customCoverUrl = coverUrl
|
||||
binding.tieCoverUrl.setText(coverUrl)
|
||||
upCover()
|
||||
// 更新封面 URL
|
||||
viewModel.updateCoverUrl(coverUrl)
|
||||
}
|
||||
|
||||
private fun coverChangeTo(uri: Uri) {
|
||||
readUri(uri) { fileDoc, inputStream ->
|
||||
runCatching {
|
||||
inputStream.use {
|
||||
var file = this.externalFiles
|
||||
val suffix = fileDoc.name.substringAfterLast(".")
|
||||
val fileName = uri.inputStream(this).getOrThrow().use {
|
||||
MD5Utils.md5Encode(it) + ".$suffix"
|
||||
}
|
||||
file = FileUtils.createFileIfNotExist(file, "covers", fileName)
|
||||
FileOutputStream(file).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
coverChangeTo(file.absolutePath)
|
||||
}
|
||||
}.onFailure {
|
||||
appCtx.toastOnUi(it.localizedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
package io.legado.app.ui.book.info.edit
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Replay
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.ExposedDropdownMenuAnchorType
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.FilledTonalButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MediumTopAppBar
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.help.book.isAudio
|
||||
import io.legado.app.help.book.isImage
|
||||
import io.legado.app.ui.book.changecover.ChangeCoverDialog
|
||||
import io.legado.app.ui.widget.components.Cover
|
||||
import io.legado.app.utils.SelectImageContract
|
||||
import io.legado.app.utils.launch
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun BookInfoEditScreen(
|
||||
viewModel: BookInfoEditViewModel,
|
||||
onBack: () -> Unit,
|
||||
onSave: () -> Unit
|
||||
) {
|
||||
val book by viewModel.bookData.collectAsStateWithLifecycle()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
MediumTopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.book_info_edit)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
FilledTonalButton(onClick = onSave) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Save,
|
||||
contentDescription = stringResource(id = R.string.action_save)
|
||||
)
|
||||
}
|
||||
},
|
||||
scrollBehavior = scrollBehavior
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
book?.let {
|
||||
BookInfoEditContent(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
book = it,
|
||||
viewModel = viewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BookInfoEditContent(
|
||||
modifier: Modifier = Modifier,
|
||||
book: Book,
|
||||
viewModel: BookInfoEditViewModel
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var name by remember(book.name) { mutableStateOf(book.name) }
|
||||
var author by remember(book.author) { mutableStateOf(book.author) }
|
||||
var coverUrl by remember(book.customCoverUrl) { mutableStateOf(book.getDisplayCover()) }
|
||||
var intro by remember(book.customIntro) { mutableStateOf(book.getDisplayIntro()) }
|
||||
var remark by remember(book.remark) { mutableStateOf(book.remark) }
|
||||
|
||||
val bookTypes = arrayOf("文本", "音频", "图片")
|
||||
val selectedTypeIndex = when {
|
||||
book.isImage -> 2
|
||||
book.isAudio -> 1
|
||||
else -> 0
|
||||
}
|
||||
var selectedType by remember { mutableStateOf(bookTypes[selectedTypeIndex]) }
|
||||
|
||||
val selectCover = rememberLauncherForActivityResult(SelectImageContract()) {
|
||||
it.uri?.let { uri ->
|
||||
viewModel.coverChangeTo(context, uri) { newCoverUrl ->
|
||||
coverUrl = newCoverUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(coverUrl) {
|
||||
viewModel.book?.customCoverUrl = coverUrl
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Cover(
|
||||
path = coverUrl,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.3f)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
FilledTonalButton(onClick = {
|
||||
(context as? BookInfoEditActivity)?.showDialogFragment(ChangeCoverDialog(book.name, book.author))
|
||||
}) {
|
||||
Text("网络搜索")
|
||||
}
|
||||
FilledTonalButton(onClick = { selectCover.launch() }) {
|
||||
Text("本地选择")
|
||||
}
|
||||
FilledTonalButton(onClick = { coverUrl = book.coverUrl ?: "" }) {
|
||||
Icon(
|
||||
Icons.Default.Replay,
|
||||
contentDescription = stringResource(id = R.string.default_cover)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = name,
|
||||
onValueChange = { name = it },
|
||||
label = { Text("书名") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = author,
|
||||
onValueChange = { author = it },
|
||||
label = { Text("作者") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BookTypeDropdown(
|
||||
bookTypes = bookTypes,
|
||||
selectedType = selectedType,
|
||||
onTypeSelected = { selectedType = it }
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = coverUrl ?: "",
|
||||
onValueChange = { coverUrl = it },
|
||||
label = { Text("封面链接") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = intro ?: "",
|
||||
onValueChange = { intro = it },
|
||||
label = { Text("简介") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 5
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = remark ?: "",
|
||||
onValueChange = { remark = it },
|
||||
label = { Text("备注") },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(name, author, selectedType, coverUrl, intro, remark) {
|
||||
viewModel.updateBookState(name, author, selectedType, bookTypes, coverUrl, intro, remark)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BookTypeDropdown(
|
||||
bookTypes: Array<String>,
|
||||
selectedType: String,
|
||||
onTypeSelected: (String) -> Unit
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = expanded,
|
||||
onExpandedChange = { expanded = !expanded }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.fillMaxWidth().menuAnchor(
|
||||
ExposedDropdownMenuAnchorType.PrimaryEditable,
|
||||
true
|
||||
),
|
||||
readOnly = true,
|
||||
value = selectedType,
|
||||
onValueChange = {},
|
||||
label = { Text("书籍类型") },
|
||||
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
bookTypes.forEach { selectionOption ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(selectionOption) },
|
||||
onClick = {
|
||||
onTypeSelected(selectionOption)
|
||||
expanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +1,50 @@
|
||||
package io.legado.app.ui.book.info.edit
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteConstraintException
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import android.net.Uri
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.Book
|
||||
import io.legado.app.help.book.BookHelp
|
||||
import io.legado.app.help.book.addType
|
||||
import io.legado.app.help.book.isLocal
|
||||
import io.legado.app.help.book.removeType
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.utils.FileUtils
|
||||
import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.inputStream
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
class BookInfoEditViewModel(application: Application) : BaseViewModel(application) {
|
||||
var book: Book? = null
|
||||
val bookData = MutableLiveData<Book>()
|
||||
private val _bookData = MutableStateFlow<Book?>(null)
|
||||
val bookData: StateFlow<Book?> = _bookData.asStateFlow()
|
||||
|
||||
fun loadBook(bookUrl: String) {
|
||||
execute {
|
||||
book = appDb.bookDao.getBook(bookUrl)
|
||||
book?.let {
|
||||
bookData.postValue(it)
|
||||
_bookData.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveBook(book: Book, success: (() -> Unit)?) {
|
||||
fun saveBook(success: (() -> Unit)?) {
|
||||
execute {
|
||||
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
||||
ReadBook.book = book
|
||||
book?.let { book ->
|
||||
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
||||
ReadBook.book = book
|
||||
}
|
||||
appDb.bookDao.update(book)
|
||||
}
|
||||
appDb.bookDao.update(book)
|
||||
}.onSuccess {
|
||||
success?.invoke()
|
||||
}.onError {
|
||||
@@ -38,4 +55,64 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateBookState(
|
||||
name: String,
|
||||
author: String,
|
||||
selectedType: String,
|
||||
bookTypes: Array<String>,
|
||||
coverUrl: String?,
|
||||
intro: String?,
|
||||
remark: String?
|
||||
) {
|
||||
book?.let { book ->
|
||||
val oldBook = book.copy()
|
||||
book.name = name
|
||||
book.author = author
|
||||
book.remark = remark
|
||||
val local = if (book.isLocal) BookType.local else 0
|
||||
val bookType = when (selectedType) {
|
||||
bookTypes[2] -> BookType.image or local
|
||||
bookTypes[1] -> BookType.audio or local
|
||||
else -> BookType.text or local
|
||||
}
|
||||
book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text)
|
||||
book.addType(bookType)
|
||||
book.customCoverUrl = if (coverUrl == book.coverUrl) null else coverUrl
|
||||
book.customIntro = if (intro == book.intro) null else intro
|
||||
BookHelp.updateCacheFolder(oldBook, book)
|
||||
}
|
||||
}
|
||||
|
||||
fun coverChangeTo(context: Context, uri: Uri, onFinally: (coverUrl: String) -> Unit) {
|
||||
execute {
|
||||
runCatching {
|
||||
context.externalCacheDir?.let { externalCacheDir ->
|
||||
val file = File(externalCacheDir, "covers")
|
||||
val suffix = context.contentResolver.getType(uri)?.substringAfterLast("/") ?: "jpg"
|
||||
val fileName = uri.inputStream(context).getOrThrow().use { MD5Utils.md5Encode(it) } + ".$suffix"
|
||||
val coverFile = FileUtils.createFileIfNotExist(file, fileName)
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
FileOutputStream(coverFile).use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
onFinally(coverFile.absolutePath)
|
||||
} ?: run {
|
||||
AppLog.put("External cache directory is null", Throwable("Null directory"), true)
|
||||
}
|
||||
}.onFailure {
|
||||
AppLog.put("书籍封面保存失败\n$it", it, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun updateCoverUrl(coverUrl: String) {
|
||||
book?.let {
|
||||
val updatedBook = it.copy(customCoverUrl = coverUrl)
|
||||
_bookData.value = updatedBook
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user