Compare commits

...

7 Commits

Author SHA1 Message Date
Amine Louveau
e36189e2e7 Merge pull request 'About config upgrade.' (#93) from aboutconfig into master
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/93
2022-11-05 21:20:17 +00:00
aminecmi
d6bdf510a4 About config upgrade.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2022-11-05 22:00:16 +01:00
Amine Louveau
a464e93370 Merge pull request 'Immediately update bottom badges after reading or starring articles' (#91) from davidoskky/ReaderForSelfoss-multiplatform:badges into master
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/91
2022-11-02 19:06:48 +00:00
4b63afe62a Update badges tests
All checks were successful
continuous-integration/drone/pr Build is passing
2022-11-01 21:51:46 +01:00
ac4c4b9441 Merge branch 'master' into badges
Some checks failed
continuous-integration/drone/pr Build is failing
2022-11-01 20:35:13 +00:00
02d734eee8 Do not edit the repository items from outside the repository
Some checks are pending
continuous-integration/drone/pr Build is running
2022-11-01 21:29:04 +01:00
c5cdfc0d53 Update bottom bar badges through a state flow 2022-11-01 21:28:14 +01:00
8 changed files with 86 additions and 62 deletions

View File

@@ -6,6 +6,7 @@ plugins {
id("com.android.application") id("com.android.application")
kotlin("android") kotlin("android")
kotlin("kapt") kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
} }
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String { fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@@ -62,7 +63,7 @@ android {
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
compileSdk = 32 compileSdk = 33
buildToolsVersion = "31.0.0" buildToolsVersion = "31.0.0"
buildFeatures { buildFeatures {
viewBinding = true viewBinding = true
@@ -70,7 +71,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android" applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21 minSdk = 21
targetSdk = 32 targetSdk = 33
versionCode = versionCodeFromGit() versionCode = versionCodeFromGit()
versionName = versionNameFromGit() versionName = versionNameFromGit()
@@ -138,9 +139,8 @@ dependencies {
implementation("androidx.multidex:multidex:2.0.1") implementation("androidx.multidex:multidex:2.0.1")
// About // About
implementation("com.mikepenz:aboutlibraries-core:8.9.4") implementation("com.mikepenz:aboutlibraries-core:10.5.1")
implementation("com.mikepenz:aboutlibraries:8.9.4") implementation("com.mikepenz:aboutlibraries:10.5.1")
implementation("com.mikepenz:aboutlibraries-definitions:8.9.4")
// Retrofit + http logging + okhttp // Retrofit + http logging + okhttp
implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:retrofit:2.9.0")
@@ -209,4 +209,14 @@ tasks.withType<Test> {
) )
showStandardStreams = true showStandardStreams = true
} }
}
aboutLibraries {
offlineMode = true
fetchRemoteLicense = false
fetchRemoteFunding = false
includePlatform = false
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
} }

View File

@@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.* import androidx.recyclerview.widget.*
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
@@ -178,8 +179,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position) adapter.handleItemAtIndex(position)
reloadBadgeContent()
val tagHashes = i.tags.map { it.longHash() } val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map { tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) { if (tagHashes.contains(it.key)) {
@@ -207,6 +206,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView) ItemTouchHelper(simpleItemTouchCallback).attachToRecyclerView(binding.recyclerView)
} }
private fun updateBottomBarBadgeCount(badge: TextBadgeItem, count: Int) {
if (count > 0) {
badge
.setText(count.toString())
.maybeShow()
} else {
badge.removeBadge()
}
}
private fun handleBottomBar() { private fun handleBottomBar() {
tabNewBadge = TextBadgeItem() tabNewBadge = TextBadgeItem()
@@ -219,6 +228,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.setText("") .setText("")
.setHideOnSelect(false).hide(false) .setHideOnSelect(false).hide(false)
if (appSettingsService.isDisplayUnreadCountEnabled()) {
lifecycleScope.launch {
repository.badgeUnread.collect {
updateBottomBarBadgeCount(tabNewBadge, it)
}
}
}
if (appSettingsService.isDisplayAllCountEnabled()) {
lifecycleScope.launch {
repository.badgeAll.collect {
updateBottomBarBadgeCount(tabArchiveBadge, it)
}
}
lifecycleScope.launch {
repository.badgeStarred.collect {
updateBottomBarBadgeCount(tabStarredBadge, it)
}
}
}
val tabNew = val tabNew =
BottomNavigationItem( BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp, R.drawable.ic_tab_fiber_new_black_24dp,
@@ -714,29 +745,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadges() { private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) { if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.reloadBadges() repository.reloadBadges()
reloadBadgeContent()
} }
} }
} }
private fun reloadBadgeContent() {
if (appSettingsService.isDisplayUnreadCountEnabled()) {
tabNewBadge
.setText(repository.badgeUnread.toString())
.maybeShow()
}
if (appSettingsService.isDisplayAllCountEnabled()) {
tabArchiveBadge
.setText(repository.badgeAll.toString())
.maybeShow()
tabStarredBadge
.setText(repository.badgeStarred.toString())
.maybeShow()
}
}
private fun reloadTagsBadges() { private fun reloadTagsBadges() {
tagsBadge.forEach { tagsBadge.forEach {
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString())) binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
@@ -858,10 +872,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun maxItemNumber(): Int = private fun maxItemNumber(): Int =
when (elementsShown) { when (elementsShown) {
ItemType.UNREAD -> repository.badgeUnread ItemType.UNREAD -> repository.badgeUnread.value
ItemType.ALL -> repository.badgeAll ItemType.ALL -> repository.badgeAll.value
ItemType.STARRED -> repository.badgeStarred ItemType.STARRED -> repository.badgeStarred.value
else -> repository.badgeUnread // if !elementsShown then unread are fetched. else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
} }
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) { private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {

View File

@@ -36,9 +36,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private fun showMenuItem(willAddToFavorite: Boolean) { private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) { if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE) toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
} else { } else {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED) toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
} }
} }

