mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
Compare commits
10 Commits
787263c20a
...
fb5f24de60
Author | SHA1 | Date | |
---|---|---|---|
|
fb5f24de60 | ||
|
f2edcdbf12 | ||
|
ca5f40704e | ||
|
1afadc8d5a | ||
|
5e9eb6f0be | ||
|
cd8fe30880 | ||
|
e33b702f75 | ||
|
fed9eec283 | ||
|
fb4b6664b4 | ||
|
f091607a48 |
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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> {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 = ""
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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" />
|
||||
|
Loading…
Reference in New Issue
Block a user