[新增] 简单用Compose重写了书籍信息编辑界面
This commit is contained in:
3 files changed
+360
-177
No files matched your search
@@ -1,192 +1,47 @@
|
|||||||
package io.legado.app.ui.book.info.edit
|
package io.legado.app.ui.book.info.edit
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import androidx.activity.compose.setContent
|
||||||
import android.view.MenuItem
|
|
||||||
import android.widget.ArrayAdapter
|
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.compose.runtime.Composable
|
||||||
import io.legado.app.R
|
import io.legado.app.base.AppTheme
|
||||||
import io.legado.app.base.VMBaseActivity
|
import io.legado.app.base.BaseComposeActivity
|
||||||
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 io.legado.app.ui.book.changecover.ChangeCoverDialog
|
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 :
|
class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
|
||||||
VMBaseActivity<ActivityBookInfoEditBinding, BookInfoEditViewModel>(),
|
|
||||||
ChangeCoverDialog.CallBack {
|
|
||||||
|
|
||||||
private val selectCover = registerForActivityResult(SelectImageContract()) {
|
private val viewModel by viewModels<BookInfoEditViewModel>()
|
||||||
it.uri?.let { uri ->
|
|
||||||
coverChangeTo(uri)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override val binding by viewBinding(ActivityBookInfoEditBinding::inflate)
|
@Composable
|
||||||
override val viewModel by viewModels<BookInfoEditViewModel>()
|
override fun Content() {
|
||||||
|
setContent {
|
||||||
// 从资源获取书籍类型数组
|
AppTheme {
|
||||||
private val bookTypes = arrayOf("文本", "音频", "图片")
|
BookInfoEditScreen(
|
||||||
|
viewModel = viewModel,
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
onBack = { finish() },
|
||||||
super.onCreate(savedInstanceState)
|
onSave = ::saveData
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tvSelectCover.setOnClickListener {
|
}
|
||||||
selectCover.launch()
|
|
||||||
}
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
tvRefreshCover.setOnClickListener {
|
super.onCreate(savedInstanceState)
|
||||||
viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString()
|
intent.getStringExtra("bookUrl")?.let {
|
||||||
upCover()
|
viewModel.loadBook(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upView(book: Book) = binding.run {
|
private fun saveData() {
|
||||||
tieBookName.setText(book.name)
|
viewModel.saveBook {
|
||||||
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) {
|
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun coverChangeTo(coverUrl: String) {
|
override fun coverChangeTo(coverUrl: String) {
|
||||||
viewModel.book?.customCoverUrl = coverUrl
|
// 更新封面 URL
|
||||||
binding.tieCoverUrl.setText(coverUrl)
|
viewModel.updateCoverUrl(coverUrl)
|
||||||
upCover()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
package io.legado.app.ui.book.info.edit
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
import android.database.sqlite.SQLiteConstraintException
|
import android.database.sqlite.SQLiteConstraintException
|
||||||
import androidx.lifecycle.MutableLiveData
|
import android.net.Uri
|
||||||
import io.legado.app.base.BaseViewModel
|
import io.legado.app.base.BaseViewModel
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.constant.BookType
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import io.legado.app.data.entities.Book
|
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.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) {
|
class BookInfoEditViewModel(application: Application) : BaseViewModel(application) {
|
||||||
var book: Book? = null
|
var book: Book? = null
|
||||||
val bookData = MutableLiveData<Book>()
|
private val _bookData = MutableStateFlow<Book?>(null)
|
||||||
|
val bookData: StateFlow<Book?> = _bookData.asStateFlow()
|
||||||
|
|
||||||
fun loadBook(bookUrl: String) {
|
fun loadBook(bookUrl: String) {
|
||||||
execute {
|
execute {
|
||||||
book = appDb.bookDao.getBook(bookUrl)
|
book = appDb.bookDao.getBook(bookUrl)
|
||||||
book?.let {
|
book?.let {
|
||||||
bookData.postValue(it)
|
_bookData.value = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveBook(book: Book, success: (() -> Unit)?) {
|
fun saveBook(success: (() -> Unit)?) {
|
||||||
execute {
|
execute {
|
||||||
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
book?.let { book ->
|
||||||
ReadBook.book = book
|
if (ReadBook.book?.bookUrl == book.bookUrl) {
|
||||||
|
ReadBook.book = book
|
||||||
|
}
|
||||||
|
appDb.bookDao.update(book)
|
||||||
}
|
}
|
||||||
appDb.bookDao.update(book)
|
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
success?.invoke()
|
success?.invoke()
|
||||||
}.onError {
|
}.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