Merge remote-tracking branch 'origin/master' into eink

# Conflicts:
#	app/src/main/java/io/legado/app/lib/theme/ATH.kt
#	app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
#	app/src/main/java/io/legado/app/utils/ContextExtensions.kt
pull/241/head
Modificator 5 years ago
commit cb374e548b
  1. 4
      app/src/main/assets/updateLog.md
  2. 13
      app/src/main/java/io/legado/app/App.kt
  3. 4
      app/src/main/java/io/legado/app/base/BaseActivity.kt
  4. 1
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  5. 5
      app/src/main/java/io/legado/app/constant/Theme.kt
  6. 51
      app/src/main/java/io/legado/app/help/AppConfig.kt
  7. 135
      app/src/main/java/io/legado/app/help/BookHelp.kt
  8. 58
      app/src/main/java/io/legado/app/help/FileHelp.kt
  9. 13
      app/src/main/java/io/legado/app/help/PrefExtensions.kt
  10. 14
      app/src/main/java/io/legado/app/help/ReadBookConfig.kt
  11. 22
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  12. 21
      app/src/main/java/io/legado/app/help/storage/Restore.kt
  13. 8
      app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt
  14. 15
      app/src/main/java/io/legado/app/lib/theme/ATH.kt
  15. 4
      app/src/main/java/io/legado/app/service/DownloadService.kt
  16. 10
      app/src/main/java/io/legado/app/service/HttpReadAloudService.kt
  17. 23
      app/src/main/java/io/legado/app/ui/README.md
  18. 4
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  19. 4
      app/src/main/java/io/legado/app/ui/book/read/Help.kt
  20. 5
      app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt
  21. 3
      app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt
  22. 4
      app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt
  23. 4
      app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt
  24. 3
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  25. 4
      app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt
  26. 61
      app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt
  27. 23
      app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt
  28. 16
      app/src/main/java/io/legado/app/ui/main/MainActivity.kt
  29. 2
      app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt
  30. 4
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  31. 2
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  32. 15
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  33. 6
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  34. 15
      app/src/main/java/io/legado/app/ui/widget/font/FontSelectDialog.kt
  35. 11
      app/src/main/java/io/legado/app/utils/ACache.kt
  36. 13
      app/src/main/java/io/legado/app/utils/ContextExtensions.kt
  37. 35
      app/src/main/java/io/legado/app/utils/DocumentUtils.kt
  38. 115
      app/src/main/java/io/legado/app/utils/FileUtils.kt
  39. 8
      app/src/main/java/io/legado/app/utils/FragmentExtensions.kt
  40. 5
      app/src/main/java/io/legado/app/utils/LogUtils.kt
  41. 29
      app/src/main/java/io/legado/app/utils/StringExtensions.kt
  42. 32
      app/src/main/java/io/legado/app/utils/StringUtils.kt
  43. 3
      app/src/main/java/io/legado/app/utils/UriExtensions.kt
  44. 2
      app/src/main/java/io/legado/app/utils/ViewExtensions.kt
  45. 8
      app/src/main/java/io/legado/app/utils/ZipUtils.kt
  46. 12
      app/src/main/res/values/arrays.xml
  47. 3
      app/src/main/res/values/strings.xml
  48. 1
      app/src/main/res/xml/pref_config_theme.xml
  49. 12
      app/src/main/res/xml/pref_main.xml

@ -3,6 +3,10 @@
* 旧版数据导入教程:
* 先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/01/27**
* 添加根据系统主题切换夜间模式
* 合并Modificator提交的代码
**2020/01/26**
* 修复bug
* 未加入书架可查看目录

