diff --git a/README.md b/README.md index d65e3dbdc..1a035aaf5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # legado ## 阅读3.0 +书源规则 https://celeter.github.io/?tdsourcetag=s_pctim_aiomsg diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23f5edd36..0f1381bf6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -78,6 +78,7 @@ + @@ -103,6 +104,7 @@ + 【备份与恢复】,选择【导入旧版本数据】。 +**2020/02/05** +* 修复bug +* Rss收藏功能完成 +* Rss已读标记不会再丢失 + +**2020/02/04** +* 主界面切换时自动隐藏键盘 +* 添加本地书籍完成,解析txt文件完成,本地txt可以看了 +* 封面换源,书籍信息界面点击封面弹出封面换源界面 +* 默认封面绘制书名和作者 +* 修复在线朗读遇到单独标点,停止朗读的问题 + +**2020/02/02** +* merged commit e584606, rss修复BaseURL模式下部分图片无法加载, 修复可能出现的乱码 +* 菜单添加网址功能完成 + **2020/01/31** -* 正在写本地导入,还没写完 * 修复搜索闪退,因为默认线程为0了 **2020/01/30** diff --git a/app/src/main/java/io/legado/app/README.md b/app/src/main/java/io/legado/app/README.md new file mode 100644 index 000000000..b23d4196d --- /dev/null +++ b/app/src/main/java/io/legado/app/README.md @@ -0,0 +1,12 @@ +## 文件结构介绍 + +* base 基类 +* constant 常量 +* data 数据 +* help 帮助 +* lib 库 +* model 解析 +* receiver 广播侦听 +* service 服务 +* ui 界面 +* web web服务 \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/base/BaseService.kt b/app/src/main/java/io/legado/app/base/BaseService.kt index c67acb95c..163ac09ec 100644 --- a/app/src/main/java/io/legado/app/base/BaseService.kt +++ b/app/src/main/java/io/legado/app/base/BaseService.kt @@ -3,17 +3,27 @@ package io.legado.app.base import android.app.Service import android.content.Intent import android.os.IBinder +import io.legado.app.help.coroutine.Coroutine import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.cancel +import kotlin.coroutines.CoroutineContext abstract class BaseService : Service(), CoroutineScope by MainScope() { + fun execute( + scope: CoroutineScope = this, + context: CoroutineContext = Dispatchers.IO, + block: suspend CoroutineScope.() -> T + ): Coroutine { + return Coroutine.async(scope, context) { block() } + } + override fun onBind(intent: Intent?): IBinder? { return null } - override fun onDestroy() { super.onDestroy() cancel() diff --git a/app/src/main/java/io/legado/app/constant/Bus.kt b/app/src/main/java/io/legado/app/constant/EventBus.kt similarity index 97% rename from app/src/main/java/io/legado/app/constant/Bus.kt rename to app/src/main/java/io/legado/app/constant/EventBus.kt index eb1dd14dd..3fc80e3ac 100644 --- a/app/src/main/java/io/legado/app/constant/Bus.kt +++ b/app/src/main/java/io/legado/app/constant/EventBus.kt @@ -1,6 +1,6 @@ package io.legado.app.constant -object Bus { +object EventBus { const val MEDIA_BUTTON = "mediaButton" const val RECREATE = "RECREATE" const val UP_BOOK = "sourceDebugLog" diff --git a/app/src/main/java/io/legado/app/constant/Action.kt b/app/src/main/java/io/legado/app/constant/IntentAction.kt similarity index 96% rename from app/src/main/java/io/legado/app/constant/Action.kt rename to app/src/main/java/io/legado/app/constant/IntentAction.kt index a13a8cca3..0dffe87a1 100644 --- a/app/src/main/java/io/legado/app/constant/Action.kt +++ b/app/src/main/java/io/legado/app/constant/IntentAction.kt @@ -1,6 +1,6 @@ package io.legado.app.constant -object Action { +object IntentAction { const val start = "start" const val play = "play" const val stop = "stop" diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index 1cb5e2edc..ec5b3a4cd 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -1,12 +1,17 @@ package io.legado.app.constant object PreferKey { - + const val versionCode = "versionCode" + const val themeMode = "themeMode" const val downloadPath = "downloadPath" const val hideStatusBar = "hideStatusBar" const val clickAllNext = "clickAllNext" const val hideNavigationBar = "hideNavigationBar" const val precisionSearch = "precisionSearch" + const val readAloudOnLine = "readAloudOnLine" + const val readAloudByPage = "readAloudByPage" + const val ttsSpeechRate = "ttsSpeechRate" + const val ttsSpeechPer = "ttsSpeechPer" const val prevKey = "prevKeyCode" const val nextKey = "nextKeyCode" const val showRss = "showRss" @@ -21,5 +26,8 @@ object PreferKey { const val backupPath = "backupUri" const val threadCount = "threadCount" const val keepLight = "keep_light" - const val autoDarkMode = "autoDarkMode" + const val webService = "webService" + const val webDavUrl = "web_dav_url" + const val webDavAccount = "web_dav_account" + const val webDavPassword = "web_dav_password" } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index eb44e9381..e548f4584 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -16,8 +16,9 @@ import kotlinx.coroutines.launch @Database( entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, - RssSource::class, Bookmark::class, RssArticle::class, RssStar::class], - version = 6, + RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class, + RssStar::class, TxtTocRule::class], + version = 8, exportSchema = true ) abstract class AppDatabase : RoomDatabase() { @@ -50,4 +51,5 @@ abstract class AppDatabase : RoomDatabase() { abstract fun rssArticleDao(): RssArticleDao abstract fun rssStarDao(): RssStarDao abstract fun cookieDao(): CookieDao + abstract fun txtTocRule(): TxtTocRuleDao } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/README.md b/app/src/main/java/io/legado/app/data/README.md index 18061b12d..ec1ddefbe 100644 --- a/app/src/main/java/io/legado/app/data/README.md +++ b/app/src/main/java/io/legado/app/data/README.md @@ -1 +1,3 @@ -## 存储数据用 \ No newline at end of file +## 存储数据用 +* dao 数据操作 +* entities 数据模型 \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index 303be7d5c..399f79c45 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -45,7 +45,7 @@ interface BookDao { val hasUpdateBooks: List @get:Query("SELECT * FROM books") - val allBooks: List + val all: List @get:Query("SELECT * FROM books where type = 0 ORDER BY durChapterTime DESC limit 1") val lastReadBook: Book? @@ -60,10 +60,10 @@ interface BookDao { fun insert(vararg book: Book) @Update - fun update(vararg books: Book) + fun update(vararg book: Book) - @Query("delete from books where bookUrl = :bookUrl") - fun delete(bookUrl: String) + @Delete + fun delete(vararg book: Book) @Query("update books set durChapterPos = :pos where bookUrl = :bookUrl") fun upProgress(bookUrl: String, pos: Int) diff --git a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt index b06e7ac9e..af15188bb 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt @@ -19,8 +19,8 @@ interface BookGroupDao { @get:Query("SELECT MAX(`order`) FROM book_groups") val maxOrder: Int - @Query("SELECT * FROM book_groups ORDER BY `order`") - fun all(): List + @get:Query("SELECT * FROM book_groups ORDER BY `order`") + val all: List @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg bookGroup: BookGroup) 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 f76ed3d13..ce8dfe950 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 @@ -29,21 +29,6 @@ interface BookSourceDao { @Query("select distinct enabled from book_sources where bookSourceName like :searchKey or bookSourceGroup like :searchKey or bookSourceUrl like :searchKey") fun searchIsEnable(searchKey: String = ""): List - @Query("update book_sources set enabled = 1 where bookSourceUrl = :sourceUrl") - fun enableSection(sourceUrl: String) - - @Query("update book_sources set enabled = 0 where bookSourceUrl = :sourceUrl") - fun disableSection(sourceUrl: String) - - @Query("update book_sources set enabledExplore = 1 where bookSourceUrl = :sourceUrl") - fun enableSectionExplore(sourceUrl: String) - - @Query("update book_sources set enabledExplore = 0 where bookSourceUrl = :sourceUrl") - fun disableSectionExplore(sourceUrl: String) - - @Query("delete from book_sources where bookSourceUrl = :sourceUrl") - fun delSection(sourceUrl: String) - @Query("select * from book_sources where enabledExplore = 1 order by customOrder asc") fun observeFind(): DataSource.Factory @@ -53,6 +38,9 @@ interface BookSourceDao { @Query("select * from book_sources where enabled = 1 and bookSourceGroup like '%' || :group || '%'") fun getEnabledByGroup(group: String): List + @get:Query("select * from book_sources where bookUrlPattern is not null || bookUrlPattern <> ''") + val hasBookUrlPattern: List + @get:Query("select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''") val noGroup: List @@ -75,7 +63,7 @@ interface BookSourceDao { fun update(vararg bookSource: BookSource) @Delete - fun delete(bookSource: BookSource) + fun delete(vararg bookSource: BookSource) @Query("delete from book_sources where bookSourceUrl = :key") fun delete(key: String) diff --git a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt index 247a5be6e..fbf3c3611 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt @@ -3,6 +3,7 @@ package io.legado.app.data.dao import androidx.lifecycle.LiveData import androidx.room.* import io.legado.app.data.entities.RssArticle +import io.legado.app.data.entities.RssReadRecord @Dao interface RssArticleDao { @@ -10,7 +11,11 @@ interface RssArticleDao { @Query("select * from rssArticles where origin = :origin and link = :link") fun get(origin: String, link: String): RssArticle? - @Query("select * from rssArticles where origin = :origin order by `order` desc") + @Query( + """select t1.link, t1.origin, t1.`order`, t1.title, t1.content, t1.description, t1.image, t1.pubDate, ifNull(t2.read, 0) as read + from rssArticles as t1 left join rssReadRecords as t2 + on t1.link = t2.record where origin = :origin order by `order` desc""" + ) fun liveByOrigin(origin: String): LiveData> @Insert(onConflict = OnConflictStrategy.REPLACE) @@ -24,4 +29,9 @@ interface RssArticleDao { @Query("delete from rssArticles where origin = :origin") fun delete(origin: String) + + @Insert(onConflict = OnConflictStrategy.IGNORE) + fun insertRecord(vararg rssReadRecord: RssReadRecord) + + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt b/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt index 87e527f75..faaf85b9f 100644 --- a/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/RssStarDao.kt @@ -7,6 +7,9 @@ import io.legado.app.data.entities.RssStar @Dao interface RssStarDao { + @get:Query("select * from rssStars order by starTime desc") + val all: List + @Query("select * from rssStars where origin = :origin and link = :link") fun get(origin: String, link: String): RssStar? diff --git a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt index 7e623bfcc..fd3f69951 100644 --- a/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt @@ -28,6 +28,14 @@ interface SearchBookDao { @Query("select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) order by originOrder") fun getByNameAuthorEnable(name: String, author: String): List + @Query( + """select * from searchBooks + where name = :name and author = :author and origin in (select bookSourceUrl from book_sources where enabled = 1) + and coverUrl is not null and coverUrl <> '' + order by originOrder""" + ) + fun getEnableHasCover(name: String, author: String): List + @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(vararg searchBook: SearchBook): List diff --git a/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt new file mode 100644 index 000000000..119daa651 --- /dev/null +++ b/app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt @@ -0,0 +1,20 @@ +package io.legado.app.data.dao + +import androidx.room.* +import io.legado.app.data.entities.TxtTocRule + +@Dao +interface TxtTocRuleDao { + + @get:Query("select * from txtTocRules order by serialNumber") + val all: List + + @get:Query("select * from txtTocRules where enable = 1 order by serialNumber") + val enabled: List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg rule: TxtTocRule) + + @Delete + fun delete(vararg rule: TxtTocRule) +} \ No newline at end of file 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 f01d95bce..204d2de7a 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 @@ -10,6 +10,7 @@ import io.legado.app.utils.GSON import io.legado.app.utils.fromJsonObject import kotlinx.android.parcel.IgnoredOnParcel import kotlinx.android.parcel.Parcelize +import java.nio.charset.Charset import kotlin.math.max @Parcelize @@ -19,15 +20,15 @@ data class Book( var bookUrl: String = "", // 详情页Url(本地书源存储完整文件路径) var tocUrl: String = "", // 目录页Url (toc=table of Contents) var origin: String = BookType.local, // 书源URL(默认BookType.local) - var originName: String = "", //书源名称 - var name: String = "", // 书籍名称(书源获取) - var author: String = "", // 作者名称(书源获取) - override var kind: String? = null, // 分类信息(书源获取) + var originName: String = "", //书源名称 or 本地书籍文件名 + var name: String = "", // 书籍名称(书源获取) + var author: String = "", // 作者名称(书源获取) + override var kind: String? = null, // 分类信息(书源获取) var customTag: String? = null, // 分类信息(用户修改) var coverUrl: String? = null, // 封面Url(书源获取) var customCoverUrl: String? = null, // 封面Url(用户修改) - var intro: String? = null, // 简介内容(书源获取) - var customIntro: String? = null, // 简介内容(用户修改) + var intro: String? = null, // 简介内容(书源获取) + var customIntro: String? = null, // 简介内容(用户修改) var charset: String? = null, // 自定义字符集名称(仅适用于本地书籍) var type: Int = 0, // @BookType var group: Int = 0, // 自定义分组索引号 @@ -52,6 +53,10 @@ data class Book( return origin == BookType.local } + fun isTxt(): Boolean { + return isLocalBook() && originName.endsWith(".txt", true) + } + @Ignore @IgnoredOnParcel override var variableMap: HashMap? = null @@ -81,6 +86,10 @@ data class Book( variable = GSON.toJson(variableMap) } + fun fileCharset(): Charset { + return charset(charset ?: "UTF-8") + } + fun toSearchBook(): SearchBook { return SearchBook( name = name, diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index 39b569565..78852a4ec 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt @@ -47,6 +47,18 @@ data class BookSource( var ruleToc: String? = null, // 目录页规则 var ruleContent: String? = null // 正文页规则 ) : Parcelable, JsExtensions { + + override fun hashCode(): Int { + return bookSourceUrl.hashCode() + } + + override fun equals(other: Any?): Boolean { + if (other is BookSource) { + return other.bookSourceUrl == bookSourceUrl + } + return false + } + @Ignore @IgnoredOnParcel private var searchRuleV: SearchRule? = null @@ -126,6 +138,16 @@ data class BookSource( return contentRuleV!! } + fun addGroup(group: String) { + bookSourceGroup?.let { + if (!it.contains(group)) { + bookSourceGroup = "$it;$group" + } + } ?: let { + bookSourceGroup = group + } + } + fun getExploreKinds(): ArrayList? { val exploreKinds = arrayListOf() exploreUrl?.let { diff --git a/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt b/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt new file mode 100644 index 000000000..edbb4c15d --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt @@ -0,0 +1,7 @@ +package io.legado.app.data.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "rssReadRecords") +data class RssReadRecord(@PrimaryKey val record: String, val read: Boolean = true) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt new file mode 100644 index 000000000..c9cd51d4d --- /dev/null +++ b/app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt @@ -0,0 +1,14 @@ +package io.legado.app.data.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + + +@Entity(tableName = "txtTocRules") +data class TxtTocRule( + @PrimaryKey + var name: String = "", + var rule: String = "", + var serialNumber: Int, + var enable: Boolean = true +) \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/AppConfig.kt b/app/src/main/java/io/legado/app/help/AppConfig.kt index f8eb4f62e..e1f80dfb5 100644 --- a/app/src/main/java/io/legado/app/help/AppConfig.kt +++ b/app/src/main/java/io/legado/app/help/AppConfig.kt @@ -8,7 +8,7 @@ object AppConfig { var isNightTheme: Boolean get() { - return when (App.INSTANCE.getPrefString("themeMode", "0")) { + return when (App.INSTANCE.getPrefString(PreferKey.themeMode, "0")) { "1" -> false "2" -> true else -> App.INSTANCE.sysIsDarkMode() @@ -16,9 +16,9 @@ object AppConfig { } set(value) { if (value) { - App.INSTANCE.putPrefString("themeMode", "2") + App.INSTANCE.putPrefString(PreferKey.themeMode, "2") } else { - App.INSTANCE.putPrefString("themeMode", "1") + App.INSTANCE.putPrefString(PreferKey.themeMode, "1") } } @@ -50,6 +50,17 @@ object AppConfig { } } + var ttsSpeechRate: Int + get() = App.INSTANCE.getPrefInt(PreferKey.ttsSpeechRate, 5) + set(value) { + App.INSTANCE.putPrefInt(PreferKey.ttsSpeechRate, value) + } + + val ttsSpeechPer: String + get() = App.INSTANCE.getPrefString(PreferKey.ttsSpeechPer) ?: "0" + val isEInkMode: Boolean get() = App.INSTANCE.getPrefBoolean("isEInkMode") + + val clickAllNext: Boolean get() = App.INSTANCE.getPrefBoolean(PreferKey.clickAllNext, false) } diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index 7635df1f7..de8c0a5a7 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -7,6 +7,7 @@ import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.ReplaceRule +import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.utils.* import org.apache.commons.text.similarity.JaccardSimilarity import java.io.File @@ -14,7 +15,7 @@ import kotlin.math.min object BookHelp { private const val cacheFolderName = "book_cache" - private var downloadPath: String = + var downloadPath: String = App.INSTANCE.getPrefString(PreferKey.downloadPath) ?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath ?: App.INSTANCE.cacheDir.absolutePath @@ -37,7 +38,7 @@ object BookHelp { } fun clearCache() { - if (downloadUri.isDocumentUri(App.INSTANCE)) { + if (downloadPath.isContentPath()) { DocumentFile.fromTreeUri(App.INSTANCE, downloadUri) ?.findFile(cacheFolderName) ?.delete() @@ -54,15 +55,17 @@ object BookHelp { @Synchronized fun saveContent(book: Book, bookChapter: BookChapter, content: String) { if (content.isEmpty()) return - if (downloadUri.isDocumentUri(App.INSTANCE)) { + if (downloadPath.isContentPath()) { DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> DocumentUtils.getDirDocument( root, subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.listFiles()?.forEach { - if (it.name?.startsWith(String.format("%05d", bookChapter.index)) == true) { - it.delete() - return@forEach + )?.uri?.let { uri -> + DocumentUtils.listFiles(App.INSTANCE, uri).forEach { + if (it.name.startsWith(String.format("%05d", bookChapter.index))) { + DocumentFile.fromSingleUri(App.INSTANCE, it.uri)?.delete() + return@forEach + } } } DocumentUtils.createFileIfNotExist( @@ -90,7 +93,7 @@ object BookHelp { } fun getChapterCount(book: Book): Int { - if (downloadUri.isDocumentUri(App.INSTANCE)) { + if (downloadPath.isContentPath()) { DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> return DocumentUtils.createFolderIfNotExist( root, @@ -107,61 +110,77 @@ object BookHelp { } fun hasContent(book: Book, bookChapter: BookChapter): Boolean { - if (downloadUri.isDocumentUri(App.INSTANCE)) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - return DocumentUtils.exists( - root, + when { + book.isLocalBook() -> { + return true + } + downloadPath.isContentPath() -> { + DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> + return DocumentUtils.exists( + root, + "${bookChapterName(bookChapter)}.nb", + subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) + ) + } + } + else -> { + return FileUtils.exists( + File(downloadPath), "${bookChapterName(bookChapter)}.nb", subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) ) } - } else { - return FileUtils.exists( - File(downloadPath), - "${bookChapterName(bookChapter)}.nb", - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ) } return false } fun getContent(book: Book, bookChapter: BookChapter): String? { - if (downloadUri.isDocumentUri(App.INSTANCE)) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - return DocumentUtils.getDirDocument( - root, - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.findFile("${bookChapterName(bookChapter)}.nb") - ?.uri?.readText(App.INSTANCE) + when { + book.isLocalBook() -> { + return AnalyzeTxtFile.getContent(book, bookChapter) } - } else { - val file = FileUtils.getFile( - File(downloadPath), - "${bookChapterName(bookChapter)}.nb", - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ) - if (file.exists()) { - return file.readText() + downloadPath.isContentPath() -> { + DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> + return DocumentUtils.getDirDocument( + root, + subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) + )?.findFile("${bookChapterName(bookChapter)}.nb") + ?.uri?.readText(App.INSTANCE) + } + } + else -> { + val file = FileUtils.getFile( + File(downloadPath), + "${bookChapterName(bookChapter)}.nb", + subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) + ) + if (file.exists()) { + return file.readText() + } } } return null } fun delContent(book: Book, bookChapter: BookChapter) { - if (downloadUri.isDocumentUri(App.INSTANCE)) { - DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> - DocumentUtils.getDirDocument( - root, + when { + book.isLocalBook() -> return + downloadPath.isContentPath() -> { + DocumentFile.fromTreeUri(App.INSTANCE, downloadUri)?.let { root -> + DocumentUtils.getDirDocument( + root, + subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) + )?.findFile("${bookChapterName(bookChapter)}.nb") + ?.delete() + } + } + else -> { + FileUtils.createFileIfNotExist( + File(downloadPath), + "${bookChapterName(bookChapter)}.nb", subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - )?.findFile("${bookChapterName(bookChapter)}.nb") - ?.delete() + ).delete() } - } else { - FileUtils.createFileIfNotExist( - File(downloadPath), - "${bookChapterName(bookChapter)}.nb", - subDirs = *arrayOf(cacheFolderName, bookFolderName(book)) - ).delete() } } diff --git a/app/src/main/java/io/legado/app/help/ImageLoader.kt b/app/src/main/java/io/legado/app/help/ImageLoader.kt index 26a9063d6..3d4272e9e 100644 --- a/app/src/main/java/io/legado/app/help/ImageLoader.kt +++ b/app/src/main/java/io/legado/app/help/ImageLoader.kt @@ -12,13 +12,15 @@ import java.io.File object ImageLoader { fun load(context: Context, path: String?): RequestBuilder { - if (path?.startsWith("http", true) == true) { - return Glide.with(context).load(path) + return when { + path.isNullOrEmpty() -> Glide.with(context).load(path) + path.startsWith("http", true) -> Glide.with(context).load(path) + else -> try { + Glide.with(context).load(File(path)) + } catch (e: Exception) { + Glide.with(context).load(path) + } } - kotlin.runCatching { - return Glide.with(context).load(File(path)) - } - return Glide.with(context).load(path) } fun load(context: Context, @DrawableRes resId: Int?): RequestBuilder { 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 fd28eea7f..30e77eab6 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 @@ -36,6 +36,7 @@ object Backup { "bookGroup.json", "bookSource.json", "rssSource.json", + "rssStar.json", "replaceRule.json", ReadBookConfig.readConfigFileName, "config.xml" @@ -44,41 +45,12 @@ object Backup { suspend fun backup(context: Context, uri: Uri?) { withContext(IO) { - App.db.bookDao().allBooks.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileUtils.createFileIfNotExist(backupPath + File.separator + "bookshelf.json") - .writeText(json) - } - } - App.db.bookGroupDao().all().let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileUtils.createFileIfNotExist(backupPath + File.separator + "bookGroup.json") - .writeText(json) - } - } - App.db.bookSourceDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileUtils.createFileIfNotExist(backupPath + File.separator + "bookSource.json") - .writeText(json) - } - } - App.db.rssSourceDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileUtils.createFileIfNotExist(backupPath + File.separator + "rssSource.json") - .writeText(json) - } - } - App.db.replaceRuleDao().all.let { - if (it.isNotEmpty()) { - val json = GSON.toJson(it) - FileUtils.createFileIfNotExist(backupPath + File.separator + "replaceRule.json") - .writeText(json) - } - } + 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) @@ -106,6 +78,13 @@ object Backup { } } + private fun writeListToJson(list: List, fileName: String, path: String) { + if (list.isNotEmpty()) { + val json = GSON.toJson(list) + FileUtils.createFileIfNotExist(path + File.separator + fileName).writeText(json) + } + } + private fun copyBackup(context: Context, uri: Uri) { DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> for (fileName in backupFileNames) { 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 new file mode 100644 index 000000000..f8321973c --- /dev/null +++ b/app/src/main/java/io/legado/app/help/storage/OldBook.kt @@ -0,0 +1,55 @@ +package io.legado.app.help.storage + +import android.util.Log +import io.legado.app.App +import io.legado.app.constant.AppConst +import io.legado.app.data.entities.Book +import io.legado.app.utils.readBool +import io.legado.app.utils.readInt +import io.legado.app.utils.readLong +import io.legado.app.utils.readString + +object OldBook { + + fun toNewBook(json: String): List { + val books = mutableListOf() + val items: List> = Restore.jsonPath.parse(json).read("$") + val existingBooks = App.db.bookDao().allBookUrls.toSet() + for (item in items) { + val jsonItem = Restore.jsonPath.parse(item) + val book = Book() + book.bookUrl = jsonItem.readString("$.noteUrl") ?: "" + if (book.bookUrl.isBlank()) continue + book.name = jsonItem.readString("$.bookInfoBean.name") ?: "" + if (book.bookUrl in existingBooks) { + Log.d(AppConst.APP_TAG, "Found existing book: ${book.name}") + continue + } + book.origin = jsonItem.readString("$.tag") ?: "" + book.originName = jsonItem.readString("$.bookInfoBean.origin") ?: "" + book.author = jsonItem.readString("$.bookInfoBean.author") ?: "" + book.type = + if (jsonItem.readString("$.bookInfoBean.bookSourceType") == "AUDIO") 1 else 0 + book.tocUrl = jsonItem.readString("$.bookInfoBean.chapterUrl") ?: book.bookUrl + book.coverUrl = jsonItem.readString("$.bookInfoBean.coverUrl") + book.customCoverUrl = jsonItem.readString("$.customCoverPath") + book.lastCheckTime = jsonItem.readLong("$.bookInfoBean.finalRefreshData") ?: 0 + book.canUpdate = jsonItem.readBool("$.allowUpdate") == true + book.totalChapterNum = jsonItem.readInt("$.chapterListSize") ?: 0 + book.durChapterIndex = jsonItem.readInt("$.durChapter") ?: 0 + 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 + book.order = jsonItem.readInt("$.serialNumber") ?: 0 + book.useReplaceRule = jsonItem.readBool("$.useReplaceRule") == true + book.variable = jsonItem.readString("$.variable") + books.add(book) + } + return books + } + +} \ No newline at end of file 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 3f7110a54..ee58fffbd 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 @@ -2,17 +2,19 @@ package io.legado.app.help.storage import android.content.Context import android.net.Uri -import android.util.Log import androidx.documentfile.provider.DocumentFile import com.jayway.jsonpath.Configuration import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.Option import com.jayway.jsonpath.ParseContext import io.legado.app.App -import io.legado.app.constant.AppConst +import io.legado.app.constant.PreferKey import io.legado.app.data.entities.* import io.legado.app.help.ReadBookConfig -import io.legado.app.utils.* +import io.legado.app.utils.DocumentUtils +import io.legado.app.utils.FileUtils +import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.GlobalScope @@ -49,51 +51,23 @@ object Restore { suspend fun restore(path: String) { withContext(IO) { - try { - val file = FileUtils.createFileIfNotExist(path + File.separator + "bookshelf.json") - val json = file.readText() - GSON.fromJsonArray(json)?.let { - App.db.bookDao().insert(*it.toTypedArray()) - } - } catch (e: Exception) { - e.printStackTrace() + fileToListT(path, "bookshelf.json")?.let { + App.db.bookDao().insert(*it.toTypedArray()) } - try { - val file = FileUtils.createFileIfNotExist(path + File.separator + "bookGroup.json") - val json = file.readText() - GSON.fromJsonArray(json)?.let { - App.db.bookGroupDao().insert(*it.toTypedArray()) - } - } catch (e: Exception) { - e.printStackTrace() + fileToListT(path, "bookGroup.json")?.let { + App.db.bookGroupDao().insert(*it.toTypedArray()) } - try { - val file = FileUtils.createFileIfNotExist(path + File.separator + "bookSource.json") - val json = file.readText() - GSON.fromJsonArray(json)?.let { - App.db.bookSourceDao().insert(*it.toTypedArray()) - } - } catch (e: Exception) { - e.printStackTrace() + fileToListT(path, "bookSource.json")?.let { + App.db.bookSourceDao().insert(*it.toTypedArray()) } - try { - val file = FileUtils.createFileIfNotExist(path + File.separator + "rssSource.json") - val json = file.readText() - GSON.fromJsonArray(json)?.let { - App.db.rssSourceDao().insert(*it.toTypedArray()) - } - } catch (e: Exception) { - e.printStackTrace() + fileToListT(path, "rssSource.json")?.let { + App.db.rssSourceDao().insert(*it.toTypedArray()) } - try { - val file = - FileUtils.createFileIfNotExist(path + File.separator + "replaceRule.json") - val json = file.readText() - GSON.fromJsonArray(json)?.let { - App.db.replaceRuleDao().insert(*it.toTypedArray()) - } - } catch (e: Exception) { - e.printStackTrace() + fileToListT(path, "rssStar.json")?.let { + App.db.rssStarDao().insert(*it.toTypedArray()) + } + fileToListT(path, "replaceRule.json")?.let { + App.db.replaceRuleDao().insert(*it.toTypedArray()) } try { val file = @@ -117,11 +91,23 @@ object Restore { is String -> edit.putString(it.key, value) else -> Unit } + edit.putInt(PreferKey.versionCode, App.INSTANCE.versionCode) edit.commit() } } } + private inline fun fileToListT(path: String, fileName: String): List? { + try { + val file = FileUtils.createFileIfNotExist(path + File.separator + fileName) + val json = file.readText() + return GSON.fromJsonArray(json) + } catch (e: Exception) { + e.printStackTrace() + } + return null + } + fun importYueDuData(context: Context) { GlobalScope.launch(IO) { try {// 导入书架 @@ -169,43 +155,7 @@ object Restore { } fun importOldBookshelf(json: String): Int { - val books = mutableListOf() - val items: List> = jsonPath.parse(json).read("$") - val existingBooks = App.db.bookDao().allBookUrls.toSet() - for (item in items) { - val jsonItem = jsonPath.parse(item) - val book = Book() - book.bookUrl = jsonItem.readString("$.noteUrl") ?: "" - if (book.bookUrl.isBlank()) continue - book.name = jsonItem.readString("$.bookInfoBean.name") ?: "" - if (book.bookUrl in existingBooks) { - Log.d(AppConst.APP_TAG, "Found existing book: ${book.name}") - continue - } - book.origin = jsonItem.readString("$.tag") ?: "" - book.originName = jsonItem.readString("$.bookInfoBean.origin") ?: "" - book.author = jsonItem.readString("$.bookInfoBean.author") ?: "" - book.type = - if (jsonItem.readString("$.bookInfoBean.bookSourceType") == "AUDIO") 1 else 0 - book.tocUrl = jsonItem.readString("$.bookInfoBean.chapterUrl") ?: book.bookUrl - book.coverUrl = jsonItem.readString("$.bookInfoBean.coverUrl") - book.customCoverUrl = jsonItem.readString("$.customCoverPath") - book.lastCheckTime = jsonItem.readLong("$.bookInfoBean.finalRefreshData") ?: 0 - book.canUpdate = jsonItem.readBool("$.allowUpdate") == true - book.totalChapterNum = jsonItem.readInt("$.chapterListSize") ?: 0 - book.durChapterIndex = jsonItem.readInt("$.durChapter") ?: 0 - 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 - book.order = jsonItem.readInt("$.serialNumber") ?: 0 - book.useReplaceRule = jsonItem.readBool("$.useReplaceRule") == true - book.variable = jsonItem.readString("$.variable") - books.add(book) - } + val books = OldBook.toNewBook(json) App.db.bookDao().insert(*books.toTypedArray()) return books.size } diff --git a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt index fb2e09a15..7612a6913 100644 --- a/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt +++ b/app/src/main/java/io/legado/app/help/storage/WebDavHelp.kt @@ -2,6 +2,7 @@ package io.legado.app.help.storage import android.content.Context import io.legado.app.App +import io.legado.app.constant.PreferKey import io.legado.app.help.coroutine.Coroutine import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.http.HttpAuth @@ -23,15 +24,15 @@ object WebDavHelp { } private fun getWebDavUrl(): String? { - var url = App.INSTANCE.getPrefString("web_dav_url") + var url = App.INSTANCE.getPrefString(PreferKey.webDavUrl) if (url.isNullOrBlank()) return null if (!url.endsWith("/")) url += "/" return url } private fun initWebDav(): Boolean { - val account = App.INSTANCE.getPrefString("web_dav_account") - val password = App.INSTANCE.getPrefString("web_dav_password") + val account = App.INSTANCE.getPrefString(PreferKey.webDavAccount) + val password = App.INSTANCE.getPrefString(PreferKey.webDavPassword) if (!account.isNullOrBlank() && !password.isNullOrBlank()) { HttpAuth.auth = HttpAuth.Auth(account, password) return true @@ -54,12 +55,12 @@ object WebDavHelp { return names } - suspend fun showRestoreDialog(context: Context): Boolean { + suspend fun showRestoreDialog(context: Context, restoreSuccess: () -> Unit): Boolean { val names = withContext(IO) { getWebDavFileNames() } return if (names.isNotEmpty()) { context.selector(title = "选择恢复文件", items = names) { _, index -> if (index in 0 until names.size) { - restoreWebDav(names[index]) + restoreWebDav(names[index], restoreSuccess) } } true @@ -68,7 +69,7 @@ object WebDavHelp { } } - private fun restoreWebDav(name: String) { + private fun restoreWebDav(name: String, success: () -> Unit) { Coroutine.async { getWebDavUrl()?.let { val file = WebDav(it + "legado/" + name) @@ -77,6 +78,8 @@ object WebDavHelp { ZipUtils.unzipFile(zipFilePath, unzipFilesPath) Restore.restore(unzipFilesPath) } + }.onSuccess { + success.invoke() } } diff --git a/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt b/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt index 7b3653eb2..f7b52aa61 100644 --- a/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt +++ b/app/src/main/java/io/legado/app/lib/theme/TintHelper.kt @@ -254,11 +254,10 @@ object TintHelper { if (!skipIndeterminate) progressBar.indeterminateTintList = sl } else { - val mode = PorterDuff.Mode.SRC_IN if (!skipIndeterminate && progressBar.indeterminateDrawable != null) - progressBar.indeterminateDrawable.setColorFilter(color, mode) + progressBar.indeterminateDrawable.setTint(color) if (progressBar.progressDrawable != null) - progressBar.progressDrawable.setColorFilter(color, mode) + progressBar.progressDrawable.setTint(color) } } diff --git a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt index 6eb5d4a18..82ec88179 100644 --- a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt @@ -2,20 +2,239 @@ package io.legado.app.model.localBook import android.content.Context import android.net.Uri +import io.legado.app.App import io.legado.app.data.entities.Book -import io.legado.app.utils.EncodingDetect +import io.legado.app.data.entities.BookChapter +import io.legado.app.data.entities.TxtTocRule +import io.legado.app.utils.* +import java.io.File +import java.io.RandomAccessFile +import java.nio.charset.Charset +import java.util.regex.Matcher +import java.util.regex.Pattern object AnalyzeTxtFile { + private const val folderName = "bookTxt" + private const val BLANK: Byte = 0x0a + //默认从文件中获取数据的长度 + private const val BUFFER_SIZE = 512 * 1024 + //没有标题的时候,每个章节的最大长度 + private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 + private val cacheFolder: File by lazy { + val rootFile = App.INSTANCE.getExternalFilesDir(null) + ?: App.INSTANCE.externalCacheDir + ?: App.INSTANCE.cacheDir + FileUtils.createFileIfNotExist(rootFile, subDirs = *arrayOf(folderName)) + } + + fun analyze(context: Context, book: Book): ArrayList { + val bookFile = getBookFile(context, book) + book.charset = EncodingDetect.getEncode(bookFile) + val charset = book.fileCharset() + val toc = arrayListOf() + //获取文件流 + val bookStream = RandomAccessFile(bookFile, "r") + val rulePattern = getTocRule(book, bookStream, charset) + //加载章节 + val buffer = ByteArray(BUFFER_SIZE) + //获取到的块起始点,在文件中的位置 + var curOffset: Long = 0 + //block的个数 + var blockPos = 0 + //读取的长度 + var length: Int + var allLength = 0 - fun analyze(context: Context, book: Book) { - context.contentResolver.openInputStream(Uri.parse(book.bookUrl))?.use { stream -> - val rawByteArray = ByteArray(2000) - stream.read(rawByteArray) - book.charset = EncodingDetect.getJavaEncode(rawByteArray) + //获取文件中的数据到buffer,直到没有数据为止 + while (bookStream.read(buffer, 0, buffer.size).also { length = it } > 0) { + ++blockPos + //如果存在Chapter + if (rulePattern != null) { //将数据转换成String + var blockContent = String(buffer, 0, length, charset) + val lastN = blockContent.lastIndexOf("\n") + if (lastN != 0) { + blockContent = blockContent.substring(0, lastN) + length = blockContent.toByteArray(charset).size + allLength += length + bookStream.seek(allLength.toLong()) + } + //当前Block下使过的String的指针 + var seekPos = 0 + //进行正则匹配 + val matcher: Matcher = rulePattern.matcher(blockContent) + //如果存在相应章节 + while (matcher.find()) { //获取匹配到的字符在字符串中的起始位置 + val chapterStart = matcher.start() + //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 + //第一种情况一定是序章 第二种情况可能是上一个章节的内容 + if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容 + val chapterContent = blockContent.substring(seekPos, chapterStart) + //设置指针偏移 + seekPos += chapterContent.length + if (toc.size == 0) { //如果当前没有章节,那么就是序章 + //加入简介 + book.intro = chapterContent + //创建当前章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = chapterContent.toByteArray(charset).size.toLong() + toc.add(curChapter) + } else { //否则就block分割之后,上一个章节的剩余内容 + //获取上一章节 + val lastChapter = toc.last() + //将当前段落添加上一章去 + lastChapter.end = + lastChapter.end!! + chapterContent.toByteArray(charset).size + //创建当前章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = lastChapter.end + toc.add(curChapter) + } + } else { //是否存在章节 + if (toc.size != 0) { //获取章节内容 + val chapterContent = blockContent.substring(seekPos, matcher.start()) + seekPos += chapterContent.length + //获取上一章节 + val lastChapter = toc.last() + lastChapter.end = + lastChapter.start!! + chapterContent.toByteArray(charset).size + //创建当前章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = lastChapter.end + toc.add(curChapter) + } else { //如果章节不存在则创建章节 + val curChapter = BookChapter() + curChapter.title = matcher.group() + curChapter.start = 0L + curChapter.end = 0L + toc.add(curChapter) + } + } + } + } else { //进行本地虚拟分章 + //章节在buffer的偏移量 + var chapterOffset = 0 + //当前剩余可分配的长度 + var strLength = length + //分章的位置 + var chapterPos = 0 + while (strLength > 0) { + ++chapterPos + //是否长度超过一章 + if (strLength > MAX_LENGTH_WITH_NO_CHAPTER) { //在buffer中一章的终止点 + var end = length + //寻找换行符作为终止点 + for (i in chapterOffset + MAX_LENGTH_WITH_NO_CHAPTER until length) { + if (buffer[i] == BLANK) { + end = i + break + } + } + val chapter = BookChapter() + chapter.title = "第${blockPos}章($chapterPos)" + chapter.start = curOffset + chapterOffset + 1 + chapter.end = curOffset + end + toc.add(chapter) + //减去已经被分配的长度 + strLength -= (end - chapterOffset) + //设置偏移的位置 + chapterOffset = end + } else { + val chapter = BookChapter() + chapter.title = "第" + blockPos + "章" + "(" + chapterPos + ")" + chapter.start = curOffset + chapterOffset + 1 + chapter.end = curOffset + length + toc.add(chapter) + strLength = 0 + } + } + } + //block的偏移点 + curOffset += length.toLong() + if (rulePattern != null) { //设置上一章的结尾 + val lastChapter = toc.last() + lastChapter.end = curOffset + } + + //当添加的block太多的时候,执行GC + //当添加的block太多的时候,执行GC + if (blockPos % 15 == 0) { + System.gc() + System.runFinalization() + } + } + bookStream.close() + for (i in toc.indices) { + val bean = toc[i] + bean.index = i + bean.bookUrl = book.bookUrl + bean.url = (MD5Utils.md5Encode16(book.originName + i + bean.title) ?: "") } + book.latestChapterTitle = toc.last().title + + System.gc() + System.runFinalization() + return toc + } + + fun getContent(book: Book, bookChapter: BookChapter): String { + val bookFile = getBookFile(App.INSTANCE, book) + //获取文件流 + val bookStream = RandomAccessFile(bookFile, "r") + bookStream.seek(bookChapter.start ?: 0) + val extent = (bookChapter.end!! - bookChapter.start!!).toInt() + val content = ByteArray(extent) + bookStream.read(content, 0, extent) + return String(content, book.fileCharset()) } + private fun getBookFile(context: Context, book: Book): File { + val uri = Uri.parse(book.bookUrl) + val bookFile = FileUtils.getFile(cacheFolder, book.originName, subDirs = *arrayOf()) + if (!bookFile.exists()) { + bookFile.createNewFile() + DocumentUtils.readBytes(context, uri)?.let { + bookFile.writeBytes(it) + } + } + return bookFile + } + + private fun getTocRule(book: Book, bookStream: RandomAccessFile, charset: Charset): Pattern? { + val tocRules = getTocRules() + var rulePattern: Pattern? = null + //首先获取128k的数据 + val buffer = ByteArray(BUFFER_SIZE / 4) + val length = bookStream.read(buffer, 0, buffer.size) + val content = String(buffer, 0, length, charset) + for (tocRule in tocRules) { + val pattern = Pattern.compile(tocRule.rule, Pattern.MULTILINE) + val matcher = pattern.matcher(content) + if (matcher.find()) { + book.tocUrl = tocRule.rule + rulePattern = pattern + break + } + } + bookStream.seek(0) + return rulePattern + } + + private fun getTocRules(): List { + val rules = App.db.txtTocRule().all + if (rules.isEmpty()) { + App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> + GSON.fromJsonArray(String(byteArray))?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + return it + } + } + } + return rules + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt new file mode 100644 index 000000000..44ee337b2 --- /dev/null +++ b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt @@ -0,0 +1,30 @@ +package io.legado.app.model.localBook + +import androidx.documentfile.provider.DocumentFile +import io.legado.app.App +import io.legado.app.data.entities.Book + + +object LocalBook { + + fun importFile(doc: DocumentFile) { + doc.name?.let { fileName -> + val str = fileName.substringBeforeLast(".") + var name = str.substringBefore("作者") + val author = str.substringAfter("作者", "") + val smhStart = name.indexOf("《") + val smhEnd = name.indexOf("》") + if (smhStart != -1 && smhEnd != -1) { + name = (name.substring(smhStart + 1, smhEnd)) + } + val book = Book( + bookUrl = doc.uri.toString(), + name = name, + author = author, + originName = fileName + ) + App.db.bookDao().insert(book) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 82f06e54a..705321694 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -51,17 +51,17 @@ object BookChapterList { ruleUrl = nextUrl, book = book, headerMapF = bookSource.getHeaderMap() ).getResponseAwait() .body?.let { nextBody -> - chapterData = analyzeChapterList( - nextBody, nextUrl, tocRule, listRule, - book, bookSource, log = false - ) - nextUrl = if (chapterData.nextUrl.isNotEmpty()) - chapterData.nextUrl[0] - else "" - chapterData.chapterList?.let { - chapterList.addAll(it) - } + chapterData = analyzeChapterList( + nextBody, nextUrl, tocRule, listRule, + book, bookSource, log = false + ) + nextUrl = if (chapterData.nextUrl.isNotEmpty()) + chapterData.nextUrl[0] + else "" + chapterData.chapterList?.let { + chapterList.addAll(it) } + } } Debug.log(bookSource.bookSourceUrl, "◇目录总页数:${nextUrlList.size}") } else if (chapterData.nextUrl.size > 1) { @@ -100,6 +100,8 @@ object BookChapterList { item.index = index } book.latestChapterTitle = chapterList.last().title + book.durChapterTitle = + chapterList.getOrNull(book.durChapterIndex)?.title ?: book.latestChapterTitle if (book.totalChapterNum < chapterList.size) { book.lastCheckCount = chapterList.size - book.totalChapterNum } diff --git a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt index 5cc36a680..12083c652 100644 --- a/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import android.view.KeyEvent import io.legado.app.App -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book import io.legado.app.help.ActivityHelp import io.legado.app.ui.audio.AudioPlayActivity @@ -51,9 +51,9 @@ class MediaButtonReceiver : BroadcastReceiver() { private fun readAloud(context: Context) { when { ActivityHelp.isExist(AudioPlayActivity::class.java) -> - postEvent(Bus.MEDIA_BUTTON, true) + postEvent(EventBus.MEDIA_BUTTON, true) ActivityHelp.isExist(ReadBookActivity::class.java) -> - postEvent(Bus.MEDIA_BUTTON, true) + postEvent(EventBus.MEDIA_BUTTON, true) else -> { GlobalScope.launch(Main) { val lastBook: Book? = withContext(IO) { diff --git a/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt b/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt index 613db4fe4..765d05b3e 100644 --- a/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt +++ b/app/src/main/java/io/legado/app/receiver/TimeElectricityReceiver.kt @@ -5,7 +5,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.utils.postEvent @@ -28,11 +28,11 @@ class TimeElectricityReceiver : BroadcastReceiver() { intent?.action?.let { when (it) { Intent.ACTION_TIME_TICK -> { - postEvent(Bus.TIME_CHANGED, "") + postEvent(EventBus.TIME_CHANGED, "") } Intent.ACTION_BATTERY_CHANGED -> { val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) - postEvent(Bus.BATTERY_CHANGED, level) + postEvent(EventBus.BATTERY_CHANGED, level) } } } 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 ef2638c29..c165f3142 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.Action +import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.Status import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp @@ -81,21 +81,21 @@ class AudioPlayService : BaseService(), override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.action?.let { action -> when (action) { - Action.play -> { + IntentAction.play -> { AudioPlay.book?.let { title = it.name position = it.durChapterPos loadContent(it.durChapterIndex) } } - Action.pause -> pause(true) - Action.resume -> resume() - Action.prev -> moveToPrev() - Action.next -> moveToNext() - Action.adjustSpeed -> upSpeed(intent.getFloatExtra("adjust", 1f)) - Action.addTimer -> addTimer() - Action.setTimer -> setTimer(intent.getIntExtra("minute", 0)) - Action.adjustProgress -> adjustProgress(intent.getIntExtra("position", position)) + IntentAction.pause -> pause(true) + IntentAction.resume -> resume() + IntentAction.prev -> moveToPrev() + IntentAction.next -> moveToNext() + IntentAction.adjustSpeed -> upSpeed(intent.getFloatExtra("adjust", 1f)) + IntentAction.addTimer -> addTimer() + IntentAction.setTimer -> setTimer(intent.getIntExtra("minute", 0)) + IntentAction.adjustProgress -> adjustProgress(intent.getIntExtra("position", position)) else -> stopSelf() } } @@ -112,7 +112,7 @@ class AudioPlayService : BaseService(), unregisterReceiver(broadcastReceiver) upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED) AudioPlay.status = Status.STOP - postEvent(Bus.AUDIO_STATE, Status.STOP) + postEvent(EventBus.AUDIO_STATE, Status.STOP) } private fun play() { @@ -120,7 +120,7 @@ class AudioPlayService : BaseService(), if (requestFocus()) { try { AudioPlay.status = Status.PLAY - postEvent(Bus.AUDIO_STATE, Status.PLAY) + postEvent(EventBus.AUDIO_STATE, Status.PLAY) mediaPlayer.reset() val analyzeUrl = AnalyzeUrl(url, headerMapF = AudioPlay.headers(), useWebView = true) @@ -146,7 +146,7 @@ class AudioPlayService : BaseService(), mediaPlayer.pause() upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) AudioPlay.status = Status.PAUSE - postEvent(Bus.AUDIO_STATE, Status.PAUSE) + postEvent(EventBus.AUDIO_STATE, Status.PAUSE) upNotification() } } @@ -159,7 +159,7 @@ class AudioPlayService : BaseService(), handler.postDelayed(mpRunnable, 1000) upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING) AudioPlay.status = Status.PLAY - postEvent(Bus.AUDIO_STATE, Status.PLAY) + postEvent(EventBus.AUDIO_STATE, Status.PLAY) upNotification() } @@ -178,7 +178,7 @@ class AudioPlayService : BaseService(), if (isPlaying) { playbackParams = playbackParams.apply { speed += adjust } } - postEvent(Bus.AUDIO_SPEED, playbackParams.speed) + postEvent(EventBus.AUDIO_SPEED, playbackParams.speed) } } } @@ -191,7 +191,7 @@ class AudioPlayService : BaseService(), if (pause) return mediaPlayer.start() mediaPlayer.seekTo(position) - postEvent(Bus.AUDIO_SIZE, mediaPlayer.duration) + postEvent(EventBus.AUDIO_SIZE, mediaPlayer.duration) bookChapter?.let { it.end = mediaPlayer.duration.toLong() } @@ -205,7 +205,7 @@ class AudioPlayService : BaseService(), override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { if (!mediaPlayer.isPlaying) { AudioPlay.status = Status.STOP - postEvent(Bus.AUDIO_STATE, Status.STOP) + postEvent(EventBus.AUDIO_STATE, Status.STOP) launch { toast("error: $what $extra $url") } } return true @@ -238,7 +238,7 @@ class AudioPlayService : BaseService(), handler.removeCallbacks(dsRunnable) handler.postDelayed(dsRunnable, 60000) } - postEvent(Bus.TTS_DS, timeMinute) + postEvent(EventBus.TTS_DS, timeMinute) upNotification() } @@ -247,7 +247,7 @@ class AudioPlayService : BaseService(), */ private fun upPlayProgress() { saveProgress() - postEvent(Bus.AUDIO_PROGRESS, mediaPlayer.currentPosition) + postEvent(EventBus.AUDIO_PROGRESS, mediaPlayer.currentPosition) handler.postDelayed(mpRunnable, 1000) } @@ -260,9 +260,9 @@ class AudioPlayService : BaseService(), if (index == AudioPlay.durChapterIndex) { bookChapter = chapter subtitle = chapter.title - postEvent(Bus.AUDIO_SUB_TITLE, subtitle) - postEvent(Bus.AUDIO_SIZE, chapter.end?.toInt() ?: 0) - postEvent(Bus.AUDIO_PROGRESS, position) + postEvent(EventBus.AUDIO_SUB_TITLE, subtitle) + postEvent(EventBus.AUDIO_SIZE, chapter.end?.toInt() ?: 0) + postEvent(EventBus.AUDIO_PROGRESS, position) } loadContent(chapter) } ?: removeLoading(index) @@ -376,7 +376,7 @@ class AudioPlayService : BaseService(), handler.postDelayed(dsRunnable, 60000) } } - postEvent(Bus.TTS_DS, timeMinute) + postEvent(EventBus.TTS_DS, timeMinute) upNotification() } @@ -485,24 +485,24 @@ class AudioPlayService : BaseService(), builder.addAction( R.drawable.ic_play_24dp, getString(R.string.resume), - thisPendingIntent(Action.resume) + thisPendingIntent(IntentAction.resume) ) } else { builder.addAction( R.drawable.ic_pause_24dp, getString(R.string.pause), - thisPendingIntent(Action.pause) + thisPendingIntent(IntentAction.pause) ) } builder.addAction( R.drawable.ic_stop_black_24dp, getString(R.string.stop), - thisPendingIntent(Action.stop) + thisPendingIntent(IntentAction.stop) ) builder.addAction( R.drawable.ic_time_add_24dp, getString(R.string.set_timer), - thisPendingIntent(Action.addTimer) + thisPendingIntent(IntentAction.addTimer) ) builder.setStyle( androidx.media.app.NotificationCompat.MediaStyle() diff --git a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt index c657f621e..cb946d145 100644 --- a/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/BaseReadAloudService.kt @@ -16,10 +16,7 @@ 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.Action -import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus -import io.legado.app.constant.Status +import io.legado.app.constant.* import io.legado.app.help.IntentDataHelp import io.legado.app.help.IntentHelp import io.legado.app.help.MediaHelp @@ -43,18 +40,18 @@ abstract class BaseReadAloudService : BaseService(), } } - private val handler = Handler() + internal val handler = Handler() private lateinit var audioManager: AudioManager private var mFocusRequest: AudioFocusRequest? = null private var broadcastReceiver: BroadcastReceiver? = null private var mediaSessionCompat: MediaSessionCompat? = null private var title: String = "" private var subtitle: String = "" - val contentList = arrayListOf() - var nowSpeak: Int = 0 - var readAloudNumber: Int = 0 - var textChapter: TextChapter? = null - var pageIndex = 0 + internal val contentList = arrayListOf() + internal var nowSpeak: Int = 0 + internal var readAloudNumber: Int = 0 + internal var textChapter: TextChapter? = null + internal var pageIndex = 0 private val dsRunnable: Runnable = Runnable { doDs() } override fun onCreate() { @@ -73,7 +70,7 @@ abstract class BaseReadAloudService : BaseService(), isRun = false pause = true unregisterReceiver(broadcastReceiver) - postEvent(Bus.ALOUD_STATE, Status.STOP) + postEvent(EventBus.ALOUD_STATE, Status.STOP) upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED) mediaSessionCompat?.release() } @@ -81,7 +78,7 @@ abstract class BaseReadAloudService : BaseService(), override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.action?.let { action -> when (action) { - Action.play -> { + IntentAction.play -> { title = intent.getStringExtra("title") ?: "" subtitle = intent.getStringExtra("subtitle") ?: "" pageIndex = intent.getIntExtra("pageIndex", 0) @@ -90,13 +87,13 @@ abstract class BaseReadAloudService : BaseService(), intent.getBooleanExtra("play", true) ) } - Action.pause -> pauseReadAloud(true) - Action.resume -> resumeReadAloud() - Action.upTtsSpeechRate -> upSpeechRate(true) - Action.prevParagraph -> prevP() - Action.nextParagraph -> nextP() - Action.addTimer -> addTimer() - Action.setTimer -> setTimer(intent.getIntExtra("minute", 0)) + IntentAction.pause -> pauseReadAloud(true) + IntentAction.resume -> resumeReadAloud() + IntentAction.upTtsSpeechRate -> upSpeechRate(true) + IntentAction.prevParagraph -> prevP() + IntentAction.nextParagraph -> nextP() + IntentAction.addTimer -> addTimer() + IntentAction.setTimer -> setTimer(intent.getIntExtra("minute", 0)) else -> stopSelf() } } @@ -111,7 +108,7 @@ abstract class BaseReadAloudService : BaseService(), nowSpeak = 0 readAloudNumber = textChapter.getReadLength(pageIndex) contentList.clear() - if (getPrefBoolean("readAloudByPage")) { + if (getPrefBoolean(PreferKey.readAloudByPage)) { for (index in pageIndex..textChapter.lastIndex()) { textChapter.page(index)?.text?.split("\n")?.let { contentList.addAll(it) @@ -127,13 +124,13 @@ abstract class BaseReadAloudService : BaseService(), open fun play() { pause = false - postEvent(Bus.ALOUD_STATE, Status.PLAY) + postEvent(EventBus.ALOUD_STATE, Status.PLAY) upNotification() } @CallSuper open fun pauseReadAloud(pause: Boolean) { - postEvent(Bus.ALOUD_STATE, Status.PAUSE) + postEvent(EventBus.ALOUD_STATE, Status.PAUSE) BaseReadAloudService.pause = pause upNotification() upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED) @@ -170,7 +167,7 @@ abstract class BaseReadAloudService : BaseService(), handler.removeCallbacks(dsRunnable) handler.postDelayed(dsRunnable, 60000) } - postEvent(Bus.TTS_DS, timeMinute) + postEvent(EventBus.TTS_DS, timeMinute) upNotification() } @@ -186,7 +183,7 @@ abstract class BaseReadAloudService : BaseService(), handler.postDelayed(dsRunnable, 60000) } } - postEvent(Bus.TTS_DS, timeMinute) + postEvent(EventBus.TTS_DS, timeMinute) upNotification() } @@ -301,24 +298,24 @@ abstract class BaseReadAloudService : BaseService(), builder.addAction( R.drawable.ic_play_24dp, getString(R.string.resume), - aloudServicePendingIntent(Action.resume) + aloudServicePendingIntent(IntentAction.resume) ) } else { builder.addAction( R.drawable.ic_pause_24dp, getString(R.string.pause), - aloudServicePendingIntent(Action.pause) + aloudServicePendingIntent(IntentAction.pause) ) } builder.addAction( R.drawable.ic_stop_black_24dp, getString(R.string.stop), - aloudServicePendingIntent(Action.stop) + aloudServicePendingIntent(IntentAction.stop) ) builder.addAction( R.drawable.ic_time_add_24dp, getString(R.string.set_timer), - aloudServicePendingIntent(Action.addTimer) + aloudServicePendingIntent(IntentAction.addTimer) ) builder.setStyle( androidx.media.app.NotificationCompat.MediaStyle() diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt index 5fa36c1ba..18f704a6e 100644 --- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt +++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt @@ -2,28 +2,77 @@ package io.legado.app.service import android.content.Intent 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.Action import io.legado.app.constant.AppConst -import io.legado.app.data.entities.BookSource +import io.legado.app.constant.IntentAction +import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.WebBook import io.legado.app.ui.book.source.manage.BookSourceActivity +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors class CheckSourceService : BaseService() { - - private var sourceList: List? = null + private var searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + private var task: Coroutine<*>? = null + private var idsCount = 0 + private val unCheckIds = LinkedHashSet() override fun onCreate() { super.onCreate() + updateNotification(0, getString(R.string.start)) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + when (intent?.action) { + IntentAction.start -> intent.getStringArrayListExtra("selectIds")?.let { + check(it) + } + else -> stopSelf() + } return super.onStartCommand(intent, flags, startId) } override fun onDestroy() { super.onDestroy() + task?.cancel() + searchPool.close() + } + + private fun check(ids: List) { + task?.cancel() + unCheckIds.clear() + idsCount = ids.size + unCheckIds.addAll(ids) + updateNotification(0, getString(R.string.progress_show, 0, idsCount)) + task = execute { + unCheckIds.forEach { sourceUrl -> + App.db.bookSourceDao().getBookSource(sourceUrl)?.let { source -> + val webBook = WebBook(source) + webBook.searchBook("我的", scope = this, context = searchPool) + .onError(IO) { + source.addGroup("失效") + App.db.bookSourceDao().update(source) + }.onFinally { + unCheckIds.remove(sourceUrl) + val checkedCount = idsCount - unCheckIds.size + updateNotification( + checkedCount, + getString(R.string.progress_show, checkedCount, idsCount) + ) + } + } + } + } + + task?.invokeOnCompletion { + stopSelf() + } } /** @@ -41,11 +90,9 @@ class CheckSourceService : BaseService() { .addAction( R.drawable.ic_stop_black_24dp, getString(R.string.cancel), - IntentHelp.servicePendingIntent(this, Action.stop) + IntentHelp.servicePendingIntent(this, IntentAction.stop) ) - sourceList?.let { - builder.setProgress(it.size, state, false) - } + builder.setProgress(idsCount, state, false) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) val notification = builder.build() startForeground(112202, notification) 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 9c2e7a2f3..df49e24d9 100644 --- a/app/src/main/java/io/legado/app/service/DownloadService.kt +++ b/app/src/main/java/io/legado/app/service/DownloadService.kt @@ -6,9 +6,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.Action +import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp import io.legado.app.help.IntentHelp @@ -34,7 +34,7 @@ class DownloadService : BaseService() { builder.addAction( R.drawable.ic_stop_black_24dp, getString(R.string.cancel), - IntentHelp.servicePendingIntent(this, Action.stop) + IntentHelp.servicePendingIntent(this, IntentAction.stop) ) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) } @@ -48,12 +48,12 @@ class DownloadService : BaseService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { intent?.action?.let { action -> when (action) { - Action.start -> download( + IntentAction.start -> download( intent.getStringExtra("bookUrl"), intent.getIntExtra("start", 0), intent.getIntExtra("end", 0) ) - Action.stop -> stopDownload() + IntentAction.stop -> stopDownload() } } return super.onStartCommand(intent, flags, startId) @@ -64,7 +64,7 @@ class DownloadService : BaseService() { searchPool.close() handler.removeCallbacks(runnable) super.onDestroy() - postEvent(Bus.UP_DOWNLOAD, false) + postEvent(EventBus.UP_DOWNLOAD, false) } private fun download(bookUrl: String?, start: Int, end: Int) { @@ -105,7 +105,7 @@ class DownloadService : BaseService() { private fun upDownload() { updateNotification(notificationContent) - postEvent(Bus.UP_DOWNLOAD, true) + postEvent(EventBus.UP_DOWNLOAD, true) handler.removeCallbacks(runnable) handler.postDelayed(runnable, 1000) } diff --git a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt index 9e1fe70cd..798307652 100644 --- a/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/HttpReadAloudService.kt @@ -2,12 +2,15 @@ package io.legado.app.service import android.app.PendingIntent import android.media.MediaPlayer -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus +import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.api.HttpPostApi import io.legado.app.service.help.ReadBook -import io.legado.app.utils.* +import io.legado.app.utils.FileUtils +import io.legado.app.utils.LogUtils +import io.legado.app.utils.postEvent import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Job import kotlinx.coroutines.isActive @@ -37,6 +40,7 @@ class HttpReadAloudService : BaseReadAloudService(), override fun onDestroy() { super.onDestroy() + job?.cancel() mediaPlayer.release() } @@ -88,11 +92,12 @@ class HttpReadAloudService : BaseReadAloudService(), @Synchronized private fun playAudio(fd: FileDescriptor) { if (playingIndex != nowSpeak && requestFocus()) { - playingIndex = nowSpeak try { mediaPlayer.reset() mediaPlayer.setDataSource(fd) mediaPlayer.prepareAsync() + playingIndex = nowSpeak + postEvent(EventBus.TTS_START, readAloudNumber + 1) } catch (e: Exception) { e.printStackTrace() } @@ -106,8 +111,8 @@ class HttpReadAloudService : BaseReadAloudService(), private fun getAudioBody(content: String): Map { return mapOf( Pair("tex", encodeTwo(content)), - Pair("spd", ((getPrefInt("ttsSpeechRate", 25) + 5) / 5).toString()), - Pair("per", getPrefString("ttsSpeechPer") ?: "0"), + Pair("spd", ((AppConfig.ttsSpeechRate + 5) / 10 + 4).toString()), + Pair("per", AppConfig.ttsSpeechPer), Pair("cuid", "baidu_speech_demo"), Pair("idx", "1"), Pair("cod", "2"), @@ -135,20 +140,26 @@ class HttpReadAloudService : BaseReadAloudService(), override fun resumeReadAloud() { super.resumeReadAloud() - mediaPlayer.start() + if (playingIndex == -1) { + play() + } else { + mediaPlayer.start() + } } + /** + * 更新朗读速度 + */ override fun upSpeechRate(reset: Boolean) { job?.cancel() - mediaPlayer.reset() - for (i in 0 until nowSpeak) { - contentList.removeAt(0) - } - nowSpeak = 0 + mediaPlayer.stop() playingIndex = -1 - play() + downloadAudio() } + /** + * 上一段 + */ override fun prevP() { if (nowSpeak > 0) { mediaPlayer.stop() @@ -158,6 +169,9 @@ class HttpReadAloudService : BaseReadAloudService(), } } + /** + * 下一段 + */ override fun nextP() { if (nowSpeak < contentList.size - 1) { mediaPlayer.stop() @@ -177,15 +191,27 @@ class HttpReadAloudService : BaseReadAloudService(), ReadBook.moveToNextPage() } } - postEvent(Bus.TTS_START, readAloudNumber + 1) + postEvent(EventBus.TTS_START, readAloudNumber + 1) } override fun onError(mp: MediaPlayer?, what: Int, extra: Int): Boolean { + LogUtils.d("mp", "what:$what extra:$extra") + if (what == -38 && extra == 0) { + return true + } + handler.postDelayed({ + readAloudNumber += contentList[nowSpeak].length + 1 + if (nowSpeak < contentList.lastIndex) { + nowSpeak++ + play() + } else { + nextChapter() + } + }, 1000) return true } override fun onCompletion(mp: MediaPlayer?) { - LogUtils.d("播放完成", contentList[nowSpeak]) readAloudNumber += contentList[nowSpeak].length + 1 if (nowSpeak < contentList.lastIndex) { nowSpeak++ diff --git a/app/src/main/java/io/legado/app/service/README.md b/app/src/main/java/io/legado/app/service/README.md index 0b66f70aa..64c4ca44c 100644 --- a/app/src/main/java/io/legado/app/service/README.md +++ b/app/src/main/java/io/legado/app/service/README.md @@ -1 +1,7 @@ -## android服务 \ No newline at end of file +## android服务 +* AudioPlayService 音频播放服务 +* CheckSourceService 书源检测服务 +* DownloadService 缓存服务 +* HttpReadAloudService 在线朗读服务 +* TTSReadAloudService tts朗读服务 +* WebService web服务 \ No newline at end of file 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 977e8b0ac..1b1f40bcc 100644 --- a/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt +++ b/app/src/main/java/io/legado/app/service/TTSReadAloudService.kt @@ -6,12 +6,12 @@ import android.speech.tts.TextToSpeech import android.speech.tts.UtteranceProgressListener import io.legado.app.R import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus +import io.legado.app.help.AppConfig import io.legado.app.help.IntentHelp import io.legado.app.help.MediaHelp import io.legado.app.service.help.ReadBook import io.legado.app.utils.getPrefBoolean -import io.legado.app.utils.getPrefInt import io.legado.app.utils.postEvent import kotlinx.coroutines.launch import org.jetbrains.anko.toast @@ -96,7 +96,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener textToSpeech = TextToSpeech(this, this) } } else { - textToSpeech?.setSpeechRate((this.getPrefInt("ttsSpeechRate", 5) + 5) / 10f) + textToSpeech?.setSpeechRate((AppConfig.ttsSpeechRate + 5) / 10f) } } @@ -152,7 +152,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener ReadBook.moveToNextPage() } } - postEvent(Bus.TTS_START, readAloudNumber + 1) + postEvent(EventBus.TTS_START, readAloudNumber + 1) } override fun onDone(s: String) { @@ -169,7 +169,7 @@ class TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener if (readAloudNumber + start > it.getReadLength(pageIndex + 1)) { pageIndex++ ReadBook.moveToNextPage() - postEvent(Bus.TTS_START, readAloudNumber + start) + postEvent(EventBus.TTS_START, readAloudNumber + start) } } } diff --git a/app/src/main/java/io/legado/app/service/WebService.kt b/app/src/main/java/io/legado/app/service/WebService.kt index 25f2b6822..e9d223956 100644 --- a/app/src/main/java/io/legado/app/service/WebService.kt +++ b/app/src/main/java/io/legado/app/service/WebService.kt @@ -6,9 +6,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.Action +import io.legado.app.constant.IntentAction import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.help.IntentHelp import io.legado.app.utils.NetworkUtils import io.legado.app.utils.getPrefInt @@ -32,7 +32,7 @@ class WebService : BaseService() { fun stop(context: Context) { if (isRun) { val intent = Intent(context, WebService::class.java) - intent.action = Action.stop + intent.action = IntentAction.stop context.startService(intent) } } @@ -56,12 +56,12 @@ class WebService : BaseService() { if (webSocketServer?.isAlive == true) { webSocketServer?.stop() } - postEvent(Bus.WEB_SERVICE_STOP, true) + postEvent(EventBus.WEB_SERVICE_STOP, true) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { - Action.stop -> stopSelf() + IntentAction.stop -> stopSelf() else -> upWebServer() } return super.onStartCommand(intent, flags, startId) @@ -115,7 +115,7 @@ class WebService : BaseService() { builder.addAction( R.drawable.ic_stop_black_24dp, getString(R.string.cancel), - IntentHelp.servicePendingIntent(this, Action.stop) + IntentHelp.servicePendingIntent(this, IntentAction.stop) ) builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC) val notification = builder.build() diff --git a/app/src/main/java/io/legado/app/service/help/AudioPlay.kt b/app/src/main/java/io/legado/app/service/help/AudioPlay.kt index add1a3fab..7c8d120b3 100644 --- a/app/src/main/java/io/legado/app/service/help/AudioPlay.kt +++ b/app/src/main/java/io/legado/app/service/help/AudioPlay.kt @@ -3,7 +3,7 @@ package io.legado.app.service.help import android.content.Context import android.content.Intent import androidx.lifecycle.MutableLiveData -import io.legado.app.constant.Action +import io.legado.app.constant.IntentAction import io.legado.app.constant.Status import io.legado.app.data.entities.Book import io.legado.app.model.WebBook @@ -27,14 +27,14 @@ object AudioPlay { fun play(context: Context) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.play + intent.action = IntentAction.play context.startService(intent) } fun pause(context: Context) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.pause + intent.action = IntentAction.pause context.startService(intent) } } @@ -42,7 +42,7 @@ object AudioPlay { fun resume(context: Context) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.resume + intent.action = IntentAction.resume context.startService(intent) } } @@ -50,7 +50,7 @@ object AudioPlay { fun stop(context: Context) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.stop + intent.action = IntentAction.stop context.startService(intent) } } @@ -58,7 +58,7 @@ object AudioPlay { fun adjustSpeed(context: Context, adjust: Float) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.adjustSpeed + intent.action = IntentAction.adjustSpeed intent.putExtra("adjust", adjust) context.startService(intent) } @@ -67,7 +67,7 @@ object AudioPlay { fun adjustProgress(context: Context, position: Int) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.adjustProgress + intent.action = IntentAction.adjustProgress intent.putExtra("position", position) context.startService(intent) } @@ -76,7 +76,7 @@ object AudioPlay { fun prev(context: Context) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.prev + intent.action = IntentAction.prev context.startService(intent) } } @@ -84,7 +84,7 @@ object AudioPlay { fun next(context: Context) { if (AudioPlayService.isRun) { val intent = Intent(context, AudioPlayService::class.java) - intent.action = Action.next + intent.action = IntentAction.next context.startService(intent) } } diff --git a/app/src/main/java/io/legado/app/service/help/CheckSource.kt b/app/src/main/java/io/legado/app/service/help/CheckSource.kt new file mode 100644 index 000000000..1f13723a2 --- /dev/null +++ b/app/src/main/java/io/legado/app/service/help/CheckSource.kt @@ -0,0 +1,35 @@ +package io.legado.app.service.help + +import android.content.Context +import android.content.Intent +import io.legado.app.R +import io.legado.app.constant.IntentAction +import io.legado.app.data.entities.BookSource +import io.legado.app.service.CheckSourceService +import org.jetbrains.anko.toast + +object CheckSource { + + fun start(context: Context, sources: LinkedHashSet) { + if (sources.isEmpty()) { + context.toast(R.string.non_select) + return + } + val selectedIds: ArrayList = arrayListOf() + sources.map { + selectedIds.add(it.bookSourceUrl) + } + Intent(context, CheckSourceService::class.java).let { + it.action = IntentAction.start + it.putExtra("selectIds", selectedIds) + context.startService(it) + } + } + + fun stop(context: Context) { + Intent(context, CheckSourceService::class.java).let { + it.action = IntentAction.stop + context.startService(it) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/service/help/Download.kt b/app/src/main/java/io/legado/app/service/help/Download.kt index 1c17a7ee1..86accbceb 100644 --- a/app/src/main/java/io/legado/app/service/help/Download.kt +++ b/app/src/main/java/io/legado/app/service/help/Download.kt @@ -2,14 +2,14 @@ package io.legado.app.service.help import android.content.Context import android.content.Intent -import io.legado.app.constant.Action +import io.legado.app.constant.IntentAction import io.legado.app.service.DownloadService object Download { fun start(context: Context, bookUrl: String, start: Int, end: Int) { Intent(context, DownloadService::class.java).let { - it.action = Action.start + it.action = IntentAction.start it.putExtra("bookUrl", bookUrl) it.putExtra("start", start) it.putExtra("end", end) diff --git a/app/src/main/java/io/legado/app/service/help/ReadAloud.kt b/app/src/main/java/io/legado/app/service/help/ReadAloud.kt index d2a789a5a..b5c12a965 100644 --- a/app/src/main/java/io/legado/app/service/help/ReadAloud.kt +++ b/app/src/main/java/io/legado/app/service/help/ReadAloud.kt @@ -3,7 +3,8 @@ package io.legado.app.service.help import android.content.Context import android.content.Intent import io.legado.app.App -import io.legado.app.constant.Action +import io.legado.app.constant.IntentAction +import io.legado.app.constant.PreferKey import io.legado.app.service.BaseReadAloudService import io.legado.app.service.HttpReadAloudService import io.legado.app.service.TTSReadAloudService @@ -13,7 +14,7 @@ object ReadAloud { var aloudClass: Class<*> = getReadAloudClass() fun getReadAloudClass(): Class<*> { - return if (App.INSTANCE.getPrefBoolean("readAloudOnLine")) { + return if (App.INSTANCE.getPrefBoolean(PreferKey.readAloudOnLine)) { HttpReadAloudService::class.java } else { TTSReadAloudService::class.java @@ -29,7 +30,7 @@ object ReadAloud { play: Boolean = true ) { val intent = Intent(context, aloudClass) - intent.action = Action.play + intent.action = IntentAction.play intent.putExtra("title", title) intent.putExtra("subtitle", subtitle) intent.putExtra("pageIndex", pageIndex) @@ -41,7 +42,7 @@ object ReadAloud { fun pause(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.pause + intent.action = IntentAction.pause context.startService(intent) } } @@ -49,7 +50,7 @@ object ReadAloud { fun resume(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.resume + intent.action = IntentAction.resume context.startService(intent) } } @@ -57,7 +58,7 @@ object ReadAloud { fun stop(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.stop + intent.action = IntentAction.stop context.startService(intent) } } @@ -65,7 +66,7 @@ object ReadAloud { fun prevParagraph(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.prevParagraph + intent.action = IntentAction.prevParagraph context.startService(intent) } } @@ -73,7 +74,7 @@ object ReadAloud { fun nextParagraph(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.nextParagraph + intent.action = IntentAction.nextParagraph context.startService(intent) } } @@ -81,7 +82,7 @@ object ReadAloud { fun upTtsSpeechRate(context: Context) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.upTtsSpeechRate + intent.action = IntentAction.upTtsSpeechRate context.startService(intent) } } @@ -89,7 +90,7 @@ object ReadAloud { fun setTimer(context: Context, minute: Int) { if (BaseReadAloudService.isRun) { val intent = Intent(context, aloudClass) - intent.action = Action.setTimer + intent.action = IntentAction.setTimer intent.putExtra("minute", minute) context.startService(intent) } 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 451883b55..f5227f731 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 @@ -39,6 +39,7 @@ object ReadBook { durChapterIndex = book.durChapterIndex durPageIndex = book.durChapterPos isLocalBook = book.origin == BookType.local + webBook = null App.db.bookSourceDao().getBookSource(book.origin)?.let { webBook = WebBook(it) } @@ -197,6 +198,7 @@ object ReadBook { private fun download(index: Int) { book?.let { book -> + if (book.isLocalBook()) return if (addLoading(index)) { Coroutine.async { App.db.bookChapterDao().getChapter(book.bookUrl, index)?.let { chapter -> diff --git a/app/src/main/java/io/legado/app/ui/README.md b/app/src/main/java/io/legado/app/ui/README.md index a1a7a2313..a8bf2b216 100644 --- a/app/src/main/java/io/legado/app/ui/README.md +++ b/app/src/main/java/io/legado/app/ui/README.md @@ -2,21 +2,22 @@ * about 关于界面 * audio 音频播放界面 +* book\arrange 书架整理界面 * book\info 书籍信息查看 * book\read 书籍阅读界面 * book\search 搜索书籍界面 * book\source 搜索书源界面 -* changecover 封面换源界面 -* changesource 换源界面 -* chapterlist 目录界面 +* changeCover 封面换源界面 +* changeSource 换源界面 +* chapterList 目录界面 * config 配置界面 * download 下载界面 * explore 发现界面 -* filechooser 文件选择界面 -* importbook 书籍导入界面 +* fileChooser 文件选择界面 +* importBook 书籍导入界面 * main 主界面 -* qrcode 二维码扫描界面 -* replacerule 替换净化界面 +* qrCode 二维码扫描界面 +* replaceRule 替换净化界面 * rss\article 订阅条目界面 * rss\read 订阅阅读界面 * rss\source 订阅源界面 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 a348b6e1e..dffd6a50f 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 @@ -14,7 +14,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestOptions import io.legado.app.R import io.legado.app.base.VMBaseActivity -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.Status import io.legado.app.constant.Theme import io.legado.app.data.entities.Book @@ -193,12 +193,12 @@ class AudioPlayActivity : } override fun observeLiveBus() { - observeEvent(Bus.MEDIA_BUTTON) { + observeEvent(EventBus.MEDIA_BUTTON) { if (it) { playButton() } } - observeEventSticky(Bus.AUDIO_STATE) { + observeEventSticky(EventBus.AUDIO_STATE) { AudioPlay.status = it if (it == Status.PLAY) { fab_play_stop.setImageResource(R.drawable.ic_pause_24dp) @@ -206,19 +206,19 @@ class AudioPlayActivity : fab_play_stop.setImageResource(R.drawable.ic_play_24dp) } } - observeEventSticky(Bus.AUDIO_SUB_TITLE) { + observeEventSticky(EventBus.AUDIO_SUB_TITLE) { tv_sub_title.text = it } - observeEventSticky(Bus.AUDIO_SIZE) { + observeEventSticky(EventBus.AUDIO_SIZE) { player_progress.max = it tv_all_time.text = DateFormatUtils.format(it.toLong(), "mm:ss") } - observeEventSticky(Bus.AUDIO_PROGRESS) { + observeEventSticky(EventBus.AUDIO_PROGRESS) { AudioPlay.durPageIndex = it if (!adjustProgress) player_progress.progress = it tv_dur_time.text = DateFormatUtils.format(it.toLong(), "mm:ss") } - observeEventSticky(Bus.AUDIO_SPEED) { + observeEventSticky(EventBus.AUDIO_SPEED) { tv_speed.text = String.format("%.1fX", it) tv_speed.visible() } 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 5adc2c7be..b5dd5bfba 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 @@ -91,7 +91,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) fun changeTo(book1: Book) { execute { AudioPlay.book?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } withContext(Dispatchers.Main) { @@ -142,7 +142,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application) fun removeFromBookshelf(success: (() -> Unit)?) { execute { AudioPlay.book?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } }.onSuccess { success?.invoke() 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 new file mode 100644 index 000000000..b6204e90b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt @@ -0,0 +1,117 @@ +package io.legado.app.ui.book.arrange + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +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.lib.dialogs.alert +import io.legado.app.lib.dialogs.okButton +import io.legado.app.lib.theme.ATH +import io.legado.app.utils.applyTint +import io.legado.app.utils.getViewModel +import kotlinx.android.synthetic.main.activity_arrange_book.* +import org.jetbrains.anko.sdk27.listeners.onClick +import org.jetbrains.anko.toast + + +class ArrangeBookActivity : VMBaseActivity(R.layout.activity_arrange_book), + ArrangeBookAdapter.CallBack { + override val viewModel: ArrangeBookViewModel + get() = getViewModel(ArrangeBookViewModel::class.java) + override val groupList: ArrayList = arrayListOf() + private lateinit var adapter: ArrangeBookAdapter + private var groupLiveData: LiveData>? = null + private var booksLiveData: LiveData>? = null + + override fun onActivityCreated(savedInstanceState: Bundle?) { + initView() + initData() + } + + override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.arrange_book, menu) + return super.onCompatCreateOptionsMenu(menu) + } + + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + + } + return super.onCompatOptionsItemSelected(item) + } + + private fun initView() { + ATH.applyEdgeEffectColor(recycler_view) + recycler_view.layoutManager = LinearLayoutManager(this) + adapter = ArrangeBookAdapter(this, this) + recycler_view.adapter = adapter + cb_selected_all.onClick { + adapter.selectAll(!adapter.isSelectAll()) + } + btn_delete.onClick { + if (adapter.selectedBooks.isEmpty()) { + toast(R.string.non_select) + return@onClick + } + alert(titleResource = R.string.sure, messageResource = R.string.sure_del) { + okButton { + viewModel.deleteBook(*adapter.selectedBooks.toTypedArray()) + } + }.show().applyTint() + } + btn_to_group.onClick { + if (adapter.selectedBooks.isEmpty()) { + toast(R.string.non_select) + return@onClick + } + selectGroup() + } + } + + private fun initData() { + groupLiveData?.removeObservers(this) + groupLiveData = App.db.bookGroupDao().liveDataAll() + groupLiveData?.observe(this, Observer { + groupList.clear() + groupList.addAll(it) + adapter.notifyDataSetChanged() + }) + booksLiveData?.removeObservers(this) + booksLiveData = App.db.bookDao().observeAll() + booksLiveData?.observe(this, Observer { + adapter.setItems(it) + upSelectCount() + }) + } + + override fun selectGroup() { + + } + + override fun upSelectCount() { + cb_selected_all.isChecked = adapter.isSelectAll() + //重置全选的文字 + if (cb_selected_all.isChecked) { + cb_selected_all.setText(R.string.cancel) + } else { + cb_selected_all.setText(R.string.select_all) + } + tv_select_count.text = + getString(R.string.select_count, adapter.selectedBooks.size, adapter.getItems().size) + } + + override fun deleteBook(book: Book) { + alert(titleResource = R.string.sure, messageResource = R.string.sure_del) { + okButton { + viewModel.deleteBook(book) + } + }.show().applyTint() + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..dab74dc4b --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt @@ -0,0 +1,87 @@ +package io.legado.app.ui.book.arrange + +import android.content.Context +import io.legado.app.R +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 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) { + + val selectedBooks: HashSet = hashSetOf() + + fun isSelectAll(): Boolean { + return if (selectedBooks.isEmpty()) { + false + } else { + selectedBooks.size >= itemCount + } + } + + fun selectAll(selectAll: Boolean) { + if (selectAll) { + getItems().forEach { + selectedBooks.add(it) + } + notifyDataSetChanged() + callBack.upSelectCount() + } else { + selectedBooks.clear() + notifyDataSetChanged() + callBack.upSelectCount() + } + } + + override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { + with(holder.itemView) { + tv_name.text = item.name + tv_author.text = context.getString(R.string.author_show, item.author) + tv_group.text = getGroupName(item.group) + checkbox.isChecked = selectedBooks.contains(item) + checkbox.onClick { + if (checkbox.isChecked) { + selectedBooks.add(item) + } else { + selectedBooks.remove(item) + } + callBack.upSelectCount() + } + onClick { + checkbox.isChecked = !checkbox.isChecked + if (checkbox.isChecked) { + selectedBooks.add(item) + } else { + selectedBooks.remove(item) + } + callBack.upSelectCount() + } + tv_delete.onClick { + callBack.deleteBook(item) + } + tv_group.onClick { + callBack.selectGroup() + } + } + } + + private fun getGroupName(groupId: Int): String { + callBack.groupList.forEach { + if (it.groupId == groupId) { + return it.groupName + } + } + return context.getString(R.string.group) + } + + interface CallBack { + val groupList: List + fun upSelectCount() + fun deleteBook(book: Book) + fun selectGroup() + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt new file mode 100644 index 000000000..c7e571447 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookViewModel.kt @@ -0,0 +1,18 @@ +package io.legado.app.ui.book.arrange + +import android.app.Application +import io.legado.app.App +import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.Book + + +class ArrangeBookViewModel(application: Application) : BaseViewModel(application) { + + + fun deleteBook(vararg book: Book) { + execute { + App.db.bookDao().delete() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt similarity index 96% rename from app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageDialog.kt rename to app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt index 1022f4ed9..af3723e11 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/GroupManageDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.main.bookshelf +package io.legado.app.ui.book.group import android.annotation.SuppressLint import android.content.Context @@ -25,6 +25,7 @@ import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.customView import io.legado.app.lib.dialogs.noButton import io.legado.app.lib.dialogs.yesButton +import io.legado.app.ui.main.bookshelf.BookshelfViewModel import io.legado.app.utils.applyTint import io.legado.app.utils.getViewModel import io.legado.app.utils.requestInputMethod @@ -77,7 +78,12 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { recycler_view.adapter = adapter App.db.bookGroupDao().liveDataAll().observe(viewLifecycleOwner, Observer { val diffResult = - DiffUtil.calculateDiff(GroupDiffCallBack(adapter.getItems(), it)) + DiffUtil.calculateDiff( + GroupDiffCallBack( + adapter.getItems(), + it + ) + ) adapter.setItems(it, false) diffResult.dispatchUpdatesTo(adapter) }) diff --git a/app/src/main/java/io/legado/app/ui/book/info/GroupSelectDialog.kt b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt similarity index 98% rename from app/src/main/java/io/legado/app/ui/book/info/GroupSelectDialog.kt rename to app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt index 517f9dc9d..6dbdbd17b 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/GroupSelectDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt @@ -1,4 +1,4 @@ -package io.legado.app.ui.book.info +package io.legado.app.ui.book.group import android.annotation.SuppressLint import android.content.Context @@ -44,7 +44,10 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener { fun show(manager: FragmentManager) { val fragment = GroupSelectDialog() - fragment.show(manager, tag) + fragment.show( + manager, + tag + ) } } 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 fb689519c..cf86b2f04 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 @@ -21,9 +21,11 @@ import io.legado.app.help.BlurTransformation import io.legado.app.help.ImageLoader import io.legado.app.help.IntentDataHelp import io.legado.app.ui.audio.AudioPlayActivity +import io.legado.app.ui.book.group.GroupSelectDialog import io.legado.app.ui.book.info.edit.BookInfoEditActivity import io.legado.app.ui.book.read.ReadBookActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity +import io.legado.app.ui.changecover.ChangeCoverDialog import io.legado.app.ui.changesource.ChangeSourceDialog import io.legado.app.ui.chapterlist.ChapterListActivity import io.legado.app.utils.getViewModel @@ -40,7 +42,8 @@ class BookInfoActivity : VMBaseActivity(R.layout.activity_book_info, theme = Theme.Dark), GroupSelectDialog.CallBack, ChapterListAdapter.CallBack, - ChangeSourceDialog.CallBack { + ChangeSourceDialog.CallBack, + ChangeCoverDialog.CallBack { private val requestCodeChapterList = 568 private val requestCodeSourceEdit = 562 @@ -104,29 +107,14 @@ class BookInfoActivity : return super.onMenuOpened(featureId, menu) } - private fun defaultCover(): RequestBuilder { - return ImageLoader.load(this, R.drawable.image_cover_default) - .apply(bitmapTransform(BlurTransformation(this, 25))) - } - private fun showBook(book: Book) { + showCover(book) tv_name.text = book.name tv_author.text = getString(R.string.author_show, book.author) tv_origin.text = getString(R.string.origin_show, book.originName) tv_lasted.text = getString(R.string.lasted_show, book.latestChapterTitle) tv_toc.text = getString(R.string.toc_s, book.latestChapterTitle) tv_intro.text = book.getDisplayIntro() - book.getDisplayCover()?.let { - ImageLoader.load(this, it) - .centerCrop() - .into(iv_cover) - ImageLoader.load(this, it) - .transition(DrawableTransitionOptions.withCrossFade(1500)) - .thumbnail(defaultCover()) - .centerCrop() - .apply(bitmapTransform(BlurTransformation(this, 25))) - .into(bg_book) //模糊、渐变、缩小效果 - } val kinds = book.getKindList() if (kinds.isEmpty()) { ll_kind.gone() @@ -159,6 +147,21 @@ class BookInfoActivity : } } + private fun showCover(book: Book) { + iv_cover.load(book.getDisplayCover(), book.name, book.author) + ImageLoader.load(this, book.getDisplayCover()) + .transition(DrawableTransitionOptions.withCrossFade(1500)) + .thumbnail(defaultCover()) + .centerCrop() + .apply(bitmapTransform(BlurTransformation(this, 25))) + .into(bg_book) //模糊、渐变、缩小效果 + } + + private fun defaultCover(): RequestBuilder { + return ImageLoader.load(this, R.drawable.image_cover_default) + .apply(bitmapTransform(BlurTransformation(this, 25))) + } + private fun showChapter(chapterList: List) { viewModel.bookData.value?.let { if (it.durChapterIndex < chapterList.size) { @@ -184,6 +187,11 @@ class BookInfoActivity : } private fun initOnClick() { + iv_cover.onClick { + viewModel.bookData.value?.let { + ChangeCoverDialog.show(supportFragmentManager, it.name, it.author) + } + } tv_read.onClick { viewModel.bookData.value?.let { readBook(it) @@ -276,6 +284,14 @@ class BookInfoActivity : viewModel.changeTo(book) } + override fun coverChangeTo(coverUrl: String) { + viewModel.bookData.value?.let { + it.coverUrl = coverUrl + viewModel.saveBook() + showCover(it) + } + } + override fun openChapter(chapter: BookChapter) { if (chapter.index != viewModel.durChapterIndex) { viewModel.bookData.value?.let { 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 8179c058a..d7ff22277 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 @@ -11,6 +11,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookGroup import io.legado.app.help.BookHelp import io.legado.app.model.WebBook +import io.legado.app.model.localBook.AnalyzeTxtFile import kotlinx.coroutines.Dispatchers.IO class BookInfoViewModel(application: Application) : BaseViewModel(application) { @@ -55,26 +56,29 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { book: Book, changeDruChapterIndex: ((chapters: List) -> Unit)? = null ) { - if (book.isLocalBook()) return execute { - isLoadingData.postValue(true) - 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) + if (book.isLocalBook()) { + loadChapter(book, changeDruChapterIndex) + } else { + isLoadingData.postValue(true) + 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) } - loadChapter(it, changeDruChapterIndex) + }.onError { + isLoadingData.postValue(false) + toast(R.string.error_get_book_info) } - }.onError { - isLoadingData.postValue(false) - toast(R.string.error_get_book_info) - } - } ?: let { - isLoadingData.postValue(false) - toast(R.string.error_no_source) + } ?: let { + isLoadingData.postValue(false) + toast(R.string.error_no_source) + } } } } @@ -85,33 +89,46 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { ) { execute { isLoadingData.postValue(true) - 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) - isLoadingData.postValue(false) + if (book.isLocalBook()) { + AnalyzeTxtFile.analyze(context, book).let { + App.db.bookDao().update(book) + App.db.bookChapterDao().insert(*it.toTypedArray()) + if (changeDruChapterIndex == null) { + chapterListData.postValue(it) + isLoadingData.postValue(false) + } else { + changeDruChapterIndex(it) + } + } + } else { + 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) + isLoadingData.postValue(false) + } else { + changeDruChapterIndex(it) + } } else { - changeDruChapterIndex(it) + isLoadingData.postValue(false) + toast(R.string.chapter_list_empty) } - } else { - isLoadingData.postValue(false) - toast(R.string.chapter_list_empty) } + }.onError { + isLoadingData.postValue(false) + toast(R.string.error_get_chapter_list) } - }.onError { - isLoadingData.postValue(false) - toast(R.string.error_get_chapter_list) - } - } ?: let { - isLoadingData.postValue(false) - toast(R.string.error_no_source) + } ?: let { + isLoadingData.postValue(false) + toast(R.string.error_no_source) + } } } } @@ -120,7 +137,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { execute { if (inBookshelf) { bookData.value?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } App.db.bookDao().insert(book) } @@ -185,7 +202,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { fun delBook(success: (() -> Unit)?) { execute { bookData.value?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } inBookshelf = false }.onSuccess { diff --git a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt index a4e420af1..6bcae9257 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt @@ -8,7 +8,6 @@ import androidx.lifecycle.Observer import io.legado.app.R import io.legado.app.base.VMBaseActivity import io.legado.app.data.entities.Book -import io.legado.app.help.ImageLoader import io.legado.app.ui.changecover.ChangeCoverDialog import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.activity_book_info_edit.* @@ -59,10 +58,8 @@ class BookInfoEditActivity : } private fun upCover() { - viewModel.book?.getDisplayCover()?.let { - ImageLoader.load(this, it) - .centerCrop() - .into(iv_cover) + viewModel.book.let { + iv_cover.load(it?.getDisplayCover(), it?.name, it?.author) } } 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 7437c3cae..e35a50a9e 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 @@ -13,13 +13,15 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.widget.EditText +import androidx.core.view.get import androidx.core.view.isVisible +import androidx.core.view.size import androidx.lifecycle.Observer import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseActivity -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.constant.Status import io.legado.app.data.entities.Book @@ -67,6 +69,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo private val requestCodeChapterList = 568 private val requestCodeEditSource = 111 private val requestCodeReplace = 312 + private var menu: Menu? = null override val viewModel: ReadBookViewModel get() = getViewModel(ReadBookViewModel::class.java) @@ -85,7 +88,10 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo initView() upScreenTimeOut() ReadBook.callBack = this - ReadBook.titleDate.observe(this, Observer { title_bar.title = it }) + ReadBook.titleDate.observe(this, Observer { + title_bar.title = it + upMenu() + }) viewModel.initData(intent) } @@ -146,6 +152,33 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo return super.onCompatCreateOptionsMenu(menu) } + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + this.menu = menu + upMenu() + return super.onPrepareOptionsMenu(menu) + } + + private fun upMenu() { + menu?.let { menu -> + ReadBook.book?.let { book -> + val onLine = !book.isLocalBook() + for (i in 0 until menu.size) { + val item = menu[i] + when (item.groupId) { + R.id.menu_group_on_line -> item.isVisible = onLine + R.id.menu_group_local -> item.isVisible = !onLine + R.id.menu_group_text -> item.isVisible = book.isTxt() + R.id.menu_group_login -> + item.isVisible = !ReadBook.webBook?.bookSource?.loginUrl.isNullOrEmpty() + else -> if (item.itemId == R.id.menu_enable_replace) { + item.isChecked = book.useReplaceRule + } + } + } + } + } + } + /** * 菜单 */ @@ -208,7 +241,8 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo ReadBook.durChapterIndex, ReadBook.durPageIndex, textChapter!!.title, - editContent) + editContent + ) App.db.bookmarkDao().insert(bookmark) } } @@ -219,6 +253,13 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo R.id.menu_copy_text -> { } + R.id.menu_update_toc -> ReadBook.book?.let { + viewModel.loadChapterList(it) + } + R.id.menu_enable_replace -> ReadBook.book?.let { + it.useReplaceRule = !it.useReplaceRule + menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.useReplaceRule + } } return super.onCompatOptionsItemSelected(item) } @@ -375,7 +416,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo seek_read_page.progress = ReadBook.durPageIndex } - override fun showMenu() { + override fun showMenuBar() { read_menu.runMenuIn() } @@ -457,12 +498,12 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo when (dialogId) { TEXT_COLOR -> { setTextColor(color) - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } BG_COLOR -> { setBg(0, "#${color.hexString}") ReadBookConfig.upBg() - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } } } @@ -509,7 +550,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo override fun observeLiveBus() { super.observeLiveBus() - observeEvent(Bus.ALOUD_STATE) { + observeEvent(EventBus.ALOUD_STATE) { if (it == Status.STOP || it == Status.PAUSE) { ReadBook.curTextChapter?.let { textChapter -> val page = textChapter.page(ReadBook.durPageIndex) @@ -520,20 +561,20 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } } - observeEvent(Bus.TIME_CHANGED) { page_view.upTime() } - observeEvent(Bus.BATTERY_CHANGED) { page_view.upBattery(it) } - observeEvent(Bus.OPEN_CHAPTER) { + observeEvent(EventBus.TIME_CHANGED) { page_view.upTime() } + observeEvent(EventBus.BATTERY_CHANGED) { page_view.upBattery(it) } + observeEvent(EventBus.OPEN_CHAPTER) { viewModel.openChapter(it.index, ReadBook.durPageIndex) page_view.upContent() } - observeEvent(Bus.MEDIA_BUTTON) { + observeEvent(EventBus.MEDIA_BUTTON) { if (it) { onClickReadAloud() } else { ReadBook.readAloud(!BaseReadAloudService.pause) } } - observeEvent(Bus.UP_CONFIG) { + observeEvent(EventBus.UP_CONFIG) { upSystemUiVisibility() content_view.upStyle() page_view.upBg() @@ -544,7 +585,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo page_view.upContent() } } - observeEventSticky(Bus.TTS_START) { chapterStart -> + observeEventSticky(EventBus.TTS_START) { chapterStart -> launch(IO) { if (BaseReadAloudService.isPlay()) { ReadBook.curTextChapter?.let { @@ -557,7 +598,7 @@ class ReadBookActivity : VMBaseActivity(R.layout.activity_boo } } } - observeEvent(Bus.REPLACE) { + observeEvent(EventBus.REPLACE) { ReplaceEditDialog().show(supportFragmentManager, "replaceEditDialog") } observeEvent(PreferKey.keepLight) { 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 9b987e505..ed5d9d04d 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 @@ -10,6 +10,7 @@ import io.legado.app.data.entities.BookChapter import io.legado.app.help.BookHelp import io.legado.app.help.IntentDataHelp import io.legado.app.model.WebBook +import io.legado.app.model.localBook.AnalyzeTxtFile import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook @@ -79,41 +80,55 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { changeDruChapterIndex: ((chapters: List) -> Unit)? = null ) { execute { - ReadBook.webBook?.getBookInfo(book, this) - ?.onSuccess { - loadChapterList(book, changeDruChapterIndex) - } + if (book.isLocalBook()) { + loadChapterList(book, changeDruChapterIndex) + } else { + ReadBook.webBook?.getBookInfo(book, this) + ?.onSuccess { + loadChapterList(book, changeDruChapterIndex) + } + } } } - private fun loadChapterList( + fun loadChapterList( book: Book, changeDruChapterIndex: ((chapters: List) -> Unit)? = null ) { execute { - ReadBook.webBook?.getChapterList(book, this) - ?.onSuccess(IO) { cList -> - if (!cList.isNullOrEmpty()) { - if (changeDruChapterIndex == null) { - App.db.bookChapterDao().insert(*cList.toTypedArray()) - ReadBook.chapterSize = cList.size - ReadBook.loadContent() + if (book.isLocalBook()) { + AnalyzeTxtFile.analyze(context, book).let { + App.db.bookChapterDao().insert(*it.toTypedArray()) + App.db.bookDao().update(book) + ReadBook.chapterSize = it.size + ReadBook.loadContent() + } + } else { + ReadBook.webBook?.getChapterList(book, this) + ?.onSuccess(IO) { cList -> + if (!cList.isNullOrEmpty()) { + if (changeDruChapterIndex == null) { + App.db.bookChapterDao().insert(*cList.toTypedArray()) + App.db.bookDao().update(book) + ReadBook.chapterSize = cList.size + ReadBook.loadContent() + } else { + changeDruChapterIndex(cList) + } } else { - changeDruChapterIndex(cList) + toast(R.string.error_load_toc) } - } else { + }?.onError { toast(R.string.error_load_toc) - } - }?.onError { - toast(R.string.error_load_toc) - } ?: autoChangeSource() + } ?: autoChangeSource() + } } } fun changeTo(book1: Book) { execute { ReadBook.book?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } ReadBook.prevTextChapter = null ReadBook.curTextChapter = null @@ -170,7 +185,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { fun removeFromBookshelf(success: (() -> Unit)?) { execute { ReadBook.book?.let { - App.db.bookDao().delete(it.bookUrl) + App.db.bookDao().delete(it) } }.onSuccess { success?.invoke() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt index 8fe683273..289658f80 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt @@ -20,7 +20,7 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialog import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.help.ImageLoader import io.legado.app.help.ReadBookConfig import io.legado.app.help.permission.Permissions @@ -54,8 +54,8 @@ class BgTextConfigDialog : DialogFragment() { it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) - it.decorView.setPadding(0, 0, 0, 0) + it.setBackgroundDrawableResource(R.color.background) + it.decorView.setPadding(0, 5, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -154,7 +154,7 @@ class BgTextConfigDialog : DialogFragment() { this.onClick { ReadBookConfig.getConfig().setBg(1, item) ReadBookConfig.upBg() - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } } } @@ -178,7 +178,7 @@ class BgTextConfigDialog : DialogFragment() { file.writeBytes(it) ReadBookConfig.getConfig().setBg(2, file.absolutePath) ReadBookConfig.upBg() - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } } } else { @@ -192,7 +192,7 @@ class BgTextConfigDialog : DialogFragment() { FileUtils.getPath(requireContext(), uri)?.let { path -> ReadBookConfig.getConfig().setBg(2, path) ReadBookConfig.upBg() - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } } .request() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt index 0eac917a1..ecb7df170 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt @@ -12,7 +12,7 @@ import androidx.fragment.app.DialogFragment import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.lib.theme.ATH import io.legado.app.ui.book.read.Help @@ -29,8 +29,8 @@ class MoreConfigDialog : DialogFragment() { it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) - it.decorView.setPadding(0, 0, 0, 0) + it.setBackgroundDrawableResource(R.color.background) + it.decorView.setPadding(0, 5, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -92,9 +92,8 @@ class MoreConfigDialog : DialogFragment() { key: String? ) { when (key) { - PreferKey.hideStatusBar -> postEvent(Bus.UP_CONFIG, true) - PreferKey.hideNavigationBar -> postEvent(Bus.UP_CONFIG, true) - PreferKey.clickAllNext -> postEvent(Bus.UP_CONFIG, true) + PreferKey.hideStatusBar -> postEvent(EventBus.UP_CONFIG, true) + PreferKey.hideNavigationBar -> postEvent(EventBus.UP_CONFIG, true) PreferKey.keepLight -> postEvent(PreferKey.keepLight, true) } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt index fe04055e9..c1a9e5e8f 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt @@ -8,7 +8,7 @@ import android.view.ViewGroup import android.widget.SeekBar import androidx.fragment.app.DialogFragment import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.help.ReadBookConfig import io.legado.app.ui.book.read.Help import io.legado.app.utils.postEvent @@ -26,8 +26,6 @@ class PaddingConfigDialog : DialogFragment() { it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) - it.decorView.setPadding(0, 0, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f it.attributes = attr @@ -63,35 +61,35 @@ class PaddingConfigDialog : DialogFragment() { private fun initView() = with(ReadBookConfig.getConfig()) { iv_padding_top_add.onClick { seek_padding_top.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_top_remove.onClick { seek_padding_top.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_bottom_add.onClick { seek_padding_bottom.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_bottom_remove.onClick { seek_padding_bottom.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_left_add.onClick { seek_padding_left.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_left_remove.onClick { seek_padding_left.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_right_add.onClick { seek_padding_right.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_padding_right_remove.onClick { seek_padding_right.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } seek_padding_top.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -103,7 +101,7 @@ class PaddingConfigDialog : DialogFragment() { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) seek_padding_bottom.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -115,7 +113,7 @@ class PaddingConfigDialog : DialogFragment() { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) seek_padding_left.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -127,7 +125,7 @@ class PaddingConfigDialog : DialogFragment() { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) seek_padding_right.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -139,7 +137,7 @@ class PaddingConfigDialog : DialogFragment() { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) } diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt index 0d066dd54..294a1c127 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt @@ -12,12 +12,13 @@ import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus +import io.legado.app.constant.PreferKey +import io.legado.app.help.AppConfig import io.legado.app.lib.theme.ATH import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.ui.book.read.Help -import io.legado.app.utils.getPrefString import io.legado.app.utils.postEvent class ReadAloudConfigDialog : DialogFragment() { @@ -58,17 +59,19 @@ class ReadAloudConfigDialog : DialogFragment() { } class ReadAloudPreferenceFragment : PreferenceFragmentCompat(), - SharedPreferences.OnSharedPreferenceChangeListener, - Preference.OnPreferenceChangeListener { + SharedPreferences.OnSharedPreferenceChangeListener { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.pref_config_aloud) + upPreferenceSummary( + findPreference(PreferKey.ttsSpeechPer), + AppConfig.ttsSpeechPer + ) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) ATH.applyEdgeEffectColor(listView) - bindPreferenceSummaryToValue(findPreference("ttsSpeechPer")) } override fun onResume() { @@ -86,42 +89,35 @@ class ReadAloudConfigDialog : DialogFragment() { key: String? ) { when (key) { - "readAloudByPage" -> { + PreferKey.readAloudByPage -> { if (BaseReadAloudService.isRun) { - postEvent(Bus.MEDIA_BUTTON, false) + postEvent(EventBus.MEDIA_BUTTON, false) } } - "readAloudOnLine" -> { - if (BaseReadAloudService.isRun) { - ReadAloud.stop(requireContext()) - ReadAloud.aloudClass = ReadAloud.getReadAloudClass() - } + PreferKey.readAloudOnLine -> { + ReadAloud.stop(requireContext()) + ReadAloud.aloudClass = ReadAloud.getReadAloudClass() + } + PreferKey.ttsSpeechPer -> { + upPreferenceSummary( + findPreference(PreferKey.ttsSpeechPer), + AppConfig.ttsSpeechPer + ) + ReadAloud.upTtsSpeechRate(requireContext()) } - "ttsSpeechPer" -> ReadAloud.upTtsSpeechRate(requireContext()) - } - } - - override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { - val stringValue = newValue.toString() - - if (preference is ListPreference) { - val index = preference.findIndexOfValue(stringValue) - // Set the summary to reflect the new value. - preference.setSummary(if (index >= 0) preference.entries[index] else null) - } else { - // For all other preferences, set the summary to the value's - preference?.summary = stringValue } - return true } - private fun bindPreferenceSummaryToValue(preference: Preference?) { - preference?.apply { - onPreferenceChangeListener = this@ReadAloudPreferenceFragment - onPreferenceChange( - this, - context.getPrefString(key) - ) + private fun upPreferenceSummary(preference: Preference?, value: String) { + when (preference) { + is ListPreference -> { + val index = preference.findIndexOfValue(value) + // Set the summary to reflect the new value. + preference.summary = if (index >= 0) preference.entries[index] else null + } + else -> { + preference?.summary = value + } } } 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 a20ac2efe..6040c0880 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 @@ -9,12 +9,15 @@ import android.view.ViewGroup import android.widget.SeekBar import androidx.fragment.app.DialogFragment import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus +import io.legado.app.help.AppConfig import io.legado.app.service.BaseReadAloudService import io.legado.app.service.help.ReadAloud import io.legado.app.service.help.ReadBook import io.legado.app.ui.book.read.Help -import io.legado.app.utils.* +import io.legado.app.utils.getPrefBoolean +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 @@ -57,15 +60,15 @@ class ReadAloudDialog : DialogFragment() { } private fun initData() { - observeEvent(Bus.ALOUD_STATE) { upPlayState() } - observeEvent(Bus.TTS_DS) { seek_timer.progress = it } + observeEvent(EventBus.ALOUD_STATE) { upPlayState() } + observeEvent(EventBus.TTS_DS) { seek_timer.progress = it } upPlayState() seek_timer.progress = BaseReadAloudService.timeMinute tv_timer.text = requireContext().getString(R.string.timer_m, BaseReadAloudService.timeMinute) cb_tts_follow_sys.isChecked = requireContext().getPrefBoolean("ttsFollowSys", true) seek_tts_SpeechRate.isEnabled = !cb_tts_follow_sys.isChecked - seek_tts_SpeechRate.progress = requireContext().getPrefInt("ttsSpeechRate", 5) + seek_tts_SpeechRate.progress = AppConfig.ttsSpeechRate } private fun initOnChange() { @@ -83,7 +86,7 @@ class ReadAloudDialog : DialogFragment() { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - requireContext().putPrefInt("ttsSpeechRate", seek_tts_SpeechRate.progress) + AppConfig.ttsSpeechRate = seek_tts_SpeechRate.progress upTtsSpeechRate() } }) @@ -101,7 +104,7 @@ class ReadAloudDialog : DialogFragment() { } private fun initOnClick() { - iv_menu.onClick { callBack?.showMenu(); dismiss() } + iv_menu.onClick { callBack?.showMenuBar(); dismiss() } iv_other_config.onClick { ReadAloudConfigDialog().show(childFragmentManager, "readAloudConfigDialog") } @@ -135,7 +138,7 @@ class ReadAloudDialog : DialogFragment() { } interface CallBack { - fun showMenu() + fun showMenuBar() fun openChapterList() fun onClickReadAloud() fun finish() diff --git a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt index 3bc5060b0..10d3d3433 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt @@ -10,7 +10,7 @@ import android.widget.SeekBar import androidx.core.view.get import androidx.fragment.app.DialogFragment import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.ImageLoader import io.legado.app.help.ReadBookConfig @@ -37,8 +37,8 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { it.windowManager?.defaultDisplay?.getMetrics(dm) } dialog?.window?.let { - it.setBackgroundDrawableResource(R.color.transparent) - it.decorView.setPadding(0, 0, 0, 0) + it.setBackgroundDrawableResource(R.color.background) + it.decorView.setPadding(0, 5, 0, 0) val attr = it.attributes attr.dimAmount = 0.0f attr.gravity = Gravity.BOTTOM @@ -83,7 +83,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { textBold = !textBold tv_text_bold.isSelected = textBold } - postEvent(Bus.UP_CONFIG, false) + postEvent(EventBus.UP_CONFIG, false) } tv_text_font.onClick { FontSelectDialog().show(childFragmentManager, "fontSelectDialog") @@ -94,7 +94,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { items = resources.getStringArray(R.array.indent).toList() ) { _, index -> putPrefInt("textIndent", index) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } } tv_padding.onClick { @@ -112,16 +112,16 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) iv_text_size_add.onClick { seek_text_size.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_text_size_remove.onClick { seek_text_size.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } seek_text_letter_spacing.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -134,16 +134,16 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) iv_text_letter_spacing_add.onClick { seek_text_letter_spacing.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_text_letter_spacing_remove.onClick { seek_text_letter_spacing.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } seek_line_size.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { @@ -156,16 +156,16 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { override fun onStartTrackingTouch(seekBar: SeekBar?) = Unit override fun onStopTrackingTouch(seekBar: SeekBar?) { - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } }) iv_line_size_add.onClick { seek_line_size.progressAdd(1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } iv_line_size_remove.onClick { seek_line_size.progressAdd(-1) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } rg_page_anim.onCheckedChange { _, checkedId -> for (i in 0 until rg_page_anim.childCount) { @@ -197,7 +197,7 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { ReadBookConfig.upBg() upStyle() upBg() - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } } @@ -266,6 +266,6 @@ class ReadStyleDialog : DialogFragment(), FontSelectDialog.CallBack { override fun selectFile(path: String) { requireContext().putPrefString(PreferKey.readBookFont, path) - postEvent(Bus.UP_CONFIG, true) + postEvent(EventBus.UP_CONFIG, true) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt index cfce5f27b..f274b2744 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ContentSelectActionCallback.kt @@ -6,7 +6,7 @@ import android.view.MenuItem import android.widget.TextView import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.utils.postEvent class ContentSelectActionCallback(private val textView: TextView) : ActionMode.Callback { @@ -15,7 +15,7 @@ class ContentSelectActionCallback(private val textView: TextView) : ActionMode.C when (item?.itemId) { R.id.menu_replace -> { val text = textView.text.substring(textView.selectionStart, textView.selectionEnd) - postEvent(Bus.REPLACE, text) + postEvent(EventBus.REPLACE, text) mode?.finish() return true } diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt index e5ccf1b65..e4cbf9560 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt @@ -9,10 +9,9 @@ import android.widget.Scroller import androidx.annotation.CallSuper import androidx.interpolator.view.animation.FastOutLinearInInterpolator import com.google.android.material.snackbar.Snackbar -import io.legado.app.constant.PreferKey +import io.legado.app.help.AppConfig import io.legado.app.ui.book.read.page.ContentView import io.legado.app.ui.book.read.page.PageView -import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.screenshot import io.legado.app.utils.snackbar import kotlin.math.abs @@ -256,7 +255,8 @@ abstract class PageDelegate(protected val pageView: PageView) { setTouchPoint(x, y) } else { bitmap = if (x > viewWidth / 2 || - pageView.context.getPrefBoolean(PreferKey.clickAllNext, false)) { + AppConfig.clickAllNext + ) { //设置动画方向 if (!hasNext()) { return true diff --git a/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt index 213157e90..2be9e6409 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt @@ -1,6 +1,5 @@ package io.legado.app.ui.book.search -import android.content.Context import io.legado.app.App import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder @@ -15,8 +14,10 @@ import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick -class HistoryKeyAdapter(context: Context, val callBack: CallBack) : - SimpleRecyclerAdapter(context, R.layout.item_text) { +class HistoryKeyAdapter(activity: SearchActivity, val callBack: CallBack) : + SimpleRecyclerAdapter(activity, R.layout.item_text) { + + private val explosionField = ExplosionField.attach2Window(activity) override fun convert(holder: ItemViewHolder, item: SearchKeyword, payloads: MutableList) { with(holder.itemView) { @@ -26,7 +27,7 @@ class HistoryKeyAdapter(context: Context, val callBack: CallBack) : } onLongClick { it?.let { - ExplosionField(context).explode(it, true) + explosionField.explode(it, true) } GlobalScope.launch(IO) { App.db.searchKeywordDao().delete(item) diff --git a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt index 04d2e46f8..4a725e854 100644 --- a/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt @@ -67,13 +67,7 @@ class SearchAdapter(context: Context, val callBack: CallBack) : } } } - searchBook.coverUrl.let { - ImageLoader.load(context, it)//Glide自动识别http://和file:// - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) - .centerCrop() - .into(iv_cover) - } + iv_cover.load(searchBook.coverUrl, searchBook.name, searchBook.author) onClick { callBack.showBookInfo(searchBook.name, searchBook.author) } diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt index 52b433a32..4f9fea1df 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt @@ -30,7 +30,7 @@ import io.legado.app.lib.dialogs.okButton import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.primaryTextColor import io.legado.app.lib.theme.view.ATEAutoCompleteTextView -import io.legado.app.service.CheckSourceService +import io.legado.app.service.help.CheckSource import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.filechooser.FileChooserDialog import io.legado.app.ui.qrcode.QrCodeActivity @@ -40,7 +40,6 @@ import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.view_search.* import org.jetbrains.anko.startActivity import org.jetbrains.anko.startActivityForResult -import org.jetbrains.anko.startService import org.jetbrains.anko.toast import java.io.FileNotFoundException @@ -81,21 +80,18 @@ class BookSourceActivity : VMBaseActivity(R.layout.activity when (item.itemId) { R.id.menu_add_book_source -> startActivity() R.id.menu_import_source_qr -> startActivityForResult(qrRequestCode) - R.id.menu_group_manage -> GroupManageDialog().show( - supportFragmentManager, - "groupManage" - ) + R.id.menu_group_manage -> + GroupManageDialog().show(supportFragmentManager, "groupManage") R.id.menu_import_source_local -> selectFileSys() R.id.menu_select_all -> adapter.selectAll() R.id.menu_revert_selection -> adapter.revertSelection() - R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelectionIds()) - R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelectionIds()) - R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelectionIds()) - R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelectionIds()) - R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelectionIds()) - R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelectionIds()) - R.id.menu_check_source -> - startService(Pair("data", adapter.getSelectionIds())) + R.id.menu_enable_selection -> viewModel.enableSelection(adapter.getSelection()) + R.id.menu_disable_selection -> viewModel.disableSelection(adapter.getSelection()) + R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.getSelection()) + R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.getSelection()) + R.id.menu_del_selection -> viewModel.delSelection(adapter.getSelection()) + R.id.menu_export_selection -> viewModel.exportSelection(adapter.getSelection()) + R.id.menu_check_source -> CheckSource.start(this, adapter.getSelection()) R.id.menu_import_source_onLine -> showImportDialog() } if (item.groupId == R.id.source_group) { diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt index c7c432e16..c5660e07d 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt @@ -1,8 +1,10 @@ package io.legado.app.ui.book.source.manage import android.content.Context +import android.os.Bundle import android.view.Menu import android.widget.PopupMenu +import androidx.core.os.bundleOf import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter @@ -16,31 +18,31 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_book_source), OnItemTouchCallbackListener { - private val selectedIds = linkedSetOf() + private val selected = linkedSetOf() fun selectAll() { getItems().forEach { - selectedIds.add(it.bookSourceUrl) + selected.add(it) } - notifyItemRangeChanged(0, itemCount, 1) + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) } fun revertSelection() { getItems().forEach { - if (selectedIds.contains(it.bookSourceUrl)) { - selectedIds.remove(it.bookSourceUrl) + if (selected.contains(it)) { + selected.remove(it) } else { - selectedIds.add(it.bookSourceUrl) + selected.add(it) } } - notifyItemRangeChanged(0, itemCount, 1) + notifyItemRangeChanged(0, itemCount, bundleOf(Pair("selected", null))) } - fun getSelectionIds(): LinkedHashSet { - val selection = linkedSetOf() + fun getSelection(): LinkedHashSet { + val selection = linkedSetOf() getItems().map { - if (selectedIds.contains(it.bookSourceUrl)) { - selection.add(it.bookSourceUrl) + if (selected.contains(it)) { + selection.add(it) } } return selection @@ -68,7 +70,8 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : override fun convert(holder: ItemViewHolder, item: BookSource, payloads: MutableList) { with(holder.itemView) { - if (payloads.isEmpty()) { + val payload = payloads.getOrNull(0) as? Bundle + if (payload == null) { this.setBackgroundColor(context.backgroundColor) if (item.bookSourceGroup.isNullOrEmpty()) { cb_book_source.text = item.bookSourceName @@ -81,12 +84,12 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : item.enabled = swt_enabled.isChecked callBack.update(item) } - cb_book_source.isChecked = selectedIds.contains(item.bookSourceUrl) + cb_book_source.isChecked = selected.contains(item) cb_book_source.setOnClickListener { if (cb_book_source.isChecked) { - selectedIds.add(item.bookSourceUrl) + selected.add(item) } else { - selectedIds.remove(item.bookSourceUrl) + selected.remove(item) } } iv_edit.onClick { callBack.edit(item) } @@ -104,9 +107,17 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) : popupMenu.show() } } else { - when (payloads[0]) { - 1 -> cb_book_source.isChecked = selectedIds.contains(item.bookSourceUrl) - 2 -> swt_enabled.isChecked = item.enabled + payload.keySet().map { + when (it) { + "select" -> cb_book_source.isChecked = selected.contains(item) + "name", "group" -> if (item.bookSourceGroup.isNullOrEmpty()) { + cb_book_source.text = item.bookSourceName + } else { + cb_book_source.text = + String.format("%s (%s)", item.bookSourceName, item.bookSourceGroup) + } + "enabled" -> swt_enabled.isChecked = payload.getBoolean(it) + } } } } 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 554ae4165..9bbaccfb9 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 @@ -43,56 +43,54 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application) } } - fun enableSelection(ids: LinkedHashSet) { + fun enableSelection(sources: LinkedHashSet) { execute { - ids.forEach { - App.db.bookSourceDao().enableSection(it) + sources.forEach { + it.enabled = true } + App.db.bookSourceDao().update(*sources.toTypedArray()) } } - fun disableSelection(ids: LinkedHashSet) { + fun disableSelection(sources: LinkedHashSet) { execute { - ids.forEach { - App.db.bookSourceDao().disableSection(it) + sources.forEach { + it.enabled = false } + App.db.bookSourceDao().update(*sources.toTypedArray()) } } - fun enableSelectExplore(ids: LinkedHashSet) { + fun enableSelectExplore(sources: LinkedHashSet) { execute { - ids.forEach { - App.db.bookSourceDao().enableSectionExplore(it) + sources.forEach { + it.enabledExplore = true } + App.db.bookSourceDao().update(*sources.toTypedArray()) } } - fun disableSelectExplore(ids: LinkedHashSet) { + fun disableSelectExplore(sources: LinkedHashSet) { execute { - ids.forEach { - App.db.bookSourceDao().disableSectionExplore(it) + sources.forEach { + it.enabledExplore = false } + App.db.bookSourceDao().update(*sources.toTypedArray()) } } - fun delSelection(ids: LinkedHashSet) { + fun delSelection(sources: LinkedHashSet) { execute { - ids.forEach { - App.db.bookSourceDao().delSection(it) - } + App.db.bookSourceDao().delete(*sources.toTypedArray()) } } - fun exportSelection(ids: LinkedHashSet) { + fun exportSelection(sources: LinkedHashSet) { execute { - ids.map { - App.db.bookSourceDao().getBookSource(it) - }.let { - val json = GSON.toJson(it) - val file = - FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json") - file.writeText(json) - } + val json = GSON.toJson(sources) + val file = + FileUtils.createFileIfNotExist(Backup.exportPath + File.separator + "exportBookSource.json") + file.writeText(json) }.onSuccess { context.toast("成功导出至\n${Backup.exportPath}") }.onError { diff --git a/app/src/main/java/io/legado/app/ui/book/source/manage/DiffCallBack.kt b/app/src/main/java/io/legado/app/ui/book/source/manage/DiffCallBack.kt index 0e8d9bead..da0d1292e 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/manage/DiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/manage/DiffCallBack.kt @@ -1,5 +1,6 @@ package io.legado.app.ui.book.source.manage +import android.os.Bundle import androidx.recyclerview.widget.DiffUtil import io.legado.app.data.entities.BookSource @@ -25,19 +26,31 @@ class DiffCallBack( override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - return oldItem.bookSourceName == newItem.bookSourceName - && oldItem.bookSourceGroup == newItem.bookSourceGroup - && oldItem.enabled == newItem.enabled + if (oldItem.bookSourceName != newItem.bookSourceName) + return false + if (oldItem.bookSourceGroup != newItem.bookSourceGroup) + return false + if (oldItem.enabled != newItem.enabled) + return false + return true } override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] - return when { - oldItem.bookSourceName == newItem.bookSourceName - && oldItem.bookSourceGroup == newItem.bookSourceGroup - && oldItem.enabled != newItem.enabled -> 2 - else -> null + val payload = Bundle() + if (oldItem.bookSourceName != newItem.bookSourceName) { + payload.putString("name", newItem.bookSourceName) } + if (oldItem.bookSourceGroup != newItem.bookSourceGroup) { + payload.putString("group", newItem.bookSourceGroup) + } + if (oldItem.enabled != newItem.enabled) { + payload.putBoolean("enabled", newItem.enabled) + } + if (payload.isEmpty) { + return null + } + return payload } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt b/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt index f566cbbbb..d1c081f76 100644 --- a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt +++ b/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverDialog.kt @@ -7,13 +7,16 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.DialogFragment import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Observer import androidx.recyclerview.widget.GridLayoutManager import io.legado.app.R import io.legado.app.utils.getViewModel import kotlinx.android.synthetic.main.dialog_change_source.* -class ChangeCoverDialog : DialogFragment() { +class ChangeCoverDialog : DialogFragment(), + ChangeCoverViewModel.CallBack, + CoverAdapter.CallBack { companion object { const val tag = "changeCoverDialog" @@ -32,7 +35,7 @@ class ChangeCoverDialog : DialogFragment() { private var callBack: CallBack? = null private lateinit var viewModel: ChangeCoverViewModel - private lateinit var adapter: CoverAdapter + override lateinit var adapter: CoverAdapter override fun onStart() { super.onStart() @@ -46,13 +49,17 @@ class ChangeCoverDialog : DialogFragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { + callBack = activity as? CallBack viewModel = getViewModel(ChangeCoverViewModel::class.java) + viewModel.callBack = this return inflater.inflate(R.layout.dialog_change_source, container) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - callBack = activity as? CallBack + viewModel.searchStateData.observe(this, Observer { + refresh_progress_bar.isAutoLoading = it + }) tool_bar.setTitle(R.string.change_cover_source) arguments?.let { bundle -> bundle.getString("name")?.let { @@ -63,8 +70,14 @@ class ChangeCoverDialog : DialogFragment() { } } recycler_view.layoutManager = GridLayoutManager(requireContext(), 3) - adapter = CoverAdapter(requireContext()) + adapter = CoverAdapter(requireContext(), this) recycler_view.adapter = adapter + viewModel.initData() + } + + override fun changeTo(coverUrl: String) { + callBack?.coverChangeTo(coverUrl) + dismiss() } interface CallBack { diff --git a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverViewModel.kt b/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverViewModel.kt index 5f12c56b2..d8ed01d9f 100644 --- a/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/changecover/ChangeCoverViewModel.kt @@ -1,12 +1,77 @@ package io.legado.app.ui.changecover import android.app.Application +import androidx.lifecycle.MutableLiveData +import io.legado.app.App import io.legado.app.base.BaseViewModel +import io.legado.app.help.AppConfig +import io.legado.app.help.coroutine.Coroutine +import io.legado.app.model.WebBook +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.withContext +import java.util.concurrent.Executors class ChangeCoverViewModel(application: Application) : BaseViewModel(application) { - + private var searchPool = + Executors.newFixedThreadPool(AppConfig.threadCount).asCoroutineDispatcher() + var callBack: CallBack? = null var name: String = "" var author: String = "" + private var task: Coroutine<*>? = null + val searchStateData = MutableLiveData() + + fun initData() { + execute { + App.db.searchBookDao().getEnableHasCover(name, author) + }.onSuccess { + it?.let { + callBack?.adapter?.setItems(it) + } + }.onFinally { + search() + } + } + + 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()) { + val searchBook = it[0] + if (searchBook.name == name && searchBook.author == author + && !searchBook.coverUrl.isNullOrEmpty() + ) { + App.db.searchBookDao().insert(searchBook) + callBack?.adapter?.let { adapter -> + if (!adapter.getItems().contains(searchBook)) { + withContext(Dispatchers.Main) { + adapter.addItem(searchBook) + } + } + } + } + } + } + } + } + + task?.invokeOnCompletion { + searchStateData.postValue(false) + } + } + override fun onCleared() { + super.onCleared() + searchPool.close() + } + interface CallBack { + var adapter: CoverAdapter + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt b/app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt index 408acbeb4..a26c21f57 100644 --- a/app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/changecover/CoverAdapter.kt @@ -5,21 +5,23 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.SearchBook -import io.legado.app.help.ImageLoader import kotlinx.android.synthetic.main.item_cover.view.* +import org.jetbrains.anko.sdk27.listeners.onClick -class CoverAdapter(context: Context) : +class CoverAdapter(context: Context, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_cover) { override fun convert(holder: ItemViewHolder, item: SearchBook, payloads: MutableList) { with(holder.itemView) { - item.coverUrl?.let { - ImageLoader.load(context, it) - .centerCrop() - .into(iv_cover) - } + iv_cover.load(item.coverUrl, item.name, item.author) tv_source.text = item.originName + onClick { + callBack.changeTo(item.coverUrl!!) + } } } + interface CallBack { + fun changeTo(coverUrl: String) + } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt index edf3057a3..c05e171b1 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt +++ b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceDialog.kt @@ -40,7 +40,7 @@ class ChangeSourceDialog : DialogFragment(), private var callBack: CallBack? = null private lateinit var viewModel: ChangeSourceViewModel - private lateinit var changeSourceAdapter: ChangeSourceAdapter + override lateinit var changeSourceAdapter: ChangeSourceAdapter override fun onStart() { super.onStart() @@ -54,13 +54,14 @@ class ChangeSourceDialog : DialogFragment(), container: ViewGroup?, savedInstanceState: Bundle? ): View? { + callBack = activity as? CallBack viewModel = getViewModel(ChangeSourceViewModel::class.java) + viewModel.callBack = this return inflater.inflate(R.layout.dialog_change_source, container) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - callBack = activity as? CallBack viewModel.searchStateData.observe(viewLifecycleOwner, Observer { refresh_progress_bar.isAutoLoading = it }) @@ -92,7 +93,6 @@ class ChangeSourceDialog : DialogFragment(), DividerItemDecoration(requireContext(), LinearLayout.VERTICAL) ) recycler_view.adapter = changeSourceAdapter - viewModel.callBack = this } private fun initSearchView() { @@ -139,10 +139,6 @@ class ChangeSourceDialog : DialogFragment(), override val bookUrl: String? get() = callBack?.oldBook?.bookUrl - override fun adapter(): ChangeSourceAdapter { - return changeSourceAdapter - } - interface CallBack { val oldBook: Book? fun changeTo(book: Book) diff --git a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt index 3b16a4359..1b6a4e679 100644 --- a/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/changesource/ChangeSourceViewModel.kt @@ -40,9 +40,8 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio private fun upAdapter() { execute { - callBack?.adapter()?.let { + callBack?.changeSourceAdapter?.let { val books = searchBooks.toList() - books.sorted() val diffResult = DiffUtil.calculateDiff(DiffCallBack(it.getItems(), books)) withContext(Main) { synchronized(this) { @@ -130,12 +129,13 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio } } - interface CallBack { - fun adapter(): ChangeSourceAdapter - } - override fun onCleared() { super.onCleared() searchPool.close() } + + interface CallBack { + var changeSourceAdapter: ChangeSourceAdapter + } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt b/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt index 803fe2e11..77eaad04f 100644 --- a/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt +++ b/app/src/main/java/io/legado/app/ui/chapterlist/BookmarkFragment.kt @@ -74,8 +74,6 @@ class BookmarkFragment : VMBaseFragment(R.layout.fragment_ } override fun delBookmark(bookmark: Bookmark) { - bookmark?.let { - App.db.bookmarkDao().delByBookmark(it.bookUrl, it.chapterName) - } + App.db.bookmarkDao().delByBookmark(bookmark.bookUrl, bookmark.chapterName) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt index d403324fe..38f5568fb 100644 --- a/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt @@ -47,26 +47,23 @@ class BackupConfigFragment : PreferenceFragmentCompat(), fun bindPreferenceSummaryToValue(preference: Preference?) { preference?.apply { onPreferenceChangeListener = this@BackupConfigFragment - onPreferenceChange( - this, - context.getPrefString(key) - ) + onPreferenceChange(this, context.getPrefString(key)) } } addPreferencesFromResource(R.xml.pref_config_backup) - findPreference("web_dav_url")?.let { + findPreference(PreferKey.webDavUrl)?.let { it.setOnBindEditTextListener { editText -> ATH.setTint(editText, requireContext().accentColor) } bindPreferenceSummaryToValue(it) } - findPreference("web_dav_account")?.let { + findPreference(PreferKey.webDavAccount)?.let { it.setOnBindEditTextListener { editText -> ATH.setTint(editText, requireContext().accentColor) } bindPreferenceSummaryToValue(it) } - findPreference("web_dav_password")?.let { + findPreference(PreferKey.webDavPassword)?.let { it.setOnBindEditTextListener { editText -> ATH.setTint(editText, requireContext().accentColor) editText.inputType = @@ -88,30 +85,35 @@ class BackupConfigFragment : PreferenceFragmentCompat(), } override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { - when { - preference?.key == "web_dav_password" -> if (newValue == null) { - preference.summary = getString(R.string.web_dav_pw_s) - } else { - preference.summary = "*".repeat(newValue.toString().length) - } - preference?.key == "web_dav_url" -> if (newValue == null) { - preference.summary = getString(R.string.web_dav_url_s) - } else { - preference.summary = newValue.toString() - } - preference?.key == "web_dav_account" -> if (newValue == null) { - preference.summary = getString(R.string.web_dav_account_s) - } else { - preference.summary = newValue.toString() - } - preference is ListPreference -> { - val index = preference.findIndexOfValue(newValue?.toString()) - // Set the summary to reflect the new value. - preference.setSummary(if (index >= 0) preference.entries[index] else null) - } - else -> preference?.summary = newValue?.toString() + when (preference?.key) { + PreferKey.webDavUrl -> + if (newValue == null) { + preference.summary = getString(R.string.web_dav_url_s) + } else { + preference.summary = newValue.toString() + } + PreferKey.webDavAccount -> + if (newValue == null) { + preference.summary = getString(R.string.web_dav_account_s) + } else { + preference.summary = newValue.toString() + } + PreferKey.webDavPassword -> + if (newValue == null) { + preference.summary = getString(R.string.web_dav_pw_s) + } else { + preference.summary = "*".repeat(newValue.toString().length) + } + else -> + if (preference is ListPreference) { + val index = preference.findIndexOfValue(newValue?.toString()) + // Set the summary to reflect the new value. + preference.setSummary(if (index >= 0) preference.entries[index] else null) + } else { + preference?.summary = newValue?.toString() + } } - return true + return false } override fun onPreferenceTreeClick(preference: Preference?): Boolean { @@ -132,6 +134,7 @@ class BackupConfigFragment : PreferenceFragmentCompat(), if (doc?.canWrite() == true) { launch { Backup.backup(requireContext(), uri) + toast(R.string.backup_success) } } else { selectBackupFolder() @@ -153,6 +156,7 @@ class BackupConfigFragment : PreferenceFragmentCompat(), .onGranted { launch { Backup.backup(requireContext(), null) + toast(R.string.backup_success) } } .request() @@ -161,7 +165,9 @@ class BackupConfigFragment : PreferenceFragmentCompat(), fun restore() { launch { - if (!WebDavHelp.showRestoreDialog(requireContext())) { + if (!WebDavHelp.showRestoreDialog(requireContext()) { + toast(R.string.restore_success) + }) { val backupPath = getPrefString(PreferKey.backupPath) if (backupPath?.isNotEmpty() == true) { val uri = Uri.parse(backupPath) @@ -308,8 +314,10 @@ class BackupConfigFragment : PreferenceFragmentCompat(), Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) putPrefString(PreferKey.backupPath, uri.toString()) + findPreference(PreferKey.backupPath)?.summary = uri.toString() launch { Backup.backup(requireContext(), uri) + toast(R.string.backup_success) } } } @@ -320,6 +328,7 @@ class BackupConfigFragment : PreferenceFragmentCompat(), Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) putPrefString(PreferKey.backupPath, uri.toString()) + findPreference(PreferKey.backupPath)?.summary = uri.toString() launch { Restore.restore(requireContext(), uri) toast(R.string.restore_success) diff --git a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt index 7a0d7fb0f..6a77a8c99 100644 --- a/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt @@ -12,7 +12,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.App import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.BookHelp @@ -27,7 +27,6 @@ import io.legado.app.utils.* class OtherConfigFragment : PreferenceFragmentCompat(), FileChooserDialog.CallBack, - Preference.OnPreferenceChangeListener, SharedPreferences.OnSharedPreferenceChangeListener { private val requestCodeDownloadPath = 25324 @@ -40,8 +39,8 @@ class OtherConfigFragment : PreferenceFragmentCompat(), override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { putPrefBoolean("process_text", isProcessTextEnabled()) addPreferencesFromResource(R.xml.pref_config_other) - bindPreferenceSummaryToValue(findPreference(PreferKey.downloadPath)) - bindPreferenceSummaryToValue(findPreference(PreferKey.threadCount)) + upPreferenceSummary(findPreference(PreferKey.downloadPath), BookHelp.downloadPath) + upPreferenceSummary(findPreference(PreferKey.threadCount), AppConfig.threadCount.toString()) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -84,48 +83,26 @@ class OtherConfigFragment : PreferenceFragmentCompat(), when (key) { PreferKey.downloadPath -> { BookHelp.upDownloadPath() - findPreference(key)?.summary = getPreferenceString(key).toString() + findPreference(key)?.summary = BookHelp.downloadPath } PreferKey.recordLog -> LogUtils.upLevel() PreferKey.processText -> sharedPreferences?.let { setProcessTextEnable(it.getBoolean("process_text", true)) } - PreferKey.showRss -> postEvent(Bus.SHOW_RSS, "unused") + PreferKey.showRss -> postEvent(EventBus.SHOW_RSS, "unused") } } - override fun onPreferenceChange(preference: Preference?, newValue: Any?): Boolean { - val stringValue = newValue.toString() - when { - preference is ListPreference -> { - val index = preference.findIndexOfValue(stringValue) + private fun upPreferenceSummary(preference: Preference?, value: String) { + when (preference) { + is ListPreference -> { + val index = preference.findIndexOfValue(value) // Set the summary to reflect the new value. - preference.setSummary(if (index >= 0) preference.entries[index] else null) + preference.summary = if (index >= 0) preference.entries[index] else null + } + else -> { + preference?.summary = value } - preference?.key == PreferKey.threadCount -> preference.summary = - getString(R.string.threads_num, stringValue) - else -> preference?.summary = stringValue - } - return true - } - - private fun bindPreferenceSummaryToValue(preference: Preference?) { - preference?.let { - preference.onPreferenceChangeListener = this - onPreferenceChange( - preference, - getPreferenceString(preference.key) - ) - } - } - - private fun getPreferenceString(key: String): Any { - return when (key) { - PreferKey.downloadPath -> getPrefString(PreferKey.downloadPath) - ?: App.INSTANCE.getExternalFilesDir(null)?.absolutePath - ?: App.INSTANCE.cacheDir.absolutePath - PreferKey.threadCount -> AppConfig.threadCount - else -> getPrefString(key) ?: "" } } @@ -166,7 +143,7 @@ class OtherConfigFragment : PreferenceFragmentCompat(), childFragmentManager, requestCodeDownloadPath, mode = FileChooserDialog.DIRECTORY, - initPath = getPreferenceString(PreferKey.downloadPath).toString() + initPath = BookHelp.downloadPath ) } .request() @@ -188,6 +165,8 @@ class OtherConfigFragment : PreferenceFragmentCompat(), Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) putPrefString(PreferKey.downloadPath, uri.toString()) + findPreference(PreferKey.downloadPath)?.summary = uri.toString() + BookHelp.upDownloadPath() } } } diff --git a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt index 2ee2f6621..c90db122d 100644 --- a/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt +++ b/app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt @@ -9,7 +9,7 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import io.legado.app.App import io.legado.app.R -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.help.AppConfig import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.noButton @@ -104,7 +104,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar .setTitle("切换默认主题") .setItems(items){ _,which -> - preference.summary = "${items[which]}" + preference.summary = items[which] putPrefInt("default_theme", which) when (which) { 0 -> { @@ -180,7 +180,7 @@ class ThemeConfigFragment : PreferenceFragmentCompat(), SharedPreferences.OnShar } private fun recreateActivities() { - postEvent(Bus.RECREATE, "") + postEvent(EventBus.RECREATE, "") Handler().postDelayed({ activity?.recreate() }, 100L) } diff --git a/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt b/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt index a2ff5d99e..dfa5f5b3d 100644 --- a/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt +++ b/app/src/main/java/io/legado/app/ui/download/DownloadActivity.kt @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseActivity -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.data.entities.Book import io.legado.app.service.help.Download import io.legado.app.utils.applyTint @@ -67,7 +67,7 @@ class DownloadActivity : BaseActivity(R.layout.activity_download) { } override fun observeLiveBus() { - observeEvent(Bus.UP_DOWNLOAD) { + observeEvent(EventBus.UP_DOWNLOAD) { if (it) { menu?.findItem(R.id.menu_download)?.setIcon(R.drawable.ic_stop_black_24dp) menu?.applyTint(this) diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt b/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt index d25e7f255..8e2fcf5e8 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/explore/ExploreShowAdapter.kt @@ -6,7 +6,6 @@ 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.SearchBook -import io.legado.app.help.ImageLoader import io.legado.app.utils.gone import io.legado.app.utils.visible import kotlinx.android.synthetic.main.item_bookshelf_list.view.iv_cover @@ -58,13 +57,7 @@ class ExploreShowAdapter(context: Context, val callBack: CallBack) : } } } - item.coverUrl.let { - ImageLoader.load(context, it)//Glide自动识别http://和file:// - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) - .centerCrop() - .into(iv_cover) - } + iv_cover.load(item.coverUrl, item.name, item.author) onClick { callBack.showBookInfo(item.toBook()) } diff --git a/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt index cf7ea9ebc..849b1ca87 100644 --- a/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/explore/ExploreShowViewModel.kt @@ -21,7 +21,7 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application execute { val sourceUrl = intent.getStringExtra("sourceUrl") exploreUrl = intent.getStringExtra("exploreUrl") - if (bookSource == null) { + if (bookSource == null && sourceUrl != null) { bookSource = App.db.bookSourceDao().getBookSource(sourceUrl) } explore() diff --git a/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt index a34c705b9..da58d501a 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/adapter/FileAdapter.kt @@ -64,7 +64,7 @@ class FileAdapter(context: Context, val callBack: CallBack) : fileParent.icon = upIcon fileParent.name = DIR_PARENT fileParent.size = 0 - fileParent.path = File(path).parent + fileParent.path = File(path).parent ?: "" data.add(fileParent) } currentPath?.let { currentPath -> diff --git a/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt b/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt index afa8ab4e6..b735ef5da 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/adapter/PathAdapter.kt @@ -15,6 +15,7 @@ import java.util.* class PathAdapter(context: Context, val callBack: CallBack) : SimpleRecyclerAdapter(context, R.layout.item_path_filepicker) { private val paths = LinkedList() + @Suppress("DEPRECATION") private val sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getARROW()) diff --git a/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt b/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt index 71e838cae..f06054bb4 100644 --- a/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt +++ b/app/src/main/java/io/legado/app/ui/filechooser/utils/FileUtils.kt @@ -34,13 +34,13 @@ object FileUtils { * 将目录分隔符统一为平台默认的分隔符,并为目录结尾添加分隔符 */ fun separator(path: String): String { - var path = path + var path1 = path val separator = File.separator - path = path.replace("\\", separator) - if (!path.endsWith(separator)) { - path += separator + path1 = path1.replace("\\", separator) + if (!path1.endsWith(separator)) { + path1 += separator } - return path + return path1 } fun closeSilently(c: Closeable?) { @@ -62,7 +62,7 @@ object FileUtils { startDirPath: String, excludeDirs: Array? = null, @SortType sortType: Int = BY_NAME_ASC ): Array { - var excludeDirs = excludeDirs + var excludeDirs1 = excludeDirs val dirList = ArrayList() val startDir = File(startDirPath) if (!startDir.isDirectory) { @@ -75,12 +75,12 @@ object FileUtils { f.isDirectory }) ?: return arrayOfNulls(0) - if (excludeDirs == null) { - excludeDirs = arrayOfNulls(0) + if (excludeDirs1 == null) { + excludeDirs1 = arrayOfNulls(0) } for (dir in dirs) { val file = dir.absoluteFile - if (!excludeDirs.contentDeepToString().contains(file.name)) { + if (!excludeDirs1.contentDeepToString().contains(file.name)) { dirList.add(file) } } @@ -191,7 +191,7 @@ object FileUtils { */ fun listFiles(startDirPath: String, allowExtensions: Array): Array? { val file = File(startDirPath) - return file.listFiles { dir, name -> + return file.listFiles { _, name -> //返回当前目录所有以某些扩展名结尾的文件 val extension = getExtension(name) allowExtensions.contentDeepToString().contains(extension) @@ -294,10 +294,8 @@ object FileUtils { bis.close() bos.close() } else if (src.isDirectory) { - val files = src.listFiles() - tar.mkdirs() - for (file in files) { + src.listFiles()?.forEach { file -> copy(file.absoluteFile, File(tar.absoluteFile, file.name)) } } @@ -399,18 +397,16 @@ object FileUtils { fun writeBytes(filepath: String, data: ByteArray): Boolean { val file = File(filepath) var fos: FileOutputStream? = null - try { + return try { if (!file.exists()) { - - file.parentFile.mkdirs() - + file.parentFile?.mkdirs() file.createNewFile() } fos = FileOutputStream(filepath) fos.write(data) - return true + true } catch (e: IOException) { - return false + false } finally { closeSilently(fos) } @@ -422,16 +418,16 @@ object FileUtils { fun appendText(path: String, content: String): Boolean { val file = File(path) var writer: FileWriter? = null - try { + return try { if (!file.exists()) { file.createNewFile() } writer = FileWriter(file, true) writer.write(content) - return true + true } catch (e: IOException) { - return false + false } finally { closeSilently(writer) } diff --git a/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt b/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt index b81167bd9..1250da682 100644 --- a/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/importbook/ImportBookViewModel.kt @@ -3,9 +3,8 @@ package io.legado.app.ui.importbook import android.app.Application import android.net.Uri import androidx.documentfile.provider.DocumentFile -import io.legado.app.App import io.legado.app.base.BaseViewModel -import io.legado.app.data.entities.Book +import io.legado.app.model.localBook.LocalBook class ImportBookViewModel(application: Application) : BaseViewModel(application) { @@ -14,18 +13,7 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application) execute { uriList.forEach { uriStr -> DocumentFile.fromSingleUri(context, Uri.parse(uriStr))?.let { doc -> - doc.name?.let { fileName -> - val str = fileName.substringBeforeLast(".") - var name = str.substringBefore("作者") - val author = str.substringAfter("作者") - val smhStart = name.indexOf("《") - val smhEnd = name.indexOf("》") - if (smhStart != -1 && smhEnd != -1) { - name = (name.substring(smhStart + 1, smhEnd)) - } - val book = Book(bookUrl = uriStr, name = name, author = author) - App.db.bookDao().insert(book) - } + LocalBook.importFile(doc) } } }.onFinally { 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 bdaf72031..9fda82311 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 @@ -14,7 +14,7 @@ import io.legado.app.App import io.legado.app.BuildConfig import io.legado.app.R import io.legado.app.base.VMBaseActivity -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.AppConfig import io.legado.app.help.coroutine.Coroutine @@ -77,8 +77,8 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } private fun upVersion() { - if (getPrefInt("versionCode") != App.INSTANCE.versionCode) { - putPrefInt("versionCode", App.INSTANCE.versionCode) + if (getPrefInt(PreferKey.versionCode) != App.INSTANCE.versionCode) { + putPrefInt(PreferKey.versionCode, App.INSTANCE.versionCode) if (!BuildConfig.DEBUG) { UpdateLog().show(supportFragmentManager, "updateLog") } @@ -86,6 +86,7 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } override fun onPageSelected(position: Int) { + view_pager_main.hideSoftInput() pagePosition = position when (position) { 0, 1, 3 -> bottom_navigation_view.menu.getItem(position).isChecked = true @@ -143,10 +144,10 @@ class MainActivity : VMBaseActivity(R.layout.activity_main), } override fun observeLiveBus() { - observeEvent(Bus.RECREATE) { + observeEvent(EventBus.RECREATE) { recreate() } - observeEvent(Bus.SHOW_RSS) { + observeEvent(EventBus.SHOW_RSS) { bottom_navigation_view.menu.findItem(R.id.menu_rss).isVisible = AppConfig.isShowRSS upFragmentList() view_pager_main.adapter?.notifyDataSetChanged() 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 960f860c1..6af83db54 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 @@ -3,7 +3,7 @@ package io.legado.app.ui.main import android.app.Application import io.legado.app.App import io.legado.app.base.BaseViewModel -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.data.entities.RssSource import io.legado.app.help.http.HttpHelper import io.legado.app.help.storage.Restore @@ -24,14 +24,14 @@ class MainViewModel(application: Application) : BaseViewModel(application) { App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> synchronized(this) { updateList.add(book.bookUrl) - postEvent(Bus.UP_BOOK, book.bookUrl) + postEvent(EventBus.UP_BOOK, book.bookUrl) } WebBook(bookSource).getChapterList(book) .timeout(300000) .onSuccess(IO) { synchronized(this) { updateList.remove(book.bookUrl) - postEvent(Bus.UP_BOOK, book.bookUrl) + postEvent(EventBus.UP_BOOK, book.bookUrl) } it?.let { App.db.bookDao().update(book) @@ -42,7 +42,7 @@ class MainViewModel(application: Application) : BaseViewModel(application) { .onError { synchronized(this) { updateList.remove(book.bookUrl) - postEvent(Bus.UP_BOOK, book.bookUrl) + postEvent(EventBus.UP_BOOK, book.bookUrl) } it.printStackTrace() } 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 0a8318618..c51bd46e2 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 @@ -1,5 +1,6 @@ package io.legado.app.ui.main.bookshelf +import android.annotation.SuppressLint import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -13,16 +14,20 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.VMBaseFragment import io.legado.app.constant.AppConst -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.BookGroup -import io.legado.app.lib.dialogs.selector +import io.legado.app.lib.dialogs.* import io.legado.app.lib.theme.ATH import io.legado.app.lib.theme.accentColor +import io.legado.app.lib.theme.view.ATEAutoCompleteTextView +import io.legado.app.ui.book.arrange.ArrangeBookActivity +import io.legado.app.ui.book.group.GroupManageDialog import io.legado.app.ui.book.search.SearchActivity import io.legado.app.ui.download.DownloadActivity import io.legado.app.ui.importbook.ImportBookActivity import io.legado.app.utils.* +import kotlinx.android.synthetic.main.dialog_edit_text.view.* import kotlinx.android.synthetic.main.fragment_bookshelf.* import kotlinx.android.synthetic.main.view_tab_layout.* import kotlinx.android.synthetic.main.view_title_bar.* @@ -60,10 +65,8 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b R.id.menu_group_manage -> GroupManageDialog() .show(childFragmentManager, "groupManageDialog") R.id.menu_add_local -> startActivity() - R.id.menu_add_url -> { - } - R.id.menu_arrange_bookshelf -> { - } + R.id.menu_add_url -> addBookByUrl() + R.id.menu_arrange_bookshelf -> startActivity() R.id.menu_download -> startActivity() } } @@ -85,7 +88,7 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b TabLayoutMediator(tab_layout, view_pager_bookshelf) { tab, position -> tab.text = bookGroups[position].groupName }.attach() - observeEvent(Bus.UP_TABS) { + observeEvent(EventBus.UP_TABS) { tab_layout.getTabAt(it)?.select() } } @@ -145,6 +148,25 @@ class BookshelfFragment : VMBaseFragment(R.layout.fragment_b } } + @SuppressLint("InflateParams") + private fun addBookByUrl() { + requireContext() + .alert(titleResource = R.string.add_book_url) { + var editText: ATEAutoCompleteTextView? = null + customView { + layoutInflater.inflate(R.layout.dialog_edit_text, null).apply { + editText = edit_view + } + } + okButton { + editText?.text?.toString()?.let { + viewModel.addBookByUrl(it) + } + } + noButton { } + }.show().applyTint() + } + override fun onTabReselected(tab: TabLayout.Tab?) { } 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 536329915..74c963763 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 @@ -2,8 +2,14 @@ package io.legado.app.ui.main.bookshelf import android.app.Application import io.legado.app.App +import io.legado.app.R import io.legado.app.base.BaseViewModel +import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookGroup +import io.legado.app.data.entities.BookSource +import io.legado.app.model.WebBook +import io.legado.app.utils.NetworkUtils +import kotlinx.coroutines.Dispatchers.IO class BookshelfViewModel(application: Application) : BaseViewModel(application) { @@ -31,4 +37,54 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application) } } + fun addBookByUrl(bookUrls: String) { + var successCount = 0 + execute { + var hasBookUrlPattern: List? = null + val urls = bookUrls.split("\n") + for (url in urls) { + val bookUrl = url.trim() + if (bookUrl.isEmpty()) continue + if (App.db.bookDao().getBook(bookUrl) != null) continue + val baseUrl = NetworkUtils.getBaseUrl(bookUrl) ?: continue + var source = App.db.bookSourceDao().getBookSource(baseUrl) + if (source == null) { + if (hasBookUrlPattern == null) { + hasBookUrlPattern = App.db.bookSourceDao().hasBookUrlPattern + } + hasBookUrlPattern.forEach { bookSource -> + if (bookUrl.matches(bookSource.bookUrlPattern!!.toRegex())) { + source = bookSource + return@forEach + } + } + } + source?.let { bookSource -> + val book = Book( + bookUrl = bookUrl, + origin = bookSource.bookSourceUrl, + originName = bookSource.bookSourceName + ) + WebBook(bookSource).getBookInfo(book, this) + .onSuccess(IO) { + it?.let { book -> + App.db.bookDao().insert(book) + successCount++ + } + }.onError { + throw Exception(it.localizedMessage) + } + } + } + }.onSuccess { + if (successCount > 0) { + toast(R.string.success) + } else { + toast("ERROR") + } + }.onError { + toast(it.localizedMessage ?: "ERROR") + } + } + } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapter.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BaseBooksAdapter.kt similarity index 90% rename from app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapter.kt rename to app/src/main/java/io/legado/app/ui/main/bookshelf/books/BaseBooksAdapter.kt index c8f085b74..9103d3c3c 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapter.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BaseBooksAdapter.kt @@ -4,7 +4,7 @@ import android.content.Context import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.data.entities.Book -abstract class BooksAdapter(context: Context, layoutId: Int) : +abstract class BaseBooksAdapter(context: Context, layoutId: Int) : SimpleRecyclerAdapter(context, layoutId) { fun notification(bookUrl: String) { 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 f6761ce78..92a47d790 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 @@ -5,7 +5,6 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.constant.BookType import io.legado.app.data.entities.Book -import io.legado.app.help.ImageLoader import io.legado.app.lib.theme.ATH import io.legado.app.utils.invisible import kotlinx.android.synthetic.main.item_bookshelf_grid.view.* @@ -13,7 +12,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick class BooksAdapterGrid(context: Context, private val callBack: CallBack) : - BooksAdapter(context, R.layout.item_bookshelf_grid) { + BaseBooksAdapter(context, R.layout.item_bookshelf_grid) { override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { @@ -21,13 +20,7 @@ class BooksAdapterGrid(context: Context, private val callBack: CallBack) : ATH.applyBackgroundTint(this) tv_name.text = item.name bv_author.text = item.author - item.getDisplayCover()?.let { - ImageLoader.load(context, it)//Glide自动识别http://和file:// - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) - .centerCrop() - .into(iv_cover) - } + iv_cover.load(item.getDisplayCover(), item.name, item.author) onClick { callBack.open(item) } onLongClick { callBack.openBookInfo(item) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt index 133f865d9..b35d6fd21 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksAdapterList.kt @@ -5,7 +5,6 @@ import io.legado.app.R import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.constant.BookType import io.legado.app.data.entities.Book -import io.legado.app.help.ImageLoader import io.legado.app.lib.theme.ATH import io.legado.app.utils.invisible import kotlinx.android.synthetic.main.item_bookshelf_list.view.* @@ -13,7 +12,7 @@ import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onLongClick class BooksAdapterList(context: Context, private val callBack: CallBack) : - BooksAdapter(context, R.layout.item_bookshelf_list) { + BaseBooksAdapter(context, R.layout.item_bookshelf_list) { override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList) { with(holder.itemView) { @@ -23,13 +22,7 @@ class BooksAdapterList(context: Context, private val callBack: CallBack) : tv_author.text = item.author tv_read.text = item.durChapterTitle tv_last.text = item.latestChapterTitle - item.getDisplayCover()?.let { - ImageLoader.load(context, it)//Glide自动识别http://和file:// - .placeholder(R.drawable.image_cover_default) - .error(R.drawable.image_cover_default) - .centerCrop() - .into(iv_cover) - } + iv_cover.load(item.getDisplayCover(), item.name, item.author) onClick { callBack.open(item) } onLongClick { callBack.openBookInfo(item) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt index 45ad277f2..fc4358f5e 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/books/BooksDiffCallBack.kt @@ -23,6 +23,7 @@ class BooksDiffCallBack(private val oldItems: List, private val newItems: val oldItem = oldItems[oldItemPosition] val newItem = newItems[newItemPosition] return oldItem.name == newItem.name + && oldItem.author == newItem.author && oldItem.durChapterTitle == newItem.durChapterTitle && oldItem.latestChapterTitle == newItem.latestChapterTitle && oldItem.getDisplayCover() == newItem.getDisplayCover() 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 0ee866fde..d7ffd7a34 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 @@ -11,7 +11,7 @@ import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment import io.legado.app.constant.BookType -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.data.entities.Book import io.legado.app.help.IntentDataHelp @@ -29,7 +29,7 @@ import org.jetbrains.anko.startActivity class BooksFragment : BaseFragment(R.layout.fragment_books), - BooksAdapter.CallBack { + BaseBooksAdapter.CallBack { companion object { fun newInstance(position: Int, groupId: Int): BooksFragment { @@ -43,7 +43,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } private lateinit var activityViewModel: MainViewModel - private lateinit var booksAdapter: BooksAdapter + private lateinit var booksAdapter: BaseBooksAdapter private var bookshelfLiveData: LiveData>? = null private var position = 0 private var groupId = -1 @@ -56,7 +56,7 @@ class BooksFragment : BaseFragment(R.layout.fragment_books), } initRecyclerView() upRecyclerData() - observeEvent(Bus.UP_BOOK) { + observeEvent(EventBus.UP_BOOK) { booksAdapter.notification(it) } } 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 e8b061144..035ca4d26 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 @@ -11,11 +11,10 @@ import android.view.View import androidx.documentfile.provider.DocumentFile import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreference import io.legado.app.App import io.legado.app.R import io.legado.app.base.BaseFragment -import io.legado.app.constant.Bus +import io.legado.app.constant.EventBus import io.legado.app.constant.PreferKey import io.legado.app.help.BookHelp import io.legado.app.help.permission.Permissions @@ -24,6 +23,7 @@ import io.legado.app.help.storage.Backup import io.legado.app.help.storage.Restore import io.legado.app.help.storage.WebDavHelp import io.legado.app.lib.theme.ATH +import io.legado.app.lib.theme.prefs.ATESwitchPreference import io.legado.app.service.WebService import io.legado.app.ui.about.AboutActivity import io.legado.app.ui.about.DonateActivity @@ -100,7 +100,9 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { fun restore() { launch { - if (!WebDavHelp.showRestoreDialog(requireContext())) { + if (!WebDavHelp.showRestoreDialog(requireContext()) { + toast(R.string.restore_success) + }) { val backupPath = getPrefString(PreferKey.backupPath) if (backupPath?.isNotEmpty() == true) { val uri = Uri.parse(backupPath) @@ -174,15 +176,14 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { if (WebService.isRun) { - putPrefBoolean("webService", true) + putPrefBoolean(PreferKey.webService, true) } else { - putPrefBoolean("webService", false) + putPrefBoolean(PreferKey.webService, false) } addPreferencesFromResource(R.xml.pref_main) - observeEvent(Bus.WEB_SERVICE_STOP) { - findPreference("webService")?.let { - it.isChecked = false - } + val webServicePre = findPreference(PreferKey.webService) + observeEvent(EventBus.WEB_SERVICE_STOP) { + webServicePre?.isChecked = false } } @@ -203,8 +204,8 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { when (key) { - "themeMode" -> App.INSTANCE.applyDayNight() - "webService" -> { + PreferKey.themeMode -> App.INSTANCE.applyDayNight() + PreferKey.webService -> { if (requireContext().getPrefBoolean("webService")) { WebService.start(requireContext()) toast("正在启动服务\n具体信息查看通知栏") @@ -213,8 +214,8 @@ class MyFragment : BaseFragment(R.layout.fragment_my_config) { toast("服务已停止") } } + PreferKey.downloadPath -> BookHelp.upDownloadPath() "recordLog" -> LogUtils.upLevel() - "downloadPath" -> BookHelp.upDownloadPath() } } diff --git a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt index 0ec49f315..b0ad73f2e 100644 --- a/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt +++ b/app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt @@ -13,6 +13,7 @@ import io.legado.app.data.entities.RssSource import io.legado.app.lib.theme.ATH import io.legado.app.ui.main.MainViewModel import io.legado.app.ui.rss.article.RssArticlesActivity +import io.legado.app.ui.rss.favorites.RssFavoritesActivity import io.legado.app.ui.rss.source.manage.RssSourceActivity import io.legado.app.utils.getViewModelOfActivity import io.legado.app.utils.startActivity @@ -38,8 +39,7 @@ class RssFragment : BaseFragment(R.layout.fragment_rss), super.onCompatOptionsItemSelected(item) when (item.itemId) { R.id.menu_rss_config -> startActivity() - R.id.menu_rss_star -> { - } + R.id.menu_rss_star -> startActivity() } } 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 8a93301b9..42bc5c2d4 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 @@ -6,6 +6,7 @@ import androidx.lifecycle.MutableLiveData import io.legado.app.App import io.legado.app.base.BaseViewModel import io.legado.app.data.entities.RssArticle +import io.legado.app.data.entities.RssReadRecord import io.legado.app.data.entities.RssSource import io.legado.app.model.Rss import kotlinx.coroutines.Dispatchers.IO @@ -101,8 +102,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application fun read(rssArticle: RssArticle) { execute { - rssArticle.read = true - App.db.rssArticleDao().update(rssArticle) + App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link)) } } diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt new file mode 100644 index 000000000..0188fe104 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt @@ -0,0 +1,58 @@ +package io.legado.app.ui.rss.favorites + +import android.os.Bundle +import androidx.core.content.ContextCompat +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import io.legado.app.App +import io.legado.app.R +import io.legado.app.base.BaseActivity +import io.legado.app.data.entities.RssStar +import io.legado.app.lib.theme.ATH +import io.legado.app.ui.rss.read.ReadRssActivity +import kotlinx.android.synthetic.main.view_refresh_recycler.* +import org.jetbrains.anko.startActivity + + +class RssFavoritesActivity : BaseActivity(R.layout.activity_rss_favorites), + RssFavoritesAdapter.CallBack { + + private var liveData: LiveData>? = null + private lateinit var adapter: RssFavoritesAdapter + + override fun onActivityCreated(savedInstanceState: Bundle?) { + initView() + initData() + } + + private fun initView() { + ATH.applyEdgeEffectColor(recycler_view) + recycler_view.layoutManager = LinearLayoutManager(this) + recycler_view.addItemDecoration( + DividerItemDecoration(this, DividerItemDecoration.VERTICAL).apply { + ContextCompat.getDrawable(baseContext, R.drawable.ic_divider)?.let { + this.setDrawable(it) + } + }) + adapter = RssFavoritesAdapter(this, this) + recycler_view.adapter = adapter + } + + private fun initData() { + liveData?.removeObservers(this) + liveData = App.db.rssStarDao().liveAll() + liveData?.observe(this, Observer { + adapter.setItems(it) + }) + } + + override fun readRss(rssStar: RssStar) { + startActivity( + Pair("title", rssStar.title), + Pair("origin", rssStar.origin), + Pair("link", rssStar.link) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesAdapter.kt b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesAdapter.kt new file mode 100644 index 000000000..2cb607df5 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesAdapter.kt @@ -0,0 +1,64 @@ +package io.legado.app.ui.rss.favorites + +import android.content.Context +import android.graphics.drawable.Drawable +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import io.legado.app.R +import io.legado.app.base.adapter.ItemViewHolder +import io.legado.app.base.adapter.SimpleRecyclerAdapter +import io.legado.app.data.entities.RssStar +import io.legado.app.help.ImageLoader +import io.legado.app.utils.gone +import io.legado.app.utils.visible +import kotlinx.android.synthetic.main.item_rss_article.view.* +import org.jetbrains.anko.sdk27.listeners.onClick + +class RssFavoritesAdapter(context: Context, val callBack: CallBack) : + SimpleRecyclerAdapter(context, R.layout.item_rss_article) { + + override fun convert(holder: ItemViewHolder, item: RssStar, payloads: MutableList) { + with(holder.itemView) { + tv_title.text = item.title + tv_pub_date.text = item.pubDate + onClick { + callBack.readRss(item) + } + if (item.image.isNullOrBlank()) { + image_view.gone() + } else { + ImageLoader.load(context, item.image) + .addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + image_view.gone() + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + image_view.visible() + return false + } + + }) + .into(image_view) + } + } + } + + interface CallBack { + fun readRss(rssStar: RssStar) + } +} \ No newline at end of file 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 894bed7a9..d5d9b9b66 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 @@ -40,15 +40,19 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r override fun onCompatCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.rss_read, menu) - starMenuItem = menu.findItem(R.id.menu_rss_star) - ttsMenuItem = menu.findItem(R.id.menu_aloud) - upStarMenu() return super.onCompatCreateOptionsMenu(menu) } + override fun onPrepareOptionsMenu(menu: Menu?): Boolean { + starMenuItem = menu?.findItem(R.id.menu_rss_star) + ttsMenuItem = menu?.findItem(R.id.menu_aloud) + upStarMenu() + return super.onPrepareOptionsMenu(menu) + } + override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - R.id.menu_rss_star -> viewModel.star() + R.id.menu_rss_star -> viewModel.favorite() R.id.menu_share_it -> viewModel.rssArticle?.let { shareText("链接分享", it.link) } @@ -73,9 +77,15 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r val url = NetworkUtils.getAbsoluteURL(it.origin, it.link) val html = viewModel.clHtml(content) if (viewModel.rssSource?.loadWithBaseUrl == true) { - webView.loadDataWithBaseURL(url, html, "text/html", "utf-8", url) + webView.loadDataWithBaseURL( + url, + html, + "text/html", + "utf-8", + url + )//不想用baseUrl进else } else { - webView.loadData(html, "text/html", "utf-8") + webView.loadData(html, "text/html;charset=utf-8", "utf-8")//经测试可以解决中文乱码 } } }) @@ -95,10 +105,10 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r override fun upStarMenu() { if (viewModel.star) { starMenuItem?.setIcon(R.drawable.ic_star) - starMenuItem?.setTitle(R.string.y_store_up) + starMenuItem?.setTitle(R.string.in_favorites) } else { starMenuItem?.setIcon(R.drawable.ic_star_border) - starMenuItem?.setTitle(R.string.w_store_up) + starMenuItem?.setTitle(R.string.out_favorites) } DrawableUtils.setTint(starMenuItem?.icon, primaryTextColor) } @@ -149,9 +159,13 @@ class ReadRssActivity : VMBaseActivity(R.layout.activity_rss_r webView.settings.javaScriptEnabled = true webView.evaluateJavascript("document.documentElement.outerHTML") { val html = StringEscapeUtils.unescapeJson(it) - viewModel.readAloud(Jsoup.clean(html, Whitelist.none())) + val text = Jsoup.clean(html, Whitelist.none()) + .replace(Regex("""&\w+;"""), "") + .trim()//朗读过程中总是听到一些杂音,清理一下 + //longToast(需读内容)调试一下 + viewModel.readAloud(text) } } } -} \ No newline at end of file +} 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 36a487a6c..0ace44358 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 @@ -70,7 +70,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } } - fun star() { + fun favorite() { execute { rssArticle?.let { if (star) { @@ -78,6 +78,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } else { App.db.rssStarDao().insert(it.toStar()) } + star = !star } }.onSuccess { callBack?.upStarMenu() @@ -85,12 +86,18 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), } fun clHtml(content: String): String { - return """ - $content""" + $content + """.trimIndent() + } } override fun onInit(status: Int) { diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt index 28bb1602a..9b80b1825 100644 --- a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt @@ -23,13 +23,11 @@ import java.util.* class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rect) : ValueAnimator() { - private val mPaint: Paint + private val mPaint: Paint = Paint() private val mParticles: Array - private val mBound: Rect + private val mBound: Rect = Rect(bound) init { - mPaint = Paint() - mBound = Rect(bound) val partLen = 15 mParticles = arrayOfNulls(partLen * partLen) val random = Random(System.currentTimeMillis()) @@ -97,7 +95,7 @@ class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rec override fun start() { super.start() - mContainer.invalidate(mBound) + mContainer.invalidate() } private inner class Particle { @@ -141,7 +139,7 @@ class ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rec internal var DEFAULT_DURATION: Long = 0x400 private val DEFAULT_INTERPOLATOR = AccelerateInterpolator(0.6f) - private val END_VALUE = 1.4f + private const val END_VALUE = 1.4f private val X = Utils.dp2Px(5).toFloat() private val Y = Utils.dp2Px(20).toFloat() private val V = Utils.dp2Px(2).toFloat() diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt index 1332a3969..9b014b7d2 100644 --- a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt @@ -1,191 +1,21 @@ -/* - * Copyright (C) 2015 tyrantgit - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package io.legado.app.ui.widget.anima.explosion_field -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.ValueAnimator import android.app.Activity -import android.content.Context -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.Rect -import android.media.MediaPlayer -import android.util.AttributeSet -import android.util.Log import android.view.View import android.view.ViewGroup import android.view.Window -import java.util.* +object ExplosionField { -class ExplosionField : View { - - private var customDuration = ExplosionAnimator.DEFAULT_DURATION - private var idPlayAnimationEffect = 0 - private var mZAnimatorListener: OnAnimatorListener? = null - private var mOnClickListener: View.OnClickListener? = null - - private val mExplosions = ArrayList() - private val mExpandInset = IntArray(2) - - constructor(context: Context) : super(context) { - init() - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - init() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - init() - } - - private fun init() { - - Arrays.fill(mExpandInset, Utils.dp2Px(32)) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - for (explosion in mExplosions) { - explosion.draw(canvas) - } - } - - fun playSoundAnimationEffect(id: Int) { - this.idPlayAnimationEffect = id - } - - fun setCustomDuration(customDuration: Long) { - this.customDuration = customDuration - } - - fun addActionEvent(ievents: OnAnimatorListener) { - this.mZAnimatorListener = ievents - } - - - fun expandExplosionBound(dx: Int, dy: Int) { - mExpandInset[0] = dx - mExpandInset[1] = dy - } - - @JvmOverloads - fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) { - val currentDuration = customDuration - val explosion = ExplosionAnimator(this, bitmap!!, bound) - explosion.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - mExplosions.remove(animation) - if (view != null) { - view.scaleX = 1f - view.scaleY = 1f - view.alpha = 1f - view.setOnClickListener(mOnClickListener)//set event - - } - } - }) - explosion.startDelay = startDelay - explosion.duration = currentDuration - mExplosions.add(explosion) - explosion.start() - } - - @JvmOverloads - fun explode(view: View, restartState: Boolean? = false) { - - val r = Rect() - view.getGlobalVisibleRect(r) - val location = IntArray(2) - getLocationOnScreen(location) - // getLocationInWindow(location); - // view.getLocationInWindow(location); - r.offset(-location[0], -location[1]) - r.inset(-mExpandInset[0], -mExpandInset[1]) - val startDelay = 100 - val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150) - animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { - - internal var random = Random() - - override fun onAnimationUpdate(animation: ValueAnimator) { - view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f - view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f - } - }) - - animator.addListener(object : Animator.AnimatorListener { - override fun onAnimationStart(animator: Animator) { - if (idPlayAnimationEffect != 0) - MediaPlayer.create(context, idPlayAnimationEffect).start() - } - - override fun onAnimationEnd(animator: Animator) { - if (mZAnimatorListener != null) { - mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionField) - } - } - - override fun onAnimationCancel(animator: Animator) { - Log.i("PRUEBA", "CANCEL") - } - - override fun onAnimationRepeat(animator: Animator) { - Log.i("PRUEBA", "REPEAT") - } - }) - - animator.start() - view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f) - .alpha(0f).start() - if (restartState!!) - explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view) - else - explode(Utils.createBitmapFromView(view), r, startDelay.toLong()) - - } - - fun clear() { - mExplosions.clear() - invalidate() - } - - override fun setOnClickListener(mOnClickListener: View.OnClickListener?) { - this.mOnClickListener = mOnClickListener - } - - companion object { - - fun attach2Window(activity: Activity): ExplosionField { - val rootView = activity.findViewById(Window.ID_ANDROID_CONTENT) as ViewGroup - val explosionField = ExplosionField(activity) - rootView.addView( - explosionField, ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT - ) + fun attach2Window(activity: Activity): ExplosionView { + val rootView = activity.findViewById(Window.ID_ANDROID_CONTENT) as ViewGroup + val explosionField = ExplosionView(activity) + rootView.addView( + explosionField, ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) - return explosionField - } + ) + return explosionField } - -} +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt new file mode 100644 index 000000000..c79695a51 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2015 tyrantgit + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.legado.app.ui.widget.anima.explosion_field + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Rect +import android.media.MediaPlayer +import android.util.AttributeSet +import android.util.Log +import android.view.View +import java.util.* + + +class ExplosionView : View { + + private var customDuration = ExplosionAnimator.DEFAULT_DURATION + private var idPlayAnimationEffect = 0 + private var mZAnimatorListener: OnAnimatorListener? = null + private var mOnClickListener: OnClickListener? = null + + private val mExplosions = ArrayList() + private val mExpandInset = IntArray(2) + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init() + } + + private fun init() { + + Arrays.fill(mExpandInset, Utils.dp2Px(32)) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + for (explosion in mExplosions) { + explosion.draw(canvas) + } + } + + fun playSoundAnimationEffect(id: Int) { + this.idPlayAnimationEffect = id + } + + fun setCustomDuration(customDuration: Long) { + this.customDuration = customDuration + } + + fun addActionEvent(ievents: OnAnimatorListener) { + this.mZAnimatorListener = ievents + } + + + fun expandExplosionBound(dx: Int, dy: Int) { + mExpandInset[0] = dx + mExpandInset[1] = dy + } + + @JvmOverloads + fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) { + val currentDuration = customDuration + val explosion = ExplosionAnimator(this, bitmap!!, bound) + explosion.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + mExplosions.remove(animation) + if (view != null) { + view.scaleX = 1f + view.scaleY = 1f + view.alpha = 1f + view.setOnClickListener(mOnClickListener)//set event + + } + } + }) + explosion.startDelay = startDelay + explosion.duration = currentDuration + mExplosions.add(explosion) + explosion.start() + } + + @JvmOverloads + fun explode(view: View, restartState: Boolean? = false) { + + val r = Rect() + view.getGlobalVisibleRect(r) + val location = IntArray(2) + getLocationOnScreen(location) + // getLocationInWindow(location); + // view.getLocationInWindow(location); + r.offset(-location[0], -location[1]) + r.inset(-mExpandInset[0], -mExpandInset[1]) + val startDelay = 100 + val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150) + animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener { + + var random = Random() + + override fun onAnimationUpdate(animation: ValueAnimator) { + view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f + view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f + } + }) + + animator.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animator: Animator) { + if (idPlayAnimationEffect != 0) + MediaPlayer.create(context, idPlayAnimationEffect).start() + } + + override fun onAnimationEnd(animator: Animator) { + if (mZAnimatorListener != null) { + mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionView) + } + } + + override fun onAnimationCancel(animator: Animator) { + Log.i("PRUEBA", "CANCEL") + } + + override fun onAnimationRepeat(animator: Animator) { + Log.i("PRUEBA", "REPEAT") + } + }) + + animator.start() + view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f) + .alpha(0f).start() + if (restartState!!) + explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view) + else + explode(Utils.createBitmapFromView(view), r, startDelay.toLong()) + + } + + fun clear() { + mExplosions.clear() + invalidate() + } + + override fun setOnClickListener(mOnClickListener: OnClickListener?) { + this.mOnClickListener = mOnClickListener + } + +} diff --git a/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt b/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt index 289790a1a..f20ace6ce 100644 --- a/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt +++ b/app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt @@ -2,31 +2,69 @@ package io.legado.app.ui.widget.image import android.annotation.SuppressLint import android.content.Context -import android.graphics.Canvas -import android.graphics.Path +import android.graphics.* +import android.graphics.drawable.Drawable +import android.text.TextPaint import android.util.AttributeSet +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target +import io.legado.app.R +import io.legado.app.help.ImageLoader class CoverImageView : androidx.appcompat.widget.AppCompatImageView { internal var width: Float = 0.toFloat() internal var height: Float = 0.toFloat() + private var nameHeight = 0f + private var authorHeight = 0f + private val namePaint = TextPaint() + private val authorPaint = TextPaint() + private var name: String? = null + private var author: String? = null + private var loadFailed = false constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + 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) - width = getWidth().toFloat() - height = getHeight().toFloat() + init { + namePaint.typeface = Typeface.DEFAULT_BOLD + namePaint.isAntiAlias = true + namePaint.textAlign = Paint.Align.CENTER + namePaint.textSkewX = -0.2f + authorPaint.typeface = Typeface.DEFAULT + authorPaint.isAntiAlias = true + authorPaint.textAlign = Paint.Align.CENTER + authorPaint.textSkewX = -0.1f } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { val measuredWidth = MeasureSpec.getSize(widthMeasureSpec) val measuredHeight = measuredWidth * 7 / 5 - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)) + super.onMeasure( + widthMeasureSpec, + MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY) + ) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + width = getWidth().toFloat() + height = getHeight().toFloat() + namePaint.textSize = width / 6 + namePaint.strokeWidth = namePaint.textSize / 10 + authorPaint.textSize = width / 9 + authorPaint.strokeWidth = authorPaint.textSize / 10 + nameHeight = height / 2 + authorHeight = nameHeight + authorPaint.fontSpacing } override fun onDraw(canvas: Canvas) { @@ -47,10 +85,74 @@ class CoverImageView : androidx.appcompat.widget.AppCompatImageView { canvas.clipPath(path) } super.onDraw(canvas) + if (!loadFailed) return + name?.let { + namePaint.color = Color.WHITE + namePaint.style = Paint.Style.STROKE + canvas.drawText(it, width / 2, nameHeight, namePaint) + namePaint.color = Color.RED + namePaint.style = Paint.Style.FILL + canvas.drawText(it, width / 2, nameHeight, namePaint) + } + author?.let { + authorPaint.color = Color.WHITE + authorPaint.style = Paint.Style.STROKE + canvas.drawText(it, width / 2, authorHeight, authorPaint) + authorPaint.color = Color.RED + authorPaint.style = Paint.Style.FILL + canvas.drawText(it, width / 2, authorHeight, authorPaint) + } } fun setHeight(height: Int) { val width = height * 5 / 7 minimumWidth = width } + + private fun setText(name: String?, author: String?) { + this.name = + when { + name == null -> null + name.length > 5 -> name.substring(0, 4) + "…" + else -> name + } + this.author = + when { + author == null -> null + author.length > 8 -> author.substring(0, 7) + "…" + else -> author + } + } + + fun load(path: String?, name: String?, author: String?) { + setText(name, author) + ImageLoader.load(context, path)//Glide自动识别http://和file:// + .placeholder(R.drawable.image_cover_default) + .error(R.drawable.image_cover_default) + .listener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target?, + isFirstResource: Boolean + ): Boolean { + loadFailed = true + return false + } + + override fun onResourceReady( + resource: Drawable?, + model: Any?, + target: Target?, + dataSource: DataSource?, + isFirstResource: Boolean + ): Boolean { + loadFailed = false + return false + } + + }) + .centerCrop() + .into(this) + } } diff --git a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt index 25af39c6a..20f638d61 100644 --- a/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt +++ b/app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt @@ -482,18 +482,17 @@ class FastScroller : LinearLayout { var showTrack = true if (attrs != null) { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, 0, 0) - if (typedArray != null) { - try { - bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor) - handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor) - trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor) - textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor) - fadeScrollbar = typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar) - showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble) - showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack) - } finally { - typedArray.recycle() - } + try { + bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor) + handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor) + trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor) + textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor) + fadeScrollbar = + typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar) + showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble) + showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack) + } finally { + typedArray.recycle() } } setTrackColor(trackColor) diff --git a/app/src/main/java/io/legado/app/utils/EncodingDetect.java b/app/src/main/java/io/legado/app/utils/EncodingDetect.java index 9f1aad37b..08b43b228 100644 --- a/app/src/main/java/io/legado/app/utils/EncodingDetect.java +++ b/app/src/main/java/io/legado/app/utils/EncodingDetect.java @@ -62,10 +62,10 @@ public class EncodingDetect { } } catch (Exception ignored) { } - return getJavaEncode(bytes); + return getEncode(bytes); } - public static String getJavaEncode(@NonNull byte[] bytes) { + public static String getEncode(@NonNull byte[] bytes) { int len = bytes.length > 2000 ? 2000 : bytes.length; byte[] cBytes = new byte[len]; System.arraycopy(bytes, 0, cBytes, 0, len); @@ -83,7 +83,7 @@ public class EncodingDetect { /** * 得到文件的编码 */ - public static String getJavaEncode(@NonNull String filePath) { + public static String getEncode(@NonNull String filePath) { BytesEncodingDetect s = new BytesEncodingDetect(); String fileCode = BytesEncodingDetect.javaname[s .detectEncoding(new File(filePath))]; @@ -102,7 +102,7 @@ public class EncodingDetect { /** * 得到文件的编码 */ - public static String getJavaEncode(@NonNull File file) { + public static String getEncode(@NonNull File file) { BytesEncodingDetect s = new BytesEncodingDetect(); String fileCode = BytesEncodingDetect.javaname[s.detectEncoding(file)]; // UTF-16LE 特殊处理 diff --git a/app/src/main/java/io/legado/app/utils/Snackbars.kt b/app/src/main/java/io/legado/app/utils/Snackbars.kt index f60def01f..2706bd7ca 100644 --- a/app/src/main/java/io/legado/app/utils/Snackbars.kt +++ b/app/src/main/java/io/legado/app/utils/Snackbars.kt @@ -4,139 +4,13 @@ import android.view.View import androidx.annotation.StringRes import com.google.android.material.snackbar.Snackbar -/** - * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.snackbar(Int)' instead.", ReplaceWith("view.snackbar(message)")) -inline fun snackbar(view: View, message: Int) = Snackbar - .make(view, message, Snackbar.LENGTH_SHORT) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_LONG] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.longSnackbar(Int)' instead.", ReplaceWith("view.longSnackbar(message)")) -inline fun longSnackbar(view: View, message: Int) = Snackbar - .make(view, message, Snackbar.LENGTH_LONG) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.indefiniteSnackbar(Int)' instead.", ReplaceWith("view.indefiniteSnackbar(message)")) -inline fun indefiniteSnackbar(view: View, message: Int) = Snackbar - .make(view, message, Snackbar.LENGTH_INDEFINITE) - .apply { show() } - -/** - * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.snackbar(CharSequence)' instead.", ReplaceWith("view.snackbar(message)")) -inline fun snackbar(view: View, message: CharSequence) = Snackbar - .make(view, message, Snackbar.LENGTH_SHORT) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_LONG] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.longSnackbar(CharSequence)' instead.", ReplaceWith("view.longSnackbar(message)")) -inline fun longSnackbar(view: View, message: CharSequence) = Snackbar - .make(view, message, Snackbar.LENGTH_LONG) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.indefiniteSnackbar(CharSequence)' instead.", ReplaceWith("view.indefiniteSnackbar(message)")) -inline fun indefiniteSnackbar(view: View, message: CharSequence) = Snackbar - .make(view, message, Snackbar.LENGTH_INDEFINITE) - .apply { show() } - -/** - * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.snackbar(Int, Int, (View) -> Unit)' instead.", ReplaceWith("view.snackbar(message, actionText, action)")) -inline fun snackbar(view: View, message: Int, actionText: Int, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_SHORT) - .setAction(actionText, action) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_LONG] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.longSnackbar(Int, Int, (View) -> Unit)' instead.", ReplaceWith("view.longSnackbar(message, actionText, action)")) -inline fun longSnackbar(view: View, message: Int, actionText: Int, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_LONG) - .setAction(actionText, action) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration. - * - * @param message the message text resource. - */ -@Deprecated("Use 'View.indefiniteSnackbar(Int, Int, (View) -> Unit)' instead.", ReplaceWith("view.indefiniteSnackbar(message, actionText, action)")) -inline fun indefiniteSnackbar(view: View, message: Int, actionText: Int, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_INDEFINITE) - .setAction(actionText, action) - .apply { show() } - -/** - * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.snackbar(CharSequence, CharSequence, (View) -> Unit)' instead.", ReplaceWith("view.snackbar(message, actionText, action)")) -inline fun snackbar(view: View, message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_SHORT) - .setAction(actionText, action) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_LONG] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.longSnackbar(CharSequence, CharSequence, (View) -> Unit)' instead.", ReplaceWith("view.longSnackbar(message, actionText, action)")) -inline fun longSnackbar(view: View, message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_LONG) - .setAction(actionText, action) - .apply { show() } - -/** - * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration. - * - * @param message the message text. - */ -@Deprecated("Use 'View.indefiniteSnackbar(CharSequence, CharSequence, (View) -> Unit)' instead.", ReplaceWith("view.indefiniteSnackbar(message, actionText, action)")) -inline fun indefiniteSnackbar(view: View, message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar - .make(view, message, Snackbar.LENGTH_INDEFINITE) - .setAction(actionText, action) - .apply { show() } - /** * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration. * * @param message the message text resource. */ @JvmName("snackbar2") -inline fun View.snackbar(@StringRes message: Int) = Snackbar +fun View.snackbar(@StringRes message: Int) = Snackbar .make(this, message, Snackbar.LENGTH_SHORT) .apply { show() } @@ -146,7 +20,7 @@ inline fun View.snackbar(@StringRes message: Int) = Snackbar * @param message the message text resource. */ @JvmName("longSnackbar2") -inline fun View.longSnackbar(@StringRes message: Int) = Snackbar +fun View.longSnackbar(@StringRes message: Int) = Snackbar .make(this, message, Snackbar.LENGTH_LONG) .apply { show() } @@ -156,7 +30,7 @@ inline fun View.longSnackbar(@StringRes message: Int) = Snackbar * @param message the message text resource. */ @JvmName("indefiniteSnackbar2") -inline fun View.indefiniteSnackbar(@StringRes message: Int) = Snackbar +fun View.indefiniteSnackbar(@StringRes message: Int) = Snackbar .make(this, message, Snackbar.LENGTH_INDEFINITE) .apply { show() } @@ -166,7 +40,7 @@ inline fun View.indefiniteSnackbar(@StringRes message: Int) = Snackbar * @param message the message text. */ @JvmName("snackbar2") -inline fun View.snackbar(message: CharSequence) = Snackbar +fun View.snackbar(message: CharSequence) = Snackbar .make(this, message, Snackbar.LENGTH_SHORT) .apply { show() } @@ -176,7 +50,7 @@ inline fun View.snackbar(message: CharSequence) = Snackbar * @param message the message text. */ @JvmName("longSnackbar2") -inline fun View.longSnackbar(message: CharSequence) = Snackbar +fun View.longSnackbar(message: CharSequence) = Snackbar .make(this, message, Snackbar.LENGTH_LONG) .apply { show() } @@ -186,7 +60,7 @@ inline fun View.longSnackbar(message: CharSequence) = Snackbar * @param message the message text. */ @JvmName("indefiniteSnackbar2") -inline fun View.indefiniteSnackbar(message: CharSequence) = Snackbar +fun View.indefiniteSnackbar(message: CharSequence) = Snackbar .make(this, message, Snackbar.LENGTH_INDEFINITE) .apply { show() } @@ -196,7 +70,7 @@ inline fun View.indefiniteSnackbar(message: CharSequence) = Snackbar * @param message the message text resource. */ @JvmName("snackbar2") -inline fun View.snackbar(message: Int, @StringRes actionText: Int, noinline action: (View) -> Unit) = Snackbar +fun View.snackbar(message: Int, @StringRes actionText: Int, action: (View) -> Unit) = Snackbar .make(this, message, Snackbar.LENGTH_SHORT) .setAction(actionText, action) .apply { show() } @@ -207,7 +81,8 @@ inline fun View.snackbar(message: Int, @StringRes actionText: Int, noinline acti * @param message the message text resource. */ @JvmName("longSnackbar2") -inline fun View.longSnackbar(@StringRes message: Int, @StringRes actionText: Int, noinline action: (View) -> Unit) = Snackbar +fun View.longSnackbar(@StringRes message: Int, @StringRes actionText: Int, action: (View) -> Unit) = + Snackbar .make(this, message, Snackbar.LENGTH_LONG) .setAction(actionText, action) .apply { show() } @@ -218,7 +93,8 @@ inline fun View.longSnackbar(@StringRes message: Int, @StringRes actionText: Int * @param message the message text resource. */ @JvmName("indefiniteSnackbar2") -inline fun View.indefiniteSnackbar(@StringRes message: Int, @StringRes actionText: Int, noinline action: (View) -> Unit) = Snackbar +fun View.indefiniteSnackbar(@StringRes message: Int, @StringRes actionText: Int, action: (View) -> Unit) = + Snackbar .make(this, message, Snackbar.LENGTH_INDEFINITE) .setAction(actionText, action) .apply { show() } @@ -229,7 +105,8 @@ inline fun View.indefiniteSnackbar(@StringRes message: Int, @StringRes actionTex * @param message the message text. */ @JvmName("snackbar2") -inline fun View.snackbar(message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar +fun View.snackbar(message: CharSequence, actionText: CharSequence, action: (View) -> Unit) = + Snackbar .make(this, message, Snackbar.LENGTH_SHORT) .setAction(actionText, action) .apply { show() } @@ -240,7 +117,8 @@ inline fun View.snackbar(message: CharSequence, actionText: CharSequence, noinli * @param message the message text. */ @JvmName("longSnackbar2") -inline fun View.longSnackbar(message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar +fun View.longSnackbar(message: CharSequence, actionText: CharSequence, action: (View) -> Unit) = + Snackbar .make(this, message, Snackbar.LENGTH_LONG) .setAction(actionText, action) .apply { show() } @@ -251,7 +129,11 @@ inline fun View.longSnackbar(message: CharSequence, actionText: CharSequence, no * @param message the message text. */ @JvmName("indefiniteSnackbar2") -inline fun View.indefiniteSnackbar(message: CharSequence, actionText: CharSequence, noinline action: (View) -> Unit) = Snackbar +fun View.indefiniteSnackbar( + message: CharSequence, + actionText: CharSequence, + action: (View) -> Unit +) = Snackbar .make(this, message, Snackbar.LENGTH_INDEFINITE) .setAction(actionText, action) .apply { show() } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/utils/StringExtensions.kt b/app/src/main/java/io/legado/app/utils/StringExtensions.kt index 19319d3ba..02faf53c3 100644 --- a/app/src/main/java/io/legado/app/utils/StringExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/StringExtensions.kt @@ -4,6 +4,8 @@ package io.legado.app.utils fun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim() +fun String?.isContentPath(): Boolean = this?.startsWith("content://") == true + fun String?.isAbsUrl() = this?.let { it.startsWith("http://", true) @@ -48,7 +50,4 @@ fun String.splitNotBlank(regex: Regex, limit: Int = 0): Array = run { this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray() } -fun String.startWithIgnoreCase(start: String): Boolean { - return if (this.isBlank()) false else startsWith(start, true) -} diff --git a/app/src/main/java/io/legado/app/utils/Toasts.kt b/app/src/main/java/io/legado/app/utils/Toasts.kt index 16c032fa0..4348b4daf 100644 --- a/app/src/main/java/io/legado/app/utils/Toasts.kt +++ b/app/src/main/java/io/legado/app/utils/Toasts.kt @@ -11,25 +11,25 @@ import org.jetbrains.anko.toast * * @param message the message text resource. */ -inline fun Fragment.toast(message: Int) = requireActivity().toast(message) +fun Fragment.toast(message: Int) = requireActivity().toast(message) /** * Display the simple Toast message with the [Toast.LENGTH_SHORT] duration. * * @param message the message text. */ -inline fun Fragment.toast(message: CharSequence) = requireActivity().toast(message) +fun Fragment.toast(message: CharSequence) = requireActivity().toast(message) /** * Display the simple Toast message with the [Toast.LENGTH_LONG] duration. * * @param message the message text resource. */ -inline fun Fragment.longToast(message: Int) = requireActivity().longToast(message) +fun Fragment.longToast(message: Int) = requireActivity().longToast(message) /** * Display the simple Toast message with the [Toast.LENGTH_LONG] duration. * * @param message the message text. */ -inline fun Fragment.longToast(message: CharSequence) = requireActivity().longToast(message) +fun Fragment.longToast(message: CharSequence) = requireActivity().longToast(message) diff --git a/app/src/main/java/io/legado/app/utils/UriExtensions.kt b/app/src/main/java/io/legado/app/utils/UriExtensions.kt index 49b76b439..4062c74df 100644 --- a/app/src/main/java/io/legado/app/utils/UriExtensions.kt +++ b/app/src/main/java/io/legado/app/utils/UriExtensions.kt @@ -5,10 +5,6 @@ import android.net.Uri import androidx.documentfile.provider.DocumentFile import java.io.File -fun Uri.isDocumentUri(context: Context): Boolean { - return DocumentFile.isDocumentUri(context, this) -} - @Throws(Exception::class) fun Uri.readBytes(context: Context): ByteArray? { if (DocumentFile.isDocumentUri(context, this)) { diff --git a/app/src/main/java/io/legado/app/web/ReadMe.md b/app/src/main/java/io/legado/app/web/ReadMe.md index cd5544c3e..aee2e2dc6 100644 --- a/app/src/main/java/io/legado/app/web/ReadMe.md +++ b/app/src/main/java/io/legado/app/web/ReadMe.md @@ -1 +1,5 @@ -# web服务 \ No newline at end of file +# web服务 + +* controller 数据操作 +* HttpServer http服务 +* WebSocketServer 持续通讯服务 \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt b/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt index 87bcb3e1c..961407998 100644 --- a/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt +++ b/app/src/main/java/io/legado/app/web/controller/BookshelfController.kt @@ -13,7 +13,7 @@ class BookshelfController { val bookshelf: ReturnData get() { - val books = App.db.bookDao().allBooks + val books = App.db.bookDao().all val returnData = ReturnData() return if (books.isEmpty()) { returnData.setErrorMsg("还没有添加小说") diff --git a/app/src/main/res/anim/moprogress_bottom_in.xml b/app/src/main/res/anim/moprogress_bottom_in.xml deleted file mode 100644 index 59904f594..000000000 --- a/app/src/main/res/anim/moprogress_bottom_in.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_bottom_out.xml b/app/src/main/res/anim/moprogress_bottom_out.xml deleted file mode 100644 index 2348594fc..000000000 --- a/app/src/main/res/anim/moprogress_bottom_out.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_in.xml b/app/src/main/res/anim/moprogress_in.xml deleted file mode 100644 index 8e922153b..000000000 --- a/app/src/main/res/anim/moprogress_in.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_in_bottom_right.xml b/app/src/main/res/anim/moprogress_in_bottom_right.xml deleted file mode 100644 index 93348f5a8..000000000 --- a/app/src/main/res/anim/moprogress_in_bottom_right.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_in_top_right.xml b/app/src/main/res/anim/moprogress_in_top_right.xml deleted file mode 100644 index 81bb2e919..000000000 --- a/app/src/main/res/anim/moprogress_in_top_right.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_out.xml b/app/src/main/res/anim/moprogress_out.xml deleted file mode 100644 index 546619cc2..000000000 --- a/app/src/main/res/anim/moprogress_out.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_out_bottom_right.xml b/app/src/main/res/anim/moprogress_out_bottom_right.xml deleted file mode 100644 index d0d1eaffa..000000000 --- a/app/src/main/res/anim/moprogress_out_bottom_right.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/anim/moprogress_out_top_right.xml b/app/src/main/res/anim/moprogress_out_top_right.xml deleted file mode 100644 index 310b29d7e..000000000 --- a/app/src/main/res/anim/moprogress_out_top_right.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/image_cover_default.jpg b/app/src/main/res/drawable/image_cover_default.jpg index 6504a4e7e..0b502033c 100644 Binary files a/app/src/main/res/drawable/image_cover_default.jpg and b/app/src/main/res/drawable/image_cover_default.jpg differ diff --git a/app/src/main/res/layout/activity_arrange_book.xml b/app/src/main/res/layout/activity_arrange_book.xml new file mode 100644 index 000000000..795e1bfa4 --- /dev/null +++ b/app/src/main/res/layout/activity_arrange_book.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_favorites.xml b/app/src/main/res/layout/activity_rss_favorites.xml new file mode 100644 index 000000000..7740c1981 --- /dev/null +++ b/app/src/main/res/layout/activity_rss_favorites.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_page_key.xml b/app/src/main/res/layout/dialog_page_key.xml index 9ee5fd289..9ec5d3d06 100644 --- a/app/src/main/res/layout/dialog_page_key.xml +++ b/app/src/main/res/layout/dialog_page_key.xml @@ -3,6 +3,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/background" android:padding="16dp"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_cover.xml b/app/src/main/res/layout/item_cover.xml index 018b5f2f8..9956e4bd1 100644 --- a/app/src/main/res/layout/item_cover.xml +++ b/app/src/main/res/layout/item_cover.xml @@ -1,13 +1,12 @@ - + android:orientation="vertical"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/menu/arrange_book.xml b/app/src/main/res/menu/arrange_book.xml new file mode 100644 index 000000000..0b47d59ef --- /dev/null +++ b/app/src/main/res/menu/arrange_book.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/main_rss.xml b/app/src/main/res/menu/main_rss.xml index 4403621bd..97343e877 100644 --- a/app/src/main/res/menu/main_rss.xml +++ b/app/src/main/res/menu/main_rss.xml @@ -5,7 +5,7 @@ diff --git a/app/src/main/res/menu/read_book.xml b/app/src/main/res/menu/read_book.xml index 2994a6dbe..b27cd41ff 100644 --- a/app/src/main/res/menu/read_book.xml +++ b/app/src/main/res/menu/read_book.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" tools:context=".ui.main.MainActivity"> - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 69ea3b49e..fc4d73a1a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -33,9 +33,10 @@ 启用 替换净化-搜索 书架 - 收藏 - 已收藏 - 未收藏 + 收藏夹 + 收藏 + 已收藏 + 未收藏 订阅 全部 最近阅读 @@ -251,6 +252,7 @@ 文字颜色和背景(长按自定义) 沉浸式状态栏 还剩%d章未下载 + 没有选择 长按输入颜色值 加载中… 追更区 @@ -329,7 +331,7 @@ 常见问题 显示所有发现 关闭则只显示勾选源的发现 - 更新目录 + 更新目录 txt目录正则 设置编码 倒序-顺序 @@ -455,6 +457,8 @@ 网络连接不可用 + 确认 + 是否确认删除? 是否删除全部书籍? 是否同时删除已下载的书籍目录? 扫描二维码需相机权限 @@ -539,6 +543,7 @@ 分组管理 分组选择 编辑分组 + 移入分组 添加分组 新建替换 分组 @@ -587,5 +592,7 @@ 自动切换夜间模式 夜间模式跟随系统 上级 + 在线朗读音色 + (%d/%d) diff --git a/app/src/main/res/xml/pref_config_aloud.xml b/app/src/main/res/xml/pref_config_aloud.xml index d142c526c..d78e3c687 100644 --- a/app/src/main/res/xml/pref_config_aloud.xml +++ b/app/src/main/res/xml/pref_config_aloud.xml @@ -16,7 +16,7 @@