Просмотр исходного кода

[优化] 优化书架的性能与稳定性

HapeLee 7 месяцев назад
Родитель
Сommit
5cd55758a1

+ 8 - 5
app/src/main/java/io/legado/app/model/BookCover.kt

@@ -115,15 +115,18 @@ object BookCover {
         }.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null))
     }
 
-    fun getRandomDefaultDrawable(seed: Any? = null): Drawable {
+    fun getRandomDefaultPath(seed: Any? = null): String? {
         val isNightTheme = AppConfig.isNightTheme
         val key = if (isNightTheme) PreferKey.defaultCoverDark else PreferKey.defaultCover
         val paths = appCtx.getPrefString(key)?.split(",")?.filter { it.isNotBlank() }
-        if (paths.isNullOrEmpty()) {
-            return appCtx.resources.getDrawable(R.drawable.image_cover_default, null)
-        }
+        if (paths.isNullOrEmpty()) return null
         val random = if (seed != null) Random(seed.hashCode()) else Random
-        val randomPath = paths[random.nextInt(paths.size)]
+        return paths[random.nextInt(paths.size)]
+    }
+
+    fun getRandomDefaultDrawable(seed: Any? = null): Drawable {
+        val randomPath = getRandomDefaultPath(seed)
+            ?: return appCtx.resources.getDrawable(R.drawable.image_cover_default, null)
         return kotlin.runCatching {
             BitmapUtils.decodeBitmap(randomPath, 600, 900)!!.toDrawable(appCtx.resources)
         }.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null))

+ 2 - 0
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt

@@ -1,6 +1,7 @@
 package io.legado.app.ui.main.bookshelf
 
 import android.content.res.Configuration
