Compare commits

...

10 Commits

Author SHA1 Message Date
Horis
fb5f24de60 优化 2024-02-06 17:19:42 +08:00
Horis
f2edcdbf12 优化 2024-02-06 16:52:15 +08:00
Horis
ca5f40704e 优化 2024-02-06 16:30:29 +08:00
Horis
1afadc8d5a 优化 2024-02-06 13:08:12 +08:00
Horis
5e9eb6f0be 优化 2024-02-06 12:54:13 +08:00
Horis
cd8fe30880 优化 2024-02-05 14:08:25 +08:00
Horis
e33b702f75 优化 2024-02-05 12:52:58 +08:00
Horis
fed9eec283 优化 2024-02-05 12:34:24 +08:00
Horis
fb4b6664b4 优化 2024-02-05 12:13:24 +08:00
Horis
f091607a48 优化 2024-02-04 23:00:31 +08:00
20 changed files with 172 additions and 61 deletions

View File

@ -31,6 +31,7 @@ import io.legado.app.help.source.SourceHelp
import io.legado.app.help.storage.Backup
import io.legado.app.model.BookCover
import io.legado.app.utils.ChineseUtils
import io.legado.app.utils.LogUtils
import io.legado.app.utils.defaultSharedPreferences
import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.launch
@ -45,6 +46,7 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
LogUtils.d("App", "onCreate")
oldConfig = Configuration(resources.configuration)
CrashHandler(this)
//预下载Cronet so
@ -53,6 +55,7 @@ class App : Application() {
LiveEventBus.config()
.lifecycleObserverAlwaysActive(true)
.autoClear(false)
.enableLogger(BuildConfig.DEBUG || AppConfig.recordLog)
applyDayNight(this)
registerActivityLifecycleCallbacks(LifecycleHelp)
defaultSharedPreferences.registerOnSharedPreferenceChangeListener(AppConfig)

View File

@ -10,6 +10,7 @@ import io.legado.app.help.LifecycleHelp
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.permission.Permissions
import io.legado.app.lib.permission.PermissionsCompat
import io.legado.app.utils.LogUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers
@ -18,6 +19,8 @@ import kotlin.coroutines.CoroutineContext
abstract class BaseService : LifecycleService() {
private val simpleName = this::class.simpleName.toString()
fun <T> execute(
scope: CoroutineScope = lifecycleScope,
context: CoroutineContext = Dispatchers.IO,
@ -35,12 +38,14 @@ abstract class BaseService : LifecycleService() {
@CallSuper
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
LogUtils.d(simpleName, "onStartCommand $intent")
startForegroundNotification()
return super.onStartCommand(intent, flags, startId)
}
@CallSuper
override fun onTaskRemoved(rootIntent: Intent?) {
LogUtils.d(simpleName, "onTaskRemoved")
super.onTaskRemoved(rootIntent)
stopSelf()
}

View File

@ -5,16 +5,15 @@ import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.os.postDelayed
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.utils.buildMainHandler
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withTimeoutOrNull
import splitties.views.onLongClick
import java.util.*
import java.util.Collections
/**
* Created by Invincible on 2017/11/24.
@ -152,9 +151,16 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
}
diffJob?.cancel()
diffJob = Coroutine.async {
val diffResult = DiffUtil.calculateDiff(callback)
ensureActive()
val diffResult = if (skipDiff) withTimeoutOrNull(500L) {
DiffUtil.calculateDiff(callback)
} else {
DiffUtil.calculateDiff(callback)
}
handler.post {
if (diffResult == null) {
setItems(items)
return@post
}
if (this@RecyclerAdapter.items.isNotEmpty()) {
this@RecyclerAdapter.items.clear()
}
@ -165,12 +171,6 @@ abstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Co
onCurrentListChanged()
}
}
if (skipDiff) handler.postDelayed(500) {
if (diffJob?.isCompleted == false) {
diffJob?.cancel()
setItems(items)
}
}
}
}

View File

@ -3,6 +3,7 @@ package io.legado.app.constant
import android.util.Log
import io.legado.app.BuildConfig
import io.legado.app.help.config.AppConfig
import io.legado.app.utils.LogUtils
import io.legado.app.utils.toastOnUi
import splitties.init.appCtx
@ -21,6 +22,11 @@ object AppLog {
if (mLogs.size > 100) {
mLogs.removeLastOrNull()
}
if (throwable == null) {
LogUtils.d("AppLog", message)
} else {
LogUtils.d("AppLog", "$message\n${throwable.stackTraceToString()}")
}
mLogs.add(0, Triple(System.currentTimeMillis(), message, throwable))
if (BuildConfig.DEBUG) {
val stackTrace = Thread.currentThread().stackTrace

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.app.Application
import android.os.Bundle
import io.legado.app.base.BaseService
import io.legado.app.utils.LogUtils
import java.lang.ref.WeakReference
/**
@ -12,6 +13,8 @@ import java.lang.ref.WeakReference
@Suppress("unused")
object LifecycleHelp : Application.ActivityLifecycleCallbacks {
private const val TAG = "LifecycleHelp"
private val activities: MutableList<WeakReference<Activity>> = arrayListOf()
private val services: MutableList<WeakReference<BaseService>> = arrayListOf()
private var appFinishedListener: (() -> Unit)? = null
@ -55,16 +58,19 @@ object LifecycleHelp : Application.ActivityLifecycleCallbacks {
}
override fun onActivityPaused(activity: Activity) {
LogUtils.d(TAG, "${activity::class.simpleName} onPause")
}
override fun onActivityResumed(activity: Activity) {
LogUtils.d(TAG, "${activity::class.simpleName} onResume")
}
override fun onActivityStarted(activity: Activity) {
LogUtils.d(TAG, "${activity::class.simpleName} onStart")
}
override fun onActivityDestroyed(activity: Activity) {
LogUtils.d(TAG, "${activity::class.simpleName} onDestroy")
for (temp in activities) {
if (temp.get() != null && temp.get() === activity) {
activities.remove(temp)
@ -77,22 +83,27 @@ object LifecycleHelp : Application.ActivityLifecycleCallbacks {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
LogUtils.d(TAG, "${activity::class.simpleName} onSaveInstanceState")
}
override fun onActivityStopped(activity: Activity) {
LogUtils.d(TAG, "${activity::class.simpleName} onStop")
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
LogUtils.d(TAG, "${activity::class.simpleName} onCreate")
activities.add(WeakReference(activity))
}
@Synchronized
fun onServiceCreate(service: BaseService) {
LogUtils.d(TAG, "${service::class.simpleName} onCreate")
services.add(WeakReference(service))
}
@Synchronized
fun onServiceDestroy(service: BaseService) {
LogUtils.d(TAG, "${service::class.simpleName} onDestroy")
for (temp in services) {
if (temp.get() != null && temp.get() === service) {
services.remove(temp)

View File

@ -36,6 +36,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
var clickActionBC = appCtx.getPrefInt(PreferKey.clickActionBC, 1)
var clickActionBR = appCtx.getPrefInt(PreferKey.clickActionBR, 1)
var themeMode = appCtx.getPrefString(PreferKey.themeMode, "0")
var useDefaultCover = appCtx.getPrefBoolean(PreferKey.useDefaultCover, false)
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
@ -43,6 +44,7 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
themeMode = appCtx.getPrefString(PreferKey.themeMode, "0")
isEInkMode = themeMode == "3"
}
PreferKey.clickActionTL -> clickActionTL =
appCtx.getPrefInt(PreferKey.clickActionTL, 2)
@ -80,6 +82,9 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
PreferKey.antiAlias -> useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)
PreferKey.useDefaultCover -> useDefaultCover =
appCtx.getPrefBoolean(PreferKey.useDefaultCover, false)
}
}
@ -132,9 +137,6 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
}
}
val useDefaultCover: Boolean
get() = appCtx.getPrefBoolean(PreferKey.useDefaultCover, false)
val isTransparentStatusBar: Boolean
get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)

View File

@ -33,6 +33,7 @@ object CacheBook {
fun getOrCreate(bookUrl: String): CacheBookModel? {
val book = appDb.bookDao.getBook(bookUrl) ?: return null
val bookSource = appDb.bookSourceDao.getBookSource(book.origin) ?: return null
updateBookSource(bookSource)
var cacheBook = cacheBookMap[bookUrl]
if (cacheBook != null) {
//存在时更新,书源可能会变化,必须更新
@ -47,6 +48,7 @@ object CacheBook {
@Synchronized
fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {
updateBookSource(bookSource)
var cacheBook = cacheBookMap[book.bookUrl]
if (cacheBook != null) {
//存在时更新,书源可能会变化,必须更新
@ -59,6 +61,15 @@ object CacheBook {
return cacheBook
}
private fun updateBookSource(newBookSource: BookSource) {
cacheBookMap.forEach {
val model = it.value
if (model.bookSource.bookSourceUrl == newBookSource.bookSourceUrl) {
model.bookSource = newBookSource
}
}
}
fun start(context: Context, book: Book, start: Int, end: Int) {
if (!book.isLocal) {
context.startService<CacheBookService> {

View File

@ -13,6 +13,7 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.service.BaseReadAloudService
import io.legado.app.service.HttpReadAloudService
import io.legado.app.service.TTSReadAloudService
import io.legado.app.utils.LogUtils
import io.legado.app.utils.StringUtils
import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi
@ -53,6 +54,7 @@ object ReadAloud {
intent.putExtra("play", play)
intent.putExtra("pageIndex", pageIndex)
intent.putExtra("startPos", startPos)
LogUtils.d("ReadAloud", intent.toString())
try {
ContextCompat.startForegroundService(context, intent)
} catch (e: Exception) {

View File

@ -23,6 +23,8 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import splitties.init.appCtx
import splitties.systemservices.notificationManager
import java.util.concurrent.Executors
@ -43,6 +45,7 @@ class CacheBookService : BaseService() {
Executors.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()
private var downloadJob: Job? = null
private var notificationContent = appCtx.getString(R.string.service_starting)
private var mutex = Mutex()
private val notificationBuilder by lazy {
val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload)
.setSmallIcon(R.drawable.ic_download)
@ -102,27 +105,29 @@ class CacheBookService : BaseService() {
val chapterCount = appDb.bookChapterDao.getChapterCount(bookUrl)
val book = cacheBook.book
if (chapterCount == 0) {
val name = book.name
if (book.tocUrl.isEmpty()) {
kotlin.runCatching {
WebBook.getBookInfoAwait(cacheBook.bookSource, book)
}.onFailure {
val msg = "$name》目录为空且加载详情页失败\n${it.localizedMessage}"
AppLog.put(msg, it, true)
mutex.withLock {
val name = book.name
if (book.tocUrl.isEmpty()) {
kotlin.runCatching {
WebBook.getBookInfoAwait(cacheBook.bookSource, book)
}.onFailure {
val msg = "$name》目录为空且加载详情页失败\n${it.localizedMessage}"
AppLog.put(msg, it, true)
return@execute
}
}
WebBook.getChapterListAwait(cacheBook.bookSource, book).onFailure {
if (book.totalChapterNum > 0) {
book.totalChapterNum = 0
book.save()
}
AppLog.put("$name》目录为空且加载目录失败\n${it.localizedMessage}", it, true)
return@execute
}.getOrNull()?.let { toc ->
appDb.bookChapterDao.insert(*toc.toTypedArray())
}
book.save()
}
WebBook.getChapterListAwait(cacheBook.bookSource, book).onFailure {
if (book.totalChapterNum > 0) {
book.totalChapterNum = 0
book.save()
}
AppLog.put("$name》目录为空且加载目录失败\n${it.localizedMessage}", it, true)
return@execute
}.getOrNull()?.let { toc ->
appDb.bookChapterDao.insert(*toc.toTypedArray())
}
book.save()
}
val end2 = if (end < 0) {
book.lastChapterIndex

View File

@ -7,6 +7,7 @@ import android.view.MenuItem
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.textfield.TextInputLayout
@ -220,7 +221,7 @@ class CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>()
else -> booksDownload.sortedByDescending { it.durChapterTime }
}
}.catch {
}.flowWithLifecycle(lifecycle).catch {
AppLog.put("缓存管理界面获取书籍列表失败\n${it.localizedMessage}", it)
}.flowOn(IO).conflate().collect { books ->
adapter.setItems(books)

View File

@ -18,8 +18,6 @@ import io.legado.app.utils.visible
class CacheAdapter(context: Context, private val callBack: CallBack) :
RecyclerAdapter<Book, ItemDownloadBinding>(context) {
override fun getViewBinding(parent: ViewGroup): ItemDownloadBinding {
return ItemDownloadBinding.inflate(inflater, parent, false)
}

View File

@ -10,6 +10,7 @@ import androidx.core.view.postDelayed
import androidx.fragment.app.activityViewModels
import androidx.preference.ListPreference
import androidx.preference.Preference
import com.jeremyliao.liveeventbus.LiveEventBus
import io.legado.app.R
import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
@ -25,7 +26,14 @@ import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.service.WebService
import io.legado.app.ui.file.HandleFileContract
import io.legado.app.ui.widget.number.NumberPickerDialog
import io.legado.app.utils.*
import io.legado.app.utils.LogUtils
import io.legado.app.utils.postEvent
import io.legado.app.utils.putPrefBoolean
import io.legado.app.utils.putPrefString
import io.legado.app.utils.removePref
import io.legado.app.utils.restart
import io.legado.app.utils.setEdgeEffectColor
import io.legado.app.utils.showDialogFragment
import splitties.init.appCtx
/**
@ -80,6 +88,7 @@ class OtherConfigFragment : PreferenceFragment(),
title = getString(R.string.select_book_folder)
mode = HandleFileContract.DIR_SYS
}
PreferKey.preDownloadNum -> NumberPickerDialog(requireContext())
.setTitle(getString(R.string.pre_download))
.setMaxValue(9999)
@ -88,6 +97,7 @@ class OtherConfigFragment : PreferenceFragment(),
.show {
AppConfig.preDownloadNum = it
}
PreferKey.threadCount -> NumberPickerDialog(requireContext())
.setTitle(getString(R.string.threads_num_title))
.setMaxValue(999)
@ -96,6 +106,7 @@ class OtherConfigFragment : PreferenceFragment(),
.show {
AppConfig.threadCount = it
}
PreferKey.webPort -> NumberPickerDialog(requireContext())
.setTitle(getString(R.string.web_port_title))
.setMaxValue(60000)
@ -104,6 +115,7 @@ class OtherConfigFragment : PreferenceFragment(),
.show {
AppConfig.webPort = it
}
PreferKey.cleanCache -> clearCache()
PreferKey.uploadRule -> showDialogFragment<DirectLinkUploadConfig>()
PreferKey.checkSource -> showDialogFragment<CheckSourceConfig>()
@ -118,6 +130,7 @@ class OtherConfigFragment : PreferenceFragment(),
ImageProvider.bitmapLruCache.resize(ImageProvider.cacheSize)
}
}
PreferKey.sourceEditMaxLine -> {
NumberPickerDialog(requireContext())
.setTitle(getString(R.string.source_edit_text_max_line))
@ -141,10 +154,12 @@ class OtherConfigFragment : PreferenceFragment(),
PreferKey.preDownloadNum -> {
upPreferenceSummary(key, AppConfig.preDownloadNum.toString())
}
PreferKey.threadCount -> {
upPreferenceSummary(key, AppConfig.threadCount.toString())
postEvent(PreferKey.threadCount, "")
}
PreferKey.webPort -> {
upPreferenceSummary(key, AppConfig.webPort.toString())
if (WebService.isRun) {
@ -152,26 +167,37 @@ class OtherConfigFragment : PreferenceFragment(),
WebService.start(requireContext())
}
}
PreferKey.defaultBookTreeUri -> {
upPreferenceSummary(key, AppConfig.defaultBookTreeUri)
}
PreferKey.recordLog -> LogUtils.upLevel()
PreferKey.recordLog -> {
LogUtils.upLevel()
LiveEventBus.config().enableLogger(AppConfig.recordLog)
}
PreferKey.processText -> sharedPreferences?.let {
setProcessTextEnable(it.getBoolean(key, true))
}
PreferKey.showDiscovery, PreferKey.showRss -> postEvent(EventBus.NOTIFY_MAIN, true)
PreferKey.language -> listView.postDelayed(1000) {
appCtx.restart()
}
PreferKey.userAgent -> listView.post {
upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent)
}
PreferKey.checkSource -> listView.post {
upPreferenceSummary(PreferKey.checkSource, CheckSource.summary)
}
PreferKey.bitmapCacheSize -> {
upPreferenceSummary(key, AppConfig.bitmapCacheSize.toString())
}
PreferKey.sourceEditMaxLine -> {
upPreferenceSummary(key, AppConfig.sourceEditMaxLine.toString())
}
@ -183,12 +209,15 @@ class OtherConfigFragment : PreferenceFragment(),
when (preferenceKey) {
PreferKey.preDownloadNum -> preference.summary =
getString(R.string.pre_download_s, value)
PreferKey.threadCount -> preference.summary = getString(R.string.threads_num, value)
PreferKey.webPort -> preference.summary = getString(R.string.web_port_summary, value)
PreferKey.bitmapCacheSize -> preference.summary =
getString(R.string.bitmap_cache_size_summary, value)
PreferKey.sourceEditMaxLine -> preference.summary =
getString(R.string.source_edit_max_line_summary, value)
else -> if (preference is ListPreference) {
val index = preference.findIndexOfValue(value)
// Set the summary to reflect the new value.

View File

@ -10,8 +10,8 @@ import io.legado.app.data.entities.Book
abstract class BaseBooksAdapter<VB : ViewBinding>(context: Context) :
DiffRecyclerAdapter<Book, VB>(context) {
override val diffItemCallback: DiffUtil.ItemCallback<Book>
get() = object : DiffUtil.ItemCallback<Book>() {
override val diffItemCallback: DiffUtil.ItemCallback<Book> =
object : DiffUtil.ItemCallback<Book>() {
override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {
return oldItem.name == newItem.name

View File

@ -40,12 +40,12 @@ class BooksAdapterList(
upRefresh(binding, item)
upLastUpdateTime(binding, item)
} else {
tvRead.text = item.durChapterTitle
tvLast.text = item.latestChapterTitle
bundle.keySet().forEach {
when (it) {
"name" -> tvName.text = item.name
"author" -> tvAuthor.text = item.author
"dur" -> tvRead.text = item.durChapterTitle
"last" -> tvLast.text = item.latestChapterTitle
"cover" -> ivCover.load(
item.getDisplayCover(),
item.name,
@ -79,7 +79,10 @@ class BooksAdapterList(
private fun upLastUpdateTime(binding: ItemBookshelfListBinding, item: Book) {
if (AppConfig.showLastUpdateTime && !item.isLocal) {
binding.tvLastUpdateTime.text = item.latestChapterTime.toTimeAgo()
val time = item.latestChapterTime.toTimeAgo()
if (binding.tvLastUpdateTime.text != time) {
binding.tvLastUpdateTime.text = time
}
} else {
binding.tvLastUpdateTime.text = ""
}

View File

@ -106,6 +106,10 @@ class BadgeView @JvmOverloads constructor(
}
override fun setBackgroundColor(color: Int) {
val background = background
if (background is ShapeDrawable && background.paint.color == color) {
return
}
setBackground(radius, color)
}

View File

@ -7,11 +7,12 @@ import io.legado.app.BuildConfig
import io.legado.app.help.config.AppConfig
import splitties.init.appCtx
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.logging.FileHandler
import java.util.logging.Level
import java.util.logging.LogRecord
import java.util.logging.Logger
import kotlin.time.Duration.Companion.days
@SuppressLint("SimpleDateFormat")
@Suppress("unused")
@ -40,7 +41,14 @@ object LogUtils {
private val fileHandler by lazy {
val root = appCtx.externalCacheDir ?: return@lazy null
val logFolder = FileUtils.createFolderIfNotExist(root, "logs")
val logPath = FileUtils.getPath(root = logFolder, "appLog")
val expiredTime = System.currentTimeMillis() - 7.days.inWholeMilliseconds
logFolder.listFiles()?.forEach {
if (it.lastModified() < expiredTime) {
it.delete()
}
}
val date = getCurrentDateStr(TIME_PATTERN)
val logPath = FileUtils.getPath(root = logFolder, "appLog-$date.txt")
FileHandler(logPath, 10240, 10).apply {
formatter = object : java.util.logging.Formatter() {
override fun format(record: LogRecord): String {

View File

@ -14,12 +14,8 @@ import io.legado.app.web.utils.AssetsWeb
import kotlinx.coroutines.runBlocking
import okio.Pipe
import okio.buffer
import java.io.BufferedWriter
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.OutputStreamWriter
import kotlin.collections.HashMap
import kotlin.collections.List
import kotlin.collections.set
class HttpServer(port: Int) : NanoHTTPD(port) {
@ -115,10 +111,8 @@ class HttpServer(port: Int) : NanoHTTPD(port) {
if (data is List<*> && data.size > 3000) {
val pipe = Pipe(16 * 1024)
Coroutine.async {
pipe.sink.buffer().outputStream().use { out ->
BufferedWriter(OutputStreamWriter(out, "UTF-8")).use {
GSON.toJson(returnData, it)
}
pipe.sink.buffer().outputStream().bufferedWriter(Charsets.UTF_8).use {
GSON.toJson(returnData, it)
}
}
newChunkedResponse(

View File

@ -5,13 +5,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:padding="16dp">
android:padding="16dp"
android:scrollbars="none">
<io.legado.app.lib.theme.view.ThemeCheckBox
android:id="@+id/cb_book_source"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:scrollbars="none"
android:singleLine="true"
android:textColor="@color/primaryText"
app:layout_constraintBottom_toTopOf="@id/iv_debug_text"
@ -27,6 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="6dp"
android:scrollbars="none"
app:layout_constraintBottom_toTopOf="@id/iv_debug_text"
app:layout_constraintRight_toLeftOf="@id/iv_edit"
app:layout_constraintTop_toTopOf="parent"
@ -40,6 +43,7 @@
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/edit"
android:padding="6dp"
android:scrollbars="none"
android:src="@drawable/ic_edit"
android:tint="@color/primaryText"
app:layout_constraintBottom_toTopOf="@id/iv_debug_text"
@ -51,10 +55,11 @@
android:layout_width="36dp"
android:layout_height="36dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/more_menu"
android:padding="6dp"
android:scrollbars="none"
android:src="@drawable/ic_more_vert"
android:tint="@color/primaryText"
android:contentDescription="@string/more_menu"
app:layout_constraintBottom_toTopOf="@id/iv_debug_text"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
@ -65,6 +70,7 @@
android:layout_width="8dp"
android:layout_height="8dp"
android:scaleType="centerCrop"
android:scrollbars="none"
android:src="@color/md_green_600"
app:layout_constraintRight_toRightOf="@id/iv_menu_more"
app:layout_constraintTop_toTopOf="@id/iv_menu_more" />
@ -73,9 +79,10 @@
android:id="@+id/iv_debug_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:scrollbars="none"
android:singleLine="true"
android:visibility="gone"
android:layout_marginTop="6dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/iv_progressBar"
@ -91,6 +98,7 @@
android:animationResolution="1000"
android:indeterminate="true"
android:indeterminateBehavior="repeat"
android:scrollbars="none"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/iv_debug_text"
app:layout_constraintRight_toRightOf="parent"

View File

@ -8,6 +8,7 @@
android:background="@drawable/bg_item_focused_on_tv"
android:clickable="true"
android:focusable="true"
android:scrollbars="none"
tools:ignore="UnusedAttribute">
<io.legado.app.ui.widget.image.CoverImageView
@ -41,6 +42,7 @@
android:layout_gravity="right"
android:layout_margin="5dp"
android:includeFontPadding="false"
android:scrollbars="none"
tools:ignore="RtlHardcoded" />
<io.legado.app.ui.widget.anima.RotateLoading
@ -62,6 +64,7 @@
android:includeFontPadding="false"
android:paddingLeft="2dp"
android:paddingBottom="4dp"
android:scrollbars="none"
android:singleLine="true"
android:text="@string/book_name"
android:textColor="@color/primaryText"
@ -79,6 +82,7 @@
android:contentDescription="@string/author"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:scrollbars="none"
android:src="@drawable/ic_author"
app:layout_constraintBottom_toBottomOf="@+id/tv_author"
app:layout_constraintLeft_toLeftOf="@+id/tv_name"
@ -93,6 +97,7 @@
android:includeFontPadding="false"
android:maxLines="1"
android:paddingEnd="6dp"
android:scrollbars="none"
android:singleLine="true"
android:text="@string/author"
android:textColor="@color/tv_text_summary"
@ -110,6 +115,7 @@
android:includeFontPadding="false"
android:maxLines="1"
android:paddingEnd="6dp"
android:scrollbars="none"
android:singleLine="true"
android:text=""
android:textColor="@color/tv_text_summary"
@ -128,6 +134,7 @@
android:contentDescription="@string/read_dur_progress"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:scrollbars="none"
android:src="@drawable/ic_history"
app:layout_constraintBottom_toBottomOf="@+id/tv_read"
app:layout_constraintLeft_toLeftOf="@+id/tv_name"
@ -140,6 +147,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:scrollbars="none"
android:singleLine="true"
android:text="@string/read_dur_progress"
android:textColor="@color/tv_text_summary"
@ -157,6 +165,7 @@
android:contentDescription="@string/lasted_show"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:scrollbars="none"
android:src="@drawable/ic_book_last"
app:layout_constraintBottom_toBottomOf="@+id/tv_last"
app:layout_constraintLeft_toLeftOf="@+id/tv_name"
@ -169,6 +178,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:scrollbars="none"
android:singleLine="true"
android:text="@string/lasted_show"
android:textColor="@color/tv_text_summary"
@ -184,6 +194,7 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="?android:attr/selectableItemBackground"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"

View File

@ -3,7 +3,8 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:scrollbars="none">
<io.legado.app.ui.widget.image.CoverImageView
android:id="@+id/iv_cover"
@ -12,6 +13,7 @@
android:layout_margin="8dp"
android:contentDescription="@string/img_cover"
android:scaleType="centerCrop"
android:scrollbars="none"
android:src="@drawable/image_cover_default"
android:transitionName="img_cover"
app:layout_constraintBottom_toBottomOf="parent"
@ -25,6 +27,7 @@
android:layout_height="8dp"
android:layout_margin="8dp"
android:scaleType="centerCrop"
android:scrollbars="none"
android:src="@color/md_green_600"
android:visibility="invisible"
app:layout_constraintLeft_toRightOf="@id/iv_cover"
@ -35,14 +38,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" />
android:scrollbars="none"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:scrollbars="none"
android:singleLine="true"
android:text="@string/app_name"
android:textColor="@color/primaryText"
@ -55,8 +60,9 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:orientation="vertical"
android:layout_marginTop="3dp"
android:orientation="vertical"
android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="@id/iv_cover"
app:layout_constraintLeft_toRightOf="@+id/iv_cover"
app:layout_constraintRight_toRightOf="@id/tv_name"
@ -68,6 +74,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:scrollbars="none"
android:text="@string/author"
android:textColor="@color/primaryText"
android:textSize="12sp" />
@ -76,7 +83,8 @@
android:id="@+id/ll_kind"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" />
android:orientation="horizontal"
android:scrollbars="none" />
<TextView
android:id="@+id/tv_lasted"
@ -84,6 +92,7 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1"
android:scrollbars="none"
android:text="@string/last_read"
android:textColor="@color/primaryText"
android:textSize="12sp" />
@ -93,6 +102,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:ellipsize="end"
android:scrollbars="none"
android:text="@string/book_intro"
android:textColor="@color/primaryText"
android:textSize="12sp" />