diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97b4132cb..b33892324 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -257,7 +257,7 @@ android:launchMode="singleTop" /> () val sortUrl = sortUrl if (sortUrl.isNullOrEmpty()) { - sortMap["default"] = sourceUrl + sortMap[""] = sourceUrl } else { sortUrl.split("(&&|\n)+".toRegex()).forEach { c -> val d = c.split("::") diff --git a/app/src/main/java/io/legado/app/data/entities/RssStar.kt b/app/src/main/java/io/legado/app/data/entities/RssStar.kt index 1d0c266f8..94196416b 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssStar.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssStar.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/model/Rss.kt b/app/src/main/java/io/legado/app/model/Rss.kt index 24dadade0..8b29913d9 100644 --- a/app/src/main/java/io/legado/app/model/Rss.kt +++ b/app/src/main/java/io/legado/app/model/Rss.kt @@ -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 { 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) } } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParser.kt b/app/src/main/java/io/legado/app/model/rss/RssParser.kt index 7bda16e6d..09cd756bb 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParser.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParser.kt @@ -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() 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() } diff --git a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt index b06950031..a90b5e1fa 100644 --- a/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt +++ b/app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt @@ -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() 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) } 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 b960041d8..5bb42f081 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 @@ -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(Pair("url", rssSource.sourceUrl)) + startActivity(Pair("url", rssSource.sourceUrl)) } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt similarity index 51% rename from app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt rename to app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt index 31ac9cdda..02f94055c 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt @@ -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(R.layout.activity_rss_artivles), - RssArticlesViewModel.CallBack, +class RssArticlesFragment : VMBaseFragment(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>? = 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(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(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(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( Pair("title", rssArticle.title), Pair("origin", rssArticle.origin), 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 61a2b14be..6d551ca0e 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 @@ -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() + val loadFinally = MutableLiveData() var isLoading = true var order = System.currentTimeMillis() private var nextPageUrl: String? = null + private val articles = arrayListOf() + 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) - } } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt new file mode 100644 index 000000000..5914443c1 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt @@ -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(R.layout.activity_rss_artivles) { + + override val viewModel: RssSortViewModel + get() = getViewModel(RssSortViewModel::class.java) + private val editSource = 12319 + private val fragments = linkedMapOf() + 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(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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt new file mode 100644 index 000000000..4b1149f2e --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt @@ -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() + 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 { + + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_rss_artivles.xml b/app/src/main/res/layout/activity_rss_artivles.xml index 39678114e..ec29791de 100644 --- a/app/src/main/res/layout/activity_rss_artivles.xml +++ b/app/src/main/res/layout/activity_rss_artivles.xml @@ -1,6 +1,5 @@ - @@ -8,21 +7,16 @@ + android:layout_height="wrap_content" /> - + android:layout_height="wrap_content" /> - - - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_rss_articles.xml b/app/src/main/res/layout/fragment_rss_articles.xml new file mode 100644 index 000000000..5ca0c9574 --- /dev/null +++ b/app/src/main/res/layout/fragment_rss_articles.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file