+import androidx.compose.animation.animateContentSize
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -48,6 +49,7 @@ fun BookshelfConfigSheet(
             modifier = Modifier
                 .fillMaxWidth()
                 .verticalScroll(rememberScrollState())
+                .animateContentSize()
                 .padding(horizontal = 16.dp)
                 .padding(bottom = 32.dp),
             verticalArrangement = Arrangement.spacedBy(8.dp)

+ 96 - 37
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -1,8 +1,10 @@
 package io.legado.app.ui.main.bookshelf
 
+import android.content.ClipData
 import android.content.res.Configuration
 import androidx.activity.compose.BackHandler
 import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -12,6 +14,7 @@ import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.grid.itemsIndexed
 import androidx.compose.foundation.pager.HorizontalPager
 import androidx.compose.foundation.pager.rememberPagerState
 import androidx.compose.material.icons.Icons
@@ -32,6 +35,8 @@ import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
 import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.PrimaryScrollableTabRow
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.SnackbarResult
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Tab
 import androidx.compose.material3.Text
@@ -49,6 +54,8 @@ import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ClipEntry
+import androidx.compose.ui.platform.LocalClipboard
 import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
@@ -56,6 +63,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import io.legado.app.R
+import io.legado.app.base.BaseRuleEvent
 import io.legado.app.data.entities.Book
 import io.legado.app.ui.about.AppLogSheet
 import io.legado.app.ui.book.cache.CacheActivity
@@ -64,7 +72,6 @@ import io.legado.app.ui.book.import.remote.RemoteBookActivity
 import io.legado.app.ui.book.manage.BookshelfManageActivity
 import io.legado.app.ui.book.search.SearchActivity
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
-import io.legado.app.ui.file.HandleFileContract
 import io.legado.app.ui.widget.components.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
 import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
@@ -75,6 +82,7 @@ import io.legado.app.utils.readText
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.toastOnUi
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 import org.koin.androidx.compose.koinViewModel
 
@@ -94,38 +102,79 @@ fun BookshelfScreen(
 
     var showAddUrlDialog by remember { mutableStateOf(false) }
     var showImportSheet by remember { mutableStateOf(false) }
+    var showExportSheet by remember { mutableStateOf(false) }
     var showConfigSheet by remember { mutableStateOf(false) }
     var showGroupManageSheet by remember { mutableStateOf(false) }
     var showLogSheet by remember { mutableStateOf(false) }
 
-    val importLauncher = rememberLauncherForActivityResult(HandleFileContract()) {
-        runCatching {
-            it.uri?.readText(context)?.let { text ->
-                val groupId = uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
-                viewModel.importBookshelf(text, groupId)
+    val clipboardManager = LocalClipboard.current
+    val snackbarHostState = remember { SnackbarHostState() }
+
+    LaunchedEffect(Unit) {
+        viewModel.events.collect { event ->
+            when (event) {
+                is BaseRuleEvent.ShowSnackbar -> {
+                    val result = snackbarHostState.showSnackbar(
+                        message = event.message,
+                        actionLabel = event.actionLabel,
+                        withDismissAction = true
+                    )
+                    if (result == SnackbarResult.ActionPerformed && event.url != null) {
+                        clipboardManager.setClipEntry(
+                            ClipEntry(
+                                ClipData.newPlainText(
+                                    "url",
+                                    event.url
+                                )
+                            )
+                        )
+                    }
+                }
             }
-        }.onFailure {
-            context.toastOnUi(it.localizedMessage ?: "ERROR")
         }
     }
 
+    val importLauncher = rememberLauncherForActivityResult(
+        contract = ActivityResultContracts.OpenDocument(),
+        onResult = { uri ->
+            uri?.let {
+                runCatching {
+                    val text = it.readText(context)
+                    val groupId =
+                        uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
+                    viewModel.importBookshelf(text, groupId)
+                }.onFailure {
+                    context.toastOnUi(it.localizedMessage ?: "ERROR")
+                }
+            }
+        }
+    )
+
+    val exportLauncher = rememberLauncherForActivityResult(
+        contract = ActivityResultContracts.CreateDocument("application/json"),
+        onResult = { uri ->
+            uri?.let { viewModel.exportToUri(it, uiState.items) }
+        }
+    )
+
     val pagerState = rememberPagerState(
         initialPage = uiState.selectedGroupIndex,
         pageCount = { uiState.groups.size }
     )
 
     LaunchedEffect(pagerState) {
-        snapshotFlow { pagerState.currentPage }.collect { page ->
-            if (uiState.groups.isNotEmpty() && page < uiState.groups.size) {
-                viewModel.changeGroup(uiState.groups[page].groupId)
+        snapshotFlow { pagerState.currentPage }
+            .distinctUntilChanged()
+            .collect { page ->
+                if (uiState.groups.isNotEmpty() && page in uiState.groups.indices) {
+                    val targetGroupId = uiState.groups[page].groupId
+                    val currentGroupId =
+                        uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId
+                    if (currentGroupId != targetGroupId) {
+                        viewModel.changeGroup(targetGroupId)
+                    }
+                }
             }
-        }
-    }
-
-    LaunchedEffect(uiState.selectedGroupIndex) {
-        if (uiState.selectedGroupIndex != pagerState.currentPage && uiState.selectedGroupIndex < pagerState.pageCount) {
-            pagerState.scrollToPage(uiState.selectedGroupIndex)
-        }
     }
 
     val bookGroupStyle = BookshelfConfig.bookGroupStyle
@@ -223,6 +272,7 @@ fun BookshelfScreen(
             RoundDropdownMenuItem(
                 text = { Text(stringResource(R.string.export_bookshelf)) },
                 onClick = {
+                    showExportSheet = true
                     dismiss()
                 },
                 leadingIcon = { Icon(Icons.Default.ImportExport, null) }
@@ -241,12 +291,12 @@ fun BookshelfScreen(
                 leadingIcon = { Icon(Icons.Default.History, null) }
             )
         },
+        snackbarHostState = snackbarHostState,
         bottomContent = if (bookGroupStyle == 0) {
             {
                 if (uiState.groups.isNotEmpty()) {
-                    val selectedTabIndex = remember(pagerState.currentPage, uiState.groups.size) {
+                    val selectedTabIndex =
                         pagerState.currentPage.coerceIn(0, uiState.groups.size - 1)
-                    }
                     PrimaryScrollableTabRow(
                         selectedTabIndex = selectedTabIndex,
                         edgePadding = 0.dp,
@@ -312,7 +362,7 @@ fun BookshelfScreen(
                     horizontalArrangement = Arrangement.spacedBy(if (bookshelfLayoutMode != 0) 8.dp else 0.dp),
                     showFastScroll = BookshelfConfig.showBookshelfFastScroller
                 ) {
-                    items(uiState.groups) { group ->
+                    itemsIndexed(uiState.groups, key = { _, it -> it.groupId }) { index, group ->
                         if (bookshelfLayoutMode == 0) {
                             BookGroupItemList(
                                 group = group,
@@ -322,11 +372,8 @@ fun BookshelfScreen(
                                 titleCenter = BookshelfConfig.bookshelfTitleCenter,
                                 titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
                                 onClick = {
-                                    val index = uiState.groups.indexOf(group)
-                                    if (index != -1) {
-                                        scope.launch { pagerState.scrollToPage(index) }
-                                        isInFolderRoot = false
-                                    }
+                                    scope.launch { pagerState.scrollToPage(index) }
+                                    isInFolderRoot = false
                                 },
                                 onLongClick = { showGroupManageSheet = true }
                             )
@@ -340,11 +387,8 @@ fun BookshelfScreen(
                                 titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
                                 coverShadow = BookshelfConfig.bookshelfCoverShadow,
                                 onClick = {
-                                    val index = uiState.groups.indexOf(group)
-                                    if (index != -1) {
-                                        scope.launch { pagerState.scrollToPage(index) }
-                                        isInFolderRoot = false
-                                    }
+                                    scope.launch { pagerState.scrollToPage(index) }
+                                    isInFolderRoot = false
                                 },
                                 onLongClick = { showGroupManageSheet = true }
                             )
@@ -360,8 +404,10 @@ fun BookshelfScreen(
                 ) { pageIndex ->
                     val group = uiState.groups.getOrNull(pageIndex)
                     if (group != null) {
-                        val books by viewModel.getBooksFlow(group.groupId)
-                            .collectAsState(emptyList())
+                        val booksFlow = remember(group.groupId) {
+                            viewModel.getBooksFlow(group.groupId)
+                        }
+                        val books by booksFlow.collectAsState(emptyList())
                         BookshelfPage(
                             paddingValues = paddingValues,
                             books = books,
@@ -409,15 +455,28 @@ fun BookshelfScreen(
             onDismissRequest = { showImportSheet = false },
             title = stringResource(R.string.import_bookshelf),
             onSelectSysFile = { types ->
-                importLauncher.launch {
-                    mode = HandleFileContract.FILE
-                    allowExtensions = types
-                }
+                importLauncher.launch(types)
                 showImportSheet = false
             },
             onManualInput = {
                 showAddUrlDialog = true
                 showImportSheet = false
+            },
+            allowExtensions = arrayOf("json", "txt")
+        )
+    }
+
+    if (showExportSheet) {
+        FilePickerSheet(
+            onDismissRequest = { showExportSheet = false },
+            title = stringResource(R.string.export_bookshelf),
+            onSelectSysDir = {
+                showExportSheet = false
+                exportLauncher.launch("bookshelf.json")
+            },
+            onUpload = {
+                showExportSheet = false
+                viewModel.uploadBookshelf(uiState.items)
             }
         )
     }

+ 68 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -1,10 +1,12 @@
 package io.legado.app.ui.main.bookshelf
 
 import android.app.Application
+import android.net.Uri
 import androidx.compose.runtime.snapshotFlow
 import androidx.lifecycle.viewModelScope
 import com.google.gson.stream.JsonWriter
 import io.legado.app.R
+import io.legado.app.base.BaseRuleEvent
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppConst
 import io.legado.app.constant.AppLog
@@ -16,6 +18,7 @@ import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.repository.BookGroupRepository
+import io.legado.app.data.repository.UploadRepository
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.addType
@@ -51,6 +54,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.ensureActive
@@ -69,11 +73,13 @@ import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onCompletion
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.sync.Semaphore
 import kotlinx.coroutines.sync.withPermit
+import kotlinx.coroutines.withContext
 import java.io.File
 import java.io.FileOutputStream
 import java.io.OutputStreamWriter
@@ -85,7 +91,8 @@ import kotlin.math.min
 
 class BookshelfViewModel(
     application: Application,
-    private val bookGroupRepository: BookGroupRepository
+    private val bookGroupRepository: BookGroupRepository,
+    private val uploadRepository: UploadRepository
 ) : BaseViewModel(application) {
     var addBookJob: Coroutine<*>? = null
 
@@ -107,6 +114,9 @@ class BookshelfViewModel(
 
     val scrollTrigger = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
+    protected val _eventChannel = Channel<BaseRuleEvent>()
+    val events = _eventChannel.receiveAsFlow()
+
     val groupsFlow: StateFlow<List<BookGroup>> = bookGroupRepository.flowShow()
         .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
 
@@ -512,6 +522,63 @@ class BookshelfViewModel(
         }
     }
 
+    fun exportToUri(uri: Uri, books: List<Book>) {
+        execute {
+            context.contentResolver.openOutputStream(uri)?.use { out ->
+                val writer = JsonWriter(OutputStreamWriter(out, "UTF-8"))
+                writer.setIndent("  ")
+                writer.beginArray()
+                books.forEach {
+                    val bookMap = hashMapOf<String, String?>()
+                    bookMap["name"] = it.name
+                    bookMap["author"] = it.author
+                    bookMap["intro"] = it.getDisplayIntro()
+                    GSON.toJson(bookMap, bookMap::class.java, writer)
+                }
+                writer.endArray()
+                writer.close()
+            }
+        }.onSuccess {
+            _eventChannel.trySend(BaseRuleEvent.ShowSnackbar("导出成功"))
+        }.onError {
+            _eventChannel.trySend(BaseRuleEvent.ShowSnackbar("导出失败\n${it.localizedMessage}"))
+        }
+    }
+
+    fun uploadBookshelf(books: List<Book>) {
+        execute {
+            val json = withContext(Dispatchers.Default) {
+                val list = books.map {
+                    val bookMap = hashMapOf<String, String?>()
+                    bookMap["name"] = it.name
+                    bookMap["author"] = it.author
+                    bookMap["intro"] = it.getDisplayIntro()
+                    bookMap
+                }
+                GSON.toJson(list)
+            }
+            uploadRepository.upload(
+                fileName = "bookshelf.json",
+                file = json,
+                contentType = "application/json"
+            )
+        }.onSuccess { url ->
+            _eventChannel.trySend(
+                BaseRuleEvent.ShowSnackbar(
+                    message = "上传成功: $url",
+                    actionLabel = "复制链接",
+                    url = url
+                )
+            )
+        }.onError {
+            _eventChannel.trySend(
+                BaseRuleEvent.ShowSnackbar(
+                    message = "上传失败: ${it.localizedMessage}"
+                )
+            )
+        }
+    }
+
     fun exportBookshelf(books: List<Book>?, success: (file: File) -> Unit) {
         execute {
             books?.let {

+ 27 - 32
app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt

@@ -13,10 +13,13 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Book
 import androidx.compose.material.icons.filled.Update
+import androidx.compose.material3.Icon
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -59,12 +62,14 @@ fun BookCover(
     val useDefault = CoverConfig.useDefaultCover
     val finalPath = if (useDefault) null else path
 
-    // 为每个 BookCover 组件实例基于其书名或路径生成一个随机封面
-    // 这样书架上的每个默认封面都会不同,但对于同一本书又是固定的
-    val randomDefault = remember(name, author, path) {
-        BookCover.getRandomDefaultDrawable(seed = name ?: author ?: path ?: "")
+    // 获取随机封面路径(字符串),而非直接获取 Drawable,避免主线程解码大图
+    val randomPath = remember(name, author, path, isNight) {
+        BookCover.getRandomDefaultPath(seed = name ?: author ?: path ?: "")
     }
 
+    // 检查是否有自定义随机封面设置
+    val hasCustomDefault = !randomPath.isNullOrBlank()
+
     var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
 
     Box(
@@ -76,25 +81,26 @@ fun BookCover(
                 } else Modifier
             )
             .clip(RoundedCornerShape(4.dp))
-            .background(MaterialTheme.colorScheme.surfaceContainerLow)
-        /*
-        .then(
-            if (CoverConfig.coverShowStroke) {
-                Modifier.border(
-                    0.5.dp,
-                    MaterialTheme.colorScheme.outlineVariant,
-                    RoundedCornerShape(4.dp)
-                )
-            } else Modifier
-        )
-         */
+            .background(MaterialTheme.colorScheme.surfaceContainer)
     ) {
-        // 1. 封面底图
+        // 如果没有自定义随机封面,显示书本图标作为底图
+        if (!hasCustomDefault) {
+            Icon(
+                Icons.Default.Book,
+                contentDescription = null,
+                tint = MaterialTheme.colorScheme.secondary,
+                modifier = Modifier
+                    .size(32.dp)
+                    .align(Alignment.Center)
+            )
+        }
+
+        // 1. 封面图层
         AsyncImage(
             model = ImageRequest.Builder(context)
-                .data(finalPath ?: randomDefault)
-                .placeholder(randomDefault)
-                .error(randomDefault)
+                .data(finalPath ?: randomPath)
+                // 限制解码分辨率
+                .size(400, 560)
                 .crossfade(true)
                 .setParameter("sourceOrigin", sourceOrigin)
                 .setParameter("loadOnlyWifi", CoverConfig.loadCoverOnlyWifi)
@@ -115,7 +121,7 @@ fun BookCover(
             }
         )
 
-        // 2. 文字叠加层(当没有在线封面或者加载失败时显示)
+        // 2. 文字叠加层:当没有书籍自身封面(或加载失败)时,在随机底图上绘制书名/作者
         if (!isOnlineCoverLoaded) {
             CoverTextOverlay(
                 name = name,
@@ -152,7 +158,6 @@ private fun CoverTextOverlay(
     val showAuthor =
         (if (isNight) CoverConfig.coverShowAuthorN else CoverConfig.coverShowAuthor) && showName
 
-    // 如果都不显示,直接返回
     if (!showName && !showAuthor) return
 
     val secondaryColor = MaterialTheme.colorScheme.secondary.toArgb()
@@ -171,7 +176,6 @@ private fun CoverTextOverlay(
         drawIntoCanvas { canvas ->
             val nativeCanvas = canvas.nativeCanvas
 
-            // 绘制书名
             if (showName && !name.isNullOrBlank()) {
                 val paint = Paint().apply {
                     isAntiAlias = true
@@ -200,9 +204,7 @@ private fun CoverTextOverlay(
                     nativeCanvas.withSave {
                         val textX = (viewWidth - maxWidth) / 2f
                         val textY = viewHeight * 0.08f
-
                         translate(textX, textY)
-
                         if (CoverConfig.coverShowStroke) {
                             textPaint.style = Paint.Style.STROKE
                             textPaint.strokeWidth = textPaint.textSize / 12
@@ -210,8 +212,6 @@ private fun CoverTextOverlay(
                             textPaint.color = Color.White.toArgb()
                             textPaint.clearShadowLayer()
                             layout.draw(this)
-
-                            // 还原
                             textPaint.style = Paint.Style.FILL
                             textPaint.color = originalColor
                             if (CoverConfig.coverShowShadow) {
@@ -225,7 +225,6 @@ private fun CoverTextOverlay(
                     var startY = viewHeight * 0.16f
                     val fm = paint.fontMetrics
                     val charHeight = fm.bottom - fm.top
-
                     name.forEach { char ->
                         if (CoverConfig.coverShowStroke) {
                             val strokePaint = Paint(paint).apply {
@@ -246,7 +245,6 @@ private fun CoverTextOverlay(
                 }
             }
 
-            // 绘制作者
             if (showAuthor && !author.isNullOrBlank()) {
                 val paint = Paint().apply {
                     isAntiAlias = true
@@ -257,7 +255,6 @@ private fun CoverTextOverlay(
                         setShadowLayer(4f, 1f, 1f, shadowColor)
                     }
                 }
-
                 if (isHorizontal) {
                     val authorText = TextUtils.ellipsize(
                         author,
@@ -265,7 +262,6 @@ private fun CoverTextOverlay(
                         viewWidth * 0.9f,
                         TextUtils.TruncateAt.END
                     )
-
                     if (CoverConfig.coverShowStroke) {
                         val strokePaint = Paint(paint).apply {
                             color = Color.White.toArgb()
@@ -292,7 +288,6 @@ private fun CoverTextOverlay(
                     val charHeight = fm.bottom - fm.top
                     var startY = viewHeight * 0.16f - (author.length * charHeight)
                     startY = startY.coerceAtLeast(viewHeight * 0.2f)
-
                     author.forEach { char ->
                         nativeCanvas.drawText(char.toString(), startX, startY, paint)
                         startY += charHeight