@ -15,13 +15,13 @@ import io.legado.app.constant.AppConst.channelIdReadAloud
import io.legado.app.constant.AppConst.channelIdWeb
import io.legado.app.data.AppDatabase
import io.legado.app.help.ActivityHelp
import io.legado.app.help.AppConfig
import io.legado.app.help.CrashHandler
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.ui.book.read.page.ChapterProvider
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.isNightTheme
@Suppress("DEPRECATION")
class App : Application() {
@ -50,7 +50,7 @@ class App : Application() {
}
if (!ThemeStore.isConfigured(this, versionCode)) applyTheme()
initNightTheme()
initNightMode()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) createChannelId()
@ -67,7 +67,7 @@ class App : Application() {
* 更新主题
*/
fun applyTheme() {
if (isNightTheme) {
if (AppConfig.isNightTheme) {
ThemeStore.editTheme(this)
.primaryColor(
getPrefInt("colorPrimaryNight", getCompatColor(R.color.shine_color))
@ -98,11 +98,12 @@ class App : Application() {
fun applyDayNight() {
ReadBookConfig.upBg()
applyTheme()
initNightTheme()
initNightMode()
}
private fun initNightTheme() {
val targetMode = if (isNightTheme) {
private fun initNightMode() {
val targetMode =
if (AppConfig.isNightTheme) {
AppCompatDelegate.MODE_NIGHT_YES
} else {
AppCompatDelegate.MODE_NIGHT_NO

@ -52,8 +52,8 @@ abstract class BaseActivity(
} ?: super.onCreateOptionsMenu(menu)
}
override fun onMenuOpened(featureId: Int, menu: Menu?): Boolean {
menu?.applyOpenTint(this)
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.applyOpenTint(this)
return super.onMenuOpened(featureId, menu)
}

@ -21,4 +21,5 @@ object PreferKey {
const val backupPath = "backupUri"
const val threadCount = "threadCount"
const val keepLight = "keep_light"
const val autoDarkMode = "autoDarkMode"
}

@ -1,14 +1,13 @@
package io.legado.app.constant
import io.legado.app.App
import io.legado.app.utils.isNightTheme
import io.legado.app.help.AppConfig
enum class Theme {
Dark, Light, Auto;
companion object {
fun getTheme(): Theme {
return if (App.INSTANCE.isNightTheme) {
return if (AppConfig.isNightTheme) {
Dark
} else Light
}

@ -0,0 +1,51 @@
package io.legado.app.help
import io.legado.app.App
import io.legado.app.constant.PreferKey
import io.legado.app.utils.*
object AppConfig {
var isNightTheme: Boolean
get() {
return when (App.INSTANCE.getPrefString("themeMode", "0")) {
"1" -> false
"2" -> true
else -> App.INSTANCE.sysIsDarkMode()
}
}
set(value) {
if (value) {
App.INSTANCE.putPrefString("themeMode", "2")
} else {
App.INSTANCE.putPrefString("themeMode", "1")
}
}
var isTransparentStatusBar: Boolean
get() = App.INSTANCE.getPrefBoolean("transparentStatusBar")
set(value) {
App.INSTANCE.putPrefBoolean("transparentStatusBar", value)
}
var isShowRSS: Boolean
get() = App.INSTANCE.getPrefBoolean(PreferKey.showRss)
set(value) {
App.INSTANCE.putPrefBoolean(PreferKey.showRss, value)
}
var threadCount: Int
get() = App.INSTANCE.getPrefInt(PreferKey.threadCount)
set(value) {
App.INSTANCE.putPrefInt(PreferKey.threadCount, value)
}
var autoDarkMode: Boolean
get() = App.INSTANCE.getPrefBoolean(PreferKey.autoDarkMode)
set(value) {
App.INSTANCE.putPrefBoolean(PreferKey.autoDarkMode, value)
}
val isEInkMode: Boolean
get() = App.INSTANCE.getPrefBoolean("isEInkMode")
}

@ -1,19 +1,19 @@
package io.legado.app.help
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.utils.MD5Utils
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getPrefString
import io.legado.app.utils.*
import org.apache.commons.text.similarity.JaccardSimilarity
import java.io.File
import kotlin.math.min
object BookHelp {
private const val cacheFolderName = "book_cache"
private var downloadPath: String =
App.INSTANCE.getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
@ -26,74 +26,125 @@ object BookHelp {
?: App.INSTANCE.cacheDir.absolutePath
}
private val downloadUri get() = Uri.parse(downloadPath)
private fun bookFolderName(book: Book): String {
return formatFolderName(book.name) + MD5Utils.md5Encode16(book.bookUrl)
}
private fun bookChapterName(bookChapter: BookChapter): String {
return String.format("%05d-%s", bookChapter.index, MD5Utils.md5Encode(bookChapter.title))
}
private fun getBookCachePath(): String {
return "$downloadPath${File.separator}book_cache"
return "$downloadPath${File.separator}$cacheFolderName"
}
fun clearCache() {
FileHelp.deleteFile(getBookCachePath())
FileHelp.getFolder(getBookCachePath())
FileUtils.deleteFile(getBookCachePath())
FileUtils.getFolder(getBookCachePath())
}
@Synchronized
fun saveContent(book: Book, bookChapter: BookChapter, content: String) {
if (content.isEmpty()) return
FileHelp.getFolder(getBookFolder(book)).listFiles()?.forEach {
if (downloadUri.isDocumentUri(App.INSTANCE)) {
DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let {
DocumentUtils.createFileIfNotExist(
it,
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)?.uri?.writeText(App.INSTANCE, content)
}
} else {
FileUtils.createFileIfNotExist(
File(downloadPath),
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
).listFiles()?.forEach {
if (it.name.startsWith(String.format("%05d", bookChapter.index))) {
it.delete()
return@forEach
}
}
val filePath = getChapterPath(book, bookChapter)
val file = FileHelp.getFile(filePath)
file.writeText(content)
FileUtils.createFileIfNotExist(
File(downloadPath),
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
).writeText(content)
}
}
fun getChapterCount(book: Book): Int {
return FileHelp.getFolder(getBookFolder(book)).list()?.size ?: 0
if (downloadUri.isDocumentUri(App.INSTANCE)) {
DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let {
return DocumentUtils.createFileIfNotExist(
it,
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)?.listFiles()?.size ?: 0
}
} else {
return FileUtils.createFileIfNotExist(
File(downloadPath),
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
).list()?.size ?: 0
}
return 0
}
fun hasContent(book: Book, bookChapter: BookChapter): Boolean {
val filePath = getChapterPath(book, bookChapter)
runCatching {
val file = File(filePath)
if (file.exists()) {
return true
if (downloadUri.isDocumentUri(App.INSTANCE)) {
DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let {
return DocumentUtils.exists(
it,
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)
}
} else {
return FileUtils.exists(
File(downloadPath),
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)
}
return false
}
fun getContent(book: Book, bookChapter: BookChapter): String? {
val filePath = getChapterPath(book, bookChapter)
runCatching {
val file = File(filePath)
if (file.exists()) {
return file.readText()
if (downloadUri.isDocumentUri(App.INSTANCE)) {
DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let {
return DocumentUtils.createFileIfNotExist(
it,
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)?.uri?.readText(App.INSTANCE)
}
} else {
return FileUtils.createFileIfNotExist(
File(downloadPath),
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
).readText()
}
return null
}
fun delContent(book: Book, bookChapter: BookChapter) {
val filePath = getChapterPath(book, bookChapter)
kotlin.runCatching {
val file = File(filePath)
if (file.exists()) {
file.delete()
}
if (downloadUri.isDocumentUri(App.INSTANCE)) {
DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let {
DocumentUtils.createFileIfNotExist(
it,
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
)?.delete()
}
} else {
FileUtils.createFileIfNotExist(
File(downloadPath),
"${bookChapterName(bookChapter)}.nb",
subDirs = *arrayOf(cacheFolderName, bookFolderName(book))
).delete()
}
private fun getBookFolder(book: Book): String {
val bookFolder = formatFolderName(book.name + MD5Utils.md5Encode16(book.bookUrl))
return "${getBookCachePath()}${File.separator}$bookFolder"
}
private fun getChapterPath(book: Book, bookChapter: BookChapter): String {
val chapterFile =
String.format("%05d-%s", bookChapter.index, MD5Utils.md5Encode(bookChapter.title))
return "${getBookFolder(book)}${File.separator}$chapterFile.nb"
}
private fun formatFolderName(folderName: String): String {
@ -124,16 +175,16 @@ object BookHelp {
}
var newIndex = 0
val jaccardSimilarity = JaccardSimilarity()
val jSimilarity = JaccardSimilarity()
var similarity = if (chapters.size > index) {
jaccardSimilarity.apply(title, chapters[index].title)
jSimilarity.apply(title, chapters[index].title)
} else 0.0
if (similarity == 1.0) {
return index
} else {
for (i in 1..50) {
if (index - i in chapters.indices) {
jaccardSimilarity.apply(title, chapters[index - i].title).let {
jSimilarity.apply(title, chapters[index - i].title).let {
if (it > similarity) {
similarity = it
newIndex = index - i
@ -144,7 +195,7 @@ object BookHelp {
}
}
if (index + i in chapters.indices) {
jaccardSimilarity.apply(title, chapters[index + i].title).let {
jSimilarity.apply(title, chapters[index + i].title).let {
if (it > similarity) {
similarity = it
newIndex = index + i

@ -1,58 +0,0 @@
package io.legado.app.help
import io.legado.app.App
import java.io.File
import java.io.IOException
object FileHelp {
//获取文件夹
fun getFolder(filePath: String): File {
val file = File(filePath)
//如果文件夹不存在,就创建它
if (!file.exists()) {
file.mkdirs()
}
return file
}
//获取文件
@Synchronized
fun getFile(filePath: String): File {
val file = File(filePath)
try {
if (!file.exists()) {
//创建父类文件夹
getFolder(file.parent)
//创建文件
file.createNewFile()
}
} catch (e: IOException) {
e.printStackTrace()
}
return file
}
fun getCachePath(): String {
return App.INSTANCE.externalCacheDir?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
}
//递归删除文件夹下的数据
@Synchronized
fun deleteFile(filePath: String) {
val file = File(filePath)
if (!file.exists()) return
if (file.isDirectory) {
val files = file.listFiles()
files?.forEach { subFile ->
val path = subFile.path
deleteFile(path)
}
}
//删除文件
file.delete()
}
}

@ -1,13 +0,0 @@
package io.legado.app.help
import android.content.Context
import io.legado.app.constant.PreferKey
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefInt
val Context.isShowRSS: Boolean
get() = getPrefBoolean(PreferKey.showRss, true)
val Context.threadCount: Int
get() = getPrefInt(PreferKey.threadCount, 16)

@ -111,7 +111,7 @@ object ReadBookConfig {
var textSize: Int = 15
) {
fun setBg(bgType: Int, bg: String) {
if (App.INSTANCE.isNightTheme) {
if (AppConfig.isNightTheme) {
bgTypeNight = bgType
bgStrNight = bg
} else {
@ -121,7 +121,7 @@ object ReadBookConfig {
}
fun setTextColor(color: Int) {
if (App.INSTANCE.isNightTheme) {
if (AppConfig.isNightTheme) {
textColorNight = "#${color.hexString}"
} else {
textColor = "#${color.hexString}"
@ -129,7 +129,7 @@ object ReadBookConfig {
}
fun setStatusIconDark(isDark: Boolean) {
if (App.INSTANCE.isNightTheme) {
if (AppConfig.isNightTheme) {
darkStatusIconNight = isDark
} else {
darkStatusIcon = isDark
@ -137,7 +137,7 @@ object ReadBookConfig {
}
fun statusIconDark(): Boolean {
return if (App.INSTANCE.isNightTheme) {
return if (AppConfig.isNightTheme) {
darkStatusIconNight
} else {
darkStatusIcon
@ -145,17 +145,17 @@ object ReadBookConfig {
}
fun textColor(): Int {
return if (App.INSTANCE.isNightTheme) Color.parseColor(textColorNight)
return if (AppConfig.isNightTheme) Color.parseColor(textColorNight)
else Color.parseColor(textColor)
}
fun bgStr(): String {
return if (App.INSTANCE.isNightTheme) bgStrNight
return if (AppConfig.isNightTheme) bgStrNight
else bgStr
}
fun bgType(): Int {
return if (App.INSTANCE.isNightTheme) bgTypeNight
return if (AppConfig.isNightTheme) bgTypeNight
else bgType
}

@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.App
import io.legado.app.help.FileHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.FileUtils
@ -48,37 +47,40 @@ object Backup {
App.db.bookDao().allBooks.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "bookshelf.json").writeText(json)
FileUtils.getFile(backupPath + File.separator + "bookshelf.json")
.writeText(json)
}
}
App.db.bookGroupDao().all().let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "bookGroup.json").writeText(json)
FileUtils.getFile(backupPath + File.separator + "bookGroup.json")
.writeText(json)
}
}
App.db.bookSourceDao().all.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "bookSource.json")
FileUtils.getFile(backupPath + File.separator + "bookSource.json")
.writeText(json)
}
}
App.db.rssSourceDao().all.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "rssSource.json").writeText(json)
FileUtils.getFile(backupPath + File.separator + "rssSource.json")
.writeText(json)
}
}
App.db.replaceRuleDao().all.let {
if (it.isNotEmpty()) {
val json = GSON.toJson(it)
FileHelp.getFile(backupPath + File.separator + "replaceRule.json")
FileUtils.getFile(backupPath + File.separator + "replaceRule.json")
.writeText(json)
}
}
GSON.toJson(ReadBookConfig.configList)?.let {
FileHelp.getFile(backupPath + File.separator + ReadBookConfig.readConfigFileName)
FileUtils.getFile(backupPath + File.separator + ReadBookConfig.readConfigFileName)
.writeText(it)
}
Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp ->
@ -111,7 +113,7 @@ object Backup {
doc?.let {
DocumentUtils.writeText(
context,
FileHelp.getFile(backupPath + File.separator + fileName).readText(),
FileUtils.getFile(backupPath + File.separator + fileName).readText(),
doc.uri
)
}
@ -122,8 +124,8 @@ object Backup {
private fun copyBackup() {
try {
for (fileName in backupFileNames) {
FileHelp.getFile(backupPath + File.separator + "bookshelf.json")
.copyTo(FileHelp.getFile(legadoPath + File.separator + "bookshelf.json"), true)
FileUtils.getFile(backupPath + File.separator + "bookshelf.json")
.copyTo(FileUtils.getFile(legadoPath + File.separator + "bookshelf.json"), true)
}
} catch (e: Exception) {
e.printStackTrace()

@ -11,7 +11,6 @@ import com.jayway.jsonpath.ParseContext
import io.legado.app.App
import io.legado.app.constant.AppConst
import io.legado.app.data.entities.*
import io.legado.app.help.FileHelp
import io.legado.app.help.ReadBookConfig
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
@ -38,7 +37,7 @@ object Restore {
for (fileName in Backup.backupFileNames) {
if (doc.name == fileName) {
DocumentUtils.readText(context, doc.uri)?.let {
FileHelp.getFile(Backup.backupPath + File.separator + fileName)
FileUtils.getFile(Backup.backupPath + File.separator + fileName)
.writeText(it)
}
}
@ -51,7 +50,7 @@ object Restore {
suspend fun restore(path: String) {
withContext(IO) {
try {
val file = FileHelp.getFile(path + File.separator + "bookshelf.json")
val file = FileUtils.getFile(path + File.separator + "bookshelf.json")
val json = file.readText()
GSON.fromJsonArray<Book>(json)?.let {
App.db.bookDao().insert(*it.toTypedArray())
@ -60,7 +59,7 @@ object Restore {
e.printStackTrace()
}
try {
val file = FileHelp.getFile(path + File.separator + "bookGroup.json")
val file = FileUtils.getFile(path + File.separator + "bookGroup.json")
val json = file.readText()
GSON.fromJsonArray<BookGroup>(json)?.let {
App.db.bookGroupDao().insert(*it.toTypedArray())
@ -69,7 +68,7 @@ object Restore {
e.printStackTrace()
}
try {
val file = FileHelp.getFile(path + File.separator + "bookSource.json")
val file = FileUtils.getFile(path + File.separator + "bookSource.json")
val json = file.readText()
GSON.fromJsonArray<BookSource>(json)?.let {
App.db.bookSourceDao().insert(*it.toTypedArray())
@ -78,7 +77,7 @@ object Restore {
e.printStackTrace()
}
try {
val file = FileHelp.getFile(path + File.separator + "rssSource.json")
val file = FileUtils.getFile(path + File.separator + "rssSource.json")
val json = file.readText()
GSON.fromJsonArray<RssSource>(json)?.let {
App.db.rssSourceDao().insert(*it.toTypedArray())
@ -87,7 +86,7 @@ object Restore {
e.printStackTrace()
}
try {
val file = FileHelp.getFile(path + File.separator + "replaceRule.json")
val file = FileUtils.getFile(path + File.separator + "replaceRule.json")
val json = file.readText()
GSON.fromJsonArray<ReplaceRule>(json)?.let {
App.db.replaceRuleDao().insert(*it.toTypedArray())
@ -97,7 +96,7 @@ object Restore {
}
try {
val file =
FileHelp.getFile(path + File.separator + ReadBookConfig.readConfigFileName)
FileUtils.getFile(path + File.separator + ReadBookConfig.readConfigFileName)
val configFile =
File(App.INSTANCE.filesDir.absolutePath + File.separator + ReadBookConfig.readConfigFileName)
if (file.exists()) {
@ -126,7 +125,7 @@ object Restore {
GlobalScope.launch(IO) {
try {// 导入书架
val shelfFile =
FileHelp.getFile(Backup.defaultPath + File.separator + "myBookShelf.json")
FileUtils.getFile(Backup.defaultPath + File.separator + "myBookShelf.json")
val json = shelfFile.readText()
val importCount = importOldBookshelf(json)
withContext(Main) {
@ -140,7 +139,7 @@ object Restore {
try {// Book source
val sourceFile =
FileHelp.getFile(Backup.defaultPath + File.separator + "myBookSource.json")
FileUtils.getFile(Backup.defaultPath + File.separator + "myBookSource.json")
val json = sourceFile.readText()
val importCount = importOldSource(json)
withContext(Main) {
@ -154,7 +153,7 @@ object Restore {
try {// Replace rules
val ruleFile =
FileHelp.getFile(Backup.defaultPath + File.separator + "myBookReplaceRule.json")
FileUtils.getFile(Backup.defaultPath + File.separator + "myBookReplaceRule.json")
val json = ruleFile.readText()
val importCount = importOldReplaceRule(json)
withContext(Main) {

@ -2,10 +2,10 @@ package io.legado.app.help.storage
import android.content.Context
import io.legado.app.App
import io.legado.app.help.FileHelp
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.http.HttpAuth
import io.legado.app.utils.FileUtils
import io.legado.app.utils.ZipUtils
import io.legado.app.utils.getPrefString
import kotlinx.coroutines.Dispatchers.IO
@ -17,9 +17,9 @@ import java.util.*
import kotlin.math.min
object WebDavHelp {
private val zipFilePath = FileHelp.getCachePath() + "/backup" + ".zip"
private val zipFilePath = FileUtils.getCachePath() + "/backup" + ".zip"
private val unzipFilesPath by lazy {
FileHelp.getCachePath()
FileUtils.getCachePath()
}
private fun getWebDavUrl(): String? {
@ -86,7 +86,7 @@ object WebDavHelp {
for (i in 0 until paths.size) {
paths[i] = path + File.separator + paths[i]
}
FileHelp.deleteFile(zipFilePath)
FileUtils.deleteFile(zipFilePath)
if (ZipUtils.zipFiles(paths, zipFilePath)) {
WebDav(getWebDavUrl() + "legado").makeAsDir()
val putUrl = getWebDavUrl() + "legado/backup" +

@ -5,7 +5,6 @@ import android.app.Activity
import android.app.ActivityManager
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Build
import android.view.View
import android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
@ -17,9 +16,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.google.android.material.bottomnavigation.BottomNavigationView
import io.legado.app.R
import io.legado.app.help.AppConfig
import io.legado.app.utils.getCompatColor
import io.legado.app.utils.isNightTheme
import io.legado.app.utils.isTransparentStatusBar
import kotlinx.android.synthetic.main.activity_main.view.*
import org.jetbrains.anko.backgroundColor
@ -38,7 +36,7 @@ object ATH {
}
fun setStatusBarColorAuto(activity: Activity, fullScreen: Boolean) {
val isTransparentStatusBar = activity.isTransparentStatusBar
val isTransparentStatusBar = AppConfig.isTransparentStatusBar
setStatusBarColor(
activity,
ThemeStore.statusBarColor(activity, isTransparentStatusBar),
@ -132,14 +130,14 @@ object ATH {
fun setTint(
view: View,
@ColorInt color: Int,
isDark: Boolean = view.context.isNightTheme
isDark: Boolean = AppConfig.isNightTheme
) {
TintHelper.setTintAuto(view, color, false, isDark)
}
fun setBackgroundTint(
view: View, @ColorInt color: Int,
isDark: Boolean = view.context.isNightTheme
isDark: Boolean = AppConfig.isNightTheme
) {
TintHelper.setTintAuto(view, color, true, isDark)
}
@ -207,11 +205,10 @@ object ATH {
.setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create()
itemIconTintList = colorStateList
itemTextColor = colorStateList
itemBackground = ColorDrawable(ThemeStore.primaryColor(bottomBar.context))
/*itemBackgroundResource = when(context.isNightTheme) {
itemBackgroundResource = when (AppConfig.isNightTheme) {
true -> R.drawable.item_bg_dark
false -> R.drawable.item_bg_light
}*/
}
}
}

@ -9,10 +9,10 @@ import io.legado.app.base.BaseService
import io.legado.app.constant.Action
import io.legado.app.constant.AppConst
import io.legado.app.constant.Bus
import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.IntentHelp
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.threadCount
import io.legado.app.model.WebBook
import io.legado.app.utils.postEvent
import kotlinx.coroutines.Dispatchers.IO
@ -21,7 +21,7 @@ import java.util.concurrent.Executors
class DownloadService : BaseService() {
private var searchPool =
Executors.newFixedThreadPool(App.INSTANCE.threadCount).asCoroutineDispatcher()
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var tasks: ArrayList<Coroutine<*>> = arrayListOf()
private val handler = Handler()
private var runnable: Runnable = Runnable { upDownload() }

@ -4,14 +4,10 @@ import android.app.PendingIntent
import android.media.MediaPlayer
import io.legado.app.constant.Bus
import io.legado.app.data.api.IHttpPostApi
import io.legado.app.help.FileHelp
import io.legado.app.help.IntentHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.service.help.ReadBook
import io.legado.app.utils.LogUtils
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getPrefString
import io.legado.app.utils.postEvent
import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
@ -65,7 +61,7 @@ class HttpReadAloudService : BaseReadAloudService(),
private fun downloadAudio() {
job = launch(IO) {
FileHelp.deleteFile(ttsFolder)
FileUtils.deleteFile(ttsFolder)
for (index in 0 until contentList.size) {
if (isActive) {
val bytes = HttpHelper.getByteRetrofit("http://tts.baidu.com")
@ -104,7 +100,7 @@ class HttpReadAloudService : BaseReadAloudService(),
}
private fun getSpeakFile(index: Int = nowSpeak): File {
return FileHelp.getFile("${ttsFolder}${File.separator}${index}.mp3")
return FileUtils.getFile("${ttsFolder}${File.separator}${index}.mp3")
}
private fun getAudioBody(content: String): Map<String, String> {

@ -1 +1,24 @@
## 放置与界面有关的类
* about 关于界面
* audio 音频播放界面
* book\info 书籍信息查看
* book\read 书籍阅读界面
* book\search 搜索书籍界面
* book\source 搜索书源界面
* changecover 封面换源界面
* changesource 换源界面
* chapterlist 目录界面
* config 配置界面
* download 下载界面
* explore 发现界面
* filechooser 文件选择界面
* importbook 书籍导入界面
* main 主界面
* qrcode 二维码扫描界面
* replacerule 替换净化界面
* rss\article 订阅条目界面
* rss\read 订阅阅读界面
* rss\source 订阅源界面
* welcome 欢迎界面
* widget 自定义插件

@ -98,8 +98,8 @@ class BookInfoActivity :
return super.onCompatOptionsItemSelected(item)
}
override fun onMenuOpened(featureId: Int, menu: Menu?): Boolean {
menu?.findItem(R.id.menu_can_update)?.isChecked =
override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {
menu.findItem(R.id.menu_can_update)?.isChecked =
viewModel.bookData.value?.canUpdate ?: true
return super.onMenuOpened(featureId, menu)
}

@ -9,11 +9,11 @@ import android.view.Window
import android.view.WindowManager
import io.legado.app.App
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.ReadBookConfig
import io.legado.app.lib.theme.ATH
import io.legado.app.lib.theme.ThemeStore
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.isTransparentStatusBar
object Help {
@ -43,7 +43,7 @@ object Help {
} else {
ATH.setLightStatusBarAuto(
activity,
ThemeStore.statusBarColor(activity, activity.isTransparentStatusBar)
ThemeStore.statusBarColor(activity, AppConfig.isTransparentStatusBar)
)
}
}

@ -11,6 +11,7 @@ import androidx.core.view.isVisible
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.accentColor
import io.legado.app.lib.theme.buttonDisabledColor
import io.legado.app.service.help.ReadBook
@ -41,7 +42,7 @@ class ReadMenu : FrameLayout {
init {
callBack = activity as? CallBack
inflate(context, R.layout.view_read_menu, this)
if (context.isNightTheme) {
if (AppConfig.isNightTheme) {
fabNightTheme.setImageResource(R.drawable.ic_daytime)
} else {
fabNightTheme.setImageResource(R.drawable.ic_brightness)
@ -144,7 +145,7 @@ class ReadMenu : FrameLayout {
//夜间模式
fabNightTheme.onClick {
context.putPrefBoolean("isNightTheme", !context.isNightTheme)
AppConfig.isNightTheme = !AppConfig.isNightTheme
App.INSTANCE.applyDayNight()
}

@ -21,7 +21,6 @@ import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.constant.Bus
import io.legado.app.help.FileHelp
import io.legado.app.help.ImageLoader
import io.legado.app.help.ReadBookConfig
import io.legado.app.help.permission.Permissions
@ -174,7 +173,7 @@ class BgTextConfigDialog : DialogFragment() {
var file = requireContext().getExternalFilesDir(null)
?: requireContext().filesDir
file =
FileHelp.getFile(file.absolutePath + File.separator + "bg" + File.separator + doc.name)
FileUtils.getFile(file.absolutePath + File.separator + "bg" + File.separator + doc.name)
DocumentUtils.readBytes(requireContext(), uri)?.let {
file.writeBytes(it)
ReadBookConfig.getConfig().setBg(2, file.absolutePath)

@ -76,7 +76,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
!getPrefBoolean(PreferKey.precisionSearch)
)
precisionSearchMenuItem?.isChecked = getPrefBoolean(PreferKey.precisionSearch)
search_view.query.toString().trim()?.let {
search_view.query?.toString()?.trim()?.let {
search_view.setQuery(it, true)
}
}
@ -88,7 +88,7 @@ class SearchActivity : VMBaseActivity<SearchViewModel>(R.layout.activity_book_se
} else {
putPrefString("searchGroup", item.title.toString())
}
search_view.query.toString().trim()?.let {
search_view.query?.toString()?.trim()?.let {
search_view.setQuery(it, true)
}
}

@ -6,8 +6,8 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.SearchBook
import io.legado.app.data.entities.SearchKeyword
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.threadCount
import io.legado.app.model.WebBook
import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.getPrefString
@ -18,7 +18,7 @@ import java.util.concurrent.Executors
class SearchViewModel(application: Application) : BaseViewModel(application) {
private var searchPool =
Executors.newFixedThreadPool(context.threadCount).asCoroutineDispatcher()
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
private var task: Coroutine<*>? = null
var callBack: CallBack? = null
var searchKey: String = ""

@ -7,7 +7,6 @@ import io.legado.app.App
import io.legado.app.base.BaseViewModel
import io.legado.app.data.api.IHttpGetApi
import io.legado.app.data.entities.BookSource
import io.legado.app.help.FileHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.OldRule
@ -92,7 +91,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
}.let {
val json = GSON.toJson(it)
val file =
FileHelp.getFile(Backup.exportPath + File.separator + "exportBookSource.json")
FileUtils.getFile(Backup.exportPath + File.separator + "exportBookSource.json")
file.writeText(json)
}
}.onSuccess {

@ -8,8 +8,8 @@ import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.threadCount
import io.legado.app.model.WebBook
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
@ -20,7 +20,7 @@ import java.util.concurrent.Executors
class ChangeSourceViewModel(application: Application) : BaseViewModel(application) {
private var searchPool =
Executors.newFixedThreadPool(context.threadCount).asCoroutineDispatcher()
Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher()
var callBack: CallBack? = null
val searchStateData = MutableLiveData<Boolean>()
var name: String = ""

@ -1,6 +1,8 @@
package io.legado.app.ui.config
import android.app.Activity.RESULT_OK
import android.content.ComponentName
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.Bundle
@ -12,8 +14,10 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.Bus
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.BookHelp
import io.legado.app.help.threadCount
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.lib.theme.ATH
import io.legado.app.receiver.SharedReceiverActivity
import io.legado.app.ui.filechooser.FileChooserDialog
@ -26,7 +30,7 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener,
SharedPreferences.OnSharedPreferenceChangeListener {
private val downloadPath = 25324
private val requestCodeDownloadPath = 25324
private val packageManager = App.INSTANCE.packageManager
private val componentName = ComponentName(
App.INSTANCE,
@ -61,18 +65,13 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
.setTitle(getString(R.string.threads_num_title))
.setMaxValue(999)
.setMinValue(1)
.setValue(requireContext().threadCount)
.setValue(AppConfig.threadCount)
.show {
requireContext().putPrefInt(PreferKey.threadCount, it)
findPreference<Preference>(PreferKey.threadCount)?.summary =
getString(R.string.threads_num, it.toString())
}
PreferKey.downloadPath -> FileChooserDialog.show(
childFragmentManager,
downloadPath,
mode = FileChooserDialog.DIRECTORY,
initPath = getPreferenceString(PreferKey.downloadPath).toString()
)
PreferKey.downloadPath -> selectDownloadPathSys()
PreferKey.cleanCache -> {
BookHelp.clearCache()
toast(R.string.clear_cache_success)
@ -125,7 +124,7 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
PreferKey.downloadPath -> getPrefString(PreferKey.downloadPath)
?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
PreferKey.threadCount -> requireContext().threadCount
PreferKey.threadCount -> AppConfig.threadCount
else -> getPrefString(key) ?: ""
}
}
@ -148,7 +147,49 @@ class OtherConfigFragment : PreferenceFragmentCompat(),
}
}
private fun selectDownloadPathSys() {
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivityForResult(intent, requestCodeDownloadPath)
} catch (e: Exception) {
selectDownloadPath()
}
}
private fun selectDownloadPath() {
PermissionsCompat.Builder(this)
.addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage)
.onGranted {
FileChooserDialog.show(
childFragmentManager,
requestCodeDownloadPath,
mode = FileChooserDialog.DIRECTORY,
initPath = getPreferenceString(PreferKey.downloadPath).toString()
)
}
.request()
}
override fun onFilePicked(requestCode: Int, currentPath: String) {
if (requestCode == requestCodeDownloadPath) {
putPrefString(PreferKey.downloadPath, currentPath)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
requestCodeDownloadPath -> if (resultCode == RESULT_OK) {
data?.data?.let { uri ->
requireContext().contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
putPrefString(PreferKey.downloadPath, uri.toString())
}
}
}
}
}

@ -10,6 +10,7 @@ import androidx.preference.PreferenceFragmentCompat
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.Bus
import io.legado.app.help.AppConfig
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.dialogs.noButton
import io.legado.app.lib.dialogs.yesButton
@ -110,31 +111,37 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
putPrefInt("colorPrimary", getCompatColor(R.color.md_grey_100))
putPrefInt("colorAccent", getCompatColor(R.color.lightBlue_color))
putPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
putPrefBoolean("isNightTheme", false)
AppConfig.isNightTheme = false
}
1 -> {
putPrefInt("colorPrimaryNight", getCompatColor(R.color.shine_color))
putPrefInt("colorAccentNight", getCompatColor(R.color.lightBlue_color))
putPrefInt("colorBackgroundNight", getCompatColor(R.color.shine_color))
putPrefBoolean("isNightTheme", true)
AppConfig.isNightTheme = true
}
2 -> {
putPrefInt("colorPrimary", getCompatColor(R.color.md_light_blue_500))
putPrefInt("colorAccent", getCompatColor(R.color.md_pink_800))
putPrefInt("colorBackground", getCompatColor(R.color.md_grey_100))
putPrefBoolean("isNightTheme", false)
AppConfig.isNightTheme = false
}
3 -> {
putPrefInt("colorPrimary", getCompatColor(R.color.white))
putPrefInt("colorAccent", getCompatColor(R.color.black))
putPrefInt("colorBackground", getCompatColor(R.color.white))
putPrefBoolean("isNightTheme", false)
AppConfig.isNightTheme = false
}
4 -> {
putPrefInt("colorPrimaryNight", getCompatColor(R.color.black))
putPrefInt("colorAccentNight", getCompatColor(R.color.md_grey_600))
putPrefInt("colorBackgroundNight", getCompatColor(R.color.black))
putPrefBoolean("isNightTheme", true)
putPrefInt(
"colorAccentNight",
getCompatColor(R.color.md_grey_600)
)
putPrefInt(
"colorBackgroundNight",
getCompatColor(R.color.black)
)
AppConfig.isNightTheme = true
}
}
App.INSTANCE.applyDayNight()
@ -166,7 +173,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar
}
private fun upTheme(isNightTheme: Boolean) {
if (this.isNightTheme == isNightTheme) {
if (AppConfig.isNightTheme == isNightTheme) {
App.INSTANCE.applyTheme()
recreateActivities()
}

@ -16,8 +16,8 @@ import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.Bus
import io.legado.app.constant.PreferKey
import io.legado.app.help.AppConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.help.isShowRSS
import io.legado.app.help.storage.Backup
import io.legado.app.lib.theme.ATH
import io.legado.app.service.BaseReadAloudService
@ -47,7 +47,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
view_pager_main.adapter = TabFragmentPageAdapter(supportFragmentManager)
view_pager_main.addOnPageChangeListener(this)
bottom_navigation_view.setOnNavigationItemSelectedListener(this)
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = isShowRSS
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
upVersion()
}
@ -68,10 +68,10 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
fragmentList.add(RssFragment())
fragmentList.add(MyFragment())
}
if (isShowRSS && fragmentList.size < 4) {
if (AppConfig.isShowRSS && fragmentList.size < 4) {
fragmentList.add(2, RssFragment())
}
if (!isShowRSS && fragmentList.size == 4) {
if (!AppConfig.isShowRSS && fragmentList.size == 4) {
fragmentList.removeAt(2)
}
}
@ -89,7 +89,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
pagePosition = position
when (position) {
0, 1, 3 -> bottom_navigation_view.menu.getItem(position).isChecked = true
2 -> if (isShowRSS) {
2 -> if (AppConfig.isShowRSS) {
bottom_navigation_view.menu.getItem(position).isChecked = true
} else {
bottom_navigation_view.menu.getItem(3).isChecked = true
@ -147,10 +147,10 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
recreate()
}
observeEvent<String>(Bus.SHOW_RSS) {
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = isShowRSS
bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS
upFragmentList()
view_pager_main.adapter?.notifyDataSetChanged()
if (isShowRSS) {
if (AppConfig.isShowRSS) {
view_pager_main.setCurrentItem(3, false)
}
}
@ -168,7 +168,7 @@ class MainActivity : VMBaseActivity<MainViewModel>(R.layout.activity_main),
}
override fun getCount(): Int {
return if (isShowRSS) 4 else 3
return if (AppConfig.isShowRSS) 4 else 3
}
}

@ -200,7 +200,7 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
"isNightTheme" -> App.INSTANCE.applyDayNight()
"themeMode" -> App.INSTANCE.applyDayNight()
"webService" -> {
if (requireContext().getPrefBoolean("webService")) {
WebService.start(requireContext())

@ -6,9 +6,9 @@ import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.FileHelp
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore
import io.legado.app.utils.FileUtils
import io.legado.app.utils.GSON
import io.legado.app.utils.splitNotBlank
import org.jetbrains.anko.toast
@ -80,7 +80,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
}.let {
val json = GSON.toJson(it)
val file =
FileHelp.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json")
FileUtils.getFile(Backup.exportPath + File.separator + "exportReplaceRule.json")
file.writeText(json)
}
}.onSuccess {

@ -149,7 +149,7 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
webView.settings.javaScriptEnabled = true
webView.evaluateJavascript("document.documentElement.outerHTML") {
val html = StringEscapeUtils.unescapeJson(it)
viewModel.readAloud(Jsoup.clean(html, Whitelist()))
viewModel.readAloud(Jsoup.clean(html, Whitelist.none()))
}
}
}

@ -85,17 +85,12 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
}
fun clHtml(content: String): String {
return if (content.contains("<style>|style=".toRegex())) {
content
} else {
"""
<style>
img{max-width:100% !important; width:auto; height:auto;}
video{object-fit:fill; max-width:100% !important; width:auto; height:auto;}
return """<style>
img{max-width:100%; width:auto; height:auto;}
video{object-fit:fill; max-width:100%; width:auto; height:auto;}
body{word-wrap:break-word; height:auto;max-width: 100%; width:auto;}
</style>$content
"""
}
</style>
$content"""
}
override fun onInit(status: Int) {

@ -4,10 +4,10 @@ import android.app.Application
import android.text.TextUtils
import com.jayway.jsonpath.JsonPath
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.BaseViewModel
import io.legado.app.data.api.IHttpGetApi
import io.legado.app.data.entities.RssSource
import io.legado.app.help.FileHelp
import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore.jsonPath
@ -65,7 +65,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
App.db.rssSourceDao().getRssSources(*ids.toTypedArray()).let {
val json = GSON.toJson(it)
val file =
FileHelp.getFile(Backup.exportPath + File.separator + "exportRssSource.json")
FileUtils.getFile(Backup.exportPath + File.separator + "exportRssSource.json")
file.writeText(json)
}
}.onSuccess {
@ -123,6 +123,8 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
App.db.rssSourceDao().insert(*it.toTypedArray())
}
}
}.onSuccess {
finally.invoke(context.getString(R.string.success))
}
}

@ -5,7 +5,6 @@ import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.MenuItem
@ -18,13 +17,9 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App
import io.legado.app.R
import io.legado.app.constant.PreferKey
import io.legado.app.help.FileHelp
import io.legado.app.help.permission.Permissions
import io.legado.app.help.permission.PermissionsCompat
import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.getPrefString
import io.legado.app.utils.putPrefString
import io.legado.app.utils.toast
import io.legado.app.utils.*
import kotlinx.android.synthetic.main.dialog_font_select.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
@ -127,11 +122,11 @@ class FontSelectDialog : DialogFragment(),
@SuppressLint("DefaultLocale")
private fun getFontFiles(uri: Uri) {
launch(IO) {
FileHelp.deleteFile(fontCacheFolder)
FileUtils.deleteFile(fontCacheFolder)
DocumentFile.fromTreeUri(App.INSTANCE, uri)?.listFiles()?.forEach { file ->
if (file.name?.toLowerCase()?.matches(".*\\.[ot]tf".toRegex()) == true) {
DocumentUtils.readBytes(App.INSTANCE, file.uri)?.let {
FileHelp.getFile(fontCacheFolder + file.name).writeBytes(it)
FileUtils.getFile(fontCacheFolder + file.name).writeBytes(it)
}
}
}
@ -154,7 +149,7 @@ class FontSelectDialog : DialogFragment(),
private fun getFontFilesOld() {
try {
val file =
File(Environment.getExternalStorageDirectory().absolutePath + File.separator + "Fonts")
File(FileUtils.getSdCardPath() + File.separator + "Fonts")
file.listFiles { pathName ->
pathName.name.toLowerCase().matches(".*\\.[ot]tf".toRegex())
}?.let {
@ -167,7 +162,7 @@ class FontSelectDialog : DialogFragment(),
override fun onClick(file: File) {
launch(IO) {
file.copyTo(FileHelp.getFile(fontFolder + file.name), true).absolutePath.let { path ->
file.copyTo(FileUtils.getFile(fontFolder + file.name), true).absolutePath.let { path ->
val cb = (parentFragment as? CallBack) ?: (activity as? CallBack)
cb?.let {
if (it.curFontPath != path) {

@ -171,7 +171,7 @@ class ACache private constructor(cacheDir: File, max_size: Long, max_count: Int)
* @return JSONObject数据
*/
fun getAsJSONObject(key: String): JSONObject? {
val json = getAsString(key)
val json = getAsString(key) ?: return null
return try {
JSONObject(json)
} catch (e: Exception) {
@ -311,17 +311,17 @@ class ACache private constructor(cacheDir: File, max_size: Long, max_count: Int)
fun getAsObject(key: String): Any? {
val data = getAsBinary(key)
if (data != null) {
var bais: ByteArrayInputStream? = null
var bis: ByteArrayInputStream? = null
var ois: ObjectInputStream? = null
try {
bais = ByteArrayInputStream(data)
ois = ObjectInputStream(bais)
bis = ByteArrayInputStream(data)
ois = ObjectInputStream(bis)
return ois.readObject()
} catch (e: Exception) {
e.printStackTrace()
} finally {
try {
bais?.close()
bis?.close()
} catch (e: IOException) {
e.printStackTrace()
}
@ -597,6 +597,7 @@ class ACache private constructor(cacheDir: File, max_size: Long, max_count: Int)
val w = drawable.intrinsicWidth
val h = drawable.intrinsicHeight
// 取 drawable 的颜色格式
@Suppress("DEPRECATION")
val config = if (drawable.opacity != PixelFormat.OPAQUE)
Bitmap.Config.ARGB_8888
else

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.provider.Settings
@ -126,11 +127,7 @@ fun Context.shareWithQr(title: String, text: String) {
}
}
val Context.isNightTheme: Boolean
get() = getPrefBoolean("isNightTheme")
val Context.isEInkMode: Boolean
get() = getPrefBoolean("isEInkMode")
val Context.isTransparentStatusBar: Boolean
get() = getPrefBoolean("transparentStatusBar", true)
fun Context.sysIsDarkMode(): Boolean {
val mode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return mode == Configuration.UI_MODE_NIGHT_YES
}

@ -2,9 +2,44 @@ package io.legado.app.utils
import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
object DocumentUtils {
fun exists(root: DocumentFile, fileName: String, vararg subDirs: String): Boolean {
val parent = getDirDocument(root, *subDirs) ?: return false
return parent.findFile(fileName)?.exists() ?: false
}
fun createFileIfNotExist(
root: DocumentFile,
fileName: String,
mimeType: String = "",
vararg subDirs: String
): DocumentFile? {
val parent: DocumentFile? = createFileIfNotExist(root, *subDirs)
return parent?.createFile(mimeType, fileName)
}
fun createFileIfNotExist(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent: DocumentFile? = root
for (subDirName in subDirs) {
val subDir = parent?.findFile(subDirName)
?: parent?.createDirectory(subDirName)
parent = subDir
}
return parent
}
fun getDirDocument(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent = root
for (subDirName in subDirs) {
val subDir = parent.findFile(subDirName)
parent = subDir ?: return null
}
return parent
}
@JvmStatic
@Throws(Exception::class)
fun writeText(context: Context, data: String, fileUri: Uri): Boolean {

@ -4,86 +4,92 @@ import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Environment
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.util.Log
import androidx.core.content.ContextCompat
import io.legado.app.App
import java.io.File
import java.io.IOException
import java.lang.reflect.Array
import java.util.*
object FileUtils {
fun getFileByPath(filePath: String): File? {
return if (filePath.isBlank()) null else File(filePath)
fun exists(file: File, fileName: String, vararg subDirs: String): Boolean {
val filePath =
file.absolutePath + File.separator + subDirs.joinToString(File.separator) + File.separator + fileName
return File(filePath).exists()
}
fun getSdCardPath(): String {
var sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath
try {
sdCardDirectory = File(sdCardDirectory).canonicalPath
} catch (ioe: IOException) {
ioe.printStackTrace()
fun createFileIfNotExist(file: File, fileName: String, vararg subDirs: String): File {
val filePath =
file.absolutePath + File.separator + subDirs.joinToString(File.separator) + File.separator + fileName
return getFile(filePath)
}
return sdCardDirectory
fun createFileIfNotExist(file: File, vararg subDirs: String): File {
val filePath = file.absolutePath + File.separator + subDirs.joinToString(File.separator)
return getFolder(filePath)
}
fun getStorageData(pContext: Context): ArrayList<String>? {
fun getCachePath(): String {
return App.INSTANCE.externalCacheDir?.absolutePath
?: App.INSTANCE.cacheDir.absolutePath
}
val storageManager = pContext.getSystemService(Context.STORAGE_SERVICE) as StorageManager
//获取文件夹
fun getFolder(filePath: String): File {
val file = File(filePath)
//如果文件夹不存在,就创建它
if (!file.exists()) {
file.mkdirs()
}
return file
}
//获取文件
@Synchronized
fun getFile(filePath: String): File {
val file = File(filePath)
try {
val getVolumeList = storageManager.javaClass.getMethod("getVolumeList")
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
val getPath = storageVolumeClazz.getMethod("getPath")
val invokeVolumeList = getVolumeList.invoke(storageManager)
val length = Array.getLength(invokeVolumeList)
val list = ArrayList<String>()
for (i in 0 until length) {
val storageVolume = Array.get(invokeVolumeList, i)//得到StorageVolume对象
val path = getPath.invoke(storageVolume) as String
list.add(path)
if (!file.exists()) {
//创建父类文件夹
file.parent?.let {
getFolder(it)
}
return list
} catch (e: Exception) {
//创建文件
file.createNewFile()
}
} catch (e: IOException) {
e.printStackTrace()
}
return null
return file
}
//递归删除文件夹下的数据
@Synchronized
fun deleteFile(filePath: String) {
val file = File(filePath)
if (!file.exists()) return
fun getExtSdCardPaths(con: Context): ArrayList<String> {
val paths = ArrayList<String>()
val files = ContextCompat.getExternalFilesDirs(con, "external")
val firstFile = files[0]
for (file in files) {
if (file != null && file != firstFile) {
val index = file.absolutePath.lastIndexOf("/Android/data")
if (index < 0) {
Log.w("", "Unexpected external file dir: " + file.absolutePath)
} else {
var path = file.absolutePath.substring(0, index)
try {
path = File(path).canonicalPath
} catch (e: IOException) {
// Keep non-canonical path.
if (file.isDirectory) {
val files = file.listFiles()
files?.forEach { subFile ->
val path = subFile.path
deleteFile(path)
}
paths.add(path)
}
//删除文件
file.delete()
}
fun getSdCardPath(): String {
@Suppress("DEPRECATION")
var sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath
try {
sdCardDirectory = File(sdCardDirectory).canonicalPath
} catch (ioe: IOException) {
ioe.printStackTrace()
}
return paths
return sdCardDirectory
}
fun getPath(context: Context, uri: Uri): String? {
@ -96,6 +102,7 @@ object FileUtils {
val type = split[0]
if ("primary".equals(type, ignoreCase = true)) {
@Suppress("DEPRECATION")
return Environment.getExternalStorageDirectory().toString() + "/" + split[1]
}
@ -145,7 +152,7 @@ object FileUtils {
private fun getDataColumn(
context: Context, uri: Uri, selection: String?,
selectionArgs: kotlin.Array<String>?
selectionArgs: Array<String>?
): String? {
val column = "_data"

@ -54,17 +54,9 @@ fun Fragment.getCompatDrawable(@DrawableRes id: Int): Drawable? = requireContext
fun Fragment.getCompatColorStateList(@ColorRes id: Int): ColorStateList? = requireContext().getCompatColorStateList(id)
val Fragment.isNightTheme: Boolean
get() = getPrefBoolean("isNightTheme")
val Fragment.isTransparentStatusBar: Boolean
get() = getPrefBoolean("transparentStatusBar")
inline fun <reified T : Activity> Fragment.startActivity(vararg params: Pair<String, Any?>) =
AnkoInternals.internalStartActivity(requireActivity(), T::class.java, params)
inline fun <reified T : Activity> Fragment.startActivityForResult(requestCode: Int, vararg params: Pair<String, Any?>) =
startActivityForResult(AnkoInternals.createIntent(requireActivity(), T::class.java, params), requestCode)

@ -2,7 +2,6 @@ package io.legado.app.utils
import android.annotation.SuppressLint
import io.legado.app.App
import io.legado.app.help.FileHelp
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
@ -30,8 +29,8 @@ object LogUtils {
}
private val fileHandler by lazy {
val logFolder = FileHelp.getCachePath() + File.separator + "logs"
FileHelp.getFolder(logFolder)
val logFolder = FileUtils.getCachePath() + File.separator + "logs"
FileUtils.getFolder(logFolder)
FileHandler(
logFolder + File.separator + "app.log",
10240,

@ -4,10 +4,14 @@ package io.legado.app.utils
fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()
fun String?.isAbsUrl() = if (this.isNullOrBlank()) false else this.startsWith("http://", true)
|| this.startsWith("https://", true)
fun String?.isAbsUrl() =
this?.let {
it.startsWith("http://", true)
|| it.startsWith("https://", true)
} ?: false
fun String?.isJson(): Boolean = this?.run {
fun String?.isJson(): Boolean =
this?.run {
val str = this.trim()
when {
str.startsWith("{") && str.endsWith("}") -> true
@ -16,22 +20,25 @@ fun String?.isJson(): Boolean = this?.run {
}
} ?: false
fun String?.isJsonObject(): Boolean = this?.run {
fun String?.isJsonObject(): Boolean =
this?.run {
val str = this.trim()
str.startsWith("{") && str.endsWith("}")
} ?: false
fun String?.isJsonArray(): Boolean = this?.run {
fun String?.isJsonArray(): Boolean =
this?.run {
val str = this.trim()
str.startsWith("[") && str.endsWith("]")
} ?: false
fun String?.htmlFormat(): String = if (this.isNullOrBlank()) "" else
this.replace("(?i)<(br[\\s/]*|/*p\\b.*?|/*div\\b.*?)>".toRegex(), "\n")// 替换特定标签为换行符
.replace("<[script>]*.*?>|&nbsp;".toRegex(), "")// 删除script标签对和空格转义符
.replace("\\s*\\n+\\s*".toRegex(), "\n  ")// 移除空行,并增加段前缩进2个汉字
.replace("^[\\n\\s]+".toRegex(), "  ")//移除开头空行,并增加段前缩进2个汉字
.replace("[\\n\\s]+$".toRegex(), "") //移除尾部空行
fun String?.htmlFormat(): String =
this?.replace("(?i)<(br[\\s/]*|/*p\\b.*?|/*div\\b.*?)>".toRegex(), "\n")
?.replace("<[script>]*.*?>|&nbsp;".toRegex(), "")
?.replace("\\s*\\n+\\s*".toRegex(), "\n  ")
?.replace("^[\\n\\s]+".toRegex(), "  ")
?.replace("[\\n\\s]+$".toRegex(), "")
?: ""
fun String.splitNotBlank(vararg delimiter: String): Array<String> = run {
this.split(*delimiter).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray()

@ -41,16 +41,18 @@ object StringUtils {
//将时间转换成日期
fun dateConvert(time: Long, pattern: String): String {
val date = Date(time)
@SuppressLint("SimpleDateFormat") val format = SimpleDateFormat(pattern)
@SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(pattern)
return format.format(date)
}
//将日期转换成昨天、今天、明天
fun dateConvert(source: String, pattern: String): String {
@SuppressLint("SimpleDateFormat") val format = SimpleDateFormat(pattern)
@SuppressLint("SimpleDateFormat")
val format = SimpleDateFormat(pattern)
val calendar = Calendar.getInstance()
try {
val date = format.parse(source)
val date = format.parse(source) ?: return ""
val curTime = calendar.timeInMillis
calendar.time = date
//将MISC 转换成 sec
@ -62,13 +64,18 @@ object StringUtils {
//如果没有时间
if (oldHour == 0) {
//比日期:昨天今天和明天
if (difDate == 0L) {
return "今天"
} else if (difDate < DAY_OF_YESTERDAY) {
return "昨天"
} else {
@SuppressLint("SimpleDateFormat") val convertFormat = SimpleDateFormat("yyyy-MM-dd")
return convertFormat.format(date)
return when {
difDate == 0L -> {
"今天"
}
difDate < DAY_OF_YESTERDAY -> {
"昨天"
}
else -> {
@SuppressLint("SimpleDateFormat")
val convertFormat = SimpleDateFormat("yyyy-MM-dd")
convertFormat.format(date)
}
}
}
@ -78,7 +85,8 @@ object StringUtils {
difHour < HOUR_OF_DAY -> difHour.toString() + "小时前"
difDate < DAY_OF_YESTERDAY -> "昨天"
else -> {
@SuppressLint("SimpleDateFormat") val convertFormat = SimpleDateFormat("yyyy-MM-dd")
@SuppressLint("SimpleDateFormat")
val convertFormat = SimpleDateFormat("yyyy-MM-dd")
convertFormat.format(date)
}
}
@ -242,7 +250,7 @@ object StringUtils {
val m = p.matcher(data)
val buf = StringBuffer(data.length)
while (m.find()) {
val ch = Integer.parseInt(m.group(1), 16).toChar().toString()
val ch = Integer.parseInt(m.group(1)!!, 16).toChar().toString()
m.appendReplacement(buf, Matcher.quoteReplacement(ch))
}
m.appendTail(buf)

@ -5,6 +5,9 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import java.io.File
fun Uri.isDocumentUri(context: Context): Boolean {
return DocumentFile.isDocumentUri(context, this)
}
@Throws(Exception::class)
fun Uri.readBytes(context: Context): ByteArray? {

@ -24,7 +24,7 @@ private tailrec fun getCompatActivity(context: Context?): AppCompatActivity? {
val View.activity: AppCompatActivity?
get() = getCompatActivity(context)
inline fun View.hideSoftInput() = run {
fun View.hideSoftInput() = run {
val imm = App.INSTANCE.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.let {
imm.hideSoftInputFromWindow(this.windowToken, 0)

@ -57,7 +57,7 @@ object ZipUtils {
}
return true
} finally {
if (zos != null) {
zos?.let {
zos.finish()
zos.close()
}
@ -89,7 +89,7 @@ object ZipUtils {
}
return true
} finally {
if (zos != null) {
zos?.let {
zos.finish()
zos.close()
}
@ -261,7 +261,7 @@ object ZipUtils {
val files = ArrayList<File>()
val zip = ZipFile(zipFile)
val entries = zip.entries()
try {
zip.use {
if (isSpace(keyword)) {
while (entries.hasMoreElements()) {
val entry = entries.nextElement() as ZipEntry
@ -285,8 +285,6 @@ object ZipUtils {
}
}
}
} finally {
zip.close()
}
return files
}

@ -63,6 +63,18 @@
<item>@string/jf_convert_f</item>
</string-array>
<string-array name="theme_mode">
<item>跟随系统</item>
<item>亮色主题</item>
<item>暗色主题</item>
</string-array>
<string-array name="theme_mode_v">
<item>0</item>
<item>1</item>
<item>2</item>
</string-array>
<string-array name="NavBarColors">
<item>自动</item>
<item>黑色</item>

@ -433,6 +433,7 @@
<string name="scan_qr_code">扫描二维码</string>
<string name="click_on_selected_show_menu">选中时点击可弹出菜单</string>
<string name="theme">主题</string>
<string name="theme_mode">主题模式</string>
<string name="default_theme">默认主题</string>
<string name="restore_default_theme">恢复主题为默认配色</string>
<string name="join_qq_group">加入QQ群</string>
@ -583,5 +584,7 @@
<string name="text_too_long_qr_error">文字太多,生成二维码失败</string>
<string name="share_rss_source">分享RSS源</string>
<string name="share_book_source">分享书源</string>
<string name="auto_dark_mode">自动切换夜间模式</string>
<string name="auto_dark_mode_s">夜间模式跟随系统</string>
</resources>

@ -2,7 +2,6 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<io.legado.app.lib.theme.prefs.ATESwitchPreference
android:defaultValue="true"
android:key="transparentStatusBar"

@ -12,10 +12,18 @@
android:title="@string/replace_purify"
app:iconSpaceReserved="false" />
<io.legado.app.lib.theme.prefs.ATEListPreference
android:defaultValue="0"
android:entries="@array/theme_mode"
android:entryValues="@array/theme_mode_v"
android:key="themeMode"
android:title="@string/theme_mode"
app:iconSpaceReserved="false" />
<io.legado.app.lib.theme.prefs.ATESwitchPreference
android:defaultValue="false"
android:key="isNightTheme"
android:title="@string/night_theme"
android:key="isEInkMode"
android:title="@string/eink_theme"
app:iconSpaceReserved="false" />
<io.legado.app.lib.theme.prefs.ATESwitchPreference

Loading…
Cancel
Save