View File

@@ -111,13 +111,11 @@ class ItemCardAdapter(
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item) repository.unstarr(item)
} }
item.starred = false
binding.favButton.isSelected = false binding.favButton.isSelected = false
} else { } else {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
repository.starr(item) repository.starr(item)
} }
item.starred = true
binding.favButton.isSelected = true binding.favButton.isSelected = true
} }
} }

View File

@@ -389,7 +389,7 @@ class ArticleFragment : Fragment(), DIAware {
} }
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() { val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean { override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick() return performClick()
} }
}) })

View File

@@ -299,9 +299,9 @@ class RepositoryTest {
} }
assertSame(true, success) assertSame(true, success)
assertSame(NUMBER_ARTICLES, repository.badgeAll) assertEquals(NUMBER_ARTICLES, repository.badgeAll.value)
assertSame(NUMBER_UNREAD, repository.badgeUnread) assertEquals(NUMBER_UNREAD, repository.badgeUnread.value)
assertSame(NUMBER_STARRED, repository.badgeStarred) assertEquals(NUMBER_STARRED, repository.badgeStarred.value)
coVerify(atLeast = 1) { api.stats() } coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() } verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
} }
@@ -318,9 +318,9 @@ class RepositoryTest {
} }
assertSame(false, success) assertSame(false, success)
assertSame(0, repository.badgeAll) assertSame(0, repository.badgeAll.value)
assertSame(0, repository.badgeUnread) assertSame(0, repository.badgeUnread.value)
assertSame(0, repository.badgeStarred) assertSame(0, repository.badgeStarred.value)
coVerify(atLeast = 1) { api.stats() } coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() } verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
} }
@@ -338,9 +338,9 @@ class RepositoryTest {
} }
assertTrue(success) assertTrue(success)
assertSame(1, repository.badgeAll) assertEquals(1, repository.badgeAll.value)
assertSame(1, repository.badgeUnread) assertEquals(1, repository.badgeUnread.value)
assertSame(1, repository.badgeStarred) assertEquals(1, repository.badgeStarred.value)
coVerify(exactly = 0) { api.stats() } coVerify(exactly = 0) { api.stats() }
verify(atLeast = 1) { db.itemsQueries.items().executeAsList() } verify(atLeast = 1) { db.itemsQueries.items().executeAsList() }
} }
@@ -358,9 +358,9 @@ class RepositoryTest {
} }
assertFalse(success) assertFalse(success)
assertSame(0, repository.badgeAll) assertSame(0, repository.badgeAll.value)
assertSame(0, repository.badgeUnread) assertSame(0, repository.badgeUnread.value)
assertSame(0, repository.badgeStarred) assertSame(0, repository.badgeStarred.value)
coVerify(exactly = 0) { api.stats() } coVerify(exactly = 0) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() } verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
} }

