# Conflicts:
#	app/src/main/res/values/colors.xml
pull/169/head
yangyxd 5 years ago
commit 78573d8e77
  1. 4
      app/build.gradle
  2. 14
      app/src/main/AndroidManifest.xml
  3. 24
      app/src/main/assets/updateLog.md
  4. 5
      app/src/main/java/io/legado/app/base/adapter/CommonRecyclerAdapter.kt
  5. 14
      app/src/main/java/io/legado/app/data/dao/BookDao.kt
  6. 14
      app/src/main/java/io/legado/app/data/entities/RssStar.kt
  7. 7
      app/src/main/java/io/legado/app/help/ItemTouchCallback.kt
  8. 45
      app/src/main/java/io/legado/app/help/storage/Backup.kt
  9. 6
      app/src/main/java/io/legado/app/model/Debug.kt
  10. 12
      app/src/main/java/io/legado/app/model/Rss.kt
  11. 6
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  12. 8
      app/src/main/java/io/legado/app/service/BaseReadAloudService.kt
  13. 2
      app/src/main/java/io/legado/app/ui/README.md
  14. 5
      app/src/main/java/io/legado/app/ui/audio/AudioPlayActivity.kt
  15. 1
      app/src/main/java/io/legado/app/ui/audio/AudioPlayViewModel.kt
  16. 18
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookActivity.kt
  17. 35
      app/src/main/java/io/legado/app/ui/book/arrange/ArrangeBookAdapter.kt
  18. 13
      app/src/main/java/io/legado/app/ui/book/download/DownloadActivity.kt
  19. 2
      app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt
  20. 2
      app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt
  21. 7
      app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt
  22. 6
      app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt
  23. 8
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  24. 3
      app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt
  25. 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  26. 4
      app/src/main/java/io/legado/app/ui/book/read/config/TocRegexDialog.kt
  27. 41
      app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt
  28. 6
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt
  29. 2
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt
  30. 5
      app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt
  31. 12
      app/src/main/java/io/legado/app/ui/config/BackupRestoreUi.kt
  32. 39
      app/src/main/java/io/legado/app/ui/config/FileAssociationActivity.kt
  33. 69
      app/src/main/java/io/legado/app/ui/config/FileAssociationViewModel.kt
  34. 1
      app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt
  35. 7
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleActivity.kt
  36. 2
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleAdapter.kt
  37. 22
      app/src/main/java/io/legado/app/ui/replacerule/ReplaceRuleViewModel.kt
  38. 2
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  39. 31
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  40. 7
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt
  41. 2
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt
  42. 5
      app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt
  43. 6
      app/src/main/java/io/legado/app/ui/widget/TitleBar.kt
  44. 8
      app/src/main/java/io/legado/app/utils/EncodingDetect.java
  45. 4
      app/src/main/res/drawable/ic_help.xml
  46. 26
      app/src/main/res/layout-land/activity_book_info.xml
  47. 11
      app/src/main/res/layout/activity_audio_play.xml
  48. 7
      app/src/main/res/layout/activity_book_info.xml
  49. 8
      app/src/main/res/layout/activity_file_association.xml
  50. 2
      app/src/main/res/menu/change_cover.xml
  51. 2
      app/src/main/res/menu/change_source.xml

@ -136,12 +136,12 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
//room //room
def room_version = '2.2.4' def room_version = '2.2.5'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version"
//paging //paging
implementation 'androidx.paging:paging-runtime:2.1.1' implementation 'androidx.paging:paging-runtime:2.1.2'
//anko //anko
def anko_version = '0.10.8' def anko_version = '0.10.8'

@ -287,7 +287,19 @@
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.config.FileAssociationActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file"/>
<data android:scheme="content"/>
<data android:mimeType="text/plain" />
<data android:mimeType="application/json" />
</intent-filter>
</activity>
<service android:name=".service.CheckSourceService" /> <service android:name=".service.CheckSourceService" />
<service android:name=".service.DownloadService" /> <service android:name=".service.DownloadService" />
<service android:name=".service.WebService" /> <service android:name=".service.WebService" />

@ -3,6 +3,21 @@
* 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。 * 请关注[开源阅读]()支持我,同时关注合作公众号[小说拾遗](),阅读公众号小编。
* 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来 * 弄了个企业公众号[开源阅读](),后面弄好后会把原来的[开源阅读软件]()迁移过来
**2020/03/22**
* 添加文件关联 by wqfantexi
* 手动排序可以了,在书架整理里面拖动排序
* 删除分组时同时删除书籍里的分组信息,下次添加新分组时不会自动出现在分组内
* 修复换源丢失分组信息的bug
* 修复部分朗读引擎不自动朗读下一章的bug
**2020/03/21**
* 详情页点击书名搜索
**2020/03/20**
* 自动备份文件和手动备份文件分开
* 修复一些rss收藏取消不了的bug
* 修复rss请求头无效bug
**2020/03/19** **2020/03/19**
* 美化界面我的 by yangyxd * 美化界面我的 by yangyxd
* 优化搜索 * 优化搜索
@ -258,14 +273,7 @@
- 调试正文页>>输入正文页URL,如:`--https://www.zhaishuyuan.com/chapter/30394/20940996` - 调试正文页>>输入正文页URL,如:`--https://www.zhaishuyuan.com/chapter/30394/20940996`
* 修改订阅中自动添加style的情景 * 修改订阅中自动添加style的情景
订阅源的内容规则中存在`<style>``style=`, 订阅源的内容规则中存在`<style>``style=`
```
<style>
img{max-width:100% !important; width:auto; height:auto;}
video{object-fit:fill; max-width:100% !important; width:auto; height:auto;}
body{word-wrap:break-word; height:auto;max-width: 100%; width:auto;}
</style>
```
**2019/12/28** **2019/12/28**
* 添加下载界面 * 添加下载界面

@ -290,12 +290,11 @@ abstract class CommonRecyclerAdapter<ITEM>(protected val context: Context) :
return footerItems?.size() ?: 0 return footerItems?.size() ?: 0
} }
fun getItem(position: Int): ITEM? = fun getItem(position: Int): ITEM? = items.getOrNull(position)
if (position in 0 until items.size) items[position] else null
fun getItemByLayoutPosition(position: Int): ITEM? { fun getItemByLayoutPosition(position: Int): ITEM? {
val pos = position - getHeaderCount() val pos = position - getHeaderCount()
return if (pos in 0 until items.size) items[pos] else null return items.getOrNull(pos)
} }
fun getItems(): List<ITEM> = items fun getItems(): List<ITEM> = items

@ -12,16 +12,16 @@ interface BookDao {
@Query("SELECT * FROM books order by durChapterTime desc") @Query("SELECT * FROM books order by durChapterTime desc")
fun observeAll(): LiveData<List<Book>> fun observeAll(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE type = ${BookType.audio} order by durChapterTime desc") @Query("SELECT * FROM books WHERE type = ${BookType.audio}")
fun observeAudio(): LiveData<List<Book>> fun observeAudio(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE origin = '${BookType.local}' order by durChapterTime desc") @Query("SELECT * FROM books WHERE origin = '${BookType.local}'")
fun observeLocal(): LiveData<List<Book>> fun observeLocal(): LiveData<List<Book>>
@Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}' order by durChapterTime desc") @Query("SELECT bookUrl FROM books WHERE origin = '${BookType.local}'")
fun observeLocalUri(): LiveData<List<String>> fun observeLocalUri(): LiveData<List<String>>
@Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0 order by durChapterTime desc") @Query("SELECT * FROM books WHERE origin <> '${BookType.local}' and type = 0")
fun observeDownload(): LiveData<List<Book>> fun observeDownload(): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE (`group` & :group) > 0") @Query("SELECT * FROM books WHERE (`group` & :group) > 0")
@ -33,6 +33,9 @@ interface BookDao {
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'") @Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
fun liveDataSearch(key: String): LiveData<List<Book>> fun liveDataSearch(key: String): LiveData<List<Book>>
@Query("SELECT * FROM books WHERE (`group` & :group) > 0")
fun getBooksByGroup(group: Int): List<Book>
@Query("SELECT * FROM books WHERE `name` in (:names)") @Query("SELECT * FROM books WHERE `name` in (:names)")
fun findByName(vararg names: String): List<Book> fun findByName(vararg names: String): List<Book>
@ -57,6 +60,9 @@ interface BookDao {
@get:Query("SELECT COUNT(*) FROM books") @get:Query("SELECT COUNT(*) FROM books")
val allBookCount: Int val allBookCount: Int
@get:Query("select max(`order`) from books")
val maxOrder: Int
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg book: Book) fun insert(vararg book: Book)

@ -16,4 +16,16 @@ data class RssStar(
var description: String? = null, var description: String? = null,
var content: String? = null, var content: String? = null,
var image: String? = null var image: String? = null
) ) {
fun toRssArticle(): RssArticle {
return RssArticle(
origin = origin,
title = title,
link = link,
pubDate = pubDate,
description = description,
content = content,
image = image
)
}
}

@ -106,7 +106,7 @@ class ItemTouchCallback : ItemTouchHelper.Callback() {
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder) super.clearView(recyclerView, viewHolder)
onItemTouchCallbackListener?.clearView(recyclerView, viewHolder) onItemTouchCallbackListener?.onClearView(recyclerView, viewHolder)
} }
interface OnItemTouchCallbackListener { interface OnItemTouchCallbackListener {
@ -131,7 +131,10 @@ class ItemTouchCallback : ItemTouchHelper.Callback() {
return true return true
} }
fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { /**
* 手指松开
*/
fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
} }

@ -21,14 +21,6 @@ object Backup {
FileUtils.getDirFile(App.INSTANCE.filesDir, "backup").absolutePath FileUtils.getDirFile(App.INSTANCE.filesDir, "backup").absolutePath
} }
val legadoPath by lazy {
FileUtils.getSdCardPath() + File.separator + "YueDu3.0"
}
val exportPath by lazy {
legadoPath + File.separator + "Export"
}
val backupFileNames by lazy { val backupFileNames by lazy {
arrayOf( arrayOf(
"bookshelf.json", "bookGroup.json", "bookSource.json", "rssSource.json", "bookshelf.json", "bookGroup.json", "bookSource.json", "rssSource.json",
@ -39,19 +31,15 @@ object Backup {
fun autoBack(context: Context) { fun autoBack(context: Context) {
val lastBackup = context.getPrefLong(PreferKey.lastBackup) val lastBackup = context.getPrefLong(PreferKey.lastBackup)
if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) { if (lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()) {
return
}
Coroutine.async { Coroutine.async {
val backupPath = context.getPrefString(PreferKey.backupPath) context.getPrefString(PreferKey.backupPath)?.let {
if (backupPath.isNullOrEmpty()) { backup(context, it, true)
backup(context) }
} else {
backup(context, backupPath)
} }
} }
} }
suspend fun backup(context: Context, path: String = legadoPath) { suspend fun backup(context: Context, path: String, isAuto: Boolean = false) {
context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis()) context.putPrefLong(PreferKey.lastBackup, System.currentTimeMillis())
withContext(IO) { withContext(IO) {
synchronized(this@Backup) { synchronized(this@Backup) {
@ -81,9 +69,9 @@ object Backup {
} }
WebDavHelp.backUpWebDav(backupPath) WebDavHelp.backUpWebDav(backupPath)
if (path.isContentPath()) { if (path.isContentPath()) {
copyBackup(context, Uri.parse(path)) copyBackup(context, Uri.parse(path), isAuto)
} else { } else {
copyBackup(File(path)) copyBackup(File(path), isAuto)
} }
} }
} }
@ -97,12 +85,19 @@ object Backup {
} }
@Throws(java.lang.Exception::class) @Throws(java.lang.Exception::class)
private fun copyBackup(context: Context, uri: Uri) { private fun copyBackup(context: Context, uri: Uri, isAuto: Boolean) {
DocumentFile.fromTreeUri(context, uri)?.let { treeDoc -> DocumentFile.fromTreeUri(context, uri)?.let { treeDoc ->
for (fileName in backupFileNames) { for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)
if (file.exists()) { if (file.exists()) {
if (isAuto) {
treeDoc.findFile("auto")?.findFile(fileName)?.delete()
DocumentUtils.createFileIfNotExist(
treeDoc,
fileName,
subDirs = *arrayOf("auto")
)?.writeBytes(context, file.readBytes())
} else {
treeDoc.findFile(fileName)?.delete() treeDoc.findFile(fileName)?.delete()
treeDoc.createFile("", fileName) treeDoc.createFile("", fileName)
?.writeBytes(context, file.readBytes()) ?.writeBytes(context, file.readBytes())
@ -110,15 +105,19 @@ object Backup {
} }
} }
} }
}
@Throws(java.lang.Exception::class) @Throws(java.lang.Exception::class)
private fun copyBackup(rootFile: File) { private fun copyBackup(rootFile: File, isAuto: Boolean) {
for (fileName in backupFileNames) { for (fileName in backupFileNames) {
val file = File(backupPath + File.separator + fileName) val file = File(backupPath + File.separator + fileName)
if (file.exists()) { if (file.exists()) {
file.copyTo( file.copyTo(
FileUtils.createFileIfNotExist(rootFile, fileName), if (isAuto) {
true FileUtils.createFileIfNotExist(rootFile, fileName, "auto")
} else {
FileUtils.createFileIfNotExist(rootFile, fileName)
}, true
) )
} }
} }

@ -67,7 +67,7 @@ object Debug {
if (ruleContent.isNullOrEmpty()) { if (ruleContent.isNullOrEmpty()) {
log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000) log(debugSource, "⇒内容规则为空,默认获取整个网页", state = 1000)
} else { } else {
rssContentDebug(it.articles[0], ruleContent) rssContentDebug(it.articles[0], ruleContent, rssSource)
} }
} else { } else {
log(debugSource, "⇒存在描述规则,不解析内容页") log(debugSource, "⇒存在描述规则,不解析内容页")
@ -80,9 +80,9 @@ object Debug {
} }
} }
private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String) { private fun rssContentDebug(rssArticle: RssArticle, ruleContent: String, rssSource: RssSource) {
log(debugSource, "︾开始解析内容页") log(debugSource, "︾开始解析内容页")
Rss.getContent(rssArticle, ruleContent) Rss.getContent(rssArticle, ruleContent, rssSource)
.onSuccess { .onSuccess {
log(debugSource, it) log(debugSource, it)
log(debugSource, "︽内容页解析完成", state = 1000) log(debugSource, "︽内容页解析完成", state = 1000)

@ -21,7 +21,10 @@ object Rss {
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<Result> { ): Coroutine<Result> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
val analyzeUrl = AnalyzeUrl(pageUrl ?: rssSource.sourceUrl) val analyzeUrl = AnalyzeUrl(
pageUrl ?: rssSource.sourceUrl,
headerMapF = rssSource.getHeaderMap()
)
val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body
RssParserByRule.parseXML(body, rssSource) RssParserByRule.parseXML(body, rssSource)
} }
@ -30,12 +33,15 @@ object Rss {
fun getContent( fun getContent(
rssArticle: RssArticle, rssArticle: RssArticle,
ruleContent: String, ruleContent: String,
rssSource: RssSource?,
scope: CoroutineScope = Coroutine.DEFAULT, scope: CoroutineScope = Coroutine.DEFAULT,
context: CoroutineContext = Dispatchers.IO context: CoroutineContext = Dispatchers.IO
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context) { return Coroutine.async(scope, context) {
val body = AnalyzeUrl(rssArticle.link, baseUrl = rssArticle.origin) val body = AnalyzeUrl(
.getResponseAwait(rssArticle.origin) rssArticle.link, baseUrl = rssArticle.origin,
headerMapF = rssSource?.getHeaderMap()
).getResponseAwait(rssArticle.origin)
.body .body
val analyzeRule = AnalyzeRule() val analyzeRule = AnalyzeRule()
analyzeRule.setContent( analyzeRule.setContent(

@ -2,6 +2,7 @@ package io.legado.app.model.webBook
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
@ -39,7 +40,12 @@ object BookContent {
var contentData = analyzeContent( var contentData = analyzeContent(
book, baseUrl, body, contentRule, bookChapter, bookSource book, baseUrl, body, contentRule, bookChapter, bookSource
) )
if (bookSource.bookSourceType == BookType.default) {
content.append(contentData.content.replace(bookChapter.title, "")).append("\n") content.append(contentData.content.replace(bookChapter.title, "")).append("\n")
} else {
content.append(contentData.content).append("\n")
}
if (contentData.nextUrl.size == 1) { if (contentData.nextUrl.size == 1) {
var nextUrl = contentData.nextUrl[0] var nextUrl = contentData.nextUrl[0]
val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty()) val nextChapterUrl = if (!nextChapterUrlF.isNullOrEmpty())

@ -111,11 +111,17 @@ abstract class BaseReadAloudService : BaseService(),
if (getPrefBoolean(PreferKey.readAloudByPage)) { if (getPrefBoolean(PreferKey.readAloudByPage)) {
for (index in pageIndex..textChapter.lastIndex()) { for (index in pageIndex..textChapter.lastIndex()) {
textChapter.page(index)?.text?.split("\n")?.let { textChapter.page(index)?.text?.split("\n")?.let {
if (it.isNotEmpty()) {
contentList.addAll(it) contentList.addAll(it)
} }
} }
}
} else { } else {
contentList.addAll(textChapter.getUnRead(pageIndex).split("\n")) textChapter.getUnRead(pageIndex).split("\n").forEach {
if (it.isNotEmpty()) {
contentList.add(it)
}
}
} }
if (play) play() if (play) play()
} ?: stopSelf() } ?: stopSelf()

@ -6,7 +6,7 @@
* book\info 书籍信息查看 * book\info 书籍信息查看
* book\read 书籍阅读界面 * book\read 书籍阅读界面
* book\search 搜索书籍界面 * book\search 搜索书籍界面
* book\source 搜索书源界面 * book\source 书源界面
* book\changeCover 封面换源界面 * book\changeCover 封面换源界面
* book\changeSource 换源界面 * book\changeSource 换源界面
* book\chapterList 目录界面 * book\chapterList 目录界面

@ -46,9 +46,8 @@ class AudioPlayActivity :
private var adjustProgress = false private var adjustProgress = false
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
setSupportActionBar(toolbar) title_bar.transparent()
supportActionBar?.setDisplayHomeAsUpEnabled(true) AudioPlay.titleData.observe(this, Observer { title_bar.title = it })
AudioPlay.titleData.observe(this, Observer { toolbar.title = it })
AudioPlay.coverData.observe(this, Observer { upCover(it) }) AudioPlay.coverData.observe(this, Observer { upCover(it) })
viewModel.initData(intent) viewModel.initData(intent)
initView() initView()

@ -91,6 +91,7 @@ class AudioPlayViewModel(application: Application) : BaseViewModel(application)
fun changeTo(book1: Book) { fun changeTo(book1: Book) {
execute { execute {
AudioPlay.book?.let { AudioPlay.book?.let {
book1.order = it.order
App.db.bookDao().delete(it) App.db.bookDao().delete(it)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

@ -11,6 +11,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import io.legado.app.App import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
@ -23,6 +24,7 @@ import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.widget.SelectActionBar import io.legado.app.ui.widget.SelectActionBar
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.applyTint import io.legado.app.utils.applyTint
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.getViewModel import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_arrange_book.* import kotlinx.android.synthetic.main.activity_arrange_book.*
@ -68,7 +70,7 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
recycler_view.adapter = adapter recycler_view.adapter = adapter
val itemTouchCallback = ItemTouchCallback() val itemTouchCallback = ItemTouchCallback()
itemTouchCallback.onItemTouchCallbackListener = adapter itemTouchCallback.onItemTouchCallbackListener = adapter
itemTouchCallback.isCanDrag = true itemTouchCallback.isCanDrag = getPrefInt(PreferKey.bookshelfSort) == 3
ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view) ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recycler_view)
select_action_bar.setMainActionText(R.string.move_to_group) select_action_bar.setMainActionText(R.string.move_to_group)
select_action_bar.inflateMenu(R.menu.arrange_book_sel) select_action_bar.inflateMenu(R.menu.arrange_book_sel)
@ -109,8 +111,14 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
-11 -> App.db.bookDao().observeNoGroup() -11 -> App.db.bookDao().observeNoGroup()
else -> App.db.bookDao().observeByGroup(groupId) else -> App.db.bookDao().observeByGroup(groupId)
} }
booksLiveData?.observe(this, Observer { booksLiveData?.observe(this, Observer { list ->
adapter.setItems(it) val books = when (getPrefInt(PreferKey.bookshelfSort)) {
1 -> list.sortedByDescending { it.latestChapterTime }
2 -> list.sortedBy { it.name }
3 -> list.sortedBy { it.order }
else -> list.sortedByDescending { it.durChapterTime }
}
adapter.setItems(books)
upSelectCount() upSelectCount()
}) })
} }
@ -205,6 +213,10 @@ class ArrangeBookActivity : VMBaseActivity<ArrangeBookViewModel>(R.layout.activi
select_action_bar.upCountView(adapter.selectedBooks().size, adapter.getItems().size) select_action_bar.upCountView(adapter.selectedBooks().size, adapter.getItems().size)
} }
override fun updateBook(vararg book: Book) {
viewModel.updateBook(*book)
}
override fun deleteBook(book: Book) { override fun deleteBook(book: Book) {
alert(titleResource = R.string.draw, messageResource = R.string.sure_del) { alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {
okButton { okButton {

@ -2,14 +2,18 @@ package io.legado.app.ui.book.arrange
import android.content.Context import android.content.Context
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.adapter.ItemViewHolder import io.legado.app.base.adapter.ItemViewHolder
import io.legado.app.base.adapter.SimpleRecyclerAdapter import io.legado.app.base.adapter.SimpleRecyclerAdapter
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookGroup import io.legado.app.data.entities.BookGroup
import io.legado.app.help.ItemTouchCallback import io.legado.app.help.ItemTouchCallback
import io.legado.app.lib.theme.backgroundColor
import kotlinx.android.synthetic.main.item_arrange_book.view.* import kotlinx.android.synthetic.main.item_arrange_book.view.*
import org.jetbrains.anko.backgroundColor
import org.jetbrains.anko.sdk27.listeners.onClick import org.jetbrains.anko.sdk27.listeners.onClick
import java.util.*
class ArrangeBookAdapter(context: Context, val callBack: CallBack) : class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book), SimpleRecyclerAdapter<Book>(context, R.layout.item_arrange_book),
@ -54,6 +58,7 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList<Any>) { override fun convert(holder: ItemViewHolder, item: Book, payloads: MutableList<Any>) {
with(holder.itemView) { with(holder.itemView) {
backgroundColor = context.backgroundColor
tv_name.text = item.name tv_name.text = item.name
tv_author.text = item.author tv_author.text = item.author
tv_author.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE tv_author.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE
@ -120,9 +125,39 @@ class ArrangeBookAdapter(context: Context, val callBack: CallBack) :
return groupNames.joinToString(",") return groupNames.joinToString(",")
} }
private var isMoved = false
override fun onMove(srcPosition: Int, targetPosition: Int): Boolean {
val srcItem = getItem(srcPosition)
val targetItem = getItem(targetPosition)
Collections.swap(getItems(), srcPosition, targetPosition)
notifyItemMoved(srcPosition, targetPosition)
if (srcItem != null && targetItem != null) {
if (srcItem.order == targetItem.order) {
for ((index, item) in getItems().withIndex()) {
item.order = index + 1
}
} else {
val pos = srcItem.order
srcItem.order = targetItem.order
targetItem.order = pos
}
}
isMoved = true
return true
}
override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (isMoved) {
callBack.updateBook(*getItems().toTypedArray())
}
isMoved = false
}
interface CallBack { interface CallBack {
val groupList: List<BookGroup> val groupList: List<BookGroup>
fun upSelectCount() fun upSelectCount()
fun updateBook(vararg book: Book)
fun deleteBook(book: Book) fun deleteBook(book: Book)
fun selectGroup(groupId: Int, requestCode: Int) fun selectGroup(groupId: Int, requestCode: Int)
} }

@ -13,6 +13,7 @@ import io.legado.app.App
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.EventBus import io.legado.app.constant.EventBus
import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookChapter
import io.legado.app.help.BookHelp import io.legado.app.help.BookHelp
@ -81,9 +82,15 @@ class DownloadActivity : VMBaseActivity<DownloadViewModel>(R.layout.activity_dow
private fun initLiveData() { private fun initLiveData() {
bookshelfLiveData?.removeObservers(this) bookshelfLiveData?.removeObservers(this)
bookshelfLiveData = App.db.bookDao().observeDownload() bookshelfLiveData = App.db.bookDao().observeDownload()
bookshelfLiveData?.observe(this, Observer { bookshelfLiveData?.observe(this, Observer { list ->
adapter.setItems(it) val books = when (getPrefInt(PreferKey.bookshelfSort)) {
initCacheSize(it) 1 -> list.sortedByDescending { it.latestChapterTime }
2 -> list.sortedBy { it.name }
3 -> list.sortedBy { it.order }
else -> list.sortedByDescending { it.durChapterTime }
}
adapter.setItems(books)
initCacheSize(books)
}) })
} }

@ -217,7 +217,7 @@ class GroupManageDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
return true return true
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (isMoved) { if (isMoved) {
for ((index, item) in getItems().withIndex()) { for ((index, item) in getItems().withIndex()) {
item.order = index + 1 item.order = index + 1

@ -203,7 +203,7 @@ class GroupSelectDialog : DialogFragment(), Toolbar.OnMenuItemClickListener {
return true return true
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (isMoved) { if (isMoved) {
for ((index, item) in getItems().withIndex()) { for ((index, item) in getItems().withIndex()) {
item.order = index + 1 item.order = index + 1

@ -32,6 +32,13 @@ class GroupViewModel(application: Application) : BaseViewModel(application) {
fun delGroup(vararg bookGroup: BookGroup) { fun delGroup(vararg bookGroup: BookGroup) {
execute { execute {
App.db.bookGroupDao().delete(*bookGroup) App.db.bookGroupDao().delete(*bookGroup)
bookGroup.forEach { group ->
val books = App.db.bookDao().getBooksByGroup(group.groupId)
books.forEach {
it.group = it.group - group.groupId
}
App.db.bookDao().update(*books.toTypedArray())
}
} }
} }

@ -57,8 +57,7 @@ class BookInfoActivity :
get() = getViewModel(BookInfoViewModel::class.java) get() = getViewModel(BookInfoViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
setSupportActionBar(toolbar) title_bar.transparent()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
viewModel.bookData.observe(this, Observer { showBook(it) }) viewModel.bookData.observe(this, Observer { showBook(it) })
viewModel.chapterListData.observe(this, Observer { upLoading(false, it) }) viewModel.chapterListData.observe(this, Observer { upLoading(false, it) })
viewModel.initData(intent) viewModel.initData(intent)
@ -231,6 +230,9 @@ class BookInfoActivity :
tv_author.onClick { tv_author.onClick {
startActivity<SearchActivity>(Pair("key", viewModel.bookData.value?.author)) startActivity<SearchActivity>(Pair("key", viewModel.bookData.value?.author))
} }
tv_name.onClick {
startActivity<SearchActivity>(Pair("key", viewModel.bookData.value?.name))
}
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")

@ -133,6 +133,8 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
if (inBookshelf) { if (inBookshelf) {
bookData.value?.let { bookData.value?.let {
book.group = it.group
book.order = it.order
App.db.bookDao().delete(it) App.db.bookDao().delete(it)
} }
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
@ -164,6 +166,9 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun saveBook(success: (() -> Unit)? = null) { fun saveBook(success: (() -> Unit)? = null) {
execute { execute {
bookData.value?.let { book -> bookData.value?.let { book ->
if (book.order == 0) {
book.order = App.db.bookDao().maxOrder + 1
}
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
} }
}.onSuccess { }.onSuccess {
@ -184,6 +189,9 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun addToBookshelf(success: (() -> Unit)?) { fun addToBookshelf(success: (() -> Unit)?) {
execute { execute {
bookData.value?.let { book -> bookData.value?.let { book ->
if (book.order == 0) {
book.order = App.db.bookDao().maxOrder + 1
}
App.db.bookDao().insert(book) App.db.bookDao().insert(book)
} }
chapterListData.value?.let { chapterListData.value?.let {

@ -609,9 +609,6 @@ class ReadBookActivity : VMBaseActivity<ReadBookViewModel>(R.layout.activity_boo
* 朗读按钮 * 朗读按钮
*/ */
override fun onClickReadAloud() { override fun onClickReadAloud() {
if (!BaseReadAloudService.isRun) {
SystemUtils.ignoreBatteryOptimization(this)
}
when { when {
!BaseReadAloudService.isRun -> ReadBook.readAloud() !BaseReadAloudService.isRun -> ReadBook.readAloud()
BaseReadAloudService.pause -> ReadAloud.resume(this) BaseReadAloudService.pause -> ReadAloud.resume(this)

@ -136,6 +136,8 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
execute { execute {
ReadBook.upMsg(null) ReadBook.upMsg(null)
ReadBook.book?.let { ReadBook.book?.let {
book1.group = it.group
book1.order = it.order
App.db.bookDao().delete(it) App.db.bookDao().delete(it)
} }
ReadBook.prevTextChapter = null ReadBook.prevTextChapter = null

@ -252,8 +252,8 @@ class TocRegexDialog : BaseDialogFragment(), Toolbar.OnMenuItemClickListener {
return super.onMove(srcPosition, targetPosition) return super.onMove(srcPosition, targetPosition)
} }
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder) super.onClearView(recyclerView, viewHolder)
if (isMoved) { if (isMoved) {
for ((index, item) in getItems().withIndex()) { for ((index, item) in getItems().withIndex()) {
item.serialNumber = index + 1 item.serialNumber = index + 1

@ -12,10 +12,7 @@ data class TextChapter(
val chaptersSize: Int val chaptersSize: Int
) { ) {
fun page(index: Int): TextPage? { fun page(index: Int): TextPage? {
if (index >= 0 && index < pages.size) { return pages.getOrNull(index)
return pages[index]
}
return null
} }
fun lastPage(): TextPage? { fun lastPage(): TextPage? {
@ -25,20 +22,6 @@ data class TextChapter(
return null return null
} }
fun scrollPage(): TextPage? {
if (pages.isNotEmpty()) {
val stringBuilder = StringBuilder()
pages.forEach {
stringBuilder.append(it.text)
}
return TextPage(
index = 0, text = stringBuilder.toString(), title = title,
pageSize = pages.size, chapterSize = chaptersSize, chapterIndex = position
)
}
return null
}
fun lastIndex(): Int { fun lastIndex(): Int {
return pages.size - 1 return pages.size - 1
} }
@ -70,28 +53,6 @@ data class TextChapter(
return stringBuilder.toString() return stringBuilder.toString()
} }
fun getStartLine(pageIndex: Int): Int {
if (pageLines.size > pageIndex) {
var lines = 0
for (index: Int in 0 until pageIndex) {
lines += pageLines[index] + 1
}
return lines
}
return 0
}
fun getPageIndex(line: Int): Int {
var lines = 0
for (pageIndex in pageLines.indices) {
lines += pageLines[pageIndex] + 1
if (line < lines) {
return pageIndex
}
}
return 0
}
fun getContent(): String { fun getContent(): String {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
pages.forEach { pages.forEach {

@ -104,10 +104,16 @@ class BookSourceActivity : VMBaseActivity<BookSourceViewModel>(R.layout.activity
when (it.path) { when (it.path) {
"/importonline" -> it.getQueryParameter("src")?.let { url -> "/importonline" -> it.getQueryParameter("src")?.let { url ->
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
if (url.startsWith("http", false)){
viewModel.importSource(url) { msg -> viewModel.importSource(url) { msg ->
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
else{
viewModel.importSourceFromFilePath(url){msg ->
title_bar.snackbar(msg)}
}
}
else -> { else -> {
toast("格式不对") toast("格式不对")
} }

@ -185,7 +185,7 @@ class BookSourceAdapter(context: Context, val callBack: CallBack) :
private val movedItems = hashSetOf<BookSource>() private val movedItems = hashSetOf<BookSource>()
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (movedItems.isNotEmpty()) { if (movedItems.isNotEmpty()) {
callBack.update(*movedItems.toTypedArray()) callBack.update(*movedItems.toTypedArray())
movedItems.clear() movedItems.clear()

@ -8,7 +8,6 @@ import io.legado.app.App
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.OldRule import io.legado.app.help.storage.OldRule
import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.help.storage.Restore.jsonPath
import io.legado.app.utils.* import io.legado.app.utils.*
@ -96,7 +95,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
FileUtils.createFileIfNotExist(file, "exportBookSource.json") FileUtils.createFileIfNotExist(file, "exportBookSource.json")
.writeText(json) .writeText(json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${file.absolutePath}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }
@ -109,7 +108,7 @@ class BookSourceViewModel(application: Application) : BaseViewModel(application)
doc.createFile("", "exportBookSource.json") doc.createFile("", "exportBookSource.json")
?.writeText(context, json) ?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${doc.uri.path}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }

@ -48,23 +48,19 @@ object BackupRestoreUi {
selectBackupFolder(fragment, backupSelectRequestCode) selectBackupFolder(fragment, backupSelectRequestCode)
} }
} else { } else {
backupUsePermission(fragment, requestCode = backupSelectRequestCode) backupUsePermission(fragment, backupPath)
} }
} }
} }
private fun backupUsePermission( private fun backupUsePermission(
fragment: Fragment, fragment: Fragment,
path: String = Backup.legadoPath, path: String
requestCode: Int = selectFolderRequestCode
) { ) {
PermissionsCompat.Builder(fragment) PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE) .addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage) .rationale(R.string.tip_perm_request_storage)
.onGranted { .onGranted {
when (requestCode) {
selectFolderRequestCode -> AppConfig.backupPath = Backup.legadoPath
else -> {
Coroutine.async { Coroutine.async {
AppConfig.backupPath = path AppConfig.backupPath = path
Backup.backup(fragment.requireContext(), path) Backup.backup(fragment.requireContext(), path)
@ -72,8 +68,6 @@ object BackupRestoreUi {
fragment.toast(R.string.backup_success) fragment.toast(R.string.backup_success)
} }
} }
}
}
.request() .request()
} }
@ -107,7 +101,7 @@ object BackupRestoreUi {
} }
} }
private fun restoreUsePermission(fragment: Fragment, path: String = Backup.legadoPath) { private fun restoreUsePermission(fragment: Fragment, path: String) {
PermissionsCompat.Builder(fragment) PermissionsCompat.Builder(fragment)
.addPermissions(*Permissions.Group.STORAGE) .addPermissions(*Permissions.Group.STORAGE)
.rationale(R.string.tip_perm_request_storage) .rationale(R.string.tip_perm_request_storage)

@ -0,0 +1,39 @@
package io.legado.app.ui.config
import android.content.Intent
import android.os.Bundle
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.ui.main.MainActivity
import io.legado.app.utils.getViewModel
class FileAssociationActivity :
VMBaseActivity<FileAssociationViewModel>(R.layout.activity_file_association) {
override val viewModel: FileAssociationViewModel
get() = getViewModel(FileAssociationViewModel::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) {
intent.data?.let { data ->
val newIntent = viewModel.dispatchIndent(data)
if (newIntent != null) {
this.startActivityForResult(newIntent, 100)
} else {
gotoMainActivity()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
//返回后直接跳转到主页面
gotoMainActivity()
}
private fun gotoMainActivity() {
val mIntent = Intent()
mIntent.setClass(this, MainActivity::class.java)
startActivity(mIntent)
}
}

@ -0,0 +1,69 @@
package io.legado.app.ui.config
import android.app.Application
import android.content.Intent
import android.net.Uri
import android.text.TextUtils
import androidx.documentfile.provider.DocumentFile
import io.legado.app.base.BaseViewModel
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.isJsonArray
import io.legado.app.utils.isJsonObject
import io.legado.app.utils.readText
import java.io.File
class FileAssociationViewModel(application: Application) : BaseViewModel(application) {
fun dispatchIndent(uri: Uri): Intent? {
val url: String
//如果是普通的url,需要根据返回的内容判断是什么
if (uri.scheme == "file" || uri.scheme == "content") {
val content = if (uri.scheme == "file") {
val file = File(uri.path.toString())
if (file.exists()) {
file.readText()
} else {
null
}
} else {
DocumentFile.fromSingleUri(context, uri)?.readText(context)
}
var scheme = ""
if (content != null) {
if (content.isJsonObject() || content.isJsonArray()) {
//暂时根据文件内容判断属于什么
when {
content.contains("bookSourceUrl") -> {
scheme = "booksource"
}
content.contains("sourceUrl") -> {
scheme = "rsssource"
}
content.contains("pattern") -> {
scheme = "replace"
}
}
}
if (TextUtils.isEmpty(scheme)) {
execute {
LocalBook.importFile(uri.path.toString())
toast("添加本地文件成功${uri.path}")
}
return null
}
} else {
toast("文件不存在")
return null
}
url = "yuedu://${scheme}/importonline?src=${uri.path}"
} else if (uri.scheme == "yuedu") {
url = uri.toString()
} else {
url = "yuedu://booksource/importonline?src=${uri.path}"
}
val data = Uri.parse(url)
val newIndent = Intent(Intent.ACTION_VIEW)
newIndent.data = data
return newIndent
}
}

@ -43,6 +43,7 @@ class BookshelfViewModel(application: Application) : BaseViewModel(application)
) )
WebBook(bookSource).getBookInfo(book, this) WebBook(bookSource).getBookInfo(book, this)
.onSuccess(IO) { .onSuccess(IO) {
it.order = App.db.bookDao().maxOrder + 1
App.db.bookDao().insert(it) App.db.bookDao().insert(it)
successCount++ successCount++
}.onError { }.onError {

@ -81,10 +81,17 @@ class ReplaceRuleActivity : VMBaseActivity<ReplaceRuleViewModel>(R.layout.activi
when (it.path) { when (it.path) {
"/importonline" -> it.getQueryParameter("src")?.let { url -> "/importonline" -> it.getQueryParameter("src")?.let { url ->
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
if (url.startsWith("http", false)){
viewModel.importSource(url) { msg -> viewModel.importSource(url) { msg ->
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
else{
viewModel.importSourceFromFilePath(url) { msg ->
title_bar.snackbar(msg)
}
}
}
else -> { else -> {
toast("格式不对") toast("格式不对")
} }

@ -148,7 +148,7 @@ class ReplaceRuleAdapter(context: Context, var callBack: CallBack) :
private val movedItems = linkedSetOf<ReplaceRule>() private val movedItems = linkedSetOf<ReplaceRule>()
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (movedItems.isNotEmpty()) { if (movedItems.isNotEmpty()) {
callBack.update(*movedItems.toTypedArray()) callBack.update(*movedItems.toTypedArray())
movedItems.clear() movedItems.clear()

@ -8,14 +8,28 @@ import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.ReplaceRule import io.legado.app.data.entities.ReplaceRule
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.ImportOldData import io.legado.app.help.storage.ImportOldData
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
import java.io.File import java.io.File
class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) { class ReplaceRuleViewModel(application: Application) : BaseViewModel(application) {
fun importSourceFromFilePath(path: String, finally: (msg: String) -> Unit) {
execute {
val file = File(path)
if (file.exists()) {
importSource(file.readText(), finally)
} else {
withContext(Dispatchers.Main) {
finally("打开文件出错")
}
}
}.onError {
finally(it.localizedMessage ?: "打开文件出错")
}
}
fun importSource(text: String, showMsg: (msg: String) -> Unit) { fun importSource(text: String, showMsg: (msg: String) -> Unit) {
execute { execute {
if (text.isAbsUrl()) { if (text.isAbsUrl()) {
@ -93,7 +107,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
FileUtils.createFileIfNotExist(file, "exportReplaceRule.json") FileUtils.createFileIfNotExist(file, "exportReplaceRule.json")
.writeText(json) .writeText(json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${file.absolutePath}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }
@ -106,7 +120,7 @@ class ReplaceRuleViewModel(application: Application) : BaseViewModel(application
doc.createFile("", "exportReplaceRule.json") doc.createFile("", "exportReplaceRule.json")
?.writeText(context, json) ?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${doc.uri.path}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }

@ -193,7 +193,7 @@ class ReadRssActivity : VMBaseActivity<ReadRssViewModel>(R.layout.activity_rss_r
} }
override fun upStarMenu() { override fun upStarMenu() {
if (viewModel.star) { if (viewModel.rssStar != null) {
starMenuItem?.setIcon(R.drawable.ic_star) starMenuItem?.setIcon(R.drawable.ic_star)
starMenuItem?.setTitle(R.string.in_favorites) starMenuItem?.setTitle(R.string.in_favorites)
} else { } else {

@ -15,6 +15,7 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.data.entities.RssArticle import io.legado.app.data.entities.RssArticle
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.data.entities.RssStar
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.model.Rss import io.legado.app.model.Rss
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
@ -22,6 +23,7 @@ import io.legado.app.utils.DocumentUtils
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
import io.legado.app.utils.isContentPath import io.legado.app.utils.isContentPath
import io.legado.app.utils.writeBytes import io.legado.app.utils.writeBytes
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -34,7 +36,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
var rssArticle: RssArticle? = null var rssArticle: RssArticle? = null
val contentLiveData = MutableLiveData<String>() val contentLiveData = MutableLiveData<String>()
val urlLiveData = MutableLiveData<AnalyzeUrl>() val urlLiveData = MutableLiveData<AnalyzeUrl>()
var star = false var rssStar: RssStar? = null
var textToSpeech: TextToSpeech? = null var textToSpeech: TextToSpeech? = null
private var ttsInitFinish = false private var ttsInitFinish = false
private var ttsTextList = arrayListOf<String>() private var ttsTextList = arrayListOf<String>()
@ -45,8 +47,8 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
val link = intent.getStringExtra("link") val link = intent.getStringExtra("link")
if (origin != null && link != null) { if (origin != null && link != null) {
rssSource = App.db.rssSourceDao().getByKey(origin) rssSource = App.db.rssSourceDao().getByKey(origin)
star = App.db.rssStarDao().get(origin, link) != null rssStar = App.db.rssStarDao().get(origin, link)
rssArticle = App.db.rssArticleDao().get(origin, link) rssArticle = rssStar?.toRssArticle() ?: App.db.rssArticleDao().get(origin, link)
rssArticle?.let { rssArticle -> rssArticle?.let { rssArticle ->
if (!rssArticle.description.isNullOrBlank()) { if (!rssArticle.description.isNullOrBlank()) {
contentLiveData.postValue(rssArticle.description) contentLiveData.postValue(rssArticle.description)
@ -78,21 +80,26 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
} }
private fun loadContent(rssArticle: RssArticle, ruleContent: String) { private fun loadContent(rssArticle: RssArticle, ruleContent: String) {
Rss.getContent(rssArticle, ruleContent, this) Rss.getContent(rssArticle, ruleContent, rssSource, this)
.onSuccess { .onSuccess(IO) { body ->
contentLiveData.postValue(it) rssArticle.description = body
App.db.rssArticleDao().insert(rssArticle)
rssStar?.let {
it.description = body
App.db.rssStarDao().insert(it)
}
contentLiveData.postValue(body)
} }
} }
fun favorite() { fun favorite() {
execute { execute {
rssArticle?.let { rssStar?.let {
if (star) {
App.db.rssStarDao().delete(it.origin, it.link) App.db.rssStarDao().delete(it.origin, it.link)
} else { rssStar = null
App.db.rssStarDao().insert(it.toStar()) } ?: rssArticle?.toStar()?.let {
} App.db.rssStarDao().insert(it)
star = !star rssStar = it
} }
}.onSuccess { }.onSuccess {
callBack?.upStarMenu() callBack?.upStarMenu()

@ -117,10 +117,17 @@ class RssSourceActivity : VMBaseActivity<RssSourceViewModel>(R.layout.activity_r
when (it.path) { when (it.path) {
"/importonline" -> it.getQueryParameter("src")?.let { url -> "/importonline" -> it.getQueryParameter("src")?.let { url ->
Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(title_bar, R.string.importing, Snackbar.LENGTH_INDEFINITE).show()
if (url.startsWith("http", false)){
viewModel.importSource(url) { msg -> viewModel.importSource(url) { msg ->
title_bar.snackbar(msg) title_bar.snackbar(msg)
} }
} }
else{
viewModel.importSourceFromFilePath(url) { msg ->
title_bar.snackbar(msg)
}
}
}
else -> { else -> {
toast("格式不对") toast("格式不对")
} }

@ -139,7 +139,7 @@ class RssSourceAdapter(context: Context, val callBack: CallBack) :
private val movedItems = hashSetOf<RssSource>() private val movedItems = hashSetOf<RssSource>()
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
if (movedItems.isNotEmpty()) { if (movedItems.isNotEmpty()) {
callBack.update(*movedItems.toTypedArray()) callBack.update(*movedItems.toTypedArray())
movedItems.clear() movedItems.clear()

@ -9,7 +9,6 @@ import io.legado.app.R
import io.legado.app.base.BaseViewModel import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.http.HttpHelper import io.legado.app.help.http.HttpHelper
import io.legado.app.help.storage.Backup
import io.legado.app.help.storage.Restore.jsonPath import io.legado.app.help.storage.Restore.jsonPath
import io.legado.app.utils.* import io.legado.app.utils.*
import org.jetbrains.anko.toast import org.jetbrains.anko.toast
@ -74,7 +73,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
FileUtils.createFileIfNotExist(file, "exportRssSource.json") FileUtils.createFileIfNotExist(file, "exportRssSource.json")
.writeText(json) .writeText(json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${file.absolutePath}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }
@ -87,7 +86,7 @@ class RssSourceViewModel(application: Application) : BaseViewModel(application)
doc.createFile("", "exportRssSource.json") doc.createFile("", "exportRssSource.json")
?.writeText(context, json) ?.writeText(context, json)
}.onSuccess { }.onSuccess {
context.toast("成功导出至\n${Backup.exportPath}") context.toast("成功导出至\n${doc.uri.path}")
}.onError { }.onError {
context.toast("导出失败\n${it.localizedMessage}") context.toast("导出失败\n${it.localizedMessage}")
} }

@ -2,6 +2,7 @@ package io.legado.app.ui.widget
import android.content.Context import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Menu import android.view.Menu
import android.view.View import android.view.View
@ -186,6 +187,11 @@ class TitleBar(context: Context, attrs: AttributeSet?) : AppBarLayout(context, a
toolbar.setSubtitleTextAppearance(context, resId) toolbar.setSubtitleTextAppearance(context, resId)
} }
fun transparent() {
elevation = 0f
backgroundColor = Color.TRANSPARENT
}
private fun attachToActivity() { private fun attachToActivity() {
if (attachToActivity) { if (attachToActivity) {
activity?.let { activity?.let {

@ -66,7 +66,7 @@ public class EncodingDetect {
} }
public static String getEncode(@NonNull byte[] bytes) { public static String getEncode(@NonNull byte[] bytes) {
int len = bytes.length > 2000 ? 2000 : bytes.length; int len = Math.min(bytes.length, 2000);
byte[] cBytes = new byte[len]; byte[] cBytes = new byte[len];
System.arraycopy(bytes, 0, cBytes, 0, len); System.arraycopy(bytes, 0, cBytes, 0, len);
BytesEncodingDetect bytesEncodingDetect = new BytesEncodingDetect(); BytesEncodingDetect bytesEncodingDetect = new BytesEncodingDetect();
@ -1010,14 +1010,8 @@ class BytesEncodingDetect extends Encoding {
column = rawtext[i + 1] + 256; column = rawtext[i + 1] + 256;
if (column < 0x9f) { if (column < 0x9f) {
adjust = 1; adjust = 1;
if (column > 0x7f) {
column -= 0x20;
} else {
column -= 0x19;
}
} else { } else {
adjust = 0; adjust = 0;
column -= 0x7e;
} }
if (row < 0xa0) { if (row < 0xa0) {
row = ((row - 0x70) << 1) - adjust; row = ((row - 0x70) << 1) - adjust;

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp" android:width="24dp"
android:height="22dp" android:height="24dp"
android:viewportWidth="1024" android:viewportWidth="1024"
android:viewportHeight="1024"> android:viewportHeight="1024">
<path <path

@ -14,13 +14,10 @@
android:contentDescription="@string/bg_image" android:contentDescription="@string/bg_image"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.Toolbar <io.legado.app.ui.widget.TitleBar
android:id="@+id/toolbar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
android:fitsSystemWindows="true"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:title="@string/book_info" /> app:title="@string/book_info" />
@ -81,7 +78,8 @@
android:paddingLeft="10dp" android:paddingLeft="10dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingRight="16dp" android:paddingRight="16dp"
android:paddingBottom="3dp"> android:paddingBottom="3dp"
tools:ignore="RtlHardcoded">
<TextView <TextView
android:id="@+id/tv_name" android:id="@+id/tv_name"
@ -147,14 +145,13 @@
android:layout_weight="1.0" android:layout_weight="1.0"
android:orientation="vertical" android:orientation="vertical"
android:layout_marginTop="65dp" android:layout_marginTop="65dp"
android:layout_marginLeft="1px"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@color/background" android:background="@color/background"
android:fillViewport="true" android:fillViewport="true"
@ -162,7 +159,8 @@
android:focusable="true" android:focusable="true"
android:padding="0dp" android:padding="0dp"
android:scrollbarStyle="outsideInset" android:scrollbarStyle="outsideInset"
android:scrollbars="vertical"> android:scrollbars="vertical"
tools:ignore="NestedWeights">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -212,7 +210,7 @@
android:text="@string/origin_format" android:text="@string/origin_format"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_change_source" android:id="@+id/tv_change_source"
@ -257,7 +255,7 @@
android:text="@string/read_dur_progress" android:text="@string/read_dur_progress"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
</LinearLayout> </LinearLayout>
@ -290,7 +288,7 @@
android:text="@string/group_s" android:text="@string/group_s"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_change_group" android:id="@+id/tv_change_group"
@ -334,7 +332,7 @@
android:text="@string/toc_s" android:text="@string/toc_s"
android:textColor="@color/tv_text_summary" android:textColor="@color/tv_text_summary"
android:textSize="13sp" android:textSize="13sp"
tools:ignore="NestedWeights" /> tools:ignore="NestedWeights,RtlHardcoded,RtlSymmetry" />
<io.legado.app.ui.widget.text.AccentBgTextView <io.legado.app.ui.widget.text.AccentBgTextView
android:id="@+id/tv_toc_view" android:id="@+id/tv_toc_view"
@ -373,7 +371,7 @@
android:layout_marginBottom="1px" android:layout_marginBottom="1px"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="8dp" android:layout_height="8dp"
android:background="@color/background"></View> android:background="@color/background" />
<LinearLayout <LinearLayout
android:id="@+id/fl_action" android:id="@+id/fl_action"

@ -21,13 +21,10 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#50000000" /> android:background="#50000000" />
<androidx.appcompat.widget.Toolbar <io.legado.app.ui.widget.TitleBar
android:id="@+id/toolbar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
android:fitsSystemWindows="true"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
@ -40,7 +37,7 @@
android:paddingStart="3dp" android:paddingStart="3dp"
android:paddingEnd="3dp" android:paddingEnd="3dp"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintTop_toBottomOf="@id/toolbar" app:layout_constraintTop_toBottomOf="@id/title_bar"
app:layout_constraintRight_toRightOf="parent" /> app:layout_constraintRight_toRightOf="parent" />
<io.legado.app.ui.widget.image.CircleImageView <io.legado.app.ui.widget.image.CircleImageView
@ -54,7 +51,7 @@
app:layout_constraintBottom_toTopOf="@+id/ll_player_progress" app:layout_constraintBottom_toTopOf="@+id/ll_player_progress"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/toolbar" /> app:layout_constraintTop_toBottomOf="@+id/title_bar" />
<TextView <TextView
android:id="@+id/tv_sub_title" android:id="@+id/tv_sub_title"

@ -21,13 +21,10 @@
android:background="#50000000" android:background="#50000000"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <io.legado.app.ui.widget.TitleBar
android:id="@+id/toolbar" android:id="@+id/title_bar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:theme="?attr/actionBarStyle"
android:fitsSystemWindows="true"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:title="@string/book_info" /> app:title="@string/book_info" />

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.legado.app.ui.config.FileAssociationActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

@ -6,7 +6,7 @@
<item <item
android:id="@+id/menu_stop" android:id="@+id/menu_stop"
android:title="@string/stop" android:title="@string/stop"
android:icon="@drawable/ic_stop_black_24dp" android:icon="@drawable/ic_refresh_white_24dp"
app:showAsAction="always" app:showAsAction="always"
tools:ignore="AlwaysShowAction" /> tools:ignore="AlwaysShowAction" />

@ -14,7 +14,7 @@
<item <item
android:id="@+id/menu_stop" android:id="@+id/menu_stop"
android:title="@string/stop" android:title="@string/stop"
android:icon="@drawable/ic_stop_black_24dp" android:icon="@drawable/ic_refresh_white_24dp"
app:showAsAction="always" app:showAsAction="always"
tools:ignore="AlwaysShowAction" /> tools:ignore="AlwaysShowAction" />

Loading…
Cancel
Save