Implement the different Source types through an interface
Some checks are pending
continuous-integration/drone/pr Build is running

This commit is contained in:
davidoskky 2023-03-12 16:53:17 +01:00
parent 6ace50c306
commit 0c942f7a80
9 changed files with 142 additions and 95 deletions

View File

@ -49,7 +49,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
super.onResume() super.onResume()
val mLayoutManager = LinearLayoutManager(this) val mLayoutManager = LinearLayoutManager(this)
var items: ArrayList<SelfossModel.Source> var items: ArrayList<SelfossModel.SourceDetail>
binding.recyclerView.setHasFixedSize(true) binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager

View File

@ -24,7 +24,7 @@ import org.kodein.di.instance
class UpsertSourceActivity : AppCompatActivity(), DIAware { class UpsertSourceActivity : AppCompatActivity(), DIAware {
private var existingSource: SelfossModel.Source? = null private var existingSource: SelfossModel.SourceDetail? = null
private var mSpoutsValue: String? = null private var mSpoutsValue: String? = null
private lateinit var binding: ActivityUpsertSourceBinding private lateinit var binding: ActivityUpsertSourceBinding

View File

@ -31,7 +31,7 @@ import org.kodein.di.instance
class SourcesListAdapter( class SourcesListAdapter(
private val app: Activity, private val app: Activity,
private val items: ArrayList<SelfossModel.Source> private val items: ArrayList<SelfossModel.SourceDetail>
) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware { ) : RecyclerView.Adapter<SourcesListAdapter.ViewHolder>(), DIAware {
private val c: Context = app.baseContext private val c: Context = app.baseContext
private val generator: ColorGenerator = ColorGenerator.MATERIAL private val generator: ColorGenerator = ColorGenerator.MATERIAL

View File

@ -84,7 +84,7 @@ class FilterSheetFragment : BottomSheetDialogFragment(), DIAware {
) { ) {
val sourceGroup = binding.sourcesGroup val sourceGroup = binding.sourcesGroup
repository.getSourcesStats().forEach { source -> repository.getSourcesDetailsOrStats().forEach { source ->
val c = Chip(context) val c = Chip(context)
c.ellipsize = TextUtils.TruncateAt.END c.ellipsize = TextUtils.TruncateAt.END

View File

@ -1,5 +1,6 @@
package bou.amine.apps.readerforselfossv2.model 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.DateUtils
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
@ -63,70 +64,117 @@ class SelfossModel {
fun isPublicModeEnabled() = publicMode ?: false fun isPublicModeEnabled() = publicMode ?: false
} }
data class Source( interface Source {
val id: Int, val id: Int
val title: String, var title: String
var unread: Int?, var unread: Int?
var tags: List<String>?, var error: String?
var spout: String?, var icon: String?
var error: String?,
var icon: String?,
var params: SourceParams?
) {
constructor(sourceDetail: SourceDetail) : this( fun checkSameSource(source: Source): Boolean {
id = sourceDetail.id, return this.id != source.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 update(source: Source) { fun update(source: Source) {
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)
fun update(source: SourceDetail)
fun toEntity(): SOURCE
operator fun component1(): Int { return id }
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() throw Exception()
} }
this.title = source.title
this.unread = source.unread this.unread = source.unread
}
override fun update(source: SourceDetail) {
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) {
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.tags = source.tags
this.spout = source.spout this.spout = source.spout
this.error = source.error this.error = source.error
this.icon = source.icon this.icon = source.icon
this.params = source.params 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 @Serializable
data class SourceParams( data class SourceParams(
val url: String val url: String

View File

@ -44,12 +44,11 @@ class Repository(
private val _badgeStarred = MutableStateFlow(0) private val _badgeStarred = MutableStateFlow(0)
val badgeStarred = _badgeStarred.asStateFlow() val badgeStarred = _badgeStarred.asStateFlow()
private var sources = ArrayList<SelfossModel.Source>()
private var fetchedTags = false private var fetchedTags = false
private var fetchedSources = false
private var _readerItems = ArrayList<SelfossModel.Item>() private var _readerItems = ArrayList<SelfossModel.Item>()
private var _selectedSource: SelfossModel.Source? = null private var _selectedSource: SelfossModel.SourceDetail? = null
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error() var fetchedItems: StatusAndData<List<SelfossModel.Item>> = StatusAndData.error()
@ -181,47 +180,62 @@ class Repository(
} }
} }
private fun updateSources(newSources: ArrayList<SelfossModel.Source>) { private fun updateSources(newSources: List<SelfossModel.Source>): List<SelfossModel.Source> {
val isDatabaseEnabled = val isDatabaseEnabled =
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
val newIds = newSources.map {it.id} return if (isDatabaseEnabled) {
val filteredSources = sources.filterNot { it.id in newIds } var sources = getDBSources().map { it.toView() }
for (source in filteredSources) { for (source in sources) {
val newSource = newSources.find { it.id == source.id } val newSource = newSources.find { it.id == source.id }
source.update(newSource!!) if (newSource != null) {
source.update(newSource)
}
} }
sources = (filteredSources + newSources).distinctBy { it.id } as ArrayList<SelfossModel.Source>
if (isDatabaseEnabled) { sources = (sources + newSources).distinctBy { it.id }
resetDBSourcesWithData(sources) resetDBSourcesWithData(sources)
sources
} else {
newSources
} }
} }
suspend fun getSourcesStats(): ArrayList<SelfossModel.Source> { suspend fun getSourcesDetailsOrStats(): ArrayList<SelfossModel.Source> {
var sources = ArrayList<SelfossModel.Source>()
val isDatabaseEnabled = val isDatabaseEnabled =
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled()
if (isNetworkAvailable() && sources.none { it.unread != null }) { val shouldFetch = if (!appSettingsService.isUpdateSourcesEnabled()) !fetchedSources else true
if (shouldFetch && isNetworkAvailable()) {
if (appSettingsService.getPublicAccess()) {
val apiSources = api.sourcesStats() val apiSources = api.sourcesStats()
if (apiSources.success && apiSources.data != null) { if (apiSources.success && apiSources.data != null) {
updateSources(apiSources.data.map { SelfossModel.Source(it) } as ArrayList) fetchedSources = true
sources = updateSources(apiSources.data) as ArrayList<SelfossModel.Source>
}
} else {
sources = getSourcesDetails() as ArrayList<SelfossModel.Source>
} }
} else if (isDatabaseEnabled) { } else if (isDatabaseEnabled) {
updateSources(getDBSources().map { it.toView() } as ArrayList) sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.Source>
} }
return sources return sources
} }
suspend fun getSourcesDetails(): ArrayList<SelfossModel.Source> { suspend fun getSourcesDetails(): ArrayList<SelfossModel.SourceDetail> {
var sources = ArrayList<SelfossModel.SourceDetail>()
val isDatabaseEnabled = val isDatabaseEnabled =
appSettingsService.isItemCachingEnabled() || !appSettingsService.isUpdateSourcesEnabled() 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() val apiSources = api.sourcesDetailed()
if (apiSources.success && apiSources.data != null) { 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) { } else if (isDatabaseEnabled) {
updateSources(getDBSources().map { it.toView() } as ArrayList) sources = getDBSources().map { it.toView() } as ArrayList<SelfossModel.SourceDetail>
} }
return sources return sources
} }
@ -400,7 +414,6 @@ class Repository(
items = ArrayList(items.filter { it.sourcetitle != title }) items = ArrayList(items.filter { it.sourcetitle != title })
setReaderItems(items) setReaderItems(items)
db.itemsQueries.deleteItemsWhereSource(title) db.itemsQueries.deleteItemsWhereSource(title)
sources.removeAll { it.id == id }
} }
return success return success
@ -620,7 +633,7 @@ class Repository(
ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1) ReaderForSelfossDB.Schema.migrate(driverFactory.createDriver(), 0, 1)
} }
fun setSelectedSource(source: SelfossModel.Source) { fun setSelectedSource(source: SelfossModel.SourceDetail) {
_selectedSource = source _selectedSource = source
} }
@ -628,7 +641,7 @@ class Repository(
_selectedSource = null _selectedSource = null
} }
fun getSelectedSource(): SelfossModel.Source? { fun getSelectedSource(): SelfossModel.SourceDetail? {
return _selectedSource return _selectedSource
} }
} }

View File

@ -13,10 +13,10 @@ fun TAG.toView(): SelfossModel.Tag =
) )
fun SOURCE.toView(): SelfossModel.Source = fun SOURCE.toView(): SelfossModel.Source =
SelfossModel.Source( SelfossModel.SourceDetail(
this.id.toInt(), this.id.toInt(),
this.title, this.title,
this.unread?.toInt(), null,
this.tags?.split(","), this.tags?.split(","),
this.spout, this.spout,
this.error, this.error,
@ -24,18 +24,6 @@ fun SOURCE.toView(): SelfossModel.Source =
if (this.url != null) SelfossModel.SourceParams(this.url) else null 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 = fun SelfossModel.Tag.toEntity(): TAG =
TAG( TAG(
this.tag, this.tag,

View File

@ -1 +0,0 @@
ALTER TABLE SOURCE ADD COLUMN `unread` INTEGER;

View File

@ -1,7 +1,6 @@
CREATE TABLE SOURCE ( CREATE TABLE SOURCE (
`id` TEXT NOT NULL, `id` TEXT NOT NULL,
`title` TEXT NOT NULL, `title` TEXT NOT NULL,
`unread` INTEGER,
`tags` TEXT, `tags` TEXT,
`spout` TEXT, `spout` TEXT,
`error` TEXT, `error` TEXT,