Commit a2cb2c5e authored by Christophe Henry's avatar Christophe Henry

Database rework

parent e7f3fc10
......@@ -12,7 +12,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.api.Endpoints
import fr.chenry.android.freshrss.utils.cleanUrlSlashes
......
......@@ -7,11 +7,14 @@ import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionArticleBinding
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.store.Article
import fr.chenry.android.freshrss.store.database.models.Article
import fr.chenry.android.freshrss.store.database.models.Articles
class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>()
{
var articles: Articles = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
......@@ -22,7 +25,7 @@ class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val article = fragment.articles[position]
val article = articles[position]
holder.bind(article)
holder.binding.root.setOnClickListener{
Router.navigate(RouteName.SUBSCRIPTION_CONTENT_DETAIL,
......@@ -30,7 +33,7 @@ class RecyclerViewAdapter(private val fragment: SubscriptionArticlesFragment):
}
}
override fun getItemCount(): Int = fragment.articles.size
override fun getItemCount(): Int = articles.size
inner class ViewHolder(val binding: FragmentSubscriptionArticleBinding):
RecyclerView.ViewHolder(binding.root) {
......
......@@ -10,13 +10,13 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.ShareActionProvider
import androidx.core.view.MenuItemCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.*
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.store.Article
import fr.chenry.android.freshrss.store.dao.store.ReadStatus
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.SubscriptionArticlesVM
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.SubscriptionArticlesVMF
import fr.chenry.android.freshrss.utils.capitalizeFull
import fr.chenry.android.freshrss.utils.e
import kotlinx.android.synthetic.main.fragment_subscription_article_detail.*
......@@ -25,10 +25,14 @@ import java.util.regex.Pattern
import kotlin.text.RegexOption.IGNORE_CASE
class SubscriptionArticlesDetailFragment: Fragment() {
private lateinit var subscriptionContentsId: String
private lateinit var subscriptionArticleId: String
private lateinit var article: Article
private lateinit var articleId: String
private val model: SubscriptionArticlesVM by lazy {
ViewModelProviders
.of(this, SubscriptionArticlesVMF(articleId))
.get(SubscriptionArticlesVM::class.java)
}
private var isFetching = MutableLiveData<Boolean>().apply {value = false}
private val article: Article get() = model.liveData.value!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......@@ -36,11 +40,11 @@ class SubscriptionArticlesDetailFragment: Fragment() {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
subscriptionContentsId = arguments?.getString("streamId")!!
subscriptionArticleId = arguments?.getString("articleId")!!
articleId = arguments?.getString("articleId")!!
article = Store.getArticle(subscriptionContentsId, subscriptionArticleId).get()
activity.let {it as AppCompatActivity}.supportActionBar?.apply {subtitle = article.title}
activity
.let {activity -> activity as AppCompatActivity}.supportActionBar
?.apply {subtitle = article.title}
return inflater.inflate(R.layout.fragment_subscription_article_detail, container, false)
}
......@@ -107,8 +111,8 @@ class SubscriptionArticlesDetailFragment: Fragment() {
private fun setUpReadStatusButton(menu: Menu) {
menu.findItem(R.id.action_mark_read_status)?.let {
fun mutateUi(readStatus: ReadStatus) {
when(readStatus) {
fun mutateUi(article: Article) {
when(article.readStatus) {
ReadStatus.READ -> {
it.icon = context?.getDrawable(R.drawable.ic_is_read_24dp)
it.title = context?.getString(R.string.mark_unread)
......@@ -121,9 +125,9 @@ class SubscriptionArticlesDetailFragment: Fragment() {
it.isVisible = true
}
mutateUi(article.readStatus.value!!)
article.readStatus.observe(this, Observer(::mutateUi))
it.setOnMenuItemClickListener {setReadStatus(article.readStatus.value!!.toggle())}
mutateUi(article)
model.liveData.observe(this, Observer(::mutateUi))
it.setOnMenuItemClickListener {setReadStatus(article.readStatus.toggle())}
}
}
......@@ -146,7 +150,7 @@ class SubscriptionArticlesDetailFragment: Fragment() {
isFetching.value = true
Store.postReadStatus(article.streamId, article.id, readStatus)
Store.postReadStatus(article, readStatus)
.successUi {
Store.subscriptions[article.streamId]?.let {
it.unreadCount = when(readStatus) {
......
......@@ -7,44 +7,42 @@ import androidx.lifecycle.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.dao.store.Articles
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.models.Articles
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.*
import fr.chenry.android.freshrss.utils.*
sealed class SubscriptionArticlesFragment: Fragment() {
protected open lateinit var model: ArticlesViewModel
lateinit var streamId: String
protected set
val articles: Articles get() = model.articles
private set
private lateinit var model: LiveData<Articles>
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
streamId = arguments?.getString("id")!!
model.streamId.value = streamId
model = getModel(streamId)
return inflater.inflate(R.layout.fragment_subscription_articles, container, false)
.let {
it as RecyclerView
it.layoutManager = LinearLayoutManager(context)
it.adapter = RecyclerViewAdapter(this)
it.adapter = RecyclerViewAdapter(this).let {adapter ->
model.observe(this, Observer {articles ->
adapter.articles = articles
adapter.notifyDataSetChanged()
})
adapter
}
it
}
}
abstract fun getModel(streamId: StreamId): LiveData<Articles>
}
class SubscriptionAllArticlesFragment: SubscriptionArticlesFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(ArticlesViewModel::class.java)
}
}
override fun getModel(streamId: StreamId) = FreshRSSDabatabase.instance.getArticleByStreamId(streamId)
}
class SubscriptionUnreadArticlesFragment: SubscriptionArticlesFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.run {
model = ViewModelProviders.of(this).get(UnreadArticlesViewModel::class.java)
}
}
override fun getModel(streamId: StreamId) = FreshRSSDabatabase.instance.getArticleByStreamIdAndUnread(streamId)
}
......@@ -3,8 +3,8 @@ package fr.chenry.android.freshrss.components.subscriptions
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.api.models.Subscription
import fr.chenry.android.freshrss.store.api.models.Subscriptions
class AllSubscriptionsFragment: SubscriptionsFragment() {
override val liveData: LiveData<Subscriptions> = MutableLiveData<Subscriptions>().apply {
......
package fr.chenry.android.freshrss.components.subscriptions
import android.os.Bundle
import androidx.lifecycle.*
import fr.chenry.android.freshrss.store.RouteName
import fr.chenry.android.freshrss.store.Router
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.UnreadSubscriptionsViewModel
import fr.chenry.android.freshrss.store.api.models.Subscription
import fr.chenry.android.freshrss.store.api.models.Subscriptions
class FavoritesSubscriptionsFragment: SubscriptionsFragment() {
override val liveData = MutableLiveData<Subscriptions>().apply {this.value = listOf()}
......
......@@ -8,9 +8,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.utils.e
import fr.chenry.android.freshrss.store.api.models.Subscription
import fr.chenry.android.freshrss.store.api.models.Subscriptions
import fr.chenry.android.freshrss.utils.getOrDefault
import kotlinx.android.synthetic.main.fragment_main_subscription.*
import kotlin.reflect.KClass
......
......@@ -6,8 +6,7 @@ import androidx.recyclerview.widget.RecyclerView
import fr.chenry.android.freshrss.BR
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.databinding.FragmentSubscriptionBinding
import fr.chenry.android.freshrss.store.*
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.api.models.Subscription
class RecyclerViewAdapter(private val fragment: SubscriptionsFragment):
RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder>()
......
......@@ -4,8 +4,7 @@ import android.os.Bundle
import androidx.lifecycle.ViewModelProviders
import fr.chenry.android.freshrss.store.RouteName
import fr.chenry.android.freshrss.store.Router
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.api.models.Subscription
import fr.chenry.android.freshrss.store.databindingsupport.viewmodels.UnreadSubscriptionsViewModel
class UnreadSubscriptionsFragment: SubscriptionsFragment() {
......
......@@ -5,15 +5,18 @@ import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.R.string
import fr.chenry.android.freshrss.components.subscriptions.SubscriptionSection
import fr.chenry.android.freshrss.store.api.Api
import fr.chenry.android.freshrss.store.dao.Account
import fr.chenry.android.freshrss.store.dao.common.StreamId
import fr.chenry.android.freshrss.store.dao.common.Subscription
import fr.chenry.android.freshrss.store.dao.store.*
import fr.chenry.android.freshrss.store.database.models.Account
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.store.api.models.Subscription
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.store.database.models.ReadStatus.UNREAD
import fr.chenry.android.freshrss.store.databindingsupport.GenericLiveData
import fr.chenry.android.freshrss.utils.NotificationHelper
import fr.chenry.android.freshrss.utils.NotificationHelper.NotificationChanels.REFRESH
import fr.chenry.android.freshrss.utils.e
import nl.komponents.kovenant.*
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.functional.bind
import nl.komponents.kovenant.ui.failUi
import nl.komponents.kovenant.ui.successUi
......@@ -24,7 +27,6 @@ object Store {
var debugMode = false
val subscriptions = GenericLiveData<StreamId, Subscription>()
val totalUnreadCount = MutableLiveData<Int>()
val contentItems = GenericLiveData<StreamId, GenericLiveData<ItemId, Article>>()
val subscriptionsSection = MutableLiveData<SubscriptionSection>().apply {this.value = SubscriptionSection.ALL}
val tags = MutableLiveData<List<String>>().apply {this.value = listOf()}
val favorites = GenericLiveData<String, Subscription>(mutableMapOf())
......@@ -74,31 +76,41 @@ object Store {
api.getStreamItems(id) then {this.e("::getStreamItems: TODO"); Unit}
fun getStreamContents(id: String): Promise<Unit, Exception> =
api.getStreamContents(id) then {
contentItems[id] = GenericLiveData(it.items.map {item -> item.id to Article.fromContentItem(item)}.toMap())
contentItems.emit()
api.getStreamContents(id) bind {
val insertPromises = it.items.map {item ->
task {FreshRSSDabatabase.instance.insertArticle(Article.fromContentItem(item))}
}
all(insertPromises, cancelOthersOnError = false).toSuccessVoid()
}
fun getTags(): Promise<Unit, Exception> = api.getTags() then {tags.postValue(it)}
fun getUnreadItems(): Promise<Unit, Exception> =
api.getUnreadItems(lastFetchTimestamp) then {
val unreadItems = it.items.groupBy {c -> c.origin.streamId}
unreadItems.forEach {u ->
u.value.forEach {a -> contentItems[u.key]?.get(a.id)?.readStatus?.postValue(ReadStatus.UNREAD)}
api.getUnreadItems(lastFetchTimestamp) bind {
val promises = it.items.map {item ->
task {
FreshRSSDabatabase.instance.upsertArticle(Article.fromContentItem(item).copy(readStatus = UNREAD))
}
}
lastFetchTimestamp = System.currentTimeMillis() % 1000
all(promises, cancelOthersOnError = false).toSuccessVoid()
}
fun getArticle(streamId: String, articleId: String): Promise<Article, ArticleNotFoundException> {
val article = contentItems[streamId]?.get(articleId)
return if(article == null) Promise.ofFail(ArticleNotFoundException(articleId)) else Promise.ofSuccess(article)
fun getArticle(articleId: String): Promise<Article, ArticleNotFoundException> {
val deferred = deferred<Article, ArticleNotFoundException>()
task {
val articles = FreshRSSDabatabase.instance.getArticleById(articleId).blockingFirst()
if(articles.isEmpty()) deferred.reject(ArticleNotFoundException(articleId))
else deferred.resolve(articles.first())
}
return deferred.promise
}
fun postReadStatus(streamId: StreamId, itemId: ItemId, readStatus: ReadStatus): Promise<Unit, Exception> =
ensureToken()
.bind {api.postReadStatus(itemId, readStatus)}
.success {contentItems[streamId]?.get(itemId)?.readStatus?.postValue(readStatus)}
fun postReadStatus(article: Article, readStatus: ReadStatus): Promise<Unit, Exception> =
ensureToken() bind {
api.postReadStatus(article.id, readStatus)
.success { FreshRSSDabatabase.instance.upsertArticle(article.copy(readStatus = readStatus)) }
}
private fun ensureToken(): Promise<Unit, Exception> {
if(!FreshRSSDabatabase.instance.account.isWriteTokenExpired) return Promise.ofSuccess(Unit)
......
package fr.chenry.android.freshrss.store.api
import com.github.kittinunf.fuel.Fuel
import fr.chenry.android.freshrss.store.dao.Account
import fr.chenry.android.freshrss.store.dao.api.*
import fr.chenry.android.freshrss.store.dao.common.Subscriptions
import fr.chenry.android.freshrss.store.dao.common.SubscriptionsHandler
import fr.chenry.android.freshrss.store.dao.store.ItemId
import fr.chenry.android.freshrss.store.dao.store.ReadStatus
import fr.chenry.android.freshrss.store.api.models.*
import fr.chenry.android.freshrss.store.database.models.Account
import fr.chenry.android.freshrss.store.api.models.Subscriptions
import fr.chenry.android.freshrss.store.api.models.SubscriptionsHandler
import fr.chenry.android.freshrss.store.database.models.ItemId
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.utils.*
import nl.komponents.kovenant.*
import nl.komponents.kovenant.functional.bind
......
package fr.chenry.android.freshrss.store.dao.api
package fr.chenry.android.freshrss.store.api.models
import kotlinx.serialization.Optional
import kotlinx.serialization.Serializable
......
package fr.chenry.android.freshrss.store.dao.common
package fr.chenry.android.freshrss.store.api.models
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
......
package fr.chenry.android.freshrss.store.dao.api
package fr.chenry.android.freshrss.store.api.models
import fr.chenry.android.freshrss.utils.e
import com.github.kittinunf.fuel.core.ResponseDeserializable
......
package fr.chenry.android.freshrss.store.dao.api
package fr.chenry.android.freshrss.store.api.models
import kotlinx.serialization.*
import kotlinx.serialization.internal.SerialClassDescImpl
......
These DAO are one-to-one equivalents to JSON response of FreshRSS' HTTP API.
They are notnecessarily intended to be used in the store as they may require transformations.
\ No newline at end of file
This package contains DAO that may be used both in the store and for HTTP JSON responses deserializations
\ No newline at end of file
This package contains DAO that are inteded to be used in the store and in the application's DB (de)serialization.
They are not suitable for (de)serialize HTTP JSON responses.
\ No newline at end of file
package fr.chenry.android.freshrss.store.database
import androidx.room.TypeConverter
import fr.chenry.android.freshrss.store.database.models.ReadStatus
import fr.chenry.android.freshrss.store.database.models.ReadStatus.READ
import fr.chenry.android.freshrss.utils.escapeHtml4
import fr.chenry.android.freshrss.utils.unescapeHtml4
import java.lang.Exception
class Converters {
@TypeConverter
fun readStatusFromString(value: String?) = try {
ReadStatus.valueOf(value ?: READ.name)
} catch(_: Exception) {
READ
}
@TypeConverter
fun readStatusToString(liveData: ReadStatus) = liveData.name
@TypeConverter
fun listOfStringToString(list: List<String>) = list.map{it.escapeHtml4()}.joinToString(" ")
@TypeConverter
fun stringToListOfString(string: String) = string.split("\\s+".toRegex()).map {it.unescapeHtml4()}
}
\ No newline at end of file
package fr.chenry.android.freshrss.store
package fr.chenry.android.freshrss.store.database
import android.database.sqlite.SQLiteConstraintException
import androidx.room.*
import fr.chenry.android.freshrss.FreshRSSApplication
import fr.chenry.android.freshrss.R
import fr.chenry.android.freshrss.store.dao.Account
import fr.chenry.android.freshrss.store.dao.AuthTokensDAO
import fr.chenry.android.freshrss.store.api.models.StreamId
import fr.chenry.android.freshrss.store.database.models.*
import fr.chenry.android.freshrss.store.database.models.ReadStatus.READ
import fr.chenry.android.freshrss.store.database.models.ReadStatus.UNREAD
import fr.chenry.android.freshrss.utils.getOrDefault
import nl.komponents.kovenant.task
import kotlin.reflect.KProperty
@Database(version = 1, entities = [Account::class], exportSchema = false)
@Database(version = 1, entities = [Account::class, Article::class], exportSchema = false)
@TypeConverters(Converters::class)
abstract class FreshRSSDabatabase: RoomDatabase() {
private val authTokensDelegate = AuthTokensDelegate()
var account: Account by authTokensDelegate
val authTokensExistInDB get() = authTokensDelegate.isInitialized
protected abstract fun getAuthTokensDAO(): AuthTokensDAO
protected abstract fun getArticlesDAO(): ArticlesDAO
fun getArticleByStreamId(streamId: StreamId) = getArticlesDAO().getByStreamId(streamId)
fun getArticleById(id: ItemId) = getArticlesDAO().getById(id)
fun getArticleByStreamIdAndUnread(streamId: StreamId) =
getArticlesDAO().getByStreamIdAndUnread(streamId, ReadStatus.UNREAD.name)
fun upsertArticle(article: Article) =
try {getArticlesDAO().update(article)} catch(_: SQLiteConstraintException) {getArticlesDAO().insert(article)}
fun insertArticle(article: Article) = getArticlesDAO().forceInsert(article)
companion object {
private val dbName by lazy {
......@@ -23,7 +36,9 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
}
val instance by lazy {
val instance =
Room.databaseBuilder(FreshRSSApplication.context, FreshRSSDabatabase::class.java, dbName).build()
Room.databaseBuilder(FreshRSSApplication.context, FreshRSSDabatabase::class.java, dbName)
.fallbackToDestructiveMigration()
.build()
instance.authTokensDelegate.fetchAuthtokensFromDB()
instance
}
......@@ -48,4 +63,4 @@ abstract class FreshRSSDabatabase: RoomDatabase() {
}
}
}
}
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.dao
package fr.chenry.android.freshrss.store.database.models
import androidx.room.*
import com.github.kittinunf.fuel.core.ResponseDeserializable
......@@ -27,12 +27,13 @@ data class Account(
val canPostRequests get() = writeToken.isNotBlank()
companion object: ResponseDeserializable<Account> {
override fun deserialize(content: String): fr.chenry.android.freshrss.store.dao.Account {
override fun deserialize(content: String): Account {
val properties = Properties()
properties.load(content.reader())
return Account(
properties.getProperty("SID", ""),
properties.getProperty("Auth", ""))
properties.getProperty("Auth", "")
)
}
}
}
......
package fr.chenry.android.freshrss.store.dao.store
package fr.chenry.android.freshrss.store.database.models
import android.net.Uri
import androidx.databinding.BaseObservable
import androidx.lifecycle.MutableLiveData
import androidx.room.Ignore
import fr.chenry.android.freshrss.store.dao.api.ContentItem
import fr.chenry.android.freshrss.store.dao.common.StreamId
import androidx.lifecycle.LiveData
import androidx.room.*
import fr.chenry.android.freshrss.store.api.models.ContentItem
import fr.chenry.android.freshrss.store.api.models.StreamId
import io.reactivex.Flowable
typealias ItemId = String
typealias Articles = List<Article>
@Entity(tableName = "articles")
data class Article(
@PrimaryKey
val id: String,
val title: String,
val href: String,
val categories: List<String>,
val author: String,
val content: String,
val streamId: StreamId
val streamId: StreamId,
val readStatus: ReadStatus = ReadStatus.READ
): BaseObservable() {
@Ignore
val readStatus = MutableLiveData<ReadStatus>().apply {this.postValue(ReadStatus.READ)}
@Ignore
val url = try { Uri.parse(href) } catch(_: Throwable) {null}
val url = try {
Uri.parse(href)
} catch(_: Throwable) {
null
}
companion object {
fun fromContentItem(item: ContentItem) = Article(
......@@ -44,4 +51,28 @@ enum class ReadStatus {
READ -> UNREAD
UNREAD -> READ
}
}
@Dao
interface ArticlesDAO {
@Insert(onConflict = OnConflictStrategy.FAIL)
fun insert(article: Article)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun forceInsert(article: Article)
@Update(onConflict = OnConflictStrategy.REPLACE)
fun update(article: Article)
@Query("SELECT * FROM articles")
fun getAll(): LiveData<Articles>
@Query("SELECT * FROM articles WHERE streamId = :streamId")
fun getByStreamId(streamId: StreamId): LiveData<Articles>
@Query("SELECT * FROM articles WHERE id = :id")
fun getById(id: ItemId): Flowable<Articles>
@Query("SELECT * FROM articles WHERE streamId = :streamId AND readStatus = :readStatus")
fun getByStreamIdAndUnread(streamId: StreamId, readStatus: String = ReadStatus.UNREAD.name): LiveData<Articles>
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.databindingsupport.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.store.Store
import fr.chenry.android.freshrss.store.dao.store.Articles
import fr.chenry.android.freshrss.utils.e
import fr.chenry.android.freshrss.utils.w
open class ArticlesViewModel: ViewModel() {
val streamId: MutableLiveData<String> = MutableLiveData<String>().apply {this.value = ""}
protected val liveData: MutableLiveData<Articles> by lazy {
MutableLiveData<Articles>().apply {
this.value = load()
Store.subscriptions.observeForever {this.value = load()}
streamId.observeForever {this.value = load()}
}
}
open val articles: Articles get() = liveData.value ?: listOf()
protected open fun load(): Articles {
val subscription = Store.subscriptions[streamId.value!!]
if(subscription == null) {
this.w("Unable to find unread articles for stream id ${streamId.value}")
return listOf()
}
return Store.contentItems[streamId.value!!]?.values ?: listOf()
}
}
\ No newline at end of file
package fr.chenry.android.freshrss.store.databindingsupport.viewmodels
import androidx.lifecycle.*
import fr.chenry.android.freshrss.store.database.FreshRSSDabatabase
import fr.chenry.android.freshrss.store.database.models.*