[修复] 修复一些问题

This commit is contained in:
HapeLee
2025-12-20 04:49:48 +08:00
parent 060ec9ce1a
commit df63206bb7
6 changed files with 146 additions and 129 deletions
@@ -16,13 +16,11 @@ import android.graphics.drawable.TransitionDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.method.ScrollingMovementMethod
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.CheckBox
import android.widget.LinearLayout
import androidx.activity.OnBackPressedCallback
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.core.app.ActivityCompat
@@ -54,7 +52,6 @@ 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.isLocalTxt
import io.legado.app.help.book.isNotShelf
import io.legado.app.help.book.isWebFile
import io.legado.app.help.book.removeType
import io.legado.app.help.config.AppConfig
@@ -62,7 +59,6 @@ import io.legado.app.help.config.LocalConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.selector
import io.legado.app.model.BookCover
import io.legado.app.model.ReadBook
import io.legado.app.model.remote.RemoteBookWebDav
import io.legado.app.ui.about.AppLogDialog
import io.legado.app.ui.book.audio.AudioPlayActivity
@@ -158,7 +154,6 @@ class BookInfoActivity :
viewModel.refreshBook(book)
}
}
private lateinit var backCallback: OnBackPressedCallback
private var surfaceFinalColor: Int = 0
private var surfaceContainerFinalColor: Int = 0
private var secondaryFinalColor: Int = 0
@@ -178,7 +173,6 @@ class BookInfoActivity :
@SuppressLint("PrivateResource")
override fun onCreate(savedInstanceState: Bundle?) {
postponeEnterTransition()
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
val transform = MaterialContainerTransform().apply {
@@ -187,7 +181,6 @@ class BookInfoActivity :
}
window.sharedElementEnterTransition = transform
window.sharedElementReturnTransition = transform
window.sharedElementsUseOverlay = false
super.onCreate(savedInstanceState)
setupBackCallback()
surfaceFinalColor =
@@ -389,7 +382,7 @@ class BookInfoActivity :
private fun setupBackCallback() {
onBackPressedDispatcher.addCallback(this) {
if (!viewModel.inBookshelf) {
if (!viewModel.inBookshelf && AppConfig.showAddToShelfAlert) {
viewModel.getBook()?.let { book ->
alert(title = getString(R.string.add_to_bookshelf)) {
setMessage(getString(R.string.check_add_bookshelf, book.name))
@@ -442,7 +435,7 @@ class BookInfoActivity :
addColorScheme(binding.ivCover.drawable)
tvName.text = book.name
tvRemark.text = book.remark
if (book.remark == null)
if (book.remark.isNullOrEmpty())
cdRemark.gone()
else
cdRemark.visible()
@@ -19,7 +19,12 @@ class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
BookInfoEditScreen(
viewModel = viewModel,
onBack = { finish() },
onSave = ::saveData
onSave = {
viewModel.save {
setResult(RESULT_OK)
finish()
}
}
)
}
}
@@ -32,16 +37,9 @@ class BookInfoEditActivity : BaseComposeActivity(), ChangeCoverDialog.CallBack {
}
}
private fun saveData() {
viewModel.saveBook {
setResult(RESULT_OK)
finish()
}
}
override fun coverChangeTo(coverUrl: String) {
// 更新封面 URL
viewModel.updateCoverUrl(coverUrl)
viewModel.onCoverUrlChange(coverUrl)
}
}
}
@@ -13,6 +13,8 @@ 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.FolderOpen
import androidx.compose.material.icons.filled.ImageSearch
import androidx.compose.material.icons.filled.Replay
import androidx.compose.material.icons.filled.Save
import androidx.compose.material3.DropdownMenuItem
@@ -21,16 +23,16 @@ 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.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults
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
@@ -43,9 +45,6 @@ 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
@@ -59,7 +58,7 @@ fun BookInfoEditScreen(
onBack: () -> Unit,
onSave: () -> Unit
) {
val book by viewModel.bookData.collectAsStateWithLifecycle()
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
Scaffold(
@@ -76,7 +75,9 @@ fun BookInfoEditScreen(
}
},
actions = {
FilledTonalButton(onClick = onSave) {
FilledTonalIconButton(
onClick = { viewModel.save(onSave) },
shapes = IconButtonDefaults.shapes()) {
Icon(
imageVector = Icons.Default.Save,
contentDescription = stringResource(id = R.string.action_save)
@@ -87,13 +88,13 @@ fun BookInfoEditScreen(
)
},
content = { paddingValues ->
book?.let {
uiState.book?.let {
BookInfoEditContent(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.verticalScroll(rememberScrollState()),
book = it,
uiState = uiState,
viewModel = viewModel
)
}
@@ -101,59 +102,56 @@ fun BookInfoEditScreen(
)
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun BookInfoEditContent(
modifier: Modifier = Modifier,
book: Book,
uiState: BookInfoEditUiState,
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
}
viewModel.coverChangeTo(context, uri)
}
}
LaunchedEffect(coverUrl) {
viewModel.book?.customCoverUrl = coverUrl
}
Column(
modifier = modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Cover(
path = coverUrl,
path = uiState.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("网络搜索")
FilledTonalIconButton(
onClick = {
(context as? BookInfoEditActivity)?.showDialogFragment(
ChangeCoverDialog(
uiState.name,
uiState.author
)
)
},
shapes = IconButtonDefaults.shapes()) {
Icon(
Icons.Default.ImageSearch,
contentDescription = stringResource(id = R.string.default_cover)
)
}
FilledTonalButton(onClick = { selectCover.launch() }) {
Text("本地选择")
FilledTonalIconButton(onClick = { selectCover.launch() },
shapes = IconButtonDefaults.shapes()) {
Icon(
Icons.Default.FolderOpen,
contentDescription = stringResource(id = R.string.default_cover)
)
}
FilledTonalButton(onClick = { coverUrl = book.coverUrl ?: "" }) {
FilledTonalIconButton(onClick = { viewModel.resetCover() },
shapes = IconButtonDefaults.shapes()) {
Icon(
Icons.Default.Replay,
contentDescription = stringResource(id = R.string.default_cover)
@@ -162,57 +160,53 @@ fun BookInfoEditContent(
}
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = name,
onValueChange = { name = it },
value = uiState.name,
onValueChange = { viewModel.onNameChange(it) },
label = { Text("书名") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = author,
onValueChange = { author = it },
value = uiState.author,
onValueChange = { viewModel.onAuthorChange(it) },
label = { Text("作者") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
BookTypeDropdown(
bookTypes = bookTypes,
selectedType = selectedType,
onTypeSelected = { selectedType = it }
bookTypes = uiState.bookTypes,
selectedType = uiState.selectedType,
onTypeSelected = { viewModel.onBookTypeChange(it) }
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = coverUrl ?: "",
onValueChange = { coverUrl = it },
value = uiState.coverUrl ?: "",
onValueChange = { viewModel.onCoverUrlChange(it) },
label = { Text("封面链接") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = intro ?: "",
onValueChange = { intro = it },
value = uiState.intro ?: "",
onValueChange = { viewModel.onIntroChange(it) },
label = { Text("简介") },
modifier = Modifier.fillMaxWidth(),
maxLines = 5
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = remark ?: "",
onValueChange = { remark = it },
value = uiState.remark ?: "",
onValueChange = { viewModel.onRemarkChange(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>,
bookTypes: List<String>,
selectedType: String,
onTypeSelected: (String) -> Unit
) {
@@ -223,10 +217,12 @@ fun BookTypeDropdown(
onExpandedChange = { expanded = !expanded }
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth().menuAnchor(
ExposedDropdownMenuAnchorType.PrimaryEditable,
true
),
modifier = Modifier
.fillMaxWidth()
.menuAnchor(
ExposedDropdownMenuAnchorType.PrimaryEditable,
true
),
readOnly = true,
value = selectedType,
onValueChange = {},
@@ -11,6 +11,8 @@ 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.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.model.ReadBook
@@ -23,30 +25,99 @@ import kotlinx.coroutines.flow.asStateFlow
import java.io.File
import java.io.FileOutputStream
data class BookInfoEditUiState(
val name: String = "",
val author: String = "",
val coverUrl: String? = null,
val intro: String? = null,
val remark: String? = null,
val selectedType: String = "文本",
val bookTypes: List<String> = listOf("文本", "音频", "图片"),
val book: Book? = null,
)
class BookInfoEditViewModel(application: Application) : BaseViewModel(application) {
var book: Book? = null
private val _bookData = MutableStateFlow<Book?>(null)
val bookData: StateFlow<Book?> = _bookData.asStateFlow()
private val _uiState = MutableStateFlow(BookInfoEditUiState())
val uiState: StateFlow<BookInfoEditUiState> = _uiState.asStateFlow()
fun loadBook(bookUrl: String) {
execute {
book = appDb.bookDao.getBook(bookUrl)
book?.let {
_bookData.value = it
val selectedTypeIndex = when {
it.isImage -> 2
it.isAudio -> 1
else -> 0
}
_uiState.value = BookInfoEditUiState(
name = it.name,
author = it.author,
coverUrl = it.getDisplayCover(),
intro = it.getDisplayIntro(),
remark = it.remark,
selectedType = _uiState.value.bookTypes[selectedTypeIndex],
book = it
)
}
}
}
fun saveBook(success: (() -> Unit)?) {
fun onNameChange(name: String) {
_uiState.value = _uiState.value.copy(name = name)
}
fun onAuthorChange(author: String) {
_uiState.value = _uiState.value.copy(author = author)
}
fun onCoverUrlChange(coverUrl: String) {
_uiState.value = _uiState.value.copy(coverUrl = coverUrl)
}
fun onIntroChange(intro: String) {
_uiState.value = _uiState.value.copy(intro = intro)
}
fun onRemarkChange(remark: String) {
_uiState.value = _uiState.value.copy(remark = remark)
}
fun onBookTypeChange(bookType: String) {
_uiState.value = _uiState.value.copy(selectedType = bookType)
}
fun resetCover() {
_uiState.value = _uiState.value.copy(coverUrl = book?.coverUrl ?: "")
}
fun save(onSuccess: () -> Unit) {
execute {
val currentState = _uiState.value
book?.let { book ->
val oldBook = book.copy()
book.name = currentState.name
book.author = currentState.author
book.remark = currentState.remark
val local = if (book.isLocal) BookType.local else 0
val bookType = when (currentState.selectedType) {
currentState.bookTypes[2] -> BookType.image or local
currentState.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 (currentState.coverUrl == book.coverUrl) null else currentState.coverUrl
book.customIntro = if (currentState.intro == book.intro) null else currentState.intro
BookHelp.updateCacheFolder(oldBook, book)
if (ReadBook.book?.bookUrl == book.bookUrl) {
ReadBook.book = book
}
appDb.bookDao.update(book)
}
}.onSuccess {
success?.invoke()
onSuccess.invoke()
}.onError {
if (it is SQLiteConstraintException) {
AppLog.put("书籍信息保存失败,存在相同书名作者书籍\n$it", it, true)
@@ -56,35 +127,7 @@ 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) {
fun coverChangeTo(context: Context, uri: Uri) {
execute {
runCatching {
context.externalCacheDir?.let { externalCacheDir ->
@@ -97,7 +140,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
inputStream.copyTo(outputStream)
}
}
onFinally(coverFile.absolutePath)
_uiState.value = _uiState.value.copy(coverUrl = coverFile.absolutePath)
} ?: run {
AppLog.put("External cache directory is null", Throwable("Null directory"), true)
}
@@ -106,13 +149,4 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
}
}
}
fun updateCoverUrl(coverUrl: String) {
book?.let {
val updatedBook = it.copy(customCoverUrl = coverUrl)
_bookData.value = updatedBook
}
}
}
@@ -286,8 +286,6 @@ class ReadBookActivity : BaseReadBookActivity(),
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
val transform = MaterialContainerTransform().apply {