View File

@@ -12,6 +12,7 @@ plugins {
kotlin("android").version("1.7.20").apply(false) kotlin("android").version("1.7.20").apply(false)
kotlin("multiplatform").version("1.7.20").apply(false) kotlin("multiplatform").version("1.7.20").apply(false)
id("org.sonarqube").version("3.4.0.2513").apply(false) id("org.sonarqube").version("3.4.0.2513").apply(false)
id("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
} }
apply(plugin = "org.sonarqube") apply(plugin = "org.sonarqube")

View File

@@ -10,6 +10,7 @@ import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) { class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) {
@@ -27,12 +28,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var offlineOverride = false var offlineOverride = false
var badgeUnread = 0 private val _badgeUnread = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeUnread = _badgeUnread.asStateFlow()
var badgeAll = 0 private val _badgeAll = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeAll = _badgeAll.asStateFlow()
var badgeStarred = 0 private val _badgeStarred = MutableStateFlow(0)
set(value) {field = if (value < 0) { 0 } else { value } } val badgeStarred = _badgeStarred.asStateFlow()
private var fetchedSources = false private var fetchedSources = false
private var fetchedTags = false private var fetchedTags = false
@@ -125,17 +126,17 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) { if (isNetworkAvailable()) {
val response = api.stats() val response = api.stats()
if (response.success && response.data != null) { if (response.success && response.data != null) {
badgeUnread = response.data.unread _badgeUnread.value = response.data.unread
badgeAll = response.data.total _badgeAll.value = response.data.total
badgeStarred = response.data.starred _badgeStarred.value = response.data.starred
success = true success = true
} }
} else if (appSettingsService.isItemCachingEnabled()) { } else if (appSettingsService.isItemCachingEnabled()) {
// TODO: do this differently, because it's not efficient // TODO: do this differently, because it's not efficient
val dbItems = getDBItems() val dbItems = getDBItems()
badgeUnread = dbItems.filter { item -> item.unread }.size _badgeUnread.value = dbItems.filter { item -> item.unread }.size
badgeStarred = dbItems.filter { item -> item.starred }.size _badgeStarred.value = dbItems.filter { item -> item.starred }.size
badgeAll = dbItems.size _badgeAll.value = dbItems.size
success = true success = true
} }
return success return success
@@ -283,7 +284,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun markAsReadLocally(item: SelfossModel.Item) { private fun markAsReadLocally(item: SelfossModel.Item) {
if (item.unread) { if (item.unread) {
item.unread = false item.unread = false
badgeUnread -= 1 _badgeUnread.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -294,7 +295,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unmarkAsReadLocally(item: SelfossModel.Item) { private fun unmarkAsReadLocally(item: SelfossModel.Item) {
if (!item.unread) { if (!item.unread) {
item.unread = true item.unread = true
badgeUnread += 1 _badgeUnread.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -305,7 +306,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun starrLocally(item: SelfossModel.Item) { private fun starrLocally(item: SelfossModel.Item) {
if (!item.starred) { if (!item.starred) {
item.starred = true item.starred = true
badgeStarred += 1 _badgeStarred.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -316,7 +317,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unstarrLocally(item: SelfossModel.Item) { private fun unstarrLocally(item: SelfossModel.Item) {
if (item.starred) { if (item.starred) {
item.starred = false item.starred = false
badgeStarred -= 1 _badgeStarred.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {