Use /sources/stats in the home #133
@ -49,7 +49,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
|
||||
super.onResume()
|
||||
val mLayoutManager = LinearLayoutManager(this)
|
||||
|
||||
var items: ArrayList<SelfossModel.Source>
|
||||
var items: ArrayList<SelfossModel.SourceDetail>
|
||||
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.layoutManager = mLayoutManager
|
||||
|
@ -24,7 +24,7 @@ import org.kodein.di.instance
|
||||
|
||||
class UpsertSourceActivity : AppCompatActivity(), DIAware {
|
||||
|
||||
private var existingSource: SelfossModel.Source? = null
|
||||
private var existingSource: SelfossModel.SourceDetail? = null
|
||||
private var mSpoutsValue: String? = null
|
||||
|
||||
private lateinit var binding: ActivityUpsertSourceBinding
|
||||
|
@ -31,7 +31,7 @@ import org.kodein.di.instance
|
||||
|
||||
class SourcesListAdapter(
|
||||
private val app: Activity,
|
||||
private val items: ArrayList<SelfossModel.Source>
|
||||
private val items: ArrayList<SelfossModel.SourceDetail>
|
||||
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
|
||||
private val c: Context = app.baseContext
|
||||
private val generator: ColorGenerator = ColorGenerator.MATERIAL
|
||||
|
@ -84,7 +84,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
|
||||
) {
|
||||
val sourceGroup = binding.sourcesGroup
|
||||
|
||||
repository.getSourcesStats().forEach { source ->
|
||||
repository.getSourcesDetailsOrStats().forEach { source ->
|
||||
val c = Chip(context)
|
||||
c.ellipsize = TextUtils.TruncateAt.END
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package bou.amine.apps.readerforselfossv2.model
|
||||
|
||||
import bou.amine.apps.readerforselfossv2.dao.SOURCE
|
||||
import bou.amine.apps.readerforselfossv2.utils.DateUtils
|
||||
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
|
||||
import kotlinx.serialization.KSerializer
|
||||
@ -63,70 +64,117 @@ class SelfossModel {
|
||||
fun isPublicModeEnabled() = publicMode ?: false
|
||||
}
|
||||
|
||||
data class Source(
|
||||
val id: Int,
|
||||
AmineB marked this conversation as resolved
|
||||
val title: String,
|
||||
var unread: Int?,
|
||||
var tags: List<String>?,
|
||||
var spout: String?,
|
||||
var error: String?,
|
||||
var icon: String?,
|
||||
var params: SourceParams?
|
||||
) {
|
||||
interface Source {
|
||||
val id: Int
|
||||
var title: String
|
||||
var unread: Int?
|
||||
var error: String?
|
||||
var icon: String?
|
||||
|
||||
constructor(sourceDetail: SourceDetail) : this(
|
||||
id = sourceDetail.id,
|
||||
title = sourceDetail.title,
|
||||
unread = null,
|
||||
tags = sourceDetail.tags,
|
||||
spout = sourceDetail.spout,
|
||||
error = sourceDetail.error,
|
||||
icon = sourceDetail.icon,
|
||||
params = sourceDetail.params
|
||||
)
|
||||
|
||||
constructor(sourceStat: SourceStats) : this(
|
||||
id = sourceStat.id,
|
||||
title = sourceStat.title,
|
||||
unread = sourceStat.unread,
|
||||
tags = null,
|
||||
spout = null,
|
||||
error = null,
|
||||
icon = null,
|
||||
params = null
|
||||
)
|
||||
fun checkSameSource(source: Source): Boolean {
|
||||
AmineB marked this conversation as resolved
AmineB
commented
I don't understand why this is needed ? I don't understand why this is needed ?
|
||||
return this.id != source.id
|
||||
}
|
||||
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This should be an extension function This should be an extension function `fun SourceDetail.toSource(): Source`
AmineB
commented
Ignore this. Ignore this.
|
||||
fun update(source: Source) {
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This should be an "abstract" method. This should be an "abstract" method.
|
||||
if (this.id != source.id || this.title != source.title) {
|
||||
if (source is SourceDetail) {
|
||||
this.update(source)
|
||||
} else if (source is SourceStats) {
|
||||
this.update(source)
|
||||
}
|
||||
}
|
||||
fun update(source: SourceStats)
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This should not be here. This should not be here.
|
||||
|
||||
fun update(source: SourceDetail)
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This should not be here. This should not be here.
|
||||
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This should be an extension function This should be an extension function `fun SourceStats.toSource(): Source`
AmineB
commented
Ignore this. Ignore this.
|
||||
fun toEntity(): SOURCE
|
||||
|
||||
operator fun component1(): Int { return id }
|
||||
AmineB marked this conversation as resolved
AmineB
commented
What are these two methods ? What are these two methods ?
|
||||
operator fun component2(): String { return title }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SourceStats(
|
||||
override val id: Int,
|
||||
override var title: String,
|
||||
override var unread: Int?,
|
||||
override var error: String? = null,
|
||||
override var icon: String? = null
|
||||
) : Source {
|
||||
override fun update(source: SourceStats) {
|
||||
if (checkSameSource(source)) {
|
||||
throw Exception()
|
||||
}
|
||||
this.title = source.title
|
||||
this.unread = source.unread
|
||||
}
|
||||
|
||||
override fun update(source: SourceDetail) {
|
||||
AmineB marked this conversation as resolved
AmineB
commented
`SourceStats` class should not contain `SourceDetail` update.
|
||||
if (checkSameSource(source)) {
|
||||
throw Exception()
|
||||
}
|
||||
this.title = source.title
|
||||
this.error = source.error
|
||||
this.icon = source.icon
|
||||
}
|
||||
|
||||
override fun toEntity(): SOURCE {
|
||||
return SOURCE(
|
||||
this.id.toString(),
|
||||
this.title.getHtmlDecoded(),
|
||||
null,
|
||||
null,
|
||||
this.error,
|
||||
this.icon,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SourceDetail(
|
||||
override val id: Int,
|
||||
override var title: String,
|
||||
override var unread: Int? = null,
|
||||
@Serializable(with = TagsListSerializer::class)
|
||||
var tags: List<String>?,
|
||||
var spout: String?,
|
||||
override var error: String?,
|
||||
override var icon: String?,
|
||||
var params: SourceParams?
|
||||
) : Source {
|
||||
override fun update(source: SourceStats) {
|
||||
AmineB marked this conversation as resolved
AmineB
commented
`SourceDetail` should not contain `SourceStats` update
|
||||
if (checkSameSource(source)) {
|
||||
throw Exception()
|
||||
}
|
||||
this.title = source.title
|
||||
this.unread = source.unread
|
||||
}
|
||||
|
||||
override fun update(source: SourceDetail) {
|
||||
if (checkSameSource(source)) {
|
||||
throw Exception()
|
||||
}
|
||||
this.title = source.title
|
||||
this.tags = source.tags
|
||||
this.spout = source.spout
|
||||
this.error = source.error
|
||||
this.icon = source.icon
|
||||
this.params = source.params
|
||||
}
|
||||
|
||||
override fun toEntity(): SOURCE {
|
||||
return SOURCE(
|
||||
this.id.toString(),
|
||||
this.title.getHtmlDecoded(),
|
||||
this.tags?.joinToString(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon.orEmpty(),
|
||||
this.params?.url
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SourceStats(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
val unread: Int
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class SourceDetail(
|
||||
val id: Int,
|
||||
val title: String,
|
||||
@Serializable(with = TagsListSerializer::class)
|
||||
val tags: List<String>,
|
||||
val spout: String,
|
||||
val error: String,
|
||||
val icon: String?,
|
||||
val params: SourceParams?
|
||||
)
|
||||
@Serializable
|
||||
data class SourceParams(
|
||||
val url: String
|
||||
|
@ -44,12 +44,11 @@ class Repository(
|
||||
private val _badgeStarred = MutableStateFlow(0)
|
||||
val badgeStarred = _badgeStarred.asStateFlow()
|
||||
|
||||
private var sources = ArrayList<SelfossModel.Source>()
|
||||
|
||||
private var fetchedTags = false
|
||||
AmineB marked this conversation as resolved
AmineB
commented
This shouldn't be changed/added. This shouldn't be changed/added.
|
||||
private var fetchedSources = false
|
||||
|
||||
private var _readerItems = ArrayList<SelfossModel.Item>()
|
||||
private var _selectedSource: SelfossModel.Source? = null
|
||||
private var _selectedSource: SelfossModel.SourceDetail? = null
|
||||
|
||||
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
|
||||
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
|
||||
@ -181,47 +180,62 @@ class Repository(
|
||||
}
|
||||
}
|
||||
|
||||
AmineB marked this conversation as resolved
AmineB
commented
There should be two methods:
There should be two methods:
```
suspend fun getSourceDetailsOrStats(): ArrayList<SelfossModel.Source> {
if (IS PUBLIC MODE) {
return api.sourcesStats()
} else {
copy/past the content of the old `getSources()` method
}
}
suspend fun getSourcesDetails(): List<SelfossModel.SourceDetail> { // This will be called in the sources activity
if (isNetworkAvailable()) {
return api.sourcesDetailed().data.orEmpty()
}
return emptyList<>()
}
```
davidoskky
commented
I'm not sure about this. Doing so is fine only as long as we don't care about the unread count. That is only available in the stats. I'm not sure about this. Doing so is fine only as long as we don't care about the unread count. That is only available in the stats.
If we don't plan on displaying the unread count, this is fine; otherwise the amount of unread articles should be available in the home activity.
AmineB
commented
Unread count was complicated code-wise. We have no idea if the feature was used/useful, so for now, let's not add it back. Unread count was complicated code-wise. We have no idea if the feature was used/useful, so for now, let's not add it back.
davidoskky
commented
I did find the tags unread count useful. I will not add this here; we can always implement it later. I did find the tags unread count useful. I will not add this here; we can always implement it later.
|
||||
private fun updateSources(newSources: ArrayList<SelfossModel.Source>) {
|
||||
private fun updateSources(newSources: List<SelfossModel.Source>): List<SelfossModel.Source> {
|
||||
val isDatabaseEnabled =
|
||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||
val newIds = newSources.map {it.id}
|
||||
val filteredSources = sources.filterNot { it.id in newIds }
|
||||
for (source in filteredSources) {
|
||||
val newSource = newSources.find { it.id == source.id }
|
||||
source.update(newSource!!)
|
||||
}
|
||||
sources = (filteredSources + newSources).distinctBy { it.id } as ArrayList<SelfossModel.Source>
|
||||
return if (isDatabaseEnabled) {
|
||||
AmineB marked this conversation as resolved
AmineB
commented
All this is overly complicated. All this is overly complicated.
|
||||
var sources = getDBSources().map { it.toView() }
|
||||
for (source in sources) {
|
||||
val newSource = newSources.find { it.id == source.id }
|
||||
if (newSource != null) {
|
||||
source.update(newSource)
|
||||
}
|
||||
}
|
||||
|
||||
sources = (sources + newSources).distinctBy { it.id }
|
||||
|
||||
if (isDatabaseEnabled) {
|
||||
resetDBSourcesWithData(sources)
|
||||
sources
|
||||
} else {
|
||||
newSources
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSourcesStats(): ArrayList<SelfossModel.Source> {
|
||||
suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
|
||||
var sources = ArrayList<SelfossModel.Source>()
|
||||
val isDatabaseEnabled =
|
||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||
if (isNetworkAvailable() && sources.none { it.unread != null }) {
|
||||
val apiSources = api.sourcesStats()
|
||||
if (apiSources.success && apiSources.data != null) {
|
||||
updateSources(apiSources.data.map { SelfossModel.Source(it) } as ArrayList)
|
||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||
if (shouldFetch && isNetworkAvailable()) {
|
||||
if (appSettingsService.getPublicAccess()) {
|
||||
val apiSources = api.sourcesStats()
|
||||
if (apiSources.success && apiSources.data != null) {
|
||||
fetchedSources = true
|
||||
sources = updateSources(apiSources.data) as ArrayList<SelfossModel.Source>
|
||||
AmineB marked this conversation as resolved
AmineB
commented
We only update the DB from We only update the DB from `getSourcesDetails`
|
||||
}
|
||||
} else {
|
||||
sources = getSourcesDetails() as ArrayList<SelfossModel.Source>
|
||||
}
|
||||
} else if (isDatabaseEnabled) {
|
||||
updateSources(getDBSources().map { it.toView() } as ArrayList)
|
||||
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.Source>
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
suspend fun getSourcesDetails(): ArrayList<SelfossModel.Source> {
|
||||
suspend fun getSourcesDetails(): ArrayList<SelfossModel.SourceDetail> {
|
||||
var sources = ArrayList<SelfossModel.SourceDetail>()
|
||||
val isDatabaseEnabled =
|
||||
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
|
||||
if (isNetworkAvailable() && sources.none { it.spout != null }) {
|
||||
val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
|
||||
if (shouldFetch && isNetworkAvailable()) {
|
||||
val apiSources = api.sourcesDetailed()
|
||||
if (apiSources.success && apiSources.data != null) {
|
||||
updateSources(apiSources.data.map { SelfossModel.Source(it) } as ArrayList)
|
||||
fetchedSources = true
|
||||
sources = updateSources(apiSources.data) as ArrayList<SelfossModel.SourceDetail>
|
||||
}
|
||||
} else if (isDatabaseEnabled) {
|
||||
updateSources(getDBSources().map { it.toView() } as ArrayList)
|
||||
sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
|
||||
}
|
||||
return sources
|
||||
}
|
||||
@ -400,7 +414,6 @@ class Repository(
|
||||
items = ArrayList(items.filter { it.sourcetitle != title })
|
||||
setReaderItems(items)
|
||||
db.itemsQueries.deleteItemsWhereSource(title)
|
||||
sources.removeAll { it.id == id }
|
||||
}
|
||||
|
||||
return success
|
||||
@ -620,7 +633,7 @@ class Repository(
|
||||
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
|
||||
}
|
||||
|
||||
fun setSelectedSource(source: SelfossModel.Source) {
|
||||
fun setSelectedSource(source: SelfossModel.SourceDetail) {
|
||||
_selectedSource = source
|
||||
}
|
||||
|
||||
@ -628,7 +641,7 @@ class Repository(
|
||||
_selectedSource = null
|
||||
}
|
||||
|
||||
fun getSelectedSource(): SelfossModel.Source? {
|
||||
fun getSelectedSource(): SelfossModel.SourceDetail? {
|
||||
return _selectedSource
|
||||
}
|
||||
}
|
@ -13,10 +13,10 @@ fun TAG.toView(): SelfossModel.Tag =
|
||||
)
|
||||
|
||||
fun SOURCE.toView(): SelfossModel.Source =
|
||||
SelfossModel.Source(
|
||||
SelfossModel.SourceDetail(
|
||||
this.id.toInt(),
|
||||
this.title,
|
||||
this.unread?.toInt(),
|
||||
null,
|
||||
this.tags?.split(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
@ -24,18 +24,6 @@ fun SOURCE.toView(): SelfossModel.Source =
|
||||
if (this.url != null) SelfossModel.SourceParams(this.url) else null
|
||||
)
|
||||
|
||||
fun SelfossModel.Source.toEntity(): SOURCE =
|
||||
SOURCE(
|
||||
this.id.toString(),
|
||||
this.title.getHtmlDecoded(),
|
||||
this.unread?.toLong(),
|
||||
this.tags?.joinToString(","),
|
||||
this.spout,
|
||||
this.error,
|
||||
this.icon.orEmpty(),
|
||||
this.params?.url
|
||||
)
|
||||
|
||||
fun SelfossModel.Tag.toEntity(): TAG =
|
||||
TAG(
|
||||
this.tag,
|
||||
|
@ -1 +0,0 @@
|
||||
ALTER TABLE SOURCE ADD COLUMN `unread` INTEGER;
|
@ -1,7 +1,6 @@
|
||||
CREATE TABLE SOURCE (
|
||||
`id` TEXT NOT NULL,
|
||||
`title` TEXT NOT NULL,
|
||||
`unread` INTEGER,
|
||||
`tags` TEXT,
|
||||
AmineB marked this conversation as resolved
AmineB
commented
As As `unread` is only in the public mode, let's not change this.
|
||||
`spout` TEXT,
|
||||
`error` TEXT,
|
||||
|
Source
data class should be changed to an interfaceSourceDetail
andSourceStats
should implement it.