diff --git a/app/build.gradle b/app/build.gradle index 633b8ba1b..7dadb803b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -136,13 +136,12 @@ dependencies { implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" //room - def room_version = '2.2.4' + def room_version = '2.2.5' implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" - kapt "android.arch.persistence.room:compiler:$room_version" //paging - implementation 'androidx.paging:paging-runtime:2.1.1' + implementation 'androidx.paging:paging-runtime:2.1.2' //anko def anko_version = '0.10.8' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 71fa00c62..a8ae915ff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -23,6 +23,7 @@ android:label="@string/app_name" android:networkSecurityConfig="@xml/network_security_config" android:supportsRtl="true" + android:requestLegacyExternalStorage="true" android:theme="@style/AppTheme.Light" tools:ignore="AllowBackup,GoogleAppIndexingWarning,UnusedAttribute"> @@ -78,6 +79,48 @@ android:resource="@xml/shortcuts" android:launchMode="singleTask" /> + + + + + + + + + + + + + + + + + + + + + + + + + android:launchMode="singleTop" /> 【备份与恢复】,选择【导入旧版本数据】。 -* 请关注[开源阅读软件]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 +* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 + +**2020/03/21** +* 详情页点击书名搜索 + +**2020/03/20** +* 自动备份文件和手动备份文件分开 +* 修复一些rss收藏取消不了的bug +* 修复rss请求头无效bug + +**2020/03/19** +* 美化界面我的 by yangyxd +* 优化搜索 + +**2020/03/18** +* 尝试修复搜索时崩溃 +* 解决看过书籍的移到顶部需要向上滚动才能看到的bug +* 只有再书源被删除找不到书源时才会自动换源 +* 美化界面 by yangyxd +* 订阅后台播放 + +**2020/03/16** +* 修复滚动模式切换章节位置不归0的bug +* 修复文字选择更多菜单在部分手机上报错的bug +* 修复文字选择菜单问题 + +**2020/03/15** +* 加载正文无书源时自动换源 + +**2020/03/14** +* 修改导航栏图标 + +**2020/03/13** +* 更改书架控件,ViewPager2替换回2.0使用的ViewPager,解决下拉不流畅问题 +* 修复点击作者搜索后,打开的详情页还是原来的书籍的bug +* 修改朗读菜单 +* 优化rss朗读 + +**2020/03/12** +* 导入本地添加需要权限模式 **2020/03/11** * 修复调节上边距时下边距一起动的bug * 适配沚水的web阅读 by 六月 * 分组管理页面调整 by yangyxd -* 导入本地添加需要权限模式 **2020/03/10** * 优化文字选择菜单弹出位置 diff --git a/app/src/main/java/io/legado/app/constant/EventBus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt index 88031b4fc..e24ba8000 100644 --- a/app/src/main/java/io/legado/app/constant/EventBus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -11,7 +11,6 @@ object EventBus { const val TIME_CHANGED = "timeChanged" const val UP_CONFIG = "upConfig" const val OPEN_CHAPTER = "openChapter" - const val REPLACE = "replace" const val AUDIO_SUB_TITLE = "audioSubTitle" const val AUDIO_STATE = "audioState" const val AUDIO_PROGRESS = "audioProgress" diff --git a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt index 9ce5c65be..55d5ada4c 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt @@ -50,9 +50,12 @@ interface BookSourceDao { @get:Query("select * from book_sources order by customOrder asc") val all: List - @get:Query("select * from book_sources where enabled = 1 order by customOrder asc") + @get:Query("select * from book_sources where enabled = 1 order by customOrder") val allEnabled: List + @get:Query("select * from book_sources where enabled = 1 and bookSourceType = 0 order by customOrder") + val allTextEnabled: List + @Query("select * from book_sources where bookSourceUrl = :key") fun getBookSource(key: String): BookSource? diff --git a/app/src/main/java/io/legado/app/data/entities/Book.kt b/app/src/main/java/io/legado/app/data/entities/Book.kt index 833561f1b..0dd5cab9e 100644 --- a/app/src/main/java/io/legado/app/data/entities/Book.kt +++ b/app/src/main/java/io/legado/app/data/entities/Book.kt @@ -31,7 +31,7 @@ data class Book( var intro: String? = null, // 简介内容(书源获取) var customIntro: String? = null, // 简介内容(用户修改) var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍) - var type: Int = 0, // @BookType + var type: Int = 0, // 0:text 1:audio var group: Int = 0, // 自定义分组索引号 var latestChapterTitle: String? = null, // 最新章节标题 var latestChapterTime: Long = System.currentTimeMillis(), // 最新章节标题更新时间 diff --git a/app/src/main/java/io/legado/app/data/entities/RssStar.kt b/app/src/main/java/io/legado/app/data/entities/RssStar.kt index 5199b586d..1d0c266f8 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssStar.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssStar.kt @@ -16,4 +16,16 @@ data class RssStar( var description: String? = null, var content: String? = null, var image: String? = null -) \ No newline at end of file +) { + fun toRssArticle(): RssArticle { + return RssArticle( + origin = origin, + title = title, + link = link, + pubDate = pubDate, + description = description, + content = content, + image = image + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt index 76bcd6116..b859336e4 100644 --- a/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt +++ b/app/src/main/java/io/legado/app/help/LauncherIconHelp.kt @@ -5,10 +5,7 @@ import android.content.pm.PackageManager import android.os.Build import io.legado.app.App import io.legado.app.R -import io.legado.app.ui.welcome.Launcher1 -import io.legado.app.ui.welcome.Launcher2 -import io.legado.app.ui.welcome.Launcher3 -import io.legado.app.ui.welcome.WelcomeActivity +import io.legado.app.ui.welcome.* import org.jetbrains.anko.toast /** @@ -20,7 +17,10 @@ object LauncherIconHelp { private val componentNames = arrayListOf( ComponentName(App.INSTANCE, Launcher1::class.java.name), ComponentName(App.INSTANCE, Launcher2::class.java.name), - ComponentName(App.INSTANCE, Launcher3::class.java.name) + ComponentName(App.INSTANCE, Launcher3::class.java.name), + ComponentName(App.INSTANCE, Launcher4::class.java.name), + ComponentName(App.INSTANCE, Launcher5::class.java.name), + ComponentName(App.INSTANCE, Launcher6::class.java.name) ) fun changeIcon(icon: String?) { diff --git a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt index e9fd0df86..0811eee10 100644 --- a/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt +++ b/app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt @@ -6,7 +6,7 @@ import kotlin.coroutines.CoroutineContext class Coroutine( - scope: CoroutineScope, + val scope: CoroutineScope, context: CoroutineContext = Dispatchers.IO, block: suspend CoroutineScope.() -> T ) { @@ -28,9 +28,10 @@ class Coroutine( private val job: Job private var start: VoidCallback? = null - private var success: Callback? = null + private var success: Callback? = null private var error: Callback? = null private var finally: VoidCallback? = null + private var cancel: VoidCallback? = null private var timeMillis: Long? = null private var errorReturn: Result? = null @@ -45,7 +46,7 @@ class Coroutine( get() = job.isCompleted init { - this.job = executeInternal(scope, context, block) + this.job = executeInternal(context, block) } fun timeout(timeMillis: () -> Long): Coroutine { @@ -78,7 +79,7 @@ class Coroutine( fun onSuccess( context: CoroutineContext? = null, - block: suspend CoroutineScope.(T?) -> Unit + block: suspend CoroutineScope.(T) -> Unit ): Coroutine { this.success = Callback(context, block) return this@Coroutine @@ -100,9 +101,28 @@ class Coroutine( return this@Coroutine } + fun onCancel( + context: CoroutineContext? = null, + block: suspend CoroutineScope.() -> Unit + ): Coroutine { + this.cancel = VoidCallback(context, block) + return this@Coroutine + } + //取消当前任务 fun cancel(cause: CancellationException? = null) { job.cancel(cause) + cancel?.let { + MainScope().launch { + if (null == it.context) { + it.block.invoke(scope) + } else { + withContext(scope.coroutineContext.plus(it.context)) { + it.block.invoke(this) + } + } + } + } } fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle { @@ -110,7 +130,6 @@ class Coroutine( } private fun executeInternal( - scope: CoroutineScope, context: CoroutineContext, block: suspend CoroutineScope.() -> T ): Job { @@ -118,21 +137,27 @@ class Coroutine( try { start?.let { dispatchVoidCallback(this, it) } val value = executeBlock(scope, context, timeMillis ?: 0L, block) - success?.let { dispatchCallback(this, value, it) } + if (isActive) { + success?.let { dispatchCallback(this, value, it) } + } } catch (e: Throwable) { if (BuildConfig.DEBUG) { e.printStackTrace() } val consume: Boolean = errorReturn?.value?.let { value -> - success?.let { dispatchCallback(this, value, it) } + if (isActive) { + success?.let { dispatchCallback(this, value, it) } + } true } ?: false - if (!consume) { + if (!consume && isActive) { error?.let { dispatchCallback(this, e, it) } } } finally { - finally?.let { dispatchVoidCallback(this, it) } + if (isActive) { + finally?.let { dispatchVoidCallback(this, it) } + } } } } @@ -152,6 +177,7 @@ class Coroutine( value: R, callback: Callback ) { + if (!scope.isActive) return if (null == callback.context) { callback.block.invoke(scope, value) } else { @@ -166,7 +192,7 @@ class Coroutine( context: CoroutineContext, timeMillis: Long, noinline block: suspend CoroutineScope.() -> T - ): T? { + ): T { return withContext(scope.coroutineContext.plus(context)) { if (timeMillis > 0L) withTimeout(timeMillis) { block() diff --git a/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt b/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt index a5ccdb266..4bbed5991 100644 --- a/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt +++ b/app/src/main/java/io/legado/app/help/permission/PermissionsCompat.kt @@ -1,8 +1,11 @@ package io.legado.app.help.permission +import android.os.Build import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import org.jetbrains.anko.startActivity +import java.util.ArrayList class PermissionsCompat private constructor() { @@ -12,6 +15,17 @@ class PermissionsCompat private constructor() { RequestManager.pushRequest(request) } + companion object { + // 检查权限, 如果已经拥有返回 true + fun check(activity: AppCompatActivity, vararg permissions: String): Boolean { + var request: Request = Request(activity) + var pers = ArrayList() + pers.addAll(listOf(*permissions)) + var data = request.getDeniedPermissions(pers.toTypedArray()) + return data == null; + } + } + class Builder { private val request: Request diff --git a/app/src/main/java/io/legado/app/help/permission/Request.kt b/app/src/main/java/io/legado/app/help/permission/Request.kt index 600cbe93c..f7ed0293a 100644 --- a/app/src/main/java/io/legado/app/help/permission/Request.kt +++ b/app/src/main/java/io/legado/app/help/permission/Request.kt @@ -102,7 +102,7 @@ internal class Request : OnRequestPermissionsResultCallback { deniedCallback = null } - private fun getDeniedPermissions(permissions: Array?): Array? { + fun getDeniedPermissions(permissions: Array?): Array? { if (permissions != null) { val deniedPermissionList = ArrayList() for (permission in permissions) { diff --git a/app/src/main/java/io/legado/app/help/storage/Backup.kt b/app/src/main/java/io/legado/app/help/storage/Backup.kt index 8c4fa8fc2..60d86d6a3 100644 --- a/app/src/main/java/io/legado/app/help/storage/Backup.kt +++ b/app/src/main/java/io/legado/app/help/storage/Backup.kt @@ -21,14 +21,6 @@ object Backup { FileUtils.getDirFile(App.INSTANCE.filesDir, "backup").absolutePath } - val legadoPath by lazy { - FileUtils.getSdCardPath() + File.separator + "YueDu3.0" - } - - val exportPath by lazy { - legadoPath + File.separator + "Export" - } - val backupFileNames by lazy { arrayOf( "bookshelf.json", "bookGroup.json", "bookSource.json", "rssSource.json", @@ -42,47 +34,46 @@ object Backup { return } Coroutine.async { - val backupPath = context.getPrefString(PreferKey.backupPath) - if (backupPath.isNullOrEmpty()) { - backup(context) - } else { - backup(context, backupPath) + context.getPrefString(PreferKey.backupPath)?.let { + backup(context, it, true) } } } - suspend fun backup(context: Context, path: String = legadoPath) { + suspend fun backup(context: Context, path: String, isAuto: Boolean = false) { context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) withContext(IO) { - writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) - writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) - writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) - writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath) - writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) - writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) - GSON.toJson(ReadBookConfig.configList)?.let { - FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) - .writeText(it) - } - Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> - val edit = sp.edit() - App.INSTANCE.defaultSharedPreferences.all.map { - when (val value = it.value) { - is Int -> edit.putInt(it.key, value) - is Boolean -> edit.putBoolean(it.key, value) - is Long -> edit.putLong(it.key, value) - is Float -> edit.putFloat(it.key, value) - is String -> edit.putString(it.key, value) - else -> Unit + synchronized(this@Backup) { + writeListToJson(App.db.bookDao().all, "bookshelf.json", backupPath) + writeListToJson(App.db.bookGroupDao().all, "bookGroup.json", backupPath) + writeListToJson(App.db.bookSourceDao().all, "bookSource.json", backupPath) + writeListToJson(App.db.rssSourceDao().all, "rssSource.json", backupPath) + writeListToJson(App.db.rssStarDao().all, "rssStar.json", backupPath) + writeListToJson(App.db.replaceRuleDao().all, "replaceRule.json", backupPath) + GSON.toJson(ReadBookConfig.configList)?.let { + FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.readConfigFileName) + .writeText(it) + } + Preferences.getSharedPreferences(App.INSTANCE, backupPath, "config")?.let { sp -> + val edit = sp.edit() + App.INSTANCE.defaultSharedPreferences.all.map { + when (val value = it.value) { + is Int -> edit.putInt(it.key, value) + is Boolean -> edit.putBoolean(it.key, value) + is Long -> edit.putLong(it.key, value) + is Float -> edit.putFloat(it.key, value) + is String -> edit.putString(it.key, value) + else -> Unit + } } + edit.commit() + } + WebDavHelp.backUpWebDav(backupPath) + if (path.isContentPath()) { + copyBackup(context, Uri.parse(path), isAuto) + } else { + copyBackup(File(path), isAuto) } - edit.commit() - } - WebDavHelp.backUpWebDav(backupPath) - if (path.isContentPath()) { - copyBackup(context, Uri.parse(path)) - } else { - copyBackup(File(path)) } } } @@ -95,27 +86,39 @@ object Backup { } @Throws(java.lang.Exception::class) - private fun copyBackup(context: Context, uri: Uri) { + private fun copyBackup(context: Context, uri: Uri, isAuto: Boolean) { DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> for (fileName in backupFileNames) { val file = File(backupPath + File.separator + fileName) if (file.exists()) { - treeDoc.findFile(fileName)?.delete() - treeDoc.createFile("", fileName) - ?.writeBytes(context, file.readBytes()) + if (isAuto) { + treeDoc.findFile("auto")?.findFile(fileName)?.delete() + DocumentUtils.createFileIfNotExist( + treeDoc, + fileName, + subDirs = *arrayOf("auto") + )?.writeBytes(context, file.readBytes()) + } else { + treeDoc.findFile(fileName)?.delete() + treeDoc.createFile("", fileName) + ?.writeBytes(context, file.readBytes()) + } } } } } @Throws(java.lang.Exception::class) - private fun copyBackup(rootFile: File) { + private fun copyBackup(rootFile: File, isAuto: Boolean) { for (fileName in backupFileNames) { val file = File(backupPath + File.separator + fileName) if (file.exists()) { file.copyTo( - FileUtils.createFileIfNotExist(rootFile, fileName), - true + if (isAuto) { + FileUtils.createFileIfNotExist(rootFile, fileName, "auto") + } else { + FileUtils.createFileIfNotExist(rootFile, fileName) + }, true ) } } diff --git a/app/src/main/java/io/legado/app/help/storage/OldBook.kt b/app/src/main/java/io/legado/app/help/storage/OldBook.kt index f8321973c..a782cf5f5 100644 --- a/app/src/main/java/io/legado/app/help/storage/OldBook.kt +++ b/app/src/main/java/io/legado/app/help/storage/OldBook.kt @@ -40,7 +40,6 @@ object OldBook { book.durChapterTitle = jsonItem.readString("$.durChapterName") book.durChapterPos = jsonItem.readInt("$.durChapterPage") ?: 0 book.durChapterTime = jsonItem.readLong("$.finalDate") ?: 0 - book.group = jsonItem.readInt("$.group") ?: 0 book.intro = jsonItem.readString("$.bookInfoBean.introduce") book.latestChapterTitle = jsonItem.readString("$.lastChapterName") book.lastCheckCount = jsonItem.readInt("$.newChapters") ?: 0 diff --git a/app/src/main/java/io/legado/app/help/storage/Restore.kt b/app/src/main/java/io/legado/app/help/storage/Restore.kt index 5393249c3..f174a6f5e 100644 --- a/app/src/main/java/io/legado/app/help/storage/Restore.kt +++ b/app/src/main/java/io/legado/app/help/storage/Restore.kt @@ -128,7 +128,7 @@ object Restore { bodyIndentCount = App.INSTANCE.getPrefInt(PreferKey.bodyIndent, 2) } ChapterProvider.upStyle() - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = false) } withContext(Main) { if (AppConfig.isNightTheme && AppCompatDelegate.getDefaultNightMode() != AppCompatDelegate.MODE_NIGHT_YES) { diff --git a/app/src/main/java/io/legado/app/lib/theme/ATH.kt b/app/src/main/java/io/legado/app/lib/theme/ATH.kt index 0bcbb114b..19304862c 100644 --- a/app/src/main/java/io/legado/app/lib/theme/ATH.kt +++ b/app/src/main/java/io/legado/app/lib/theme/ATH.kt @@ -199,13 +199,12 @@ object ATH { fun applyBottomNavigationColor(bottomBar: BottomNavigationView?) { bottomBar?.apply { - setBackgroundColor(ThemeStore.backgroundColor(context)) + setBackgroundColor(ThemeStore.bottomBackground(context)) val colorStateList = Selector.colorBuild() .setDefaultColor(context.getCompatColor(R.color.btn_bg_press_tp)) .setSelectedColor(ThemeStore.accentColor(bottom_navigation_view.context)).create() itemIconTintList = colorStateList itemTextColor = colorStateList - setBackgroundColor(ThemeStore.bottomBackground(context)) } } diff --git a/app/src/main/java/io/legado/app/model/Debug.kt b/app/src/main/java/io/legado/app/model/Debug.kt index 7d7ec3627..51ae12afd 100644 --- a/app/src/main/java/io/legado/app/model/Debug.kt +++ b/app/src/main/java/io/legado/app/model/Debug.kt @@ -56,9 +56,7 @@ object Debug { log(debugSource, "︾开始解析") Rss.getArticles(rssSource, null) .onSuccess { - if (it == null) { - log(debugSource, "︽解析失败", state = -1) - } else if(it.articles.isEmpty()) { + if (it.articles.isEmpty()) { log(debugSource, "⇒列表页解析成功,为空") log(debugSource, "︽解析完成", state = 1000) } else { @@ -69,7 +67,7 @@ object Debug { if (ruleContent.isNullOrEmpty()) { log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000) } else { - rssContentDebug(it.articles[0], ruleContent) + rssContentDebug(it.articles[0], ruleContent, rssSource) } } else { log(debugSource, "⇒存在描述规则,不解析内容页") @@ -82,9 +80,9 @@ object Debug { } } - private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String) { + private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String, rssSource: RssSource) { log(debugSource, "︾开始解析内容页") - Rss.getContent(rssArticle, ruleContent) + Rss.getContent(rssArticle, ruleContent, rssSource) .onSuccess { log(debugSource, it) log(debugSource, "︽内容页解析完成", state = 1000) @@ -140,14 +138,12 @@ object Debug { log(debugSource, "︾开始解析发现页") val explore = webBook.exploreBook(url, 1) .onSuccess { exploreBooks -> - exploreBooks?.let { - if (exploreBooks.isNotEmpty()) { - log(debugSource, "︽发现页解析完成") - log(debugSource, showTime = false) - infoDebug(webBook, exploreBooks[0].toBook()) - } else { - log(debugSource, "︽未获取到书籍", state = -1) - } + if (exploreBooks.isNotEmpty()) { + log(debugSource, "︽发现页解析完成") + log(debugSource, showTime = false) + infoDebug(webBook, exploreBooks[0].toBook()) + } else { + log(debugSource, "︽未获取到书籍", state = -1) } } .onError { @@ -164,14 +160,12 @@ object Debug { log(debugSource, "︾开始解析搜索页") val search = webBook.searchBook(key, 1) .onSuccess { searchBooks -> - searchBooks?.let { - if (searchBooks.isNotEmpty()) { - log(debugSource, "︽搜索页解析完成") - log(debugSource, showTime = false) - infoDebug(webBook, searchBooks[0].toBook()) - } else { - log(debugSource, "︽未获取到书籍", state = -1) - } + if (searchBooks.isNotEmpty()) { + log(debugSource, "︽搜索页解析完成") + log(debugSource, showTime = false) + infoDebug(webBook, searchBooks[0].toBook()) + } else { + log(debugSource, "︽未获取到书籍", state = -1) } } .onError { @@ -197,16 +191,14 @@ object Debug { private fun tocDebug(webBook: WebBook, book: Book) { log(debugSource, "︾开始解析目录页") val chapterList = webBook.getChapterList(book) - .onSuccess { chapterList -> - chapterList?.let { - if (it.isNotEmpty()) { - log(debugSource, "︽目录页解析完成") - log(debugSource, showTime = false) - val nextChapterUrl = if (it.size > 1) it[1].url else null - contentDebug(webBook, book, it[0], nextChapterUrl) - } else { - log(debugSource, "︽目录列表为空", state = -1) - } + .onSuccess { + if (it.isNotEmpty()) { + log(debugSource, "︽目录页解析完成") + log(debugSource, showTime = false) + val nextChapterUrl = if (it.size > 1) it[1].url else null + contentDebug(webBook, book, it[0], nextChapterUrl) + } else { + log(debugSource, "︽目录列表为空", state = -1) } } .onError { diff --git a/app/src/main/java/io/legado/app/model/Rss.kt b/app/src/main/java/io/legado/app/model/Rss.kt index b3622b40b..24dadade0 100644 --- a/app/src/main/java/io/legado/app/model/Rss.kt +++ b/app/src/main/java/io/legado/app/model/Rss.kt @@ -21,7 +21,10 @@ object Rss { context: CoroutineContext = Dispatchers.IO ): Coroutine { return Coroutine.async(scope, context) { - val analyzeUrl = AnalyzeUrl(pageUrl ?: rssSource.sourceUrl) + val analyzeUrl = AnalyzeUrl( + pageUrl ?: rssSource.sourceUrl, + headerMapF = rssSource.getHeaderMap() + ) val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body RssParserByRule.parseXML(body, rssSource) } @@ -30,12 +33,15 @@ object Rss { fun getContent( rssArticle: RssArticle, ruleContent: String, + rssSource: RssSource?, scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO ): Coroutine { return Coroutine.async(scope, context) { - val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin) - .getResponseAwait(rssArticle.origin) + val body = AnalyzeUrl( + rssArticle.link, baseUrl = rssArticle.origin, + headerMapF = rssSource?.getHeaderMap() + ).getResponseAwait(rssArticle.origin) .body val analyzeRule = AnalyzeRule() analyzeRule.setContent( diff --git a/app/src/main/java/io/legado/app/model/SearchBookModel.kt b/app/src/main/java/io/legado/app/model/SearchBookModel.kt new file mode 100644 index 000000000..1f6cac065 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/SearchBookModel.kt @@ -0,0 +1,93 @@ +package io.legado.app.model + +import io.legado.app.App +import io.legado.app.data.entities.SearchBook +import io.legado.app.help.AppConfig +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.utils.getPrefString +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors + +class SearchBookModel(private val scope: CoroutineScope, private val callBack: CallBack) { + private var searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private var mSearchId = System.currentTimeMillis() + private var searchPage = 1 + private var searchKey: String = "" + private var task: Coroutine<*>? = null + + private fun initSearchPool() { + searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + } + + fun search(searchId: Long, key: String) { + if (searchId != mSearchId) { + task?.cancel() + searchPool.close() + initSearchPool() + mSearchId = searchId + searchPage = 1 + if (key.isEmpty()) { + return + } else { + this.searchKey = key + } + } else { + searchPage++ + } + task = Coroutine.async(scope, searchPool) { + val searchGroup = App.INSTANCE.getPrefString("searchGroup") ?: "" + val bookSourceList = if (searchGroup.isBlank()) { + App.db.bookSourceDao().allEnabled + } else { + App.db.bookSourceDao().getEnabledByGroup(searchGroup) + } + for (item in bookSourceList) { + //task取消时自动取消 by (scope = this@execute) + WebBook(item).searchBook( + searchKey, + searchPage, + scope = this, + context = searchPool + ) + .timeout(30000L) + .onSuccess(IO) { + if (searchId == mSearchId) { + callBack.onSearchSuccess(it) + } + } + } + }.onStart { + callBack.onSearchStart() + } + + task?.invokeOnCompletion { + if (searchId == mSearchId) { + callBack.onSearchFinish() + } + } + } + + fun cancelSearch() { + task?.cancel() + mSearchId = 0 + callBack.onSearchCancel() + } + + fun close() { + task?.cancel() + mSearchId = 0 + searchPool.close() + } + + interface CallBack { + fun onSearchStart() + fun onSearchSuccess(searchBooks: ArrayList) + fun onSearchFinish() + fun onSearchCancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/WebBook.kt b/app/src/main/java/io/legado/app/model/WebBook.kt index 69dfb943c..cb1742132 100644 --- a/app/src/main/java/io/legado/app/model/WebBook.kt +++ b/app/src/main/java/io/legado/app/model/WebBook.kt @@ -27,26 +27,36 @@ class WebBook(val bookSource: BookSource) { page: Int? = 1, scope: CoroutineScope = Coroutine.DEFAULT, context: CoroutineContext = Dispatchers.IO - ): Coroutine> { + ): Coroutine> { return Coroutine.async(scope, context) { - bookSource.searchUrl?.let { searchUrl -> - val analyzeUrl = AnalyzeUrl( - ruleUrl = searchUrl, - key = key, - page = page, - baseUrl = sourceUrl, - headerMapF = bookSource.getHeaderMap() - ) - val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) - BookList.analyzeBookList( - res.body, - bookSource, - analyzeUrl, - res.url, - true - ) - } ?: arrayListOf() + searchBookSuspend(scope, key, page) + } + } + + suspend fun searchBookSuspend( + scope: CoroutineScope, + key: String, + page: Int? = 1 + ): ArrayList { + bookSource.searchUrl?.let { searchUrl -> + val analyzeUrl = AnalyzeUrl( + ruleUrl = searchUrl, + key = key, + page = page, + baseUrl = sourceUrl, + headerMapF = bookSource.getHeaderMap() + ) + val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) + return BookList.analyzeBookList( + scope, + res.body, + bookSource, + analyzeUrl, + res.url, + true + ) } + return arrayListOf() } /** @@ -67,6 +77,7 @@ class WebBook(val bookSource: BookSource) { ) val res = analyzeUrl.getResponseAwait(bookSource.bookSourceUrl) BookList.analyzeBookList( + scope, res.body, bookSource, analyzeUrl, diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 568615aa1..873b6b3a9 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -2,6 +2,7 @@ package io.legado.app.model.webBook import io.legado.app.App import io.legado.app.R +import io.legado.app.constant.BookType import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookSource @@ -39,7 +40,12 @@ object BookContent { var contentData = analyzeContent( book, baseUrl, body, contentRule, bookChapter, bookSource ) - content.append(contentData.content.replace(bookChapter.title, "")).append("\n") + if (bookSource.bookSourceType == BookType.default) { + content.append(contentData.content.replace(bookChapter.title, "")).append("\n") + } else { + content.append(contentData.content).append("\n") + } + if (contentData.nextUrl.size == 1) { var nextUrl = contentData.nextUrl[0] val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty()) diff --git a/app/src/main/java/io/legado/app/model/webBook/BookList.kt b/app/src/main/java/io/legado/app/model/webBook/BookList.kt index a205c950a..372a4eec6 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookList.kt @@ -9,11 +9,15 @@ import io.legado.app.model.Debug import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.utils.NetworkUtils +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive object BookList { @Throws(Exception::class) fun analyzeBookList( + scope: CoroutineScope, body: String?, bookSource: BookSource, analyzeUrl: AnalyzeUrl, @@ -28,12 +32,13 @@ object BookList { ) ) Debug.log(bookSource.bookSourceUrl, "≡获取成功:${analyzeUrl.ruleUrl}") + if (!scope.isActive) throw CancellationException() val analyzeRule = AnalyzeRule(null) analyzeRule.setContent(body, baseUrl) bookSource.bookUrlPattern?.let { if (baseUrl.matches(it.toRegex())) { Debug.log(bookSource.bookSourceUrl, "≡链接为详情页") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -59,7 +64,7 @@ object BookList { collections = analyzeRule.getElements(ruleList) if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) { Debug.log(bookSource.bookSourceUrl, "└列表为空,按详情页解析") - getInfoItem(analyzeRule, bookSource, baseUrl)?.let { searchBook -> + getInfoItem(scope, analyzeRule, bookSource, baseUrl)?.let { searchBook -> searchBook.infoHtml = body bookList.add(searchBook) } @@ -74,8 +79,9 @@ object BookList { val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount) Debug.log(bookSource.bookSourceUrl, "└列表大小:${collections.size}") for ((index, item) in collections.withIndex()) { + if (!scope.isActive) throw CancellationException() getSearchItem( - item, analyzeRule, bookSource, baseUrl, index == 0, + scope, item, analyzeRule, bookSource, baseUrl, index == 0, ruleName = ruleName, ruleBookUrl = ruleBookUrl, ruleAuthor = ruleAuthor, ruleCoverUrl = ruleCoverUrl, ruleIntro = ruleIntro, ruleKind = ruleKind, ruleLastChapter = ruleLastChapter, ruleWordCount = ruleWordCount @@ -93,7 +99,9 @@ object BookList { return bookList } + @Throws(Exception::class) private fun getInfoItem( + scope: CoroutineScope, analyzeRule: AnalyzeRule, bookSource: BookSource, baseUrl: String @@ -108,29 +116,37 @@ object BookList { with(bookSource.getBookInfoRule()) { init?.let { if (it.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") analyzeRule.setContent(analyzeRule.getElement(it)) } } + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名") searchBook.name = analyzeRule.getString(name) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}") if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者") searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(author)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类") searchBook.kind = analyzeRule.getStringList(kind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数") searchBook.wordCount = analyzeRule.getString(wordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节") searchBook.latestChapterTitle = analyzeRule.getString(lastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}") + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介") searchBook.intro = analyzeRule.getString(intro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") searchBook.coverUrl = analyzeRule.getString(coverUrl, true) Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}") @@ -140,7 +156,9 @@ object BookList { return null } + @Throws(Exception::class) private fun getSearchItem( + scope: CoroutineScope, item: Any, analyzeRule: AnalyzeRule, bookSource: BookSource, @@ -162,30 +180,38 @@ object BookList { searchBook.originOrder = bookSource.customOrder analyzeRule.book = searchBook analyzeRule.setContent(item) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取书名", log) searchBook.name = analyzeRule.getString(ruleName) Debug.log(bookSource.bookSourceUrl, "└${searchBook.name}", log) if (searchBook.name.isNotEmpty()) { + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) searchBook.author = BookHelp.formatAuthor(analyzeRule.getString(ruleAuthor)) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(",") Debug.log(bookSource.bookSourceUrl, "└${searchBook.kind}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取字数", log) searchBook.wordCount = analyzeRule.getString(ruleWordCount) Debug.log(bookSource.bookSourceUrl, "└${searchBook.wordCount}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取最新章节", log) searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter) Debug.log(bookSource.bookSourceUrl, "└${searchBook.latestChapterTitle}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取简介", log) searchBook.intro = analyzeRule.getString(ruleIntro) Debug.log(bookSource.bookSourceUrl, "└${searchBook.intro}", log, true) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取封面链接", log) analyzeRule.getString(ruleCoverUrl).let { if (it.isNotEmpty()) searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) } Debug.log(bookSource.bookSourceUrl, "└${searchBook.coverUrl}", log) + if (!scope.isActive) throw CancellationException() Debug.log(bookSource.bookSourceUrl, "┌获取详情页链接", log) searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, true) if (searchBook.bookUrl.isEmpty()) { diff --git a/app/src/main/java/io/legado/app/service/AudioPlayService.kt b/app/src/main/java/io/legado/app/service/AudioPlayService.kt index c165f3142..986e7c4ed 100644 --- a/app/src/main/java/io/legado/app/service/AudioPlayService.kt +++ b/app/src/main/java/io/legado/app/service/AudioPlayService.kt @@ -18,9 +18,9 @@ import androidx.core.app.NotificationCompat import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseService -import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst import io.legado.app.constant.EventBus +import io.legado.app.constant.IntentAction import io.legado.app.constant.Status import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp @@ -275,7 +275,7 @@ class AudioPlayService : BaseService(), AudioPlay.book?.let { book -> AudioPlay.webBook?.getContent(book, chapter, scope = this) ?.onSuccess(IO) { content -> - if (content.isNullOrEmpty()) { + if (content.isEmpty()) { withContext(Main) { toast("未获取到资源链接") } diff --git a/app/src/main/java/io/legado/app/service/DownloadService.kt b/app/src/main/java/io/legado/app/service/DownloadService.kt index 732506449..dd48933e8 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -130,10 +130,8 @@ class DownloadService : BaseService() { // notificationContent = "启动:" + chapter.title //} .onSuccess(IO) { content -> - content?.let { - downloadCount[entry.key]?.increaseSuccess() - BookHelp.saveContent(book, chapter, content) - } + downloadCount[entry.key]?.increaseSuccess() + BookHelp.saveContent(book, chapter, content) } .onFinally(IO) { synchronized(this@DownloadService) { diff --git a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt index 61507bf7c..1f63dac89 100644 --- a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt @@ -29,7 +29,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener } } - private var ttsIsSuccess: Boolean = false + private var ttsInitFinish = false override fun onCreate() { super.onCreate() @@ -42,22 +42,23 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener clearTTS() } + @Synchronized override fun onInit(status: Int) { - launch { - if (status == TextToSpeech.SUCCESS) { - textToSpeech?.language = Locale.CHINA - textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener()) - ttsIsSuccess = true - play() - } else { + if (status == TextToSpeech.SUCCESS) { + textToSpeech?.language = Locale.CHINA + textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener()) + ttsInitFinish = true + play() + } else { + launch { toast(R.string.tts_init_failed) } } } - @Suppress("DEPRECATION") + @Synchronized override fun play() { - if (contentList.isEmpty() || !ttsIsSuccess) { + if (contentList.isEmpty() || !ttsInitFinish) { return } if (requestFocus()) { diff --git a/app/src/main/java/io/legado/app/service/help/ReadBook.kt b/app/src/main/java/io/legado/app/service/help/ReadBook.kt index 85f2fff45..f48f13d11 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadBook.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadBook.kt @@ -21,7 +21,6 @@ import org.jetbrains.anko.toast object ReadBook { - var titleDate = MutableLiveData() var book: Book? = null var inBookshelf = false @@ -34,9 +33,10 @@ object ReadBook { var curTextChapter: TextChapter? = null var nextTextChapter: TextChapter? = null var webBook: WebBook? = null + var msg: String? = null private val loadingChapters = arrayListOf() - fun resetData(book: Book) { + fun resetData(book: Book, noSource: (name: String, author: String) -> Unit) { this.book = book titleDate.postValue(book.name) durChapterIndex = book.durChapterIndex @@ -46,12 +46,27 @@ object ReadBook { prevTextChapter = null curTextChapter = null nextTextChapter = null - upWebBook(book.origin) + upWebBook(book, noSource) } - fun upWebBook(origin: String) { - val bookSource = App.db.bookSourceDao().getBookSource(origin) - webBook = if (bookSource != null) WebBook(bookSource) else null + fun upWebBook(book: Book?, noSource: (name: String, author: String) -> Unit) { + book ?: return + if (book.origin == BookType.local) { + webBook = null + } else { + val bookSource = App.db.bookSourceDao().getBookSource(book.origin) + if (bookSource != null) { + webBook = WebBook(bookSource) + } else { + webBook = null + noSource.invoke(book.name, book.author) + } + } + } + + fun upMsg(msg: String?) { + this.msg = msg + callBack?.upContent() } fun moveToNextPage() { @@ -69,11 +84,11 @@ object ReadBook { nextTextChapter = null book?.let { if (curTextChapter == null) { - loadContent(durChapterIndex, upContent) + loadContent(durChapterIndex, upContent, false) } else if (upContent) { callBack?.upContent() } - loadContent(durChapterIndex.plus(1), upContent) + loadContent(durChapterIndex.plus(1), upContent, false) GlobalScope.launch(Dispatchers.IO) { for (i in 2..10) { delay(100) @@ -99,11 +114,11 @@ object ReadBook { prevTextChapter = null book?.let { if (curTextChapter == null) { - loadContent(durChapterIndex, upContent) + loadContent(durChapterIndex, upContent, false) } else if (upContent) { callBack?.upContent() } - loadContent(durChapterIndex.minus(1), upContent) + loadContent(durChapterIndex.minus(1), upContent, false) GlobalScope.launch(Dispatchers.IO) { for (i in -5..-2) { delay(100) @@ -184,21 +199,21 @@ object ReadBook { /** * 加载章节内容 */ - fun loadContent() { - loadContent(durChapterIndex) - loadContent(durChapterIndex + 1) - loadContent(durChapterIndex - 1) + fun loadContent(resetPageOffset: Boolean) { + loadContent(durChapterIndex, resetPageOffset = resetPageOffset) + loadContent(durChapterIndex + 1, resetPageOffset = resetPageOffset) + loadContent(durChapterIndex - 1, resetPageOffset = resetPageOffset) } - fun loadContent(index: Int, upContent: Boolean = true) { + fun loadContent(index: Int, upContent: Boolean = true, resetPageOffset: Boolean) { book?.let { book -> if (addLoading(index)) { Coroutine.async { App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> BookHelp.getContent(book, chapter)?.let { - contentLoadFinish(chapter, it, upContent) + contentLoadFinish(chapter, it, upContent, resetPageOffset) removeLoading(chapter.index) - } ?: download(chapter) + } ?: download(chapter, resetPageOffset = resetPageOffset) } ?: removeLoading(index) }.onError { removeLoading(index) @@ -216,7 +231,7 @@ object ReadBook { if (BookHelp.hasContent(book, chapter)) { removeLoading(chapter.index) } else { - download(chapter) + download(chapter, false) } } ?: removeLoading(index) }.onError { @@ -226,20 +241,28 @@ object ReadBook { } } - private fun download(chapter: BookChapter) { + private fun download(chapter: BookChapter, resetPageOffset: Boolean) { book?.let { book -> webBook?.getContent(book, chapter) ?.onSuccess(Dispatchers.IO) { content -> - if (content.isNullOrEmpty()) { - contentLoadFinish(chapter, App.INSTANCE.getString(R.string.content_empty)) + if (content.isEmpty()) { + contentLoadFinish( + chapter, + App.INSTANCE.getString(R.string.content_empty), + resetPageOffset = resetPageOffset + ) removeLoading(chapter.index) } else { BookHelp.saveContent(book, chapter, content) - contentLoadFinish(chapter, content) + contentLoadFinish(chapter, content, resetPageOffset = resetPageOffset) removeLoading(chapter.index) } }?.onError { - contentLoadFinish(chapter, it.localizedMessage ?: "未知错误") + contentLoadFinish( + chapter, + it.localizedMessage ?: "未知错误", + resetPageOffset = resetPageOffset + ) removeLoading(chapter.index) } } @@ -265,7 +288,8 @@ object ReadBook { private fun contentLoadFinish( chapter: BookChapter, content: String, - upContent: Boolean = true + upContent: Boolean = true, + resetPageOffset: Boolean ) { Coroutine.async { if (chapter.index in durChapterIndex - 1..durChapterIndex + 1) { @@ -279,18 +303,18 @@ object ReadBook { when (chapter.index) { durChapterIndex -> { curTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - if (upContent) callBack?.upContent() + if (upContent) callBack?.upContent(resetPageOffset = resetPageOffset) callBack?.upView() curPageChanged() callBack?.contentLoadFinish() } durChapterIndex - 1 -> { prevTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - if (upContent) callBack?.upContent(-1) + if (upContent) callBack?.upContent(-1, resetPageOffset) } durChapterIndex + 1 -> { nextTextChapter = ChapterProvider.getTextChapter(chapter, c, chapterSize) - if (upContent) callBack?.upContent(1) + if (upContent) callBack?.upContent(1, resetPageOffset) } } } @@ -316,7 +340,7 @@ object ReadBook { } interface CallBack { - fun upContent(relativePosition: Int = 0) + fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) fun upView() fun upPageProgress() fun contentLoadFinish() diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt index bece64c93..3c960093b 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt @@ -46,9 +46,8 @@ class AudioPlayActivity : private var adjustProgress = false override fun onActivityCreated(savedInstanceState: Bundle?) { - setSupportActionBar(toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - AudioPlay.titleData.observe(this, Observer { toolbar.title = it }) + title_bar.transparent() + AudioPlay.titleData.observe(this, Observer { title_bar.title = it }) AudioPlay.coverData.observe(this, Observer { upCover(it) }) viewModel.initData(intent) initView() diff --git a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt index b5dd5bfba..892a99f41 100644 --- a/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt @@ -72,7 +72,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) execute { AudioPlay.webBook?.getChapterList(book, this) ?.onSuccess(Dispatchers.IO) { cList -> - if (!cList.isNullOrEmpty()) { + if (cList.isNotEmpty()) { if (changeDruChapterIndex == null) { App.db.bookChapterDao().insert(*cList.toTypedArray()) AudioPlay.chapterSize = cList.size diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt index f3b0d5ee5..15217f3bc 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -6,12 +6,14 @@ import android.view.MenuItem import androidx.appcompat.widget.PopupMenu import androidx.lifecycle.LiveData import androidx.lifecycle.Observer +import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup +import io.legado.app.help.ItemTouchCallback import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.okButton @@ -64,6 +66,10 @@ class ArrangeBookActivity : VMBaseActivity(R.layout.activi recycler_view.addItemDecoration(VerticalDivider(this)) adapter = ArrangeBookAdapter(this, this) recycler_view.adapter = adapter + val itemTouchCallback = ItemTouchCallback() + itemTouchCallback.onItemTouchCallbackListener = adapter + itemTouchCallback.isCanDrag = true + ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) select_action_bar.setMainActionText(R.string.move_to_group) select_action_bar.inflateMenu(R.menu.arrange_book_sel) select_action_bar.setOnMenuItemClickListener(this) diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt index fd51e3a3d..abc72b905 100644 --- a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt @@ -7,14 +7,13 @@ import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup -import io.legado.app.utils.gone -import io.legado.app.utils.visible +import io.legado.app.help.ItemTouchCallback import kotlinx.android.synthetic.main.item_arrange_book.view.* import org.jetbrains.anko.sdk27.listeners.onClick - class ArrangeBookAdapter(context: Context, val callBack: CallBack) : - SimpleRecyclerAdapter(context, R.layout.item_arrange_book) { + SimpleRecyclerAdapter(context, R.layout.item_arrange_book), + ItemTouchCallback.OnItemTouchCallbackListener { val groupRequestCode = 12 private val selectedBooks: HashSet = hashSetOf() var actionItem: Book? = null @@ -56,12 +55,7 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) : override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { tv_name.text = item.name -// tv_name.text = if (item.author.isEmpty()) { -// item.name -// } else { -// "${item.name}(${item.author})" -// } - tv_author.text = item.author // resources.getString(R.string.author_show, item.author) + tv_author.text = item.author tv_author.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE tv_group_s.text = getGroupName(item.group) checkbox.isChecked = selectedBooks.contains(item) @@ -115,7 +109,7 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) : groupNames.add(it.groupName) } } - return groupNames; + return groupNames } private fun getGroupName(groupId: Int): String { diff --git a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt index 627bacc09..0c91fe0f9 100644 --- a/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt @@ -51,14 +51,13 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application fun search() { task = execute { - searchStateData.postValue(true) val bookSourceList = App.db.bookSourceDao().allEnabled for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) WebBook(item).searchBook(name, scope = this@execute, context = searchPool) .timeout(30000L) .onSuccess(Dispatchers.IO) { - if (it != null && it.isNotEmpty()) { + if (it.isNotEmpty()) { val searchBook = it[0] if (searchBook.name == name && searchBook.author == author && !searchBook.coverUrl.isNullOrEmpty() @@ -72,6 +71,10 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { diff --git a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt index e0800c0a1..b375aed11 100644 --- a/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt @@ -72,14 +72,13 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio fun search() { task = execute { - searchStateData.postValue(true) val bookSourceList = App.db.bookSourceDao().allEnabled for (item in bookSourceList) { //task取消时自动取消 by (scope = this@execute) WebBook(item).searchBook(name, scope = this@execute, context = searchPool) .timeout(30000L) .onSuccess(IO) { - it?.forEach { searchBook -> + it.forEach { searchBook -> if (searchBook.name == name && searchBook.author == author) { if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (searchBook.tocUrl.isEmpty()) { @@ -95,6 +94,10 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } } + }.onStart { + searchStateData.postValue(true) + }.onCancel { + searchStateData.postValue(false) } task?.invokeOnCompletion { @@ -107,7 +110,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getBookInfo(book, this) .onSuccess { - it?.let { loadChapter(it) } + loadChapter(it) }.onError { debug { context.getString(R.string.error_get_book_info) } } @@ -119,13 +122,11 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio execute { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getChapterList(book, this) - .onSuccess(IO) { - it?.let { chapters -> - if (chapters.isNotEmpty()) { - book.latestChapterTitle = chapters.last().title - val searchBook: SearchBook = book.toSearchBook() - searchFinish(searchBook) - } + .onSuccess(IO) { chapters -> + if (chapters.isNotEmpty()) { + book.latestChapterTitle = chapters.last().title + val searchBook: SearchBook = book.toSearchBook() + searchFinish(searchBook) } }.onError { debug { context.getString(R.string.error_get_chapter_list) } diff --git a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt index 51c8b8096..d717cb25f 100644 --- a/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt +++ b/app/src/main/java/io/legado/app/ui/book/chapterlist/ChapterListFragment.kt @@ -36,6 +36,7 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme private var durChapterIndex = 0 private lateinit var mLayoutManager: UpLinearLayoutManager private var tocLiveData: LiveData>? = null + private var scrollToDurChapter = false override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { viewModel.chapterCallBack = this @@ -74,7 +75,6 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme book?.let { durChapterIndex = it.durChapterIndex tv_current_chapter_info.text = it.durChapterTitle - mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) initCacheFileNames(it) } } @@ -85,7 +85,10 @@ class ChapterListFragment : VMBaseFragment(R.layout.fragme tocLiveData = App.db.bookChapterDao().observeByBook(viewModel.bookUrl) tocLiveData?.observe(viewLifecycleOwner, Observer { adapter.setItems(it) - mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) + if (!scrollToDurChapter) { + mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0) + scrollToDurChapter = true + } }) } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index d34e21916..bd22b3968 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -35,11 +35,9 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application WebBook(source).exploreBook(url, page, this) .timeout(30000L) .onSuccess(IO) { searchBooks -> - searchBooks?.let { - booksData.postValue(searchBooks) - App.db.searchBookDao().insert(*searchBooks.toTypedArray()) - page++ - } + booksData.postValue(searchBooks) + App.db.searchBookDao().insert(*searchBooks.toTypedArray()) + page++ } } } diff --git a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 1086f0416..8be9ed34f 100644 --- a/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -33,6 +33,7 @@ import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import io.legado.app.utils.requestInputMethod +import io.legado.app.utils.visible import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* @@ -74,6 +75,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter tv_ok.setTextColor(requireContext().accentColor) + tv_ok.visible() tv_ok.onClick { dismiss() } App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { val diffResult = diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt index 39cca5312..e0b6f275a 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt @@ -1,11 +1,14 @@ package io.legado.app.ui.book.info +import android.annotation.SuppressLint import android.app.Activity import android.content.Intent import android.graphics.drawable.Drawable import android.os.Bundle import android.view.Menu import android.view.MenuItem +import android.widget.CheckBox +import android.widget.LinearLayout import androidx.lifecycle.Observer import com.bumptech.glide.RequestBuilder import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions @@ -29,6 +32,7 @@ import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity +import io.legado.app.utils.dp import io.legado.app.utils.getViewModel import io.legado.app.utils.gone import io.legado.app.utils.visible @@ -53,8 +57,7 @@ class BookInfoActivity : get() = getViewModel(BookInfoViewModel::class.java) override fun onActivityCreated(savedInstanceState: Bundle?) { - setSupportActionBar(toolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + title_bar.transparent() viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.initData(intent) @@ -227,25 +230,33 @@ class BookInfoActivity : tv_author.onClick { startActivity(Pair("key", viewModel.bookData.value?.author)) } + tv_name.onClick { + startActivity(Pair("key", viewModel.bookData.value?.name)) + } } + @SuppressLint("InflateParams") private fun deleteBook() { viewModel.bookData.value?.let { if (it.isLocalBook()) { alert( titleResource = R.string.sure, - messageResource = R.string.sure_delete_book_file + messageResource = R.string.sure_del ) { - positiveButton(R.string.yes) { - viewModel.delBook(true) { - finish() - } + val checkBox = CheckBox(this@BookInfoActivity).apply { + setText(R.string.delete_book_file) + } + val view = LinearLayout(this@BookInfoActivity).apply { + setPadding(16.dp, 0, 16.dp, 0) + addView(checkBox) } - negativeButton(R.string.no) { - viewModel.delBook(false) { + customView = view + positiveButton(R.string.yes) { + viewModel.delBook(checkBox.isChecked) { finish() } } + negativeButton(R.string.no) }.show() } else { viewModel.delBook { diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index 485d704ce..a8d8f59ef 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -59,13 +59,11 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getBookInfo(book, this) .onSuccess(IO) { - it?.let { - bookData.postValue(book) - if (inBookshelf) { - App.db.bookDao().update(book) - } - loadChapter(it, changeDruChapterIndex) + bookData.postValue(book) + if (inBookshelf) { + App.db.bookDao().update(book) } + loadChapter(it, changeDruChapterIndex) }.onError { toast(R.string.error_get_book_info) } @@ -92,20 +90,18 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> WebBook(bookSource).getChapterList(book, this) .onSuccess(IO) { - it?.let { - if (it.isNotEmpty()) { - if (inBookshelf) { - App.db.bookDao().update(book) - App.db.bookChapterDao().insert(*it.toTypedArray()) - } - if (changeDruChapterIndex == null) { - chapterListData.postValue(it) - } else { - changeDruChapterIndex(it) - } + if (it.isNotEmpty()) { + if (inBookshelf) { + App.db.bookDao().update(book) + App.db.bookChapterDao().insert(*it.toTypedArray()) + } + if (changeDruChapterIndex == null) { + chapterListData.postValue(it) } else { - toast(R.string.chapter_list_empty) + changeDruChapterIndex(it) } + } else { + toast(R.string.chapter_list_empty) } }.onError { chapterListData.postValue(null) diff --git a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt index 2958325fe..3ce47ee99 100644 --- a/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/local/ImportBookActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.provider.DocumentsContract import android.view.Menu import android.view.MenuItem +import android.view.View import androidx.appcompat.widget.PopupMenu import androidx.documentfile.provider.DocumentFile import androidx.lifecycle.LiveData @@ -17,6 +18,8 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.help.AppConfig +import io.legado.app.help.permission.Permissions +import io.legado.app.help.permission.PermissionsCompat import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.filechooser.FilePicker import io.legado.app.ui.widget.SelectActionBar @@ -40,7 +43,8 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity private val subDocs = arrayListOf() private lateinit var adapter: ImportBookAdapter private var localUriLiveData: LiveData>? = null - private var path = FileUtils.getSdCardPath() + private var sdPath = FileUtils.getSdCardPath() + private var path = sdPath override val viewModel: ImportBookViewModel get() = getViewModel(ImportBookViewModel::class.java) @@ -79,6 +83,7 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity } } }) + } private fun initEvent() { @@ -123,6 +128,24 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity subDocs.clear() path = it } + } ?: let { + // 没有权限就显示一个授权提示和按钮 + if (PermissionsCompat.check(this, *Permissions.Group.STORAGE)) { + hint_per.visibility = View.GONE + } else { + hint_per.visibility = View.VISIBLE + tv_request_per.onClick { + PermissionsCompat.Builder(this) + .addPermissions(*Permissions.Group.STORAGE) + .rationale(R.string.tip_perm_request_storage) + .onGranted { + hint_per.visibility = View.GONE + initData() + upRootDoc() + } + .request() + } + } } upPath() } @@ -159,7 +182,7 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity } } } ?: let { - tv_path.text = path + tv_path.text = path.replace(sdPath, "SD") val docList = arrayListOf() File(path).listFiles()?.forEach { if (it.isDirectory) { @@ -228,7 +251,7 @@ class ImportBookActivity : VMBaseActivity(R.layout.activity @Synchronized private fun goBackDir(): Boolean { if (rootDoc == null) { - if (path != FileUtils.getSdCardPath()) { + if (path != sdPath) { File(path).parent?.let { path = it upPath() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 3de366ec3..e81d7cad9 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -25,6 +25,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.help.ReadBookConfig import io.legado.app.help.coroutine.Coroutine +import io.legado.app.help.storage.Backup import io.legado.app.help.storage.SyncBookProgress import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton @@ -91,6 +92,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override val headerHeight: Int get() = page_view.curPage.headerHeight override fun onCreate(savedInstanceState: Bundle?) { + ReadBook.msg = null Help.setOrientation(this) super.onCreate(savedInstanceState) } @@ -115,7 +117,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = false) } override fun onResume() { @@ -402,11 +404,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo */ override fun showTextActionMenu() { textActionMenu ?: let { - textActionMenu = TextActionMenu(this, this).apply { - contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) - } + textActionMenu = TextActionMenu(this, this) } textActionMenu?.let { popup -> + popup.contentView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) val popupHeight = popup.contentView.measuredHeight val x = text_menu_position.x.toInt() var y = text_menu_position.y.toInt() - popupHeight @@ -481,9 +482,9 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo /** * 更新内容 */ - override fun upContent(relativePosition: Int) { + override fun upContent(relativePosition: Int, resetPageOffset: Boolean) { launch { - page_view.upContent(relativePosition) + page_view.upContent(relativePosition, resetPageOffset) } } @@ -579,7 +580,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun onReplaceRuleSave() { Coroutine.async { BookHelp.upReplaceRules() - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = false) } } @@ -682,8 +683,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo mHandler.removeCallbacks(keepScreenRunnable) textActionMenu?.dismiss() page_view.onDestroy() + ReadBook.msg = null if (!BuildConfig.DEBUG) { SyncBookProgress.uploadBookProgress() + Backup.autoBack(this) } } @@ -707,9 +710,9 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo page_view.upBg() page_view.upStyle() if (it) { - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = false) } else { - page_view.upContent() + page_view.upContent(resetPageOffset = false) } } observeEvent(EventBus.ALOUD_STATE) { @@ -718,7 +721,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo val page = textChapter.page(ReadBook.durPageIndex) if (page != null) { page.removePageAloudSpan() - page_view.upContent() + page_view.upContent(resetPageOffset = false) } } } @@ -735,9 +738,6 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } } - observeEvent(EventBus.REPLACE) { - ReplaceEditDialog().show(supportFragmentManager, "replaceEditDialog") - } observeEvent(PreferKey.keepLight) { upScreenTimeOut() } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 2ebab7875..231f83e11 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -39,7 +39,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { private fun initBook(book: Book) { if (ReadBook.book?.bookUrl != book.bookUrl) { - ReadBook.resetData(book) + ReadBook.resetData(book) { name, author -> + autoChangeSource(name, author) + } isInitFinish = true ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) if (ReadBook.chapterSize == 0) { @@ -52,7 +54,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { if (ReadBook.durChapterIndex > ReadBook.chapterSize - 1) { ReadBook.durChapterIndex = ReadBook.chapterSize - 1 } - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = true) } if (ReadBook.inBookshelf) { ReadBook.saveRead() @@ -60,7 +62,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } else { isInitFinish = true ReadBook.titleDate.postValue(book.name) - ReadBook.upWebBook(book.origin) + ReadBook.upWebBook(book) { name, author -> + autoChangeSource(name, author) + } ReadBook.chapterSize = App.db.bookChapterDao().getChapterCount(book.bookUrl) if (ReadBook.chapterSize == 0) { if (book.tocUrl.isEmpty()) { @@ -70,7 +74,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } } else { if (ReadBook.curTextChapter != null) { - ReadBook.callBack?.upContent() + ReadBook.callBack?.upContent(resetPageOffset = false) } } } @@ -103,32 +107,34 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = it.size - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = true) } } else { ReadBook.webBook?.getChapterList(book, this) ?.onSuccess(IO) { cList -> - if (!cList.isNullOrEmpty()) { + if (cList.isNotEmpty()) { if (changeDruChapterIndex == null) { App.db.bookChapterDao().insert(*cList.toTypedArray()) App.db.bookDao().update(book) ReadBook.chapterSize = cList.size - ReadBook.loadContent() + ReadBook.upMsg(null) + ReadBook.loadContent(resetPageOffset = true) } else { changeDruChapterIndex(cList) } } else { - toast(R.string.error_load_toc) + ReadBook.upMsg(context.getString(R.string.error_load_toc)) } }?.onError { - toast(R.string.error_load_toc) - } ?: autoChangeSource() + ReadBook.upMsg(context.getString(R.string.error_load_toc)) + } } } } fun changeTo(book1: Book) { execute { + ReadBook.upMsg(null) ReadBook.book?.let { App.db.bookDao().delete(it) } @@ -151,8 +157,26 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { } } - private fun autoChangeSource() { - + private fun autoChangeSource(name: String, author: String) { + execute { + App.db.bookSourceDao().allTextEnabled.forEach { source -> + try { + val searchBooks = WebBook(source).searchBookSuspend(this, name) + searchBooks.getOrNull(0)?.let { + if (it.name == name && (it.author == author || author == "")) { + changeTo(it.toBook()) + return@forEach + } + } + } catch (e: Exception) { + //nothing + } + } + }.onStart { + ReadBook.upMsg("正在自动换源") + }.onFinally { + ReadBook.upMsg(null) + } } private fun upChangeDurChapterIndex(book: Book, chapters: List) { @@ -167,7 +191,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { App.db.bookDao().update(book) App.db.bookChapterDao().insert(*chapters.toTypedArray()) ReadBook.chapterSize = chapters.size - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = true) } } @@ -181,7 +205,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { ReadBook.durPageIndex = pageIndex } ReadBook.saveRead() - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = true) } fun removeFromBookshelf(success: (() -> Unit)?) { @@ -208,9 +232,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { execute { App.db.bookChapterDao().getChapter(book.bookUrl, ReadBook.durChapterIndex) ?.let { chapter -> - BookHelp.delContent(book, chapter) - ReadBook.loadContent(ReadBook.durChapterIndex) - } + BookHelp.delContent(book, chapter) + ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false) + } } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt index 9d7046f6e..5c8d1d214 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.pm.ResolveInfo import android.net.Uri import android.os.Build +import android.speech.tts.TextToSpeech import android.view.LayoutInflater import android.view.Menu import android.view.ViewGroup @@ -15,13 +16,12 @@ import androidx.annotation.RequiresApi import androidx.appcompat.view.SupportMenuInflater import androidx.appcompat.view.menu.MenuBuilder import androidx.appcompat.view.menu.MenuItemImpl -import androidx.appcompat.widget.PopupMenu -import androidx.core.view.size -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView +import androidx.core.view.isVisible import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.service.BaseReadAloudService +import io.legado.app.utils.gone import io.legado.app.utils.isAbsUrl import io.legado.app.utils.sendToClip import io.legado.app.utils.visible @@ -30,10 +30,16 @@ import kotlinx.android.synthetic.main.popup_action_menu.view.* import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.share import org.jetbrains.anko.toast +import java.util.* @SuppressLint("RestrictedApi") class TextActionMenu(private val context: Context, private val callBack: CallBack) : - PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) { + PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT), + TextToSpeech.OnInitListener { + + private val adapter = Adapter(context) + private val menu = MenuBuilder(context) + private val moreMenu = MenuBuilder(context) init { @SuppressLint("InflateParams") @@ -44,33 +50,38 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac isFocusable = false initRecyclerView() + setOnDismissListener { + contentView.apply { + iv_menu_more.setImageResource(R.drawable.ic_more_vert) + recycler_view_more.gone() + adapter.setItems(menu.visibleItems) + recycler_view.visible() + } + } } private fun initRecyclerView() = with(contentView) { - val adapter = Adapter(context) - recycler_view.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) recycler_view.adapter = adapter - val menu = MenuBuilder(context) + recycler_view_more.adapter = adapter SupportMenuInflater(context).inflate(R.menu.content_select_action, menu) adapter.setItems(menu.visibleItems) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val popupMenu = PopupMenu(context, iv_menu_more) - onInitializeMenu(popupMenu.menu) - if (popupMenu.menu.size > 0) { - iv_menu_more.visible() - popupMenu.setOnMenuItemClickListener { item -> - item.intent?.let { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText) - context.startActivity(it) - } - } - this@TextActionMenu.dismiss() - true - } - } - iv_menu_more.onClick { - popupMenu.show() + onInitializeMenu(moreMenu) + } + if (moreMenu.size() > 0) { + iv_menu_more.visible() + } + iv_menu_more.onClick { + if (recycler_view.isVisible) { + iv_menu_more.setImageResource(R.drawable.ic_arrow_back) + adapter.setItems(moreMenu.visibleItems) + recycler_view.gone() + recycler_view_more.visible() + } else { + iv_menu_more.setImageResource(R.drawable.ic_more_vert) + recycler_view_more.gone() + adapter.setItems(menu.visibleItems) + recycler_view.visible() } } } @@ -104,6 +115,13 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac when (item.itemId) { R.id.menu_copy -> context.sendToClip(callBack.selectedText) R.id.menu_share_str -> context.share(callBack.selectedText) + R.id.menu_aloud -> { + if (BaseReadAloudService.isRun) { + context.toast(R.string.alouding_disable) + return + } + readAloud(callBack.selectedText) + } R.id.menu_browser -> { try { val intent = if (callBack.selectedText.isAbsUrl()) { @@ -121,9 +139,41 @@ class TextActionMenu(private val context: Context, private val callBack: CallBac context.toast(e.localizedMessage ?: "ERROR") } } + else -> item.intent?.let { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText) + context.startActivity(it) + } + } } } + private var textToSpeech: TextToSpeech? = null + private var ttsInitFinish = false + private var lastText: String = "" + + @SuppressLint("SetJavaScriptEnabled") + private fun readAloud(text: String) { + lastText = text + if (textToSpeech == null) { + textToSpeech = TextToSpeech(context, this) + return + } + if (!ttsInitFinish) return + if (text == "") return + if (textToSpeech?.isSpeaking == true) + textToSpeech?.stop() + textToSpeech?.speak(text, TextToSpeech.QUEUE_ADD, null, "select_text") + lastText = "" + } + + @Synchronized + override fun onInit(status: Int) { + textToSpeech?.language = Locale.CHINA + ttsInitFinish = true + readAloud(lastText) + } + @RequiresApi(Build.VERSION_CODES.M) private fun createProcessTextIntent(): Intent { return Intent() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt index 5540d0386..9c118c068 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt @@ -21,7 +21,6 @@ import io.legado.app.utils.observeEvent import io.legado.app.utils.putPrefBoolean import kotlinx.android.synthetic.main.dialog_read_aloud.* import org.jetbrains.anko.sdk27.listeners.onClick -import org.jetbrains.anko.sdk27.listeners.onLongClick class ReadAloudDialog : BaseDialogFragment() { var callBack: CallBack? = null @@ -34,7 +33,7 @@ class ReadAloudDialog : BaseDialogFragment() { it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) + it.setBackgroundDrawableResource(R.color.background) it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f @@ -54,10 +53,10 @@ class ReadAloudDialog : BaseDialogFragment() { } override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { - ll_bottom_bg.setBackgroundColor(requireContext().bottomBackground) + root_view.setBackgroundColor(requireContext().bottomBackground) initOnChange() initData() - initOnClick() + initEvent() } private fun initData() { @@ -101,22 +100,19 @@ class ReadAloudDialog : BaseDialogFragment() { }) } - private fun initOnClick() { - iv_menu.onClick { callBack?.showMenuBar(); dismiss() } - iv_other_config.onClick { + private fun initEvent() { + ll_main_menu.onClick { callBack?.showMenuBar(); dismiss() } + ll_setting.onClick { ReadAloudConfigDialog().show(childFragmentManager, "readAloudConfigDialog") } + tv_pre.onClick { ReadBook.moveToPrevChapter(upContent = true, toLast = false) } + tv_next.onClick { ReadBook.moveToNextChapter(true) } iv_stop.onClick { ReadAloud.stop(requireContext()); dismiss() } iv_play_pause.onClick { callBack?.onClickReadAloud() } iv_play_prev.onClick { ReadAloud.prevParagraph(requireContext()) } - iv_play_prev.onLongClick { - ReadBook.moveToPrevChapter(upContent = true, toLast = false) - true - } iv_play_next.onClick { ReadAloud.nextParagraph(requireContext()) } - iv_play_next.onLongClick { ReadBook.moveToNextChapter(true); true } - fabToc.onClick { callBack?.openChapterList() } - fabBack.onClick { callBack?.finish() } + ll_catalog.onClick { callBack?.openChapterList() } + ll_to_backstage.onClick { callBack?.finish() } } private fun upPlayState() { diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt index 6dc89f716..1cc006512 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentView.kt @@ -108,8 +108,10 @@ class ContentView(context: Context) : FrameLayout(context) { } } - fun setContent(textPage: TextPage) { + fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) { setProgress(textPage) + if (resetPageOffset) + resetPageOffset() content_text_view.setContent(textPage) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt index 4f7afa04f..3a128f969 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/DataSource.kt @@ -17,5 +17,5 @@ interface DataSource { fun hasPrevChapter(): Boolean - fun upContent(relativePosition: Int = 0) + fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true) } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt index 1b7b71b07..dd19fdc0e 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt @@ -38,7 +38,7 @@ class PageView(context: Context, attrs: AttributeSet) : prevPage.x = -w.toFloat() pageDelegate?.setViewSize(w, h) if (oldw != 0 && oldh != 0) { - ReadBook.loadContent() + ReadBook.loadContent(resetPageOffset = false) } } @@ -93,9 +93,9 @@ class PageView(context: Context, attrs: AttributeSet) : upContent() } - override fun upContent(relativePosition: Int) { + override fun upContent(relativePosition: Int, resetPageOffset: Boolean) { if (ReadBookConfig.isScroll) { - curPage.setContent(pageFactory.currentPage) + curPage.setContent(pageFactory.currentPage, resetPageOffset) } else { when (relativePosition) { -1 -> prevPage.setContent(pageFactory.prevPage) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt index a867daf3d..a38ed6e51 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/TextPageFactory.kt @@ -38,7 +38,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } else { ReadBook.setPageIndex(pageIndex.plus(1)) } - if (upContent) upContent() + if (upContent) upContent(resetPageOffset = false) true } else false @@ -51,7 +51,7 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource } else { ReadBook.setPageIndex(pageIndex.minus(1)) } - if (upContent) upContent() + if (upContent) upContent(resetPageOffset = false) true } else false @@ -59,6 +59,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override val currentPage: TextPage get() = with(dataSource) { + ReadBook.msg?.let { + return@with TextPage(text = it).format() + } currentChapter?.let { return@with it.page(pageIndex) ?: TextPage(title = it.title).format() @@ -68,6 +71,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override val nextPage: TextPage get() = with(dataSource) { + ReadBook.msg?.let { + return@with TextPage(text = it).format() + } currentChapter?.let { if (pageIndex < it.pageSize() - 1) { return@with it.page(pageIndex + 1)?.removePageAloudSpan() @@ -86,6 +92,9 @@ class TextPageFactory(dataSource: DataSource) : PageFactory(dataSource override val prevPage: TextPage get() = with(dataSource) { + ReadBook.msg?.let { + return@with TextPage(text = it).format() + } if (pageIndex > 0) { currentChapter?.let { return@with it.page(pageIndex - 1)?.removePageAloudSpan() diff --git a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt index 07a3b16c5..b03e54fb7 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/DiffCallBack.kt @@ -49,9 +49,9 @@ class DiffCallBack(private val oldItems: List, private val newItems: } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { - val oldItem = oldItems[oldItemPosition] - val newItem = newItems[newItemPosition] val payload = Bundle() + val newItem = newItems[newItemPosition] + val oldItem = oldItems[oldItemPosition] if (oldItem.name != newItem.name) { payload.putString("name", newItem.name) } @@ -73,6 +73,9 @@ class DiffCallBack(private val oldItems: List, private val newItems: if (oldItem.intro != newItem.intro) { payload.putString("intro", newItem.intro) } + if (payload.isEmpty) { + return null + } return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt index 2d1054859..e81545adc 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt @@ -112,6 +112,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se viewModel.saveSearchKey(query) viewModel.search(it) } + openOrCloseHistory(false) return true } @@ -128,6 +129,7 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se openOrCloseHistory(hasFocus) } } + openOrCloseHistory(true) } private fun initRecyclerView() { @@ -283,10 +285,12 @@ class SearchActivity : VMBaseActivity(R.layout.activity_book_se */ @Synchronized private fun upSearchItems(items: List, isMandatoryUpdate: Boolean) { + val searchItems = ArrayList(items) if (isMandatoryUpdate || System.currentTimeMillis() - refreshTime > 500) { refreshTime = System.currentTimeMillis() - val diffResult = DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), items)) - adapter.setItems(items, diffResult) + val diffResult = + DiffUtil.calculateDiff(DiffCallBack(adapter.getItems(), searchItems)) + adapter.setItems(searchItems, diffResult) } } diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt index 47671626d..ccfc5dd15 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt @@ -7,82 +7,62 @@ 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.model.WebBook +import io.legado.app.model.SearchBookModel import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.getPrefString -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.asCoroutineDispatcher -import java.util.concurrent.Executors +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.isActive -class SearchViewModel(application: Application) : BaseViewModel(application) { - private var searchPool = - Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() - private var task: Coroutine<*>? = null +class SearchViewModel(application: Application) : BaseViewModel(application) + , SearchBookModel.CallBack { + private val searchBookModel = SearchBookModel(this, this) var isSearchLiveData = MutableLiveData() var searchBookLiveData = MutableLiveData>() var searchKey: String = "" - var searchPage = 1 var isLoading = false var searchBooks = arrayListOf() + private var searchID = 0L /** * 开始搜索 */ fun search(key: String) { - task?.cancel() - if (key.isEmpty() && searchKey.isEmpty()) { - return - } else if (key.isEmpty()) { - isLoading = true - searchPage++ - } else if (key.isNotEmpty()) { - isLoading = true - searchPage = 1 - searchKey = key + if (searchKey != key && key.isNotEmpty()) { + searchBookModel.cancelSearch() searchBooks.clear() + searchBookLiveData.postValue(searchBooks) + searchID = System.currentTimeMillis() + searchKey = key } + searchBookModel.search(searchID, searchKey) + } + + override fun onSearchStart() { isSearchLiveData.postValue(true) - task = execute { - val searchGroup = context.getPrefString("searchGroup") ?: "" - val bookSourceList = if (searchGroup.isBlank()) { - App.db.bookSourceDao().allEnabled - } else { - App.db.bookSourceDao().getEnabledByGroup(searchGroup) - } - for (item in bookSourceList) { - //task取消时自动取消 by (scope = this@execute) - WebBook(item).searchBook( - searchKey, - searchPage, - scope = this@execute, - context = searchPool - ) - .timeout(30000L) - .onSuccess(IO) { - it?.let { list -> - if (context.getPrefBoolean(PreferKey.precisionSearch)) { - precisionSearch(list) - } else { - App.db.searchBookDao().insert(*list.toTypedArray()) - mergeItems(list) - } - } - } - } - } + } - task?.invokeOnCompletion { - isSearchLiveData.postValue(false) - isLoading = false + override fun onSearchSuccess(searchBooks: ArrayList) { + if (context.getPrefBoolean(PreferKey.precisionSearch)) { + precisionSearch(this, searchBooks) + } else { + App.db.searchBookDao().insert(*searchBooks.toTypedArray()) + mergeItems(this, searchBooks) } } + override fun onSearchFinish() { + isSearchLiveData.postValue(false) + isLoading = false + } + + override fun onSearchCancel() { + isSearchLiveData.postValue(false) + isLoading = false + } + /** * 精确搜索处理 */ - private fun precisionSearch(searchBooks: List) { + private fun precisionSearch(scope: CoroutineScope, searchBooks: List) { val books = arrayListOf() searchBooks.forEach { searchBook -> if (searchBook.name.contains(searchKey, true) @@ -90,14 +70,16 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { ) books.add(searchBook) } App.db.searchBookDao().insert(*books.toTypedArray()) - mergeItems(books) + if (scope.isActive) { + mergeItems(scope, books) + } } /** * 合并搜索结果并排序 */ @Synchronized - private fun mergeItems(newDataS: List) { + private fun mergeItems(scope: CoroutineScope, newDataS: List) { if (newDataS.isNotEmpty()) { val copyDataS = ArrayList(searchBooks) val searchBooksAdd = ArrayList() @@ -141,6 +123,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { } } } + if (!scope.isActive) return searchBooks.sortWith(Comparator { o1, o2 -> if (o1.name == searchKey && o2.name != searchKey) { 1 @@ -166,6 +149,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { 0 } }) + if (!scope.isActive) return searchBooks = copyDataS searchBookLiveData.postValue(copyDataS) } @@ -175,7 +159,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { * 停止搜索 */ fun stop() { - task?.cancel() + searchBookModel.cancelSearch() } /** @@ -211,7 +195,7 @@ class SearchViewModel(application: Application) : BaseViewModel(application) { override fun onCleared() { super.onCleared() - searchPool.close() + searchBookModel.close() } } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt index 85e3239bc..c84497536 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt @@ -8,7 +8,6 @@ import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.BookSource import io.legado.app.help.http.HttpHelper -import io.legado.app.help.storage.Backup import io.legado.app.help.storage.OldRule import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* @@ -96,7 +95,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) FileUtils.createFileIfNotExist(file, "exportBookSource.json") .writeText(json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${file.absolutePath}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } @@ -109,7 +108,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) doc.createFile("", "exportBookSource.json") ?.writeText(context, json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${doc.uri.path}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } @@ -211,7 +210,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) }.onError { finally(it.localizedMessage ?: "") }.onSuccess { - finally(it ?: "导入完成") + finally(it) } } diff --git a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt index 409638a4c..c849ae1de 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt @@ -48,30 +48,24 @@ object BackupRestoreUi { selectBackupFolder(fragment, backupSelectRequestCode) } } else { - backupUsePermission(fragment, requestCode = backupSelectRequestCode) + backupUsePermission(fragment, backupPath) } } } private fun backupUsePermission( fragment: Fragment, - path: String = Backup.legadoPath, - requestCode: Int = selectFolderRequestCode + path: String ) { PermissionsCompat.Builder(fragment) .addPermissions(*Permissions.Group.STORAGE) .rationale(R.string.tip_perm_request_storage) .onGranted { - when (requestCode) { - selectFolderRequestCode -> AppConfig.backupPath = Backup.legadoPath - else -> { - Coroutine.async { - AppConfig.backupPath = path - Backup.backup(fragment.requireContext(), path) - }.onSuccess { - fragment.toast(R.string.backup_success) - } - } + Coroutine.async { + AppConfig.backupPath = path + Backup.backup(fragment.requireContext(), path) + }.onSuccess { + fragment.toast(R.string.backup_success) } } .request() @@ -107,7 +101,7 @@ object BackupRestoreUi { } } - private fun restoreUsePermission(fragment: Fragment, path: String = Backup.legadoPath) { + private fun restoreUsePermission(fragment: Fragment, path: String) { PermissionsCompat.Builder(fragment) .addPermissions(*Permissions.Group.STORAGE) .rationale(R.string.tip_perm_request_storage) diff --git a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt index 8f5549709..717ad8f11 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/FilePicker.kt @@ -31,7 +31,7 @@ object FilePicker { 0 -> default?.invoke() 1 -> { try { - val intent = getSelectDirIntent() + val intent = createSelectDirIntent() activity.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { e.printStackTrace() @@ -68,7 +68,7 @@ object FilePicker { 0 -> default?.invoke() 1 -> { try { - val intent = getSelectDirIntent() + val intent = createSelectDirIntent() fragment.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { e.printStackTrace() @@ -106,7 +106,7 @@ object FilePicker { 0 -> default?.invoke() 1 -> { try { - val intent = getSelectFileIntent() + val intent = createSelectFileIntent() intent.type = type//设置类型 activity.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { @@ -147,7 +147,7 @@ object FilePicker { 0 -> default?.invoke() 1 -> { try { - val intent = getSelectFileIntent() + val intent = createSelectFileIntent() intent.type = type//设置类型 fragment.startActivityForResult(intent, requestCode) } catch (e: java.lang.Exception) { @@ -168,14 +168,14 @@ object FilePicker { }.show() } - private fun getSelectFileIntent(): Intent { + private fun createSelectFileIntent(): Intent { val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) return intent } - private fun getSelectDirIntent(): Intent { + private fun createSelectDirIntent(): Intent { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) return intent diff --git a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt index 0d43b28c2..0aa49ede7 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainActivity.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainActivity.kt @@ -15,7 +15,6 @@ import io.legado.app.base.VMBaseActivity import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig -import io.legado.app.help.storage.Backup import io.legado.app.lib.theme.ATH import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud @@ -78,7 +77,7 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) if (!BuildConfig.DEBUG) { val log = String(assets.open("updateLog.md").readBytes()) - TextDialog.show(supportFragmentManager, log, TextDialog.MD) + TextDialog.show(supportFragmentManager, log, TextDialog.MD, 5000) } } } @@ -114,13 +113,6 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), return super.onKeyUp(keyCode, event) } - override fun finish() { - if (!BuildConfig.DEBUG) { - Backup.autoBack(this) - } - super.finish() - } - override fun onDestroy() { super.onDestroy() ReadAloud.stop(this) diff --git a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt index 4fe57d758..61c951ae5 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainViewModel.kt @@ -33,11 +33,9 @@ class MainViewModel(application: Application) : BaseViewModel(application) { updateList.remove(book.bookUrl) postEvent(EventBus.UP_BOOK, book.bookUrl) } - it?.let { - App.db.bookDao().update(book) - App.db.bookChapterDao().delByBook(book.bookUrl) - App.db.bookChapterDao().insert(*it.toTypedArray()) - } + App.db.bookDao().update(book) + App.db.bookChapterDao().delByBook(book.bookUrl) + App.db.bookChapterDao().insert(*it.toTypedArray()) } .onError { synchronized(this) { diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt deleted file mode 100644 index 55115ad94..000000000 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfAdapter.kt +++ /dev/null @@ -1,36 +0,0 @@ -package io.legado.app.ui.main.bookshelf - -import androidx.fragment.app.Fragment -import androidx.viewpager2.adapter.FragmentStateAdapter -import io.legado.app.data.entities.BookGroup -import io.legado.app.ui.main.bookshelf.books.BooksFragment - - -class BookshelfAdapter(fragment: Fragment, val callBack: CallBack) : - FragmentStateAdapter(fragment) { - - private val ids = hashSetOf() - - override fun getItemCount(): Int { - return callBack.groupSize - } - - override fun getItemId(position: Int): Long { - return callBack.getGroup(position).groupId.toLong() - } - - override fun containsItem(itemId: Long): Boolean { - return ids.contains(itemId) - } - - override fun createFragment(position: Int): Fragment { - val groupId = callBack.getGroup(position).groupId - ids.add(groupId.toLong()) - return BooksFragment.newInstance(position, groupId) - } - - interface CallBack { - val groupSize: Int - fun getGroup(position: Int): BookGroup - } -} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt index 3608a79ed..55dca8d77 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfFragment.kt @@ -7,10 +7,12 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.fragment.app.FragmentStatePagerAdapter import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import com.google.android.material.tabs.TabLayout -import com.google.android.material.tabs.TabLayoutMediator import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseFragment @@ -29,6 +31,7 @@ import io.legado.app.ui.book.download.DownloadActivity import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.local.ImportBookActivity import io.legado.app.ui.book.search.SearchActivity +import io.legado.app.ui.main.bookshelf.books.BooksFragment import io.legado.app.ui.widget.text.AutoCompleteTextView import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_bookshelf_config.view.* @@ -42,15 +45,14 @@ import org.jetbrains.anko.startActivity class BookshelfFragment : VMBaseFragment(R.layout.fragment_bookshelf), TabLayout.OnTabSelectedListener, SearchView.OnQueryTextListener, - GroupManageDialog.CallBack, - BookshelfAdapter.CallBack { + GroupManageDialog.CallBack { override val viewModel: BookshelfViewModel get() = getViewModel(BookshelfViewModel::class.java) - private lateinit var bookshelfAdapter: BookshelfAdapter private var bookGroupLiveData: LiveData>? = null private val bookGroups = mutableListOf() + private val fragmentMap = hashMapOf() override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { setSupportToolbar(toolbar) @@ -79,26 +81,17 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } } - override val groupSize: Int - get() = bookGroups.size - - override fun getGroup(position: Int): BookGroup { - return bookGroups[position] - } - private val selectedGroup: BookGroup get() = bookGroups[view_pager_bookshelf.currentItem] private fun initView() { + ATH.applyEdgeEffectColor(view_pager_bookshelf) tab_layout.isTabIndicatorFullWidth = false tab_layout.tabMode = TabLayout.MODE_SCROLLABLE tab_layout.setSelectedTabIndicatorColor(requireContext().accentColor) - ATH.applyEdgeEffectColor(view_pager_bookshelf) - bookshelfAdapter = BookshelfAdapter(this, this) - view_pager_bookshelf.adapter = bookshelfAdapter - TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position -> - tab.text = bookGroups[position].groupName - }.attach() + tab_layout.setupWithViewPager(view_pager_bookshelf) + view_pager_bookshelf.offscreenPageLimit = 1 + view_pager_bookshelf.adapter = TabFragmentPageAdapter(childFragmentManager) } private fun initBookGroupData() { @@ -119,7 +112,7 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b bookGroups.add(AppConst.bookGroupAudio) } bookGroups.addAll(it) - bookshelfAdapter.notifyDataSetChanged() + view_pager_bookshelf.adapter?.notifyDataSetChanged() tab_layout.getTabAt(getPrefInt(PreferKey.saveTabPosition, 0))?.select() tab_layout.addOnTabSelectedListener(this) } @@ -149,7 +142,7 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b if (AppConfig.bookGroupAllShow) { bookGroups.add(0, AppConst.bookGroupAll) } - bookshelfAdapter.notifyDataSetChanged() + view_pager_bookshelf.adapter?.notifyDataSetChanged() } } @@ -212,4 +205,31 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b putPrefInt(PreferKey.saveTabPosition, it) } } + + private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) : + FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + + override fun getPageTitle(position: Int): CharSequence? { + return bookGroups[position].groupName + } + + override fun getItemPosition(`object`: Any): Int { + return POSITION_NONE + } + + override fun getItem(position: Int): Fragment { + val group = bookGroups[position] + var fragment = fragmentMap[group.groupId] + if (fragment == null) { + fragment = BooksFragment.newInstance(position, group.groupId) + fragmentMap[group.groupId] = fragment + } + return fragment + } + + override fun getCount(): Int { + return bookGroups.size + } + + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 87d2b3e06..2415db1be 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -43,10 +43,8 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application) ) WebBook(bookSource).getBookInfo(book, this) .onSuccess(IO) { - it?.let { book -> - App.db.bookDao().insert(book) - successCount++ - } + App.db.bookDao().insert(it) + successCount++ }.onError { throw Exception(it.localizedMessage) } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt index d647afd3b..1438c4507 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterGrid.kt @@ -21,7 +21,6 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) : if (bundle == null) { ATH.applyBackgroundTint(this) tv_name.text = item.name - bv_author.text = item.author iv_cover.load(item.getDisplayCover(), item.name, item.author) if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { bv_unread.invisible() @@ -35,7 +34,6 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) : bundle.keySet().map { when (it) { "name" -> tv_name.text = item.name - "author" -> bv_author.text = item.author "cover" -> iv_cover.load(item.getDisplayCover(), item.name, item.author) "refresh" -> if (item.origin != BookType.local && callBack.isUpdate(item.bookUrl)) { bv_unread.invisible() diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt index 43375288e..d82480ca2 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksFragment.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.Observer import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment @@ -26,6 +27,7 @@ import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.observeEvent import kotlinx.android.synthetic.main.fragment_books.* import org.jetbrains.anko.startActivity +import kotlin.math.max class BooksFragment : BaseFragment(R.layout.fragment_books), @@ -71,9 +73,26 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), booksAdapter = BooksAdapterList(requireContext(), this) } else { rv_bookshelf.layoutManager = GridLayoutManager(context, bookshelfLayout + 2) - booksAdapter = BooksAdapterGrid(requireContext(),this) + booksAdapter = BooksAdapterGrid(requireContext(), this) } rv_bookshelf.adapter = booksAdapter + booksAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + val layoutManager = rv_bookshelf.layoutManager + if (positionStart == 0 && layoutManager is LinearLayoutManager) { + val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount + rv_bookshelf.scrollToPosition(max(0, scrollTo)) + } + } + + override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { + val layoutManager = rv_bookshelf.layoutManager + if (toPosition == 0 && layoutManager is LinearLayoutManager) { + val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount + rv_bookshelf.scrollToPosition(max(0, scrollTo)) + } + } + }) } private fun upRecyclerData() { diff --git a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt index 3ceeca8b6..ccfa5bf82 100644 --- a/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt @@ -12,6 +12,7 @@ import io.legado.app.data.entities.BookSource import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.theme.accentColor import io.legado.app.utils.ACache +import io.legado.app.utils.dp import io.legado.app.utils.gone import io.legado.app.utils.visible import kotlinx.android.synthetic.main.item_fillet_text.view.* @@ -23,16 +24,21 @@ import org.jetbrains.anko.sdk27.listeners.onLongClick class ExploreAdapter(context: Context, private val scope: CoroutineScope, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_find_book) { - private var exIndex = 0 + private var exIndex = -1 private var scrollTo = -1 override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList) { with(holder.itemView) { + if (holder.layoutPosition == getActualItemCount() - 1) { + setPadding(16.dp, 12.dp, 16.dp, 12.dp) + } else { + setPadding(16.dp, 12.dp, 16.dp, 0) + } if (payloads.isEmpty()) { tv_name.text = item.bookSourceName } if (exIndex == holder.layoutPosition) { - iv_status.setImageResource(R.drawable.ic_remove) + iv_status.setImageResource(R.drawable.ic_arrow_down) rotate_loading.loadingColor = context.accentColor rotate_loading.show() if (scrollTo >= 0) { @@ -68,7 +74,7 @@ class ExploreAdapter(context: Context, private val scope: CoroutineScope, val ca } } } else { - iv_status.setImageResource(R.drawable.ic_add) + iv_status.setImageResource(R.drawable.ic_arrow_right) rotate_loading.hide() gl_child.gone() } diff --git a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt index 7dcecaf11..f3087f5e7 100644 --- a/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt @@ -24,6 +24,7 @@ import io.legado.app.ui.config.ConfigActivity import io.legado.app.ui.config.ConfigViewModel import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.replacerule.ReplaceRuleActivity +import io.legado.app.ui.widget.dialog.TextDialog import io.legado.app.ui.widget.prefs.NameListPreference import io.legado.app.ui.widget.prefs.PreferenceCategory import io.legado.app.ui.widget.prefs.SwitchPreference @@ -48,9 +49,10 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config), FileChooserDialog. override fun onCompatOptionsItemSelected(item: MenuItem) { when (item.itemId) { - R.id.menu_help -> startActivity() - R.id.menu_backup -> BackupRestoreUi.backup(this) - R.id.menu_restore -> BackupRestoreUi.restore(this) + R.id.menu_help -> { + val text = String(requireContext().assets.open("help.md").readBytes()) + TextDialog.show(childFragmentManager, text, TextDialog.MD) + } } } diff --git a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt index 8f0e848f0..ec75e78ed 100644 --- a/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt @@ -8,7 +8,6 @@ import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.ReplaceRule import io.legado.app.help.http.HttpHelper -import io.legado.app.help.storage.Backup import io.legado.app.help.storage.ImportOldData import io.legado.app.utils.* import kotlinx.coroutines.Dispatchers @@ -108,7 +107,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application FileUtils.createFileIfNotExist(file, "exportReplaceRule.json") .writeText(json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${file.absolutePath}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } @@ -121,7 +120,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application doc.createFile("", "exportReplaceRule.json") ?.writeText(context, json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${doc.uri.path}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt index ccfff44df..61a2b14be 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt @@ -44,8 +44,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application rssSource?.let { rssSource -> Rss.getArticles(rssSource, null) .onSuccess(IO) { - nextPageUrl = it?.nextPageUrl - it?.articles?.let { list -> + nextPageUrl = it.nextPageUrl + it.articles.let { list -> list.forEach { rssArticle -> rssArticle.order = order-- } @@ -76,8 +76,8 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application if (source != null && !pageUrl.isNullOrEmpty()) { Rss.getArticles(source, pageUrl) .onSuccess(IO) { - nextPageUrl = it?.nextPageUrl - it?.articles?.let { list -> + nextPageUrl = it.nextPageUrl + it.articles.let { list -> if (list.isEmpty()) { callBack?.loadFinally(false) return@let diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt index 79ea599d1..73c55961b 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt @@ -22,7 +22,6 @@ import org.apache.commons.text.StringEscapeUtils import org.jetbrains.anko.share import org.jetbrains.anko.toast import org.jsoup.Jsoup -import org.jsoup.safety.Whitelist class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_read), @@ -194,7 +193,7 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r } override fun upStarMenu() { - if (viewModel.star) { + if (viewModel.rssStar != null) { starMenuItem?.setIcon(R.drawable.ic_star) starMenuItem?.setTitle(R.string.in_favorites) } else { @@ -246,18 +245,15 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r @SuppressLint("SetJavaScriptEnabled") private fun readAloud() { - if (viewModel.textToSpeech.isSpeaking) { - viewModel.textToSpeech.stop() + if (viewModel.textToSpeech?.isSpeaking == true) { + viewModel.textToSpeech?.stop() upTtsMenu(false) } else { web_view.settings.javaScriptEnabled = true web_view.evaluateJavascript("document.documentElement.outerHTML") { val html = StringEscapeUtils.unescapeJson(it) - val text = Jsoup.clean(html, Whitelist.none()) - .replace(Regex("""&\w+;"""), "") - .trim()//朗读过程中总是听到一些杂音,清理一下 - //longToast(需读内容)调试一下 - viewModel.readAloud(text) + Jsoup.parse(html).text() + viewModel.readAloud(Jsoup.parse(html).textArray()) } } } diff --git a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt index 6d90fd181..f5b84642f 100644 --- a/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt @@ -15,6 +15,7 @@ import io.legado.app.base.BaseViewModel import io.legado.app.constant.AppConst import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssSource +import io.legado.app.data.entities.RssStar import io.legado.app.help.http.HttpHelper import io.legado.app.model.Rss import io.legado.app.model.analyzeRule.AnalyzeUrl @@ -22,6 +23,7 @@ import io.legado.app.utils.DocumentUtils import io.legado.app.utils.FileUtils import io.legado.app.utils.isContentPath import io.legado.app.utils.writeBytes +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.launch import java.io.File import java.util.* @@ -34,8 +36,10 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), var rssArticle: RssArticle? = null val contentLiveData = MutableLiveData() val urlLiveData = MutableLiveData() - var star = false - var textToSpeech: TextToSpeech = TextToSpeech(context, this) + var rssStar: RssStar? = null + var textToSpeech: TextToSpeech? = null + private var ttsInitFinish = false + private var ttsTextList = arrayListOf() fun initData(intent: Intent) { execute { @@ -43,8 +47,8 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), val link = intent.getStringExtra("link") if (origin != null && link != null) { rssSource = App.db.rssSourceDao().getByKey(origin) - star = App.db.rssStarDao().get(origin, link) != null - rssArticle = App.db.rssArticleDao().get(origin, link) + rssStar = App.db.rssStarDao().get(origin, link) + rssArticle = rssStar?.toRssArticle() ?: App.db.rssArticleDao().get(origin, link) rssArticle?.let { rssArticle -> if (!rssArticle.description.isNullOrBlank()) { contentLiveData.postValue(rssArticle.description) @@ -76,21 +80,26 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } private fun loadContent(rssArticle: RssArticle, ruleContent: String) { - Rss.getContent(rssArticle, ruleContent, this) - .onSuccess { - contentLiveData.postValue(it) + Rss.getContent(rssArticle, ruleContent, rssSource, this) + .onSuccess(IO) { body -> + rssArticle.description = body + App.db.rssArticleDao().insert(rssArticle) + rssStar?.let { + it.description = body + App.db.rssStarDao().insert(it) + } + contentLiveData.postValue(body) } } fun favorite() { execute { - rssArticle?.let { - if (star) { - App.db.rssStarDao().delete(it.origin, it.link) - } else { - App.db.rssStarDao().insert(it.toStar()) - } - star = !star + rssStar?.let { + App.db.rssStarDao().delete(it.origin, it.link) + rssStar = null + } ?: rssArticle?.toStar()?.let { + App.db.rssStarDao().insert(it) + rssStar = it } }.onSuccess { callBack?.upStarMenu() @@ -143,28 +152,43 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } } + @Synchronized override fun onInit(status: Int) { - launch { - if (status == TextToSpeech.SUCCESS) { - textToSpeech.language = Locale.CHINA - textToSpeech.setOnUtteranceProgressListener(TTSUtteranceListener()) - } else { + if (status == TextToSpeech.SUCCESS) { + textToSpeech?.language = Locale.CHINA + textToSpeech?.setOnUtteranceProgressListener(TTSUtteranceListener()) + ttsInitFinish = true + play() + } else { + launch { toast(R.string.tts_init_failed) } } } - fun readAloud(text: String) { - textToSpeech.stop() - text.split("\n", " ", "  ").forEach { - textToSpeech.speak(it, TextToSpeech.QUEUE_ADD, null, "rss") + @Synchronized + private fun play() { + if (!ttsInitFinish) return + textToSpeech?.stop() + ttsTextList.forEach { + textToSpeech?.speak(it, TextToSpeech.QUEUE_ADD, null, "rss") + } + } + + fun readAloud(textArray: Array) { + ttsTextList.clear() + ttsTextList.addAll(textArray) + textToSpeech?.let { + play() + } ?: let { + textToSpeech = TextToSpeech(context, this) } } override fun onCleared() { super.onCleared() - textToSpeech.stop() - textToSpeech.shutdown() + textToSpeech?.stop() + textToSpeech?.shutdown() } /** diff --git a/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt new file mode 100644 index 000000000..0e6e1b40b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt @@ -0,0 +1,17 @@ +package io.legado.app.ui.rss.read + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.webkit.WebView + +class VisibleWebView( + context: Context, + attrs: AttributeSet? = null +) : WebView(context, attrs) { + + override fun onWindowVisibilityChanged(visibility: Int) { + super.onWindowVisibilityChanged(View.VISIBLE) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt index ed09cec93..ce2313f5a 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt @@ -24,10 +24,7 @@ import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton import io.legado.app.lib.theme.accentColor import io.legado.app.ui.widget.recycler.VerticalDivider -import io.legado.app.utils.applyTint -import io.legado.app.utils.getViewModelOfActivity -import io.legado.app.utils.requestInputMethod -import io.legado.app.utils.splitNotBlank +import io.legado.app.utils.* import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.dialog_recycler_view.* import kotlinx.android.synthetic.main.item_group_manage.view.* @@ -68,6 +65,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { recycler_view.addItemDecoration(VerticalDivider(requireContext())) recycler_view.adapter = adapter tv_ok.setTextColor(requireContext().accentColor) + tv_ok.visible() tv_ok.onClick { dismiss() } App.db.rssSourceDao().liveGroup().observe(viewLifecycleOwner, Observer { val groups = linkedSetOf() diff --git a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt index 9160155b3..300d7468c 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt @@ -9,7 +9,6 @@ import io.legado.app.R import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssSource import io.legado.app.help.http.HttpHelper -import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.utils.* import org.jetbrains.anko.toast @@ -74,7 +73,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) FileUtils.createFileIfNotExist(file, "exportRssSource.json") .writeText(json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${file.absolutePath}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } @@ -87,7 +86,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) doc.createFile("", "exportRssSource.json") ?.writeText(context, json) }.onSuccess { - context.toast("成功导出至\n${Backup.exportPath}") + context.toast("成功导出至\n${doc.uri.path}") }.onError { context.toast("导出失败\n${it.localizedMessage}") } @@ -187,7 +186,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application) }.onError { finally(it.localizedMessage ?: "") }.onSuccess { - finally(it ?: "导入完成") + finally(it) } } diff --git a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt index 46e1e446e..fdf77bc85 100644 --- a/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt +++ b/app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt @@ -58,4 +58,7 @@ open class WelcomeActivity : BaseActivity(R.layout.activity_welcome) { class Launcher1 : WelcomeActivity() class Launcher2 : WelcomeActivity() -class Launcher3 : WelcomeActivity() \ No newline at end of file +class Launcher3 : WelcomeActivity() +class Launcher4 : WelcomeActivity() +class Launcher5 : WelcomeActivity() +class Launcher6 : WelcomeActivity() \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/SearchView.kt b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt new file mode 100644 index 000000000..765a0a580 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/SearchView.kt @@ -0,0 +1,110 @@ +package io.legado.app.ui.widget + +import android.app.SearchableInfo +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.drawable.Drawable +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.style.ImageSpan +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.widget.TextView +import androidx.appcompat.widget.SearchView +import io.legado.app.R + +class SearchView : SearchView { + private var mSearchHintIcon: Drawable? = null + private var textView: TextView? = null + + constructor( + context: Context?, + attrs: AttributeSet? = null + ) : super(context, attrs) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int + ) : super(context, attrs, defStyleAttr) + + override fun onLayout( + changed: Boolean, + left: Int, + top: Int, + right: Int, + bottom: Int + ) { + super.onLayout(changed, left, top, right, bottom) + try { + if (textView == null) { + textView = findViewById(androidx.appcompat.R.id.search_src_text) + mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint) + updateQueryHint() + } + // 改变字体 + textView!!.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) + textView!!.gravity = Gravity.CENTER_VERTICAL + } catch (e: Exception) { + e.printStackTrace() + } + } + + private fun getDecoratedHint(hintText: CharSequence): CharSequence { + // If the field is always expanded or we don't have a search hint icon, + // then don't add the search icon to the hint. + if (mSearchHintIcon == null) { + return hintText + } + val textSize = (textView!!.textSize * 0.8).toInt() + mSearchHintIcon!!.setBounds(0, 0, textSize, textSize) + val ssb = SpannableStringBuilder(" ") + ssb.setSpan(CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + ssb.append(hintText) + return ssb + } + + private fun updateQueryHint() { + textView?.let { + it.hint = getDecoratedHint(queryHint ?: "") + } + } + + override fun setIconifiedByDefault(iconified: Boolean) { + super.setIconifiedByDefault(iconified) + updateQueryHint() + } + + override fun setSearchableInfo(searchable: SearchableInfo?) { + super.setSearchableInfo(searchable) + searchable?.let { + updateQueryHint() + } + } + + override fun setQueryHint(hint: CharSequence?) { + super.setQueryHint(hint) + updateQueryHint() + } + + internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) { + override fun draw( + canvas: Canvas, text: CharSequence, + start: Int, end: Int, x: Float, + top: Int, y: Int, bottom: Int, paint: Paint + ) { + // image to draw + val b = drawable + // font metrics of text to be replaced + val fm = paint.fontMetricsInt + val transY = ((y + fm.descent + y + fm.ascent) / 2 + - b.bounds.bottom / 2) + canvas.save() + canvas.translate(x, transY.toFloat()) + b.draw(canvas) + canvas.restore() + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt new file mode 100644 index 000000000..0f6ad3200 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt @@ -0,0 +1,185 @@ +package io.legado.app.ui.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import io.legado.app.R +import io.legado.app.utils.getCompatColor + +/** + * ShadowLayout.java + * + * + * Created by lijiankun on 17/8/11. + */ +class ShadowLayout( + context: Context, + attrs: AttributeSet? = null +) : RelativeLayout(context, attrs) { + private val mPaint = + Paint(Paint.ANTI_ALIAS_FLAG) + private val mRectF = RectF() + + /** + * 阴影的颜色 + */ + private var mShadowColor = Color.TRANSPARENT + + /** + * 阴影的大小范围 + */ + private var mShadowRadius = 0f + + /** + * 阴影 x 轴的偏移量 + */ + private var mShadowDx = 0f + + /** + * 阴影 y 轴的偏移量 + */ + private var mShadowDy = 0f + + /** + * 阴影显示的边界 + */ + private var mShadowSide = ALL + + /** + * 阴影的形状,圆形/矩形 + */ + private var mShadowShape = SHAPE_RECTANGLE + + + init { + setLayerType(View.LAYER_TYPE_SOFTWARE, null) // 关闭硬件加速 + setWillNotDraw(false) // 调用此方法后,才会执行 onDraw(Canvas) 方法 + val typedArray = + context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout) + mShadowColor = typedArray.getColor( + R.styleable.ShadowLayout_shadowColor, + context.getCompatColor(android.R.color.black) + ) + mShadowRadius = + typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0f)) + mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0f)) + mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0f)) + mShadowSide = + typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL) + mShadowShape = typedArray.getInt( + R.styleable.ShadowLayout_shadowShape, + SHAPE_RECTANGLE + ) + typedArray.recycle() + + setUpShadowPaint() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val effect = mShadowRadius + dip2px(5f) + var rectLeft = 0f + var rectTop = 0f + var rectRight = this.measuredWidth.toFloat() + var rectBottom = this.measuredHeight.toFloat() + var paddingLeft = 0 + var paddingTop = 0 + var paddingRight = 0 + var paddingBottom = 0 + this.width + if (mShadowSide and LEFT == LEFT) { + rectLeft = effect + paddingLeft = effect.toInt() + } + if (mShadowSide and TOP == TOP) { + rectTop = effect + paddingTop = effect.toInt() + } + if (mShadowSide and RIGHT == RIGHT) { + rectRight = this.measuredWidth - effect + paddingRight = effect.toInt() + } + if (mShadowSide and BOTTOM == BOTTOM) { + rectBottom = this.measuredHeight - effect + paddingBottom = effect.toInt() + } + if (mShadowDy != 0.0f) { + rectBottom -= mShadowDy + paddingBottom += mShadowDy.toInt() + } + if (mShadowDx != 0.0f) { + rectRight -= mShadowDx + paddingRight += mShadowDx.toInt() + } + mRectF.left = rectLeft + mRectF.top = rectTop + mRectF.right = rectRight + mRectF.bottom = rectBottom + setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom) + } + + /** + * 真正绘制阴影的方法 + */ + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + setUpShadowPaint() + if (mShadowShape == SHAPE_RECTANGLE) { + canvas.drawRect(mRectF, mPaint) + } else if (mShadowShape == SHAPE_OVAL) { + canvas.drawCircle( + mRectF.centerX(), + mRectF.centerY(), + mRectF.width().coerceAtMost(mRectF.height()) / 2, + mPaint + ) + } + } + + fun setShadowColor(shadowColor: Int) { + mShadowColor = shadowColor + requestLayout() + postInvalidate() + } + + fun setShadowRadius(shadowRadius: Float) { + mShadowRadius = shadowRadius + requestLayout() + postInvalidate() + } + + private fun setUpShadowPaint() { + mPaint.reset() + mPaint.isAntiAlias = true + mPaint.color = Color.TRANSPARENT + mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor) + } + + /** + * dip2px dp 值转 px 值 + * + * @param dpValue dp 值 + * @return px 值 + */ + private fun dip2px(dpValue: Float): Float { + val dm = context.resources.displayMetrics + val scale = dm.density + return dpValue * scale + 0.5f + } + + companion object { + const val ALL = 0x1111 + const val LEFT = 0x0001 + const val TOP = 0x0010 + const val RIGHT = 0x0100 + const val BOTTOM = 0x1000 + const val SHAPE_RECTANGLE = 0x0001 + const val SHAPE_OVAL = 0x0010 + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt index 663bb55a8..94d3b7be6 100644 --- a/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt +++ b/app/src/main/java/io/legado/app/ui/widget/TitleBar.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.widget import android.content.Context import android.content.res.ColorStateList +import android.graphics.Color import android.util.AttributeSet import android.view.Menu import android.view.View @@ -186,6 +187,11 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a toolbar.setSubtitleTextAppearance(context, resId) } + fun transparent() { + elevation = 0f + backgroundColor = Color.TRANSPARENT + } + private fun attachToActivity() { if (attachToActivity) { activity?.let { diff --git a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt index 01a55f2da..9c1c75d5e 100644 --- a/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt +++ b/app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt @@ -5,29 +5,40 @@ import android.util.DisplayMetrics import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager import io.legado.app.R +import io.legado.app.base.BaseDialogFragment import kotlinx.android.synthetic.main.dialog_text_view.* +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import ru.noties.markwon.Markwon -class TextDialog : DialogFragment() { +class TextDialog : BaseDialogFragment() { companion object { const val MD = 1 - fun show(fragmentManager: FragmentManager, content: String?, mode: Int = 0) { + fun show( + fragmentManager: FragmentManager, + content: String?, + mode: Int = 0, + time: Long = 0 + ) { TextDialog().apply { val bundle = Bundle() bundle.putString("content", content) bundle.putInt("mode", mode) + bundle.putLong("time", time) arguments = bundle + isCancelable = false }.show(fragmentManager, "textDialog") } } + private var time = 0L + override fun onStart() { super.onStart() val dm = DisplayMetrics() @@ -43,8 +54,7 @@ class TextDialog : DialogFragment() { return inflater.inflate(R.layout.dialog_text_view, container) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { arguments?.let { val content = it.getString("content") ?: "" when (it.getInt("mode")) { @@ -57,8 +67,27 @@ class TextDialog : DialogFragment() { } else -> text_view.text = content } + time = it.getLong("time", 0L) + } + if (time > 0) { + badge_view.setBadgeCount((time / 1000).toInt()) + launch { + while (time > 0) { + delay(1000) + time -= 1000 + badge_view.setBadgeCount((time / 1000).toInt()) + if (time <= 0) { + view.post { + dialog?.setCancelable(true) + } + } + } + } + } else { + view.post { + dialog?.setCancelable(true) + } } - } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt index 6bea75a51..101b6678a 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/ColorPreference.kt @@ -37,6 +37,8 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex init { isPersistent = true + layoutResource = io.legado.app.R.layout.view_preference + val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference) showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true) @@ -109,9 +111,12 @@ class ColorPreference(context: Context, attrs: AttributeSet) : Preference(contex } override fun onBindViewHolder(holder: PreferenceViewHolder) { + val v = io.legado.app.ui.widget.prefs.Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, + io.legado.app.R.id.cpv_preference_preview_color_panel, 30, 30) + if (v is ColorPanelView) { + v.color = color + } super.onBindViewHolder(holder) - val preview = holder.itemView.findViewById(R.id.cpv_preference_preview_color_panel) as ColorPanelView - preview.color = color } override fun onSetInitialValue(defaultValue: Any?) { diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt new file mode 100644 index 000000000..8c0e39da3 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/EditTextPreference.kt @@ -0,0 +1,21 @@ +package io.legado.app.ui.widget.prefs + +import android.content.Context +import android.util.AttributeSet +import android.widget.TextView +import androidx.preference.PreferenceViewHolder +import io.legado.app.R + +class EditTextPreference(context: Context, attrs: AttributeSet) : androidx.preference.EditTextPreference(context, attrs) { + + init { + // isPersistent = true + layoutResource = R.layout.view_preference + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + Preference.bindView(context, holder, icon, title, summary, null, null) + super.onBindViewHolder(holder) + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt index b295e5ad3..fa8b4eeb7 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/IconListPreference.kt @@ -29,6 +29,7 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference private val mEntryDrawables = arrayListOf() init { + layoutResource = R.layout.view_preference widgetLayoutResource = R.layout.view_icon val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0) @@ -52,11 +53,12 @@ class IconListPreference(context: Context, attrs: AttributeSet) : ListPreference override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) - holder?.itemView?.findViewById(R.id.preview)?.let { + val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.preview, 50, 50) + if (v is ImageView) { val selectedIndex = findIndexOfValue(value) if (selectedIndex >= 0) { val drawable = mEntryDrawables[selectedIndex] - it.setImageDrawable(drawable) + v.setImageDrawable(drawable) } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt index d8b51e90f..0763c8474 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/NameListPreference.kt @@ -11,12 +11,15 @@ import io.legado.app.R class NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) { init { + layoutResource = R.layout.view_preference widgetLayoutResource = R.layout.item_fillet_text } override fun onBindViewHolder(holder: PreferenceViewHolder?) { + val v = Preference.bindView(context, holder, icon, title, summary, widgetLayoutResource, R.id.text_view) + if (v is TextView) { + v.text = entry + } super.onBindViewHolder(holder) - val textView = holder?.itemView?.findViewById(R.id.text_view) - textView?.text = entry } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt new file mode 100644 index 000000000..0190dcd9f --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/Preference.kt @@ -0,0 +1,85 @@ +package io.legado.app.ui.widget.prefs + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.preference.PreferenceViewHolder +import io.legado.app.R +import io.legado.app.lib.theme.accentColor +import org.jetbrains.anko.layoutInflater +import kotlin.math.roundToInt + +class Preference(context: Context, attrs: AttributeSet) : androidx.preference.Preference(context, attrs) { + + init { + // isPersistent = true + layoutResource = R.layout.view_preference + } + + companion object { + + fun bindView(context: Context, it: PreferenceViewHolder?, icon: Drawable?, title: CharSequence?, summary: CharSequence?, weightLayoutRes: Int?, viewId: Int?, + weightWidth: Int = 0, weightHeight: Int = 0): T? { + if (it == null) return null + val view = it.findViewById(R.id.preference_title) + if (view is TextView) { + view.text = title + view.isVisible = title != null && title.isNotEmpty() + + val tvSummary = it.findViewById(R.id.preference_desc) + if (tvSummary is TextView) { + tvSummary.text = summary + tvSummary.isVisible = summary != null && summary.isNotEmpty() + } + + val iconView = it.findViewById(R.id.preference_icon) + if (iconView is ImageView) { + iconView.isVisible = icon != null && icon.isVisible + iconView.setImageDrawable(icon) + iconView.setColorFilter(context.accentColor) + } + } + + if (weightLayoutRes != null && weightLayoutRes != 0 && viewId != null && viewId != 0) { + val lay = it.findViewById(R.id.preference_widget) + if (lay is FrameLayout) { + var v = it.itemView.findViewById(viewId) + if (v == null) { + val inflater: LayoutInflater = context.layoutInflater + val childView = inflater.inflate(weightLayoutRes, null) + lay.removeAllViews() + lay.addView(childView) + lay.isVisible = true + v = lay.findViewById(viewId) + } + + if (weightWidth > 0 || weightHeight > 0) { + val lp = lay.layoutParams + if (weightHeight > 0) + lp.height = (context.resources.displayMetrics.density * weightHeight).roundToInt() + if (weightWidth > 0) + lp.width = (context.resources.displayMetrics.density * weightWidth).roundToInt() + lay.layoutParams = lp + } + + return v + } + } + + return null + } + + } + + override fun onBindViewHolder(holder: PreferenceViewHolder?) { + bindView(context, holder, icon, title, summary, null, null) + super.onBindViewHolder(holder) + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt index e1458b76e..a7dcd2e6c 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/PreferenceCategory.kt @@ -1,22 +1,41 @@ package io.legado.app.ui.widget.prefs import android.content.Context +import android.graphics.Color import android.util.AttributeSet +import android.view.View import android.widget.TextView +import androidx.core.view.isVisible import androidx.preference.PreferenceCategory import androidx.preference.PreferenceViewHolder +import io.legado.app.R import io.legado.app.lib.theme.accentColor -class PreferenceCategory(context: Context, attrs: AttributeSet) : - PreferenceCategory(context, attrs) { +class PreferenceCategory(context: Context, attrs: AttributeSet) : PreferenceCategory(context, attrs) { + + init { + isPersistent = true + layoutResource = R.layout.view_preference_category + } override fun onBindViewHolder(holder: PreferenceViewHolder?) { super.onBindViewHolder(holder) holder?.let { - val view = it.findViewById(android.R.id.title) - if (view is TextView && !view.isInEditMode) { - view.setTextColor(context.accentColor)//设置title文本的颜色 + val view = it.findViewById(R.id.preference_title) + if (view is TextView) { // && !view.isInEditMode + view.text = title + view.setTextColor(context.accentColor) //设置title文本的颜色 + view.isVisible = title != null && title.isNotEmpty() + + val da = it.findViewById(R.id.preference_divider_above) + if (da is View) { + da.isVisible = it.isDividerAllowedAbove + } + val db = it.findViewById(R.id.preference_divider_below) + if (db is View) { + db.isVisible = it.isDividerAllowedBelow + } } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt index 4fe66af7b..2f6becfc2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt +++ b/app/src/main/java/io/legado/app/ui/widget/prefs/SwitchPreference.kt @@ -12,14 +12,24 @@ import io.legado.app.lib.theme.accentColor class SwitchPreference(context: Context, attrs: AttributeSet) : SwitchPreferenceCompat(context, attrs) { + init { + layoutResource = R.layout.view_preference + } + override fun onBindViewHolder(holder: PreferenceViewHolder?) { - super.onBindViewHolder(holder) - holder?.let { - val view = it.findViewById(R.id.switchWidget) - if (view is SwitchCompat) { - ATH.setTint(view, context.accentColor) - } + val v = Preference.bindView( + context, + holder, + icon, + title, + summary, + widgetLayoutResource, + R.id.switchWidget + ) + if (v is SwitchCompat) { + ATH.setTint(v, context.accentColor) } + super.onBindViewHolder(holder) } } diff --git a/app/src/main/java/io/legado/app/utils/DocumentUtils.kt b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt index f4fd0e8eb..b53cebb14 100644 --- a/app/src/main/java/io/legado/app/utils/DocumentUtils.kt +++ b/app/src/main/java/io/legado/app/utils/DocumentUtils.kt @@ -98,7 +98,7 @@ object DocumentUtils { DocumentsContract.Document.COLUMN_LAST_MODIFIED, DocumentsContract.Document.COLUMN_SIZE, DocumentsContract.Document.COLUMN_MIME_TYPE - ), null, null, null + ), null, null, DocumentsContract.Document.COLUMN_DISPLAY_NAME ) c?.let { val ici = c.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID) diff --git a/app/src/main/java/io/legado/app/utils/JsoupExtensions.kt b/app/src/main/java/io/legado/app/utils/JsoupExtensions.kt new file mode 100644 index 000000000..3f347e4a1 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/JsoupExtensions.kt @@ -0,0 +1,66 @@ +package io.legado.app.utils + +import org.jsoup.internal.StringUtil +import org.jsoup.nodes.CDataNode +import org.jsoup.nodes.Element +import org.jsoup.nodes.Node +import org.jsoup.nodes.TextNode +import org.jsoup.select.NodeTraversor +import org.jsoup.select.NodeVisitor + + +fun Element.textArray(): Array { + val accum = StringUtil.borrowBuilder() + NodeTraversor.traverse(object : NodeVisitor { + override fun head(node: Node, depth: Int) { + if (node is TextNode) { + appendNormalisedText(accum, node) + } else if (node is Element) { + if (accum.isNotEmpty() && + (node.isBlock || node.tag().name == "br") && + !lastCharIsWhitespace(accum) + ) accum.append("\n") + } + } + + override fun tail(node: Node, depth: Int) { + if (node is Element) { + if (node.isBlock && node.nextSibling() is TextNode && !lastCharIsWhitespace( + accum + ) + ) accum.append("\n") + } + } + }, this) + val text = StringUtil.releaseBuilder(accum).trim { it <= ' ' } + return text.splitNotBlank("\n") +} + +private fun appendNormalisedText(accum: StringBuilder, textNode: TextNode) { + val text = textNode.wholeText + if (preserveWhitespace(textNode.parentNode()) || textNode is CDataNode) + accum.append(text) + else StringUtil.appendNormalisedWhitespace( + accum, + text, + lastCharIsWhitespace(accum) + ) +} + +private fun preserveWhitespace(node: Node?): Boolean { + if (node is Element) { + var el = node as Element? + var i = 0 + do { + if (el!!.tag().preserveWhitespace()) return true + el = el.parent() + i++ + } while (i < 6 && el != null) + } + return false +} + +private fun lastCharIsWhitespace(sb: java.lang.StringBuilder): Boolean { + return sb.isNotEmpty() && sb[sb.length - 1] == ' ' +} + diff --git a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt index f0334504e..e37539851 100644 --- a/app/src/main/java/io/legado/app/utils/ViewExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/ViewExtensions.kt @@ -6,6 +6,7 @@ import android.graphics.Canvas import android.os.Build import android.view.View import android.view.View.* +import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.RadioGroup import android.widget.SeekBar @@ -75,6 +76,13 @@ fun View.screenshot(): Bitmap? { }.getOrNull() } +fun View.setMargin(left: Int, top: Int, right: Int, bottom: Int) { + if (layoutParams is ViewGroup.MarginLayoutParams) { + (layoutParams as ViewGroup.MarginLayoutParams).setMargins(left, top, right, bottom) + requestLayout() + } +} + fun SeekBar.progressAdd(int: Int) { progress += int } diff --git a/app/src/main/res/drawable-v24/ic_launcher_2.xml b/app/src/main/res/drawable-v24/ic_launcher_2.xml deleted file mode 100644 index b4343492c..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_2.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable-v24/ic_launcher_2b.xml b/app/src/main/res/drawable-v24/ic_launcher_2b.xml deleted file mode 100644 index be66fb2a7..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_2b.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/bg_find_book_group.xml b/app/src/main/res/drawable/bg_find_book_group.xml new file mode 100644 index 000000000..b00a89b22 --- /dev/null +++ b/app/src/main/res/drawable/bg_find_book_group.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_prefs_color.xml b/app/src/main/res/drawable/bg_prefs_color.xml new file mode 100644 index 000000000..ff4a5de4e --- /dev/null +++ b/app/src/main/res/drawable/bg_prefs_color.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_searchview.xml b/app/src/main/res/drawable/bg_searchview.xml new file mode 100644 index 000000000..beee4af1e --- /dev/null +++ b/app/src/main/res/drawable/bg_searchview.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 000000000..19a41e8db --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 000000000..72bcbdb79 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bottom_books.xml b/app/src/main/res/drawable/ic_bottom_books.xml new file mode 100644 index 000000000..807aaaf30 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_books.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bottom_books_e.xml b/app/src/main/res/drawable/ic_bottom_books_e.xml new file mode 100644 index 000000000..04d36c375 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_books_e.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_books_s.xml b/app/src/main/res/drawable/ic_bottom_books_s.xml new file mode 100644 index 000000000..8f5f72542 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_books_s.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_explore.xml b/app/src/main/res/drawable/ic_bottom_explore.xml new file mode 100644 index 000000000..439f69ba9 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_explore.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bottom_explore_e.xml b/app/src/main/res/drawable/ic_bottom_explore_e.xml new file mode 100644 index 000000000..47e4bde9d --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_explore_e.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_explore_s.xml b/app/src/main/res/drawable/ic_bottom_explore_s.xml new file mode 100644 index 000000000..6dc6e4322 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_explore_s.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_bottom_person.xml b/app/src/main/res/drawable/ic_bottom_person.xml new file mode 100644 index 000000000..50053997a --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_person.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bottom_person_e.xml b/app/src/main/res/drawable/ic_bottom_person_e.xml new file mode 100644 index 000000000..d01128391 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_person_e.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_person_s.xml b/app/src/main/res/drawable/ic_bottom_person_s.xml new file mode 100644 index 000000000..3ff7fc8c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_person_s.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_rss_feed.xml b/app/src/main/res/drawable/ic_bottom_rss_feed.xml new file mode 100644 index 000000000..4cd080b7f --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_rss_feed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_bottom_rss_feed_e.xml b/app/src/main/res/drawable/ic_bottom_rss_feed_e.xml new file mode 100644 index 000000000..c1c4ba0e3 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_rss_feed_e.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bottom_rss_feed_s.xml b/app/src/main/res/drawable/ic_bottom_rss_feed_s.xml new file mode 100644 index 000000000..a73c01430 --- /dev/null +++ b/app/src/main/res/drawable/ic_bottom_rss_feed_s.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cfg_about.xml b/app/src/main/res/drawable/ic_cfg_about.xml new file mode 100644 index 000000000..42e3d30d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cfg_backup.xml b/app/src/main/res/drawable/ic_cfg_backup.xml new file mode 100644 index 000000000..9c647c009 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_backup.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cfg_e_lnk.xml b/app/src/main/res/drawable/ic_cfg_e_lnk.xml new file mode 100644 index 000000000..b94a8eb4d --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_e_lnk.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_jz.xml b/app/src/main/res/drawable/ic_cfg_jz.xml new file mode 100644 index 000000000..710bae7fd --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_jz.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_other.xml b/app/src/main/res/drawable/ic_cfg_other.xml new file mode 100644 index 000000000..a3d26b642 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_other.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_replace.xml b/app/src/main/res/drawable/ic_cfg_replace.xml new file mode 100644 index 000000000..53c9d1d04 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_replace.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_source.xml b/app/src/main/res/drawable/ic_cfg_source.xml new file mode 100644 index 000000000..e71bb109d --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_source.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_cfg_theme.xml b/app/src/main/res/drawable/ic_cfg_theme.xml new file mode 100644 index 000000000..2fe949d19 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_theme.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_cfg_web.xml b/app/src/main/res/drawable/ic_cfg_web.xml new file mode 100644 index 000000000..7a34ae004 --- /dev/null +++ b/app/src/main/res/drawable/ic_cfg_web.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_explore_black_24dp.xml b/app/src/main/res/drawable/ic_explore_black_24dp.xml deleted file mode 100644 index d841826b0..000000000 --- a/app/src/main/res/drawable/ic_explore_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_help.xml b/app/src/main/res/drawable/ic_help.xml new file mode 100644 index 000000000..32838437b --- /dev/null +++ b/app/src/main/res/drawable/ic_help.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launch.xml b/app/src/main/res/drawable/ic_launch.xml deleted file mode 100644 index 6b423cdc7..000000000 --- a/app/src/main/res/drawable/ic_launch.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher1.xml b/app/src/main/res/drawable/ic_launcher1.xml new file mode 100644 index 000000000..18c829634 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher1.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher1_b.xml b/app/src/main/res/drawable/ic_launcher1_b.xml new file mode 100644 index 000000000..c5b40eb91 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher1_b.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_1.xml b/app/src/main/res/drawable/ic_launcher2.xml similarity index 100% rename from app/src/main/res/drawable-v24/ic_launcher_1.xml rename to app/src/main/res/drawable/ic_launcher2.xml diff --git a/app/src/main/res/drawable-v24/ic_launcher_3.xml b/app/src/main/res/drawable/ic_launcher3.xml similarity index 100% rename from app/src/main/res/drawable-v24/ic_launcher_3.xml rename to app/src/main/res/drawable/ic_launcher3.xml diff --git a/app/src/main/res/drawable-v24/ic_launcher_0.xml b/app/src/main/res/drawable/ic_launcher4.xml similarity index 100% rename from app/src/main/res/drawable-v24/ic_launcher_0.xml rename to app/src/main/res/drawable/ic_launcher4.xml diff --git a/app/src/main/res/drawable/ic_launcher5.xml b/app/src/main/res/drawable/ic_launcher5.xml new file mode 100644 index 000000000..29435836b --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher5.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher5_b.xml b/app/src/main/res/drawable/ic_launcher5_b.xml new file mode 100644 index 000000000..9a5153369 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher5_b.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher6.xml b/app/src/main/res/drawable/ic_launcher6.xml new file mode 100644 index 000000000..1bb711f8f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher6.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher7.xml b/app/src/main/res/drawable/ic_launcher7.xml new file mode 100644 index 000000000..983784aef --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher7.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher7_b.xml b/app/src/main/res/drawable/ic_launcher7_b.xml new file mode 100644 index 000000000..76214f1e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher7_b.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_library_books_black_24dp.xml b/app/src/main/res/drawable/ic_library_books_black_24dp.xml deleted file mode 100644 index 06deda209..000000000 --- a/app/src/main/res/drawable/ic_library_books_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_live_help_black_24dp.xml b/app/src/main/res/drawable/ic_live_help_black_24dp.xml deleted file mode 100644 index 8b9c1ac46..000000000 --- a/app/src/main/res/drawable/ic_live_help_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_person_black_24dp.xml b/app/src/main/res/drawable/ic_person_black_24dp.xml deleted file mode 100644 index 9f1acc792..000000000 --- a/app/src/main/res/drawable/ic_person_black_24dp.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_rss_feed.xml b/app/src/main/res/drawable/ic_rss_feed.xml deleted file mode 100644 index ed6228cc2..000000000 --- a/app/src/main/res/drawable/ic_rss_feed.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/ic_search_hint.xml b/app/src/main/res/drawable/ic_search_hint.xml new file mode 100644 index 000000000..42de64c26 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_hint.xml @@ -0,0 +1,8 @@ + + + + diff --git a/app/src/main/res/drawable/shape_fillet_btn.xml b/app/src/main/res/drawable/shape_fillet_btn.xml index 10dac7541..83ccf2776 100644 --- a/app/src/main/res/drawable/shape_fillet_btn.xml +++ b/app/src/main/res/drawable/shape_fillet_btn.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_fillet_btn_press.xml b/app/src/main/res/drawable/shape_fillet_btn_press.xml index b569081a4..b0c46f6f5 100644 --- a/app/src/main/res/drawable/shape_fillet_btn_press.xml +++ b/app/src/main/res/drawable/shape_fillet_btn_press.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/res/layout-land/activity_book_info.xml b/app/src/main/res/layout-land/activity_book_info.xml index 27e0bec0d..1dc7d789c 100644 --- a/app/src/main/res/layout-land/activity_book_info.xml +++ b/app/src/main/res/layout-land/activity_book_info.xml @@ -14,13 +14,10 @@ android:contentDescription="@string/bg_image" app:layout_constraintTop_toTopOf="parent" /> - @@ -81,7 +78,8 @@ android:paddingLeft="10dp" android:paddingTop="8dp" android:paddingRight="16dp" - android:paddingBottom="3dp"> + android:paddingBottom="3dp" + tools:ignore="RtlHardcoded"> + android:scrollbars="vertical" + tools:ignore="NestedWeights"> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> @@ -290,7 +288,7 @@ android:text="@string/group_s" android:textColor="@color/tv_text_summary" android:textSize="13sp" - tools:ignore="NestedWeights" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" /> + android:background="@color/background" /> - + app:layout_constraintTop_toBottomOf="@+id/title_bar" /> - diff --git a/app/src/main/res/layout/activity_import_book.xml b/app/src/main/res/layout/activity_import_book.xml index e93a5b6c6..38d0b4f55 100644 --- a/app/src/main/res/layout/activity_import_book.xml +++ b/app/src/main/res/layout/activity_import_book.xml @@ -1,20 +1,23 @@ - + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="parent" /> + + + + + + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9f2d2044e..701dea9b5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -7,10 +7,10 @@ diff --git a/app/src/main/res/layout/activity_rss_read.xml b/app/src/main/res/layout/activity_rss_read.xml index 76f630002..ea4131764 100644 --- a/app/src/main/res/layout/activity_rss_read.xml +++ b/app/src/main/res/layout/activity_rss_read.xml @@ -15,7 +15,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" /> - diff --git a/app/src/main/res/layout/dialog_read_aloud.xml b/app/src/main/res/layout/dialog_read_aloud.xml index 627bd9414..504865171 100644 --- a/app/src/main/res/layout/dialog_read_aloud.xml +++ b/app/src/main/res/layout/dialog_read_aloud.xml @@ -1,245 +1,337 @@ + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal"> - + + - - + + + + + + - + + + + + + - + android:layout_weight="1" + android:max="60" /> - + - + - - + android:layout_gravity="center_vertical" + android:orientation="horizontal" + android:padding="8dp"> - - - - - - - - - - - - - + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="1" + android:text="@string/read_aloud_speed" + android:textColor="@color/tv_text_default" + android:textSize="14sp" /> + + - + android:layout_marginStart="15dp" + android:layout_marginEnd="15dp" + android:max="45" /> + + + + + + + - - + android:contentDescription="@string/chapter_list" + android:src="@drawable/ic_toc" + app:tint="@color/tv_text_default" + tools:ignore="NestedWeights" /> - + android:layout_gravity="center_horizontal" + android:layout_marginTop="3dp" + android:text="@string/chapter_list" + android:maxLines="1" + android:textColor="@color/tv_text_default" + android:textSize="12sp" /> + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="2" /> + + + + + + + + + + android:id="@+id/ll_to_backstage" + android:layout_width="50dp" + android:layout_height="50dp" + android:background="?android:attr/selectableItemBackgroundBorderless" + android:clickable="true" + android:contentDescription="@string/interface_setting" + android:focusable="true" + android:orientation="vertical" + android:paddingBottom="7dp"> - + + - - - - - - - - + + + + + + + + android:layout_height="0dp" + android:layout_weight="1" + android:contentDescription="@string/aloud_config" + android:src="@drawable/ic_settings" + app:tint="@color/tv_text_default" + tools:ignore="NestedWeights" /> + diff --git a/app/src/main/res/layout/dialog_recycler_view.xml b/app/src/main/res/layout/dialog_recycler_view.xml index 98f67043f..5b1d900a9 100644 --- a/app/src/main/res/layout/dialog_recycler_view.xml +++ b/app/src/main/res/layout/dialog_recycler_view.xml @@ -15,7 +15,7 @@ android:id="@+id/recycler_view" android:background="@color/background_card" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="0dp" android:layout_weight="1" /> diff --git a/app/src/main/res/layout/dialog_text_view.xml b/app/src/main/res/layout/dialog_text_view.xml index 3a2b347c3..c1a1a70a6 100644 --- a/app/src/main/res/layout/dialog_text_view.xml +++ b/app/src/main/res/layout/dialog_text_view.xml @@ -1,7 +1,25 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + + + + + + diff --git a/app/src/main/res/layout/fragment_bookshelf.xml b/app/src/main/res/layout/fragment_bookshelf.xml index 2eb390c9a..ea0a9ab40 100644 --- a/app/src/main/res/layout/fragment_bookshelf.xml +++ b/app/src/main/res/layout/fragment_bookshelf.xml @@ -14,7 +14,7 @@ app:contentLayout="@layout/view_tab_layout_min" app:layout_constraintTop_toTopOf="parent" /> - + android:paddingTop="2dp" + android:paddingRight="10dp" + android:paddingLeft="10dp"> - + app:shadowColor="#16000000" + app:shadowShape="rectangle" + app:shadowDx="1dp" + app:shadowDy="3dp" + app:shadowRadius="4dp" + app:shadowSide="all"> - - - + android:scaleType="centerCrop" + android:src="@drawable/image_cover_default" + android:transitionName="img_cover" + tools:ignore="UnusedAttribute" /> - - + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_fillet_text.xml b/app/src/main/res/layout/item_fillet_text.xml index 331bd8a2c..dcdf75935 100644 --- a/app/src/main/res/layout/item_fillet_text.xml +++ b/app/src/main/res/layout/item_fillet_text.xml @@ -8,7 +8,10 @@ android:background="@drawable/selector_fillet_btn_bg" android:ellipsize="end" android:gravity="center" - android:padding="5dp" + android:paddingTop="4dp" + android:paddingBottom="4dp" + android:paddingLeft="12dp" + android:paddingRight="12dp" android:maxLines="1" android:textColor="@color/tv_text_default" android:textSize="14sp" diff --git a/app/src/main/res/layout/item_find_book.xml b/app/src/main/res/layout/item_find_book.xml index 8a95ed1e2..d689630d2 100644 --- a/app/src/main/res/layout/item_find_book.xml +++ b/app/src/main/res/layout/item_find_book.xml @@ -4,8 +4,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingLeft="10dp" - android:paddingRight="10dp" android:gravity="center" android:orientation="vertical"> @@ -17,8 +15,7 @@ android:paddingRight="10dp" android:paddingTop="6dp" android:paddingBottom="6dp" - android:layout_margin="4dp" - android:background="@color/btn_bg_press" + android:background="@drawable/bg_find_book_group" android:orientation="horizontal" android:gravity="center_vertical"> @@ -28,11 +25,22 @@ android:layout_height="wrap_content" android:layout_weight="1" /> + + @@ -41,6 +49,8 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_text.xml b/app/src/main/res/layout/item_text.xml index e37188e47..357efdde3 100644 --- a/app/src/main/res/layout/item_text.xml +++ b/app/src/main/res/layout/item_text.xml @@ -9,6 +9,7 @@ android:gravity="center" android:padding="5dp" android:maxLines="1" + android:scrollbars="none" android:textColor="@color/tv_text_default" android:textSize="14sp" tools:ignore="UnusedAttribute" /> diff --git a/app/src/main/res/layout/popup_action_menu.xml b/app/src/main/res/layout/popup_action_menu.xml index 98cceb33c..0f6b6e0e5 100644 --- a/app/src/main/res/layout/popup_action_menu.xml +++ b/app/src/main/res/layout/popup_action_menu.xml @@ -1,27 +1,46 @@ - + android:orientation="vertical"> - + android:orientation="horizontal" + android:gravity="center_vertical"> + + + + + + - + android:orientation="vertical" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> - + diff --git a/app/src/main/res/layout/view_book_page.xml b/app/src/main/res/layout/view_book_page.xml index 922819b1e..3f09664a3 100644 --- a/app/src/main/res/layout/view_book_page.xml +++ b/app/src/main/res/layout/view_book_page.xml @@ -18,6 +18,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" + android:maxLines="1" + android:ellipsize="end" android:textSize="12sp" /> @@ -62,7 +66,8 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:singleLine="true" + android:maxLines="1" + android:ellipsize="end" android:textSize="12sp" /> diff --git a/app/src/main/res/layout/view_preference.xml b/app/src/main/res/layout/view_preference.xml new file mode 100644 index 000000000..87b10ef5b --- /dev/null +++ b/app/src/main/res/layout/view_preference.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_preference_category.xml b/app/src/main/res/layout/view_preference_category.xml new file mode 100644 index 000000000..28f4e73ff --- /dev/null +++ b/app/src/main/res/layout/view_preference_category.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_read_menu.xml b/app/src/main/res/layout/view_read_menu.xml index 135f5b5d9..3754c0914 100644 --- a/app/src/main/res/layout/view_read_menu.xml +++ b/app/src/main/res/layout/view_read_menu.xml @@ -210,7 +210,7 @@ diff --git a/app/src/main/res/layout/view_search.xml b/app/src/main/res/layout/view_search.xml index 5fd67be63..96ff311da 100644 --- a/app/src/main/res/layout/view_search.xml +++ b/app/src/main/res/layout/view_search.xml @@ -1,10 +1,17 @@ - \ No newline at end of file diff --git a/app/src/main/res/menu/content_select_action.xml b/app/src/main/res/menu/content_select_action.xml index d11ee6154..ff83efd96 100644 --- a/app/src/main/res/menu/content_select_action.xml +++ b/app/src/main/res/menu/content_select_action.xml @@ -9,6 +9,10 @@ android:id="@+id/menu_copy" android:title="@android:string/copy" /> + + diff --git a/app/src/main/res/menu/main_bnv.xml b/app/src/main/res/menu/main_bnv.xml index fb34daff3..9197bec7b 100644 --- a/app/src/main/res/menu/main_bnv.xml +++ b/app/src/main/res/menu/main_bnv.xml @@ -4,20 +4,20 @@ tools:showIn="bottom_navigation_view"> + android:id="@+id/menu_bookshelf" + android:icon="@drawable/ic_bottom_books" + android:title="@string/bookshelf" /> + android:id="@+id/menu_find_book" + android:icon="@drawable/ic_bottom_explore" + android:title="@string/find" /> + android:id="@+id/menu_my_config" + android:icon="@drawable/ic_bottom_person" + android:title="@string/my" /> diff --git a/app/src/main/res/menu/main_my.xml b/app/src/main/res/menu/main_my.xml index db1161c28..02b6acbd0 100644 --- a/app/src/main/res/menu/main_my.xml +++ b/app/src/main/res/menu/main_my.xml @@ -6,20 +6,8 @@ - - - - diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index e8409cf31..7a68d4392 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher1.xml b/app/src/main/res/mipmap-anydpi-v26/launcher1.xml index 57e845ad3..b3aa68018 100644 --- a/app/src/main/res/mipmap-anydpi-v26/launcher1.xml +++ b/app/src/main/res/mipmap-anydpi-v26/launcher1.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher2.xml b/app/src/main/res/mipmap-anydpi-v26/launcher2.xml index 409e0d965..5de236bd2 100644 --- a/app/src/main/res/mipmap-anydpi-v26/launcher2.xml +++ b/app/src/main/res/mipmap-anydpi-v26/launcher2.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher3.xml b/app/src/main/res/mipmap-anydpi-v26/launcher3.xml index 24428243f..9e7ef6711 100644 --- a/app/src/main/res/mipmap-anydpi-v26/launcher3.xml +++ b/app/src/main/res/mipmap-anydpi-v26/launcher3.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher4.xml b/app/src/main/res/mipmap-anydpi-v26/launcher4.xml new file mode 100644 index 000000000..63e51b918 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/launcher4.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher5.xml b/app/src/main/res/mipmap-anydpi-v26/launcher5.xml new file mode 100644 index 000000000..7a23c7c66 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/launcher5.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/launcher6.xml b/app/src/main/res/mipmap-anydpi-v26/launcher6.xml new file mode 100644 index 000000000..4ba689a93 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/launcher6.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png index a7e087bc8..d2c418293 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png index f0e9ad532..2d18ac346 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 371e045cd..4b44e0bba 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index feff4e965..df9bfebb8 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 63cb54502..6d101d806 100644 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 18ab012e0..8ac7aa66a 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -7,9 +7,11 @@ @color/md_grey_900 @color/md_grey_850 @color/md_grey_800 + #10303030 #69000000 + #10ffffff #30ffffff #363636 diff --git a/app/src/main/res/values/array_values.xml b/app/src/main/res/values/array_values.xml index c517c46ce..40bc6684e 100644 --- a/app/src/main/res/values/array_values.xml +++ b/app/src/main/res/values/array_values.xml @@ -6,9 +6,11 @@ launcher1 launcher2 launcher3 + launcher4 + launcher5 + launcher6 - 0 60 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index a53546e7f..39c85cae3 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -79,6 +79,9 @@ icon1 icon2 icon3 + icon4 + icon5 + icon6 diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index d5a4d11e1..9e920088b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -173,4 +173,17 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index d6999a7ae..0fe64b7ca 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -16,8 +16,10 @@ @color/md_grey_50 @color/md_grey_100 @color/md_grey_200 + @color/md_grey_50 #00000000 + #10000000 #30000000 #d3321b diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9f8946337..f7ecf2030 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -6,7 +6,6 @@ 176dp 16dp - 14sp 16sp 18sp @@ -35,4 +34,6 @@ 8dp 8dp 8dp + + 8dp \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 84e6e6bb2..bc219ab39 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,7 @@ 删除 替换 替换净化 + 配置替换净化规则 暂无 启用 替换净化-搜索 @@ -58,6 +59,7 @@ 添加本地 书源 书源管理 + 新建/导入/编辑/管理书源 设置 主题设置 与界面/颜色相关的一些设置 @@ -80,8 +82,9 @@ 加载中… 重试 Web 服务 + 启用Web服务 web编辑书源 - http://%s:%d + http://%1$s:%2$d 离线下载 离线下载 下载选择的章节到本地 @@ -207,14 +210,14 @@ 替换规则为空或者不满足正则表达式要求 选择操作 全选 - 全选(%d/%d) - 取消(%d/%d) + 全选(%1$d/%2$d) + 取消(%1$d/%2$d) 深色模式 启动页 开始下载 取消下载 暂无任务 - 已下载 %d/%d + 已下载 %1$d/%2$d 导入选择书籍 更新和搜索线程数,太多会卡顿 切换图标 @@ -225,7 +228,7 @@ 内容简介 简介:%s 打开外部书籍 - 来源:%s + 来源: %s 本地导入 导入在线规则 检查更新间隔 @@ -289,7 +292,7 @@ 右边距 校验书源 校验所选 - 进度 %d/%d + 进度 %1$d/%2$d 请安装并选择中文TTS! TTS初始化失败! 简繁转换 @@ -442,6 +445,7 @@ 选中时点击可弹出菜单 主题 主题模式 + 选择主题模式 默认主题 恢复主题为默认配色 加入QQ群 @@ -457,6 +461,7 @@ 导入本地书籍需存储权限 夜间模式 E-Ink 模式 + 电子墨水屏模式 本软件需要存储权限来存储备份书籍信息 再按一次退出程序 导入本地书籍需存储权限 @@ -581,7 +586,7 @@ 类型: 文本 音频 - 转到后台 + 后台 正在导入 正在导出 自定义翻页按键 @@ -599,7 +604,7 @@ 夜间模式跟随系统 上级 在线朗读音色 - (%d/%d) + (%1$d/%2$d) 显示订阅 服务已停止 正在启动服务\n具体信息查看通知栏 @@ -628,9 +633,8 @@ 更多菜单 - 其它朗读设置 系统内置字体样式 - 是否删除源文件 + 删除源文件 预设一 预设二 预设三 @@ -646,5 +650,8 @@ 导航栏阴影 当前阴影大小(elevation): %s 默认 - + 主菜单 + 点击授予权限 + 阅读需要访问存储卡权限,请点击下方的"授予权限"按钮,或前往“设置”—“应用权限”—打开所需权限。如果授予权限后仍然不正常,请点击右上角的“选择文件夹”,使用系统文件夹选择器。 + 全文朗读中不能朗读选中文字 diff --git a/app/src/main/res/xml/about.xml b/app/src/main/res/xml/about.xml index 130e16e60..2970ab12b 100644 --- a/app/src/main/res/xml/about.xml +++ b/app/src/main/res/xml/about.xml @@ -2,68 +2,78 @@ - - - - - - - - - - diff --git a/app/src/main/res/xml/donate.xml b/app/src/main/res/xml/donate.xml index 13d448544..8732f864c 100644 --- a/app/src/main/res/xml/donate.xml +++ b/app/src/main/res/xml/donate.xml @@ -4,51 +4,64 @@ - - - - - - - - - - + - - - - diff --git a/app/src/main/res/xml/pref_config_other.xml b/app/src/main/res/xml/pref_config_other.xml index b049bcf22..560692986 100644 --- a/app/src/main/res/xml/pref_config_other.xml +++ b/app/src/main/res/xml/pref_config_other.xml @@ -4,6 +4,9 @@ - - - - diff --git a/app/src/main/res/xml/pref_config_theme.xml b/app/src/main/res/xml/pref_config_theme.xml index 2f34e86ec..bf90dd7ab 100644 --- a/app/src/main/res/xml/pref_config_theme.xml +++ b/app/src/main/res/xml/pref_config_theme.xml @@ -26,21 +26,26 @@ android:title="@string/navigation_bar_color_change" app:iconSpaceReserved="false" /> - - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:divider="@color/transparent" + app:allowDividerBelow="false" + app:allowDividerAbove="false" + > - - + app:iconSpaceReserved="false" + app:allowDividerAbove="true" + app:allowDividerBelow="false" > - - - - - diff --git a/build.gradle b/build.gradle index 5eb9c16b8..839736b35 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.61' + ext.kotlin_version = '1.3.70' repositories { google() jcenter() @@ -29,4 +29,4 @@ allprojects { task clean(type: Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 23339e0df..667087ef0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx2048m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects