feat: 优化代码

pull/182/head
kunfei 5 years ago
parent 06fcfc94b6
commit 1cd3bc1f69
  1. 2
      app/src/main/AndroidManifest.xml
  2. 2
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  3. 2
      app/src/main/java/io/legado/app/data/entities/RssArticle.kt
  4. 2
      app/src/main/java/io/legado/app/data/entities/RssSource.kt
  5. 2
      app/src/main/java/io/legado/app/data/entities/RssStar.kt
  6. 6
      app/src/main/java/io/legado/app/model/Rss.kt
  7. 3
      app/src/main/java/io/legado/app/model/rss/RssParser.kt
  8. 7
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  9. 4
      app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt
  10. 104
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt
  11. 123
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  12. 102
      app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt
  13. 52
      app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt
  14. 26
      app/src/main/res/layout/activity_rss_artivles.xml
  15. 15
      app/src/main/res/layout/fragment_rss_articles.xml

@ -257,7 +257,7 @@
android:launchMode="singleTop" />
<!--订阅条目-->
<activity
android:name=".ui.rss.article.RssArticlesActivity"
android:name=".ui.rss.article.RssSortActivity"
android:launchMode="singleTop" />
<!--Rss收藏-->
<activity

@ -18,7 +18,7 @@ import kotlinx.coroutines.launch
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,
RssStar::class, TxtTocRule::class],
version = 9,
version = 10,
exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {

@ -9,6 +9,7 @@ import androidx.room.Entity
)
data class RssArticle(
var origin: String = "",
var sort: String = "",
var title: String = "",
var order: Long = 0,
var link: String = "",
@ -36,6 +37,7 @@ data class RssArticle(
fun toStar(): RssStar {
return RssStar(
origin = origin,
sort = sort,
title = title,
starTime = System.currentTimeMillis(),
link = link,

@ -104,7 +104,7 @@ data class RssSource(
val sortMap = linkedMapOf<String, String>()
val sortUrl = sortUrl
if (sortUrl.isNullOrEmpty()) {
sortMap["default"] = sourceUrl
sortMap[""] = sourceUrl
} else {
sortUrl.split("(&&|\n)+".toRegex()).forEach { c ->
val d = c.split("::")

@ -9,6 +9,7 @@ import androidx.room.Entity
)
data class RssStar(
var origin: String = "",
var sort: String = "",
var title: String = "",
var starTime: Long = 0,
var link: String = "",
@ -20,6 +21,7 @@ data class RssStar(
fun toRssArticle(): RssArticle {
return RssArticle(
origin = origin,
sort = sort,
title = title,
link = link,
pubDate = pubDate,

@ -15,6 +15,8 @@ import kotlin.coroutines.CoroutineContext
object Rss {
fun getArticles(
sortName: String,
sortUrl: String,
rssSource: RssSource,
pageUrl: String? = null,
scope: CoroutineScope = Coroutine.DEFAULT,
@ -22,11 +24,11 @@ object Rss {
): Coroutine<Result> {
return Coroutine.async(scope, context) {
val analyzeUrl = AnalyzeUrl(
pageUrl ?: rssSource.sourceUrl,
pageUrl ?: sortUrl,
headerMapF = rssSource.getHeaderMap()
)
val body = analyzeUrl.getResponseAwait(rssSource.sourceUrl).body
RssParserByRule.parseXML(body, rssSource)
RssParserByRule.parseXML(sortName, sortUrl, body, rssSource)
}
}

@ -11,7 +11,7 @@ import java.io.StringReader
object RssParser {
@Throws(XmlPullParserException::class, IOException::class)
fun parseXML(xml: String, sourceUrl: String): Result {
fun parseXML(sortName: String, xml: String, sourceUrl: String): Result {
val articleList = mutableListOf<RssArticle>()
var currentArticle = RssArticle()
@ -87,6 +87,7 @@ object RssParser {
// The item is correctly parsed
insideItem = false
currentArticle.origin = sourceUrl
currentArticle.sort = sortName
articleList.add(currentArticle)
currentArticle = RssArticle()
}

@ -13,7 +13,7 @@ import io.legado.app.utils.NetworkUtils
object RssParserByRule {
@Throws(Exception::class)
fun parseXML(body: String?, rssSource: RssSource): Result {
fun parseXML(sortName: String, sortUrl: String, body: String?, rssSource: RssSource): Result {
val sourceUrl = rssSource.sourceUrl
var nextUrl: String? = null
if (body.isNullOrBlank()) {
@ -28,11 +28,11 @@ object RssParserByRule {
var ruleArticles = rssSource.ruleArticles
if (ruleArticles.isNullOrBlank()) {
Debug.log(sourceUrl, "⇒列表规则为空, 使用默认规则解析")
return RssParser.parseXML(body, sourceUrl)
return RssParser.parseXML(sortName, body, sourceUrl)
} else {
val articleList = mutableListOf<RssArticle>()
val analyzeRule = AnalyzeRule()
analyzeRule.setContent(body, rssSource.sourceUrl)
analyzeRule.setContent(body, sortUrl)
var reverse = false
if (ruleArticles.startsWith("-")) {
reverse = true
@ -59,6 +59,7 @@ object RssParserByRule {
sourceUrl, item, analyzeRule, index == 0,
ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink
)?.let {
it.sort = sortName
it.origin = rssSource.sourceUrl
articleList.add(it)
}

@ -12,7 +12,7 @@ import io.legado.app.base.BaseFragment
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.article.RssSortActivity
import io.legado.app.ui.rss.favorites.RssFavoritesActivity
import io.legado.app.ui.rss.source.manage.RssSourceActivity
import io.legado.app.utils.getViewModelOfActivity
@ -60,6 +60,6 @@ class RssFragment : BaseFragment(R.layout.fragment_rss),
}
override fun openRss(rssSource: RssSource) {
startActivity<RssArticlesActivity>(Pair("url", rssSource.sourceUrl))
startActivity<RssSortActivity>(Pair("url", rssSource.sourceUrl))
}
}

@ -1,83 +1,67 @@
package io.legado.app.ui.rss.article
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.App
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.base.VMBaseFragment
import io.legado.app.data.entities.RssArticle
import io.legado.app.lib.theme.ATH
import io.legado.app.ui.rss.read.ReadRssActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.ui.widget.recycler.LoadMoreView
import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.getViewModel
import kotlinx.android.synthetic.main.activity_rss_artivles.*
import io.legado.app.utils.getViewModelOfActivity
import io.legado.app.utils.startActivity
import kotlinx.android.synthetic.main.fragment_rss_articles.*
import kotlinx.android.synthetic.main.view_load_more.view.*
import kotlinx.android.synthetic.main.view_refresh_recycler.*
import org.jetbrains.anko.startActivity
import org.jetbrains.anko.startActivityForResult
class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activity_rss_artivles),
RssArticlesViewModel.CallBack,
class RssArticlesFragment : VMBaseFragment<RssArticlesViewModel>(R.layout.fragment_rss_articles),
RssArticlesAdapter.CallBack {
companion object {
fun create(sortName: String, sortUrl: String): RssArticlesFragment {
return RssArticlesFragment().apply {
val bundle = Bundle()
bundle.putString("sortName", sortName)
bundle.putString("sortUrl", sortUrl)
arguments = bundle
}
}
}
private val activityViewModel: RssSortViewModel
get() = getViewModelOfActivity(RssSortViewModel::class.java)
override val viewModel: RssArticlesViewModel
get() = getViewModel(RssArticlesViewModel::class.java)
override lateinit var adapter: RssArticlesAdapter
private val editSource = 12319
lateinit var adapter: RssArticlesAdapter
private lateinit var loadMoreView: LoadMoreView
private var rssArticlesData: LiveData<List<RssArticle>>? = null
override fun onActivityCreated(savedInstanceState: Bundle?) {
viewModel.callBack = this
viewModel.titleLiveData.observe(this, Observer {
title_bar.title = it
})
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
viewModel.init(arguments)
initView()
viewModel.initData(intent) {
initData()
refresh_recycler_view.startLoading()
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.rss_articles, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let {
startActivityForResult<RssSourceEditActivity>(editSource, Pair("data", it))
}
R.id.menu_clear -> {
viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true
viewModel.clearArticles()
}
}
}
return super.onCompatOptionsItemSelected(item)
refresh_recycler_view.startLoading()
initView()
initData()
}
private fun initView() {
ATH.applyEdgeEffectColor(recycler_view)
recycler_view.layoutManager = LinearLayoutManager(this)
recycler_view.addItemDecoration(VerticalDivider(this))
adapter = RssArticlesAdapter(this, this)
recycler_view.layoutManager = LinearLayoutManager(requireContext())
recycler_view.addItemDecoration(VerticalDivider(requireContext()))
adapter = RssArticlesAdapter(requireContext(), this)
recycler_view.adapter = adapter
loadMoreView = LoadMoreView(this)
loadMoreView = LoadMoreView(requireContext())
adapter.addFooterView(loadMoreView)
refresh_recycler_view.onRefreshStart = {
viewModel.url?.let {
viewModel.loadContent()
activityViewModel.rssSource?.let {
viewModel.loadContent(it)
}
}
recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {
@ -91,10 +75,10 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
}
private fun initData() {
viewModel.url?.let {
activityViewModel.url?.let {
rssArticlesData?.removeObservers(this)
rssArticlesData = App.db.rssArticleDao().liveByOrigin(it)
rssArticlesData?.observe(this, Observer { list ->
rssArticlesData?.observe(viewLifecycleOwner, Observer { list ->
adapter.setItems(list)
})
}
@ -104,21 +88,25 @@ class RssArticlesActivity : VMBaseActivity<RssArticlesViewModel>(R.layout.activi
if (viewModel.isLoading) return
if (loadMoreView.hasMore && adapter.getActualItemCount() > 0) {
loadMoreView.rotate_loading.show()
viewModel.loadMore()
activityViewModel.rssSource?.let {
viewModel.loadMore(it)
}
}
}
override fun loadFinally(hasMore: Boolean) {
refresh_recycler_view.stopLoading()
if (hasMore) {
loadMoreView.startLoad()
} else {
loadMoreView.noMore()
}
override fun observeLiveBus() {
viewModel.loadFinally.observe(viewLifecycleOwner, Observer {
refresh_recycler_view.stopLoading()
if (it) {
loadMoreView.startLoad()
} else {
loadMoreView.noMore()
}
})
}
override fun readRss(rssArticle: RssArticle) {
viewModel.read(rssArticle)
activityViewModel.read(rssArticle)
startActivity<ReadRssActivity>(
Pair("title", rssArticle.title),
Pair("origin", rssArticle.origin),

@ -1,124 +1,85 @@
package io.legado.app.ui.rss.article
import android.app.Application
import android.content.Intent
import android.os.Bundle
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
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class RssArticlesViewModel(application: Application) : BaseViewModel(application) {
var callBack: CallBack? = null
var url: String? = null
var rssSource: RssSource? = null
val titleLiveData = MutableLiveData<String>()
val loadFinally = MutableLiveData<Boolean>()
var isLoading = true
var order = System.currentTimeMillis()
private var nextPageUrl: String? = null
private val articles = arrayListOf<RssArticle>()
var sortName: String = ""
var sortUrl: String = ""
fun initData(intent: Intent, finally: () -> Unit) {
execute {
url = intent.getStringExtra("url")
url?.let { url ->
rssSource = App.db.rssSourceDao().getByKey(url)
rssSource?.let {
titleLiveData.postValue(it.sourceName)
} ?: let {
rssSource = RssSource(sourceUrl = url)
}
}
}.onFinally {
finally()
fun init(bundle: Bundle?) {
bundle?.let {
sortName = it.getString("sortName") ?: ""
sortUrl = it.getString("sortUrl") ?: ""
}
}
fun loadContent() {
fun loadContent(rssSource: RssSource) {
isLoading = true
rssSource?.let { rssSource ->
Rss.getArticles(rssSource, null)
.onSuccess(IO) {
nextPageUrl = it.nextPageUrl
it.articles.let { list ->
list.forEach { rssArticle ->
rssArticle.order = order--
}
App.db.rssArticleDao().insert(*list.toTypedArray())
if (!rssSource.ruleNextPage.isNullOrEmpty()) {
App.db.rssArticleDao().clearOld(url!!, order)
withContext(Main) {
callBack?.loadFinally(true)
}
} else {
withContext(Main) {
callBack?.loadFinally(false)
}
Rss.getArticles(sortName, sortUrl, rssSource, null)
.onSuccess(Dispatchers.IO) {
nextPageUrl = it.nextPageUrl
it.articles.let { list ->
list.forEach { rssArticle ->
rssArticle.order = order--
}
App.db.rssArticleDao().insert(*list.toTypedArray())
if (!rssSource.ruleNextPage.isNullOrEmpty()) {
App.db.rssArticleDao().clearOld(rssSource.sourceUrl, order)
loadFinally.postValue(true)
} else {
withContext(Dispatchers.Main) {
loadFinally.postValue(false)
}
isLoading = false
}
}.onError {
toast(it.localizedMessage)
isLoading = false
}
}
}.onError {
toast(it.localizedMessage)
}
}
fun loadMore() {
fun loadMore(rssSource: RssSource) {
isLoading = true
val source = rssSource
val pageUrl = nextPageUrl
if (source != null && !pageUrl.isNullOrEmpty()) {
Rss.getArticles(source, pageUrl)
.onSuccess(IO) {
if (!pageUrl.isNullOrEmpty()) {
Rss.getArticles(sortName, sortUrl, rssSource, pageUrl)
.onSuccess(Dispatchers.IO) {
nextPageUrl = it.nextPageUrl
it.articles.let { list ->
if (list.isEmpty()) {
callBack?.loadFinally(false)
loadFinally.postValue(true)
return@let
}
callBack?.adapter?.getItems()?.let { adapterItems ->
if (adapterItems.contains(list.first())) {
callBack?.loadFinally(false)
} else {
list.forEach { rssArticle ->
rssArticle.order = order--
}
App.db.rssArticleDao().insert(*list.toTypedArray())
if (articles.contains(list.first())) {
loadFinally.postValue(false)
} else {
list.forEach { rssArticle ->
rssArticle.order = order--
}
App.db.rssArticleDao().insert(*list.toTypedArray())
}
}
isLoading = false
}
} else {
callBack?.loadFinally(false)
loadFinally.postValue(false)
}
}
fun read(rssArticle: RssArticle) {
execute {
App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link))
}
}
fun clearArticles() {
execute {
url?.let {
App.db.rssArticleDao().delete(it)
}
order = System.currentTimeMillis()
}.onSuccess {
loadContent()
}
}
interface CallBack {
var adapter: RssArticlesAdapter
fun loadFinally(hasMore: Boolean)
}
}

@ -0,0 +1,102 @@
package io.legado.app.ui.rss.article
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.lifecycle.Observer
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
import io.legado.app.ui.rss.source.edit.RssSourceEditActivity
import io.legado.app.utils.getViewModel
import io.legado.app.utils.gone
import io.legado.app.utils.visible
import kotlinx.android.synthetic.main.activity_rss_artivles.*
import kotlinx.android.synthetic.main.view_refresh_recycler.*
import org.jetbrains.anko.startActivityForResult
class RssSortActivity : VMBaseActivity<RssSortViewModel>(R.layout.activity_rss_artivles) {
override val viewModel: RssSortViewModel
get() = getViewModel(RssSortViewModel::class.java)
private val editSource = 12319
private val fragments = linkedMapOf<String, RssArticlesFragment>()
private lateinit var adapter: TabFragmentPageAdapter
override fun onActivityCreated(savedInstanceState: Bundle?) {
adapter = TabFragmentPageAdapter(supportFragmentManager)
tab_layout.setupWithViewPager(view_pager)
view_pager.adapter = adapter
viewModel.titleLiveData.observe(this, Observer {
title_bar.title = it
})
viewModel.initData(intent) {
upFragments()
}
}
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.rss_articles, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let {
startActivityForResult<RssSourceEditActivity>(editSource, Pair("data", it))
}
R.id.menu_clear -> {
viewModel.url?.let {
refresh_progress_bar.isAutoLoading = true
viewModel.clearArticles()
}
}
}
return super.onCompatOptionsItemSelected(item)
}
private fun upFragments() {
fragments.clear()
viewModel.rssSource?.sortUrls()?.forEach {
fragments[it.key] = RssArticlesFragment.create(it.key, it.value)
}
if (fragments.size == 1) {
tab_layout.gone()
} else {
tab_layout.visible()
}
adapter.notifyDataSetChanged()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
editSource -> if (resultCode == Activity.RESULT_OK) {
viewModel.initData(intent) {
upFragments()
}
}
}
}
private inner class TabFragmentPageAdapter internal constructor(fm: FragmentManager) :
FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getPageTitle(position: Int): CharSequence? {
return fragments.keys.elementAt(position)
}
override fun getItem(position: Int): Fragment {
return fragments.values.elementAt(position)
}
override fun getCount(): Int {
return fragments.size
}
}
}

@ -0,0 +1,52 @@
package io.legado.app.ui.rss.article
import android.app.Application
import android.content.Intent
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
class RssSortViewModel(application: Application) : BaseViewModel(application) {
var url: String? = null
var rssSource: RssSource? = null
val titleLiveData = MutableLiveData<String>()
var order = System.currentTimeMillis()
fun initData(intent: Intent, finally: () -> Unit) {
execute {
url = intent.getStringExtra("url")
url?.let { url ->
rssSource = App.db.rssSourceDao().getByKey(url)
rssSource?.let {
titleLiveData.postValue(it.sourceName)
} ?: let {
rssSource = RssSource(sourceUrl = url)
}
}
}.onFinally {
finally()
}
}
fun read(rssArticle: RssArticle) {
execute {
App.db.rssArticleDao().insertRecord(RssReadRecord(rssArticle.link))
}
}
fun clearArticles() {
execute {
url?.let {
App.db.rssArticleDao().delete(it)
}
order = System.currentTimeMillis()
}.onSuccess {
}
}
}

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -8,21 +7,16 @@
<io.legado.app.ui.widget.TitleBar
android:id="@+id/title_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent" />
android:layout_height="wrap_content" />
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout
android:id="@+id/content_view"
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_bar">
android:layout_height="wrap_content" />
<io.legado.app.ui.widget.recycler.RefreshRecyclerView
android:id="@+id/refresh_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/content_view"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/title_bar">
<io.legado.app.ui.widget.recycler.RefreshRecyclerView
android:id="@+id/refresh_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>
Loading…
Cancel
Save