Compare commits

..
18 Commits
Author SHA1 Message Date
Amine Louveau e36189e2e7 Merge pull request 'About config upgrade.' (#93) from aboutconfig into master
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. 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
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/91
2022-11-02 19:06:48 +00:00
davidoskky 4b63afe62a Update badges tests 2022-11-01 21:51:46 +01:00
davidoskky ac4c4b9441 Merge branch 'master' into badges 2022-11-01 20:35:13 +00:00
Amine Louveau 16b10dc1b7 Merge pull request 'Show all sources in the sources list' (#90) from davidoskky/ReaderForSelfoss-multiplatform:sources into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/90
2022-11-01 20:30:30 +00:00
davidoskky 02d734eee8 Do not edit the repository items from outside the repository 2022-11-01 21:29:04 +01:00
davidoskky c5cdfc0d53 Update bottom bar badges through a state flow 2022-11-01 21:28:14 +01:00
davidoskky 6d610ed61a Fix repeating items in recyclerview 2022-11-01 19:53:22 +01:00
davidoskky 792950be7c Remove unreachable condition 2022-11-01 19:52:43 +01:00
Amine Louveau af8969ce4a Merge pull request 'Cleaning.' (#88) from cleaning into master
Reviewed-on: https://gitea.amine-louveau.fr/Louvorg/ReaderForSelfoss-multiplatform/pulls/88
2022-10-31 20:42:20 +00:00
aminecmi 27c55e59a1 Cleaning still. 2022-10-31 21:28:11 +01:00
aminecmi 94a0747947 More cleaning. 2022-10-30 22:02:07 +01:00
aminecmi d862bfba4f Still cleaning. 2022-10-30 21:38:04 +01:00
aminecmi b0d1d9c29a No daemon 2022-10-30 21:27:53 +01:00
aminecmi 7b40a31979 Cleaning. 2022-10-30 21:21:43 +01:00
aminecmi 823a8c3692 Date formatter 2022-10-30 21:12:01 +01:00
aminecmi 5494978db8 Cleaning. 2022-10-29 22:58:25 +02:00
24 changed files with 316 additions and 304 deletions
+11 -4
View File
@@ -6,16 +6,19 @@ steps:
- name: AnylyseBuildTest
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Analysing..."
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\""
- ./gradlew sonarqube -Dsonar.projectKey=RFS2 -Dsonar.host.url=$SONAR_HOST_URL -Dsonar.login=$SONAR_LOGIN
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- ./gradlew build
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- ./gradlew test
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
@@ -85,8 +88,12 @@ steps:
- name: build
image: mingc/android-build-box:latest
commands:
- echo "---------------------------------------------------------"
- echo "Configure gradle..."
- mkdir -p ~/.gradle && echo "org.gradle.daemon=false\nignoreGitVersion=true\nappLoginUrl=\"URL\"\nappLoginUsername=\"LOGIN\"\nappLoginPassword=\"PASS\"\npushCache=false" >> ~/.gradle/gradle.properties
- echo "---------------------------------------------------------"
- echo "Generate APK"
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- ./gradlew :androidApp:assembleGithubConfigRelease -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key
+24 -14
View File
@@ -6,6 +6,7 @@ plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
id("com.mikepenz.aboutlibraries.plugin")
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
@@ -54,11 +55,15 @@ fun versionNameFromGit(): String {
android {
compileOptions {
// Flag to enable support for the new language APIs
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
compileSdk = 32
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
}
compileSdk = 33
buildToolsVersion = "31.0.0"
buildFeatures {
viewBinding = true
@@ -66,7 +71,7 @@ android {
defaultConfig {
applicationId = "bou.amine.apps.readerforselfossv2.android"
minSdk = 21
targetSdk = 32
targetSdk = 33
versionCode = versionCodeFromGit()
versionName = versionNameFromGit()
@@ -130,18 +135,12 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("org.jsoup:jsoup:1.14.3")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
//multidex
implementation("androidx.multidex:multidex:2.0.1")
// About
implementation("com.mikepenz:aboutlibraries-core:8.9.4")
implementation("com.mikepenz:aboutlibraries:8.9.4")
implementation("com.mikepenz:aboutlibraries-definitions:8.9.4")
// Async
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
implementation("com.mikepenz:aboutlibraries-core:10.5.1")
implementation("com.mikepenz:aboutlibraries:10.5.1")
// Retrofit + http logging + okhttp
implementation("com.squareup.retrofit2:retrofit:2.9.0")
@@ -189,12 +188,13 @@ dependencies {
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("com.squareup.sqldelight:android-driver:1.5.3")
implementation("com.squareup.sqldelight:android-driver:1.5.4")
//test
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.12.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
}
tasks.withType<Test> {
@@ -209,4 +209,14 @@ tasks.withType<Test> {
)
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
}
@@ -94,7 +94,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items != null) {
if (items.isNotEmpty()) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
@@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
import androidx.core.view.doOnNextLayout
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.*
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
@@ -178,8 +179,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
adapter.handleItemAtIndex(position)
reloadBadgeContent()
val tagHashes = i.tags.map { it.longHash() }
tagsBadge = tagsBadge.map {
if (tagHashes.contains(it.key)) {
@@ -207,6 +206,16 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
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() {
tabNewBadge = TextBadgeItem()
@@ -219,6 +228,28 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
.setText("")
.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 =
BottomNavigationItem(
R.drawable.ic_tab_fiber_new_black_24dp,
@@ -714,29 +745,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun reloadBadges() {
if (appSettingsService.isDisplayUnreadCountEnabled() || appSettingsService.isDisplayAllCountEnabled()) {
CoroutineScope(Dispatchers.Main).launch {
CoroutineScope(Dispatchers.IO).launch {
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() {
tagsBadge.forEach {
binding.mainDrawer.updateBadge(it.key, StringHolder(it.value.toString()))
@@ -858,10 +872,10 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar
private fun maxItemNumber(): Int =
when (elementsShown) {
ItemType.UNREAD -> repository.badgeUnread
ItemType.ALL -> repository.badgeAll
ItemType.STARRED -> repository.badgeStarred
else -> repository.badgeUnread // if !elementsShown then unread are fetched.
ItemType.UNREAD -> repository.badgeUnread.value
ItemType.ALL -> repository.badgeAll.value
ItemType.STARRED -> repository.badgeStarred.value
else -> repository.badgeUnread.value // if !elementsShown then unread are fetched.
}
private fun updateItems(adapterItems: ArrayList<SelfossModel.Item>) {
@@ -101,7 +101,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
finish()
}
private fun preferenceError(t: Throwable) {
private fun preferenceError() {
appSettingsService.resetLoginInformation()
binding.urlView.error = getString(R.string.wrong_infos)
@@ -169,7 +169,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
goToMain()
} else {
CoroutineScope(Dispatchers.Main).launch {
preferenceError(Exception("Not success"))
preferenceError()
}
}
}
@@ -36,9 +36,9 @@ class ReaderActivity : AppCompatActivity(), DIAware {
private fun showMenuItem(willAddToFavorite: Boolean) {
if (willAddToFavorite) {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.WHITE)
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.WHITE)
} else {
toolbarMenu.findItem(R.id.star).icon.setTint(Color.RED)
toolbarMenu.findItem(R.id.star).icon?.setTint(Color.RED)
}
}
@@ -56,20 +56,13 @@ class SourcesActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
if (response != null) {
if (response.isNotEmpty()) {
items = response
val mAdapter = SourcesListAdapter(
this@SourcesActivity, items
)
binding.recyclerView.adapter = mAdapter
mAdapter.notifyDataSetChanged()
if (items.isEmpty()) {
Toast.makeText(
this@SourcesActivity,
R.string.nothing_here,
Toast.LENGTH_SHORT
).show()
}
} else {
Toast.makeText(
this@SourcesActivity,
@@ -10,9 +10,12 @@ import androidx.recyclerview.widget.RecyclerView
import bou.amine.apps.readerforselfossv2.android.R
import bou.amine.apps.readerforselfossv2.android.databinding.CardItemBinding
import bou.amine.apps.readerforselfossv2.android.model.toTextDrawableString
import bou.amine.apps.readerforselfossv2.android.utils.*
import bou.amine.apps.readerforselfossv2.android.utils.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.glide.bitmapCenterCrop
import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable
import bou.amine.apps.readerforselfossv2.android.utils.openInBrowserAsNewTask
import bou.amine.apps.readerforselfossv2.android.utils.openItemUrl
import bou.amine.apps.readerforselfossv2.android.utils.shareLink
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
@@ -59,7 +62,7 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight
@@ -108,13 +111,11 @@ class ItemCardAdapter(
CoroutineScope(Dispatchers.IO).launch {
repository.unstarr(item)
}
item.starred = false
binding.favButton.isSelected = false
} else {
CoroutineScope(Dispatchers.IO).launch {
repository.starr(item)
}
item.starred = true
binding.favButton.isSelected = true
}
}
@@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {
@@ -61,9 +61,13 @@ class SourcesListAdapter(
binding.sourceTitle.text = itm.title.getHtmlDecoded()
}
override fun getItemId(position: Int) = position.toLong()
override fun getItemViewType(position: Int) = position
override fun getItemCount(): Int = items.size
inner class ViewHolder(internal val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
inner class ViewHolder(private val mView: ConstraintLayout) : RecyclerView.ViewHolder(mView) {
init {
handleClickListeners()
@@ -74,13 +78,13 @@ class SourcesListAdapter(
val deleteBtn: Button = mView.findViewById(R.id.deleteBtn)
deleteBtn.setOnClickListener {
val (id) = items[adapterPosition]
val (id) = items[bindingAdapterPosition]
CoroutineScope(Dispatchers.IO).launch {
val successfullyDeletedSource = repository.deleteSource(id)
if (successfullyDeletedSource) {
items.removeAt(adapterPosition)
notifyItemRemoved(adapterPosition)
notifyItemRangeChanged(adapterPosition, itemCount)
items.removeAt(bindingAdapterPosition)
notifyItemRemoved(bindingAdapterPosition)
notifyItemRangeChanged(bindingAdapterPosition, itemCount)
} else {
Toast.makeText(
app,
@@ -101,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText(repository.dateUtils)
contentSource = item.sourceAndDateText()
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()
@@ -389,7 +389,7 @@ class ArticleFragment : Fragment(), DIAware {
}
val gestureDetector = GestureDetector(activity, object : GestureDetector.SimpleOnGestureListener() {
override fun onSingleTapUp(e: MotionEvent?): Boolean {
override fun onSingleTapUp(e: MotionEvent): Boolean {
return performClick()
}
})
+33
View File
@@ -0,0 +1,33 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.utils.DateUtils
import junit.framework.TestCase.assertEquals
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import org.junit.Test
class DatesTest {
private val v3Date = "2013-04-07T13:43:00+01:00"
private val v4Date = "2013-04-07 13:43:00"
@Test
fun v3_date_should_be_parsed() {
val date = DateUtils.parseDate(v3Date)
val expected = LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.of("UTC+1")) .toEpochMilliseconds()
assertEquals(date, expected)
}
@Test
fun v4_date_should_be_parsed() {
val date = DateUtils.parseDate(v4Date)
val expected =
LocalDateTime(2013, 4, 7, 13, 43, 0, 0).toInstant(TimeZone.currentSystemDefault())
.toEpochMilliseconds()
assertEquals(date, expected)
}
}
+71 -124
View File
@@ -1,6 +1,5 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.dao.SOURCE
import bou.amine.apps.readerforselfossv2.dao.TAG
@@ -64,14 +63,14 @@ class RepositoryTest {
}
@Test
fun Instantiate_repository() {
fun instantiate_repository() {
initializeRepository()
coVerify(exactly = 1) { api.version() }
}
@Test
fun Instantiate_repository_without_api_version() {
fun instantiate_repository_without_api_version() {
every { appSettingsService.getApiVersion() } returns -1
initializeRepository(MutableStateFlow(false))
@@ -81,7 +80,7 @@ class RepositoryTest {
}
@Test
fun Get_api_4_date_with_api_1_version_stored() {
fun get_api_4_date_with_api_1_version_stored() {
every { appSettingsService.getApiVersion() } returns 1
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -97,7 +96,7 @@ class RepositoryTest {
}
@Test
fun Get_api_1_date_with_api_4_version_stored() {
fun get_api_1_date_with_api_4_version_stored() {
every { appSettingsService.getApiVersion() } returns 4
coEvery { api.version() } returns SelfossModel.StatusAndData(success = false, null)
val itemParameters = FakeItemParameters()
@@ -117,7 +116,7 @@ class RepositoryTest {
}
@Test
fun Get_newer_items() {
fun get_newer_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -132,7 +131,7 @@ class RepositoryTest {
}
@Test
fun Get_all_newer_items() {
fun get_all_newer_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -148,7 +147,7 @@ class RepositoryTest {
}
@Test
fun Get_newer_starred_items() {
fun get_newer_starred_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -164,7 +163,7 @@ class RepositoryTest {
}
@Test
fun Get_newer_items_without_connectivity() {
fun get_newer_items_without_connectivity() {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
@@ -178,7 +177,7 @@ class RepositoryTest {
}
@Test
fun Get_newer_items_without_connectivity_and_tag_filter() {
fun get_newer_items_without_connectivity_and_tag_filter() {
val itemParameter1 = FakeItemParameters()
val itemParameter2 = FakeItemParameters()
val itemParameter3 = FakeItemParameters()
@@ -206,7 +205,7 @@ class RepositoryTest {
}
@Test
fun Get_newer_items_without_connectivity_and_source_filter() {
fun get_newer_items_without_connectivity_and_source_filter() {
val itemParameter1 = FakeItemParameters()
val itemParameter2 = FakeItemParameters()
val itemParameter3 = FakeItemParameters()
@@ -241,7 +240,7 @@ class RepositoryTest {
}
@Test
fun Get_older_items() {
fun get_older_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -257,7 +256,7 @@ class RepositoryTest {
}
@Test
fun Get_all_older_items() {
fun get_all_older_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -274,7 +273,7 @@ class RepositoryTest {
}
@Test
fun Get_older_starred_items() {
fun get_older_starred_items() {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
SelfossModel.StatusAndData(success = true, data = generateTestApiItem())
@@ -291,8 +290,8 @@ class RepositoryTest {
}
@Test
fun Reload_badges() {
var success = false
fun reload_badges() {
var success: Boolean
initializeRepository()
runBlocking {
@@ -300,18 +299,18 @@ class RepositoryTest {
}
assertSame(true, success)
assertSame(NUMBER_ARTICLES, repository.badgeAll)
assertSame(NUMBER_UNREAD, repository.badgeUnread)
assertSame(NUMBER_STARRED, repository.badgeStarred)
assertEquals(NUMBER_ARTICLES, repository.badgeAll.value)
assertEquals(NUMBER_UNREAD, repository.badgeUnread.value)
assertEquals(NUMBER_STARRED, repository.badgeStarred.value)
coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
}
@Test
fun Reload_badges_without_response() {
fun reload_badges_without_response() {
coEvery { api.stats() } returns SelfossModel.StatusAndData(success = false, data = null)
var success = false
var success: Boolean
initializeRepository()
runBlocking {
@@ -319,19 +318,19 @@ class RepositoryTest {
}
assertSame(false, success)
assertSame(0, repository.badgeAll)
assertSame(0, repository.badgeUnread)
assertSame(0, repository.badgeStarred)
assertSame(0, repository.badgeAll.value)
assertSame(0, repository.badgeUnread.value)
assertSame(0, repository.badgeStarred.value)
coVerify(atLeast = 1) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
}
@Test
fun Reload_badges_without_connection() {
fun reload_badges_without_connection() {
every { appSettingsService.isItemCachingEnabled() } returns true
every { db.itemsQueries.items().executeAsList() } returns generateTestDBItems()
var success = false
var success: Boolean
initializeRepository(MutableStateFlow(false))
runBlocking {
@@ -339,19 +338,19 @@ class RepositoryTest {
}
assertTrue(success)
assertSame(1, repository.badgeAll)
assertSame(1, repository.badgeUnread)
assertSame(1, repository.badgeStarred)
assertEquals(1, repository.badgeAll.value)
assertEquals(1, repository.badgeUnread.value)
assertEquals(1, repository.badgeStarred.value)
coVerify(exactly = 0) { api.stats() }
verify(atLeast = 1) { db.itemsQueries.items().executeAsList() }
}
@Test
fun Reload_badges_without_connection_and_items_caching_disabled() {
fun reload_badges_without_connection_and_items_caching_disabled() {
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true
var success = false
var success: Boolean
initializeRepository(MutableStateFlow(false))
runBlocking {
@@ -359,15 +358,15 @@ class RepositoryTest {
}
assertFalse(success)
assertSame(0, repository.badgeAll)
assertSame(0, repository.badgeUnread)
assertSame(0, repository.badgeStarred)
assertSame(0, repository.badgeAll.value)
assertSame(0, repository.badgeUnread.value)
assertSame(0, repository.badgeStarred.value)
coVerify(exactly = 0) { api.stats() }
verify(exactly = 0) { db.itemsQueries.items().executeAsList() }
}
@Test
fun Get_tags() {
fun get_tags() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -383,7 +382,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository()
var testTags: List<SelfossModel.Tag>? = null
var testTags: List<SelfossModel.Tag>?
runBlocking {
testTags = repository.getTags()
}
@@ -394,7 +393,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_with_sources_update_disabled() {
fun get_tags_with_sources_update_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -410,7 +409,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository()
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
// Tags will be fetched from the database on the second call, thus testTags != tags
@@ -424,7 +423,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_with_items_caching_disabled() {
fun get_tags_with_items_caching_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -440,7 +439,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository()
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
}
@@ -451,7 +450,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_with_sources_update_and_items_caching_disabled() {
fun get_tags_with_sources_update_and_items_caching_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -467,7 +466,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository()
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
testTags = repository.getTags()
@@ -480,7 +479,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_without_connection() {
fun get_tags_without_connection() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -496,7 +495,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
}
@@ -508,7 +507,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_without_connection_and_items_caching_disabled() {
fun get_tags_without_connection_and_items_caching_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -524,7 +523,7 @@ class RepositoryTest {
every { appSettingsService.isUpdateSourcesEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
}
@@ -535,7 +534,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_without_connection_and_sources_update_disabled() {
fun get_tags_without_connection_and_sources_update_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -551,7 +550,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
}
@@ -563,7 +562,7 @@ class RepositoryTest {
}
@Test
fun Get_tags_without_connection_and_sources_update_and_items_caching_disabled() {
fun get_tags_without_connection_and_sources_update_and_items_caching_disabled() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@@ -579,7 +578,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag> = emptyList()
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
}
@@ -631,7 +630,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository()
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -685,7 +684,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository()
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
// Sources will be fetched from the database on the second call, thus testSources != sources
@@ -742,7 +741,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository()
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -796,7 +795,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository()
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -848,7 +847,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -902,7 +901,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -956,7 +955,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -1010,7 +1009,7 @@ class RepositoryTest {
coEvery { api.sources() } returns SelfossModel.StatusAndData(success = true, data = sources)
every { db.sourcesQueries.sources().executeAsList() } returns sourcesDB
initializeRepository(MutableStateFlow(false))
var testSources: List<SelfossModel.Source>? = null
var testSources: List<SelfossModel.Source>?
runBlocking {
testSources = repository.getSources()
}
@@ -1026,7 +1025,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(true)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.createSource(
"test",
@@ -1056,7 +1055,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(false)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.createSource(
"test",
@@ -1086,7 +1085,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(true)
initializeRepository(MutableStateFlow(false))
var response = false
var response: Boolean
runBlocking {
response = repository.createSource(
"test",
@@ -1115,7 +1114,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(true)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
}
@@ -1129,7 +1128,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
}
@@ -1143,7 +1142,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false)
initializeRepository(MutableStateFlow(false))
var response = false
var response: Boolean
runBlocking {
response = repository.deleteSource(5)
}
@@ -1160,7 +1159,7 @@ class RepositoryTest {
)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.updateRemote()
}
@@ -1177,7 +1176,7 @@ class RepositoryTest {
)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.updateRemote()
}
@@ -1194,7 +1193,7 @@ class RepositoryTest {
)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.updateRemote()
}
@@ -1211,7 +1210,7 @@ class RepositoryTest {
)
initializeRepository(MutableStateFlow(false))
var response = false
var response: Boolean
runBlocking {
response = repository.updateRemote()
}
@@ -1225,7 +1224,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.login()
}
@@ -1239,7 +1238,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = false)
initializeRepository()
var response = false
var response: Boolean
runBlocking {
response = repository.login()
}
@@ -1253,7 +1252,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
initializeRepository(MutableStateFlow(false))
var response = false
var response: Boolean
runBlocking {
response = repository.login()
}
@@ -1366,56 +1365,4 @@ class RepositoryTest {
coVerify(exactly = 0) { api.getItems(any(), 0, null, null, null, null, 200) }
}
}
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
return listOf(
ITEM(
id = item.id,
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags
)
)
}
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
return listOf(
SelfossModel.Item(
id = item.id.toInt(),
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags.split(',')
)
)
}
class FakeItemParameters {
var id = "20"
var datetime = "2022-09-09T03:32:01-04:00"
val title = "Etica della ricerca sotto i riflettori."
val content =
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>Letica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>Lultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui lintelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne lorigine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dellinnovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto allattenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>Largomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>Lembrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nellofferta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso lAteneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
var unread = true
var starred = true
val thumbnail = null
val icon = "ba79e238383ce83c23a169929c8906ef.png"
val link =
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing"
}
}
+57
View File
@@ -0,0 +1,57 @@
package bou.amine.apps.readerforselfossv2.repository
import bou.amine.apps.readerforselfossv2.dao.ITEM
import bou.amine.apps.readerforselfossv2.model.SelfossModel
fun generateTestDBItems(item: FakeItemParameters = FakeItemParameters()): List<ITEM> {
return listOf(
ITEM(
id = item.id,
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags
)
)
}
fun generateTestApiItem(item: FakeItemParameters = FakeItemParameters()): List<SelfossModel.Item> {
return listOf(
SelfossModel.Item(
id = item.id.toInt(),
datetime = item.datetime,
title = item.title,
content = item.content,
unread = item.unread,
starred = item.starred,
thumbnail = item.thumbnail,
icon = item.icon,
link = item.link,
sourcetitle = item.sourcetitle,
tags = item.tags.split(',')
)
)
}
class FakeItemParameters {
var id = "20"
var datetime = "2022-09-09T03:32:01-04:00"
val title = "Etica della ricerca sotto i riflettori."
val content =
"<p><strong>Luigi Campanella, già Presidente SCI</strong></p>\n<p>Letica della scienza è di certo ambito di cui continuiamo a scoprire nuovi aspetti e risvolti.</p>\n<p>Lultimo è quello delle intelligenze artificiali capaci di creare opere complesse basate su immagini e parole memorizzate con il rischio di fake news e di contenuti disturbanti.</p>\n<p>Per evitare che ciò accada si sta procedendo filtrando secondo criteri di autocensura i dati da cui lintelligenza artificiale parte.</p>\n<p>Comincia ad intravedersi un futuro prossimo di competizione fra autori umani ed artificiali nel quale sarà importante, quando i loro prodotti saranno indistinguibili, dichiararne lorigine.</p>\n<p>Come si comprende, si conferma che gli aspetti etici dellinnovazione e della ricerca si diversificato sempre di più.</p>\n<p>La biologia molecolare e la genetica già in passato hanno posto allattenzione comune aspetti di etica della scienza che hanno indotto a nuove riflessioni circa i limiti delle ricerche.</p>\n<p>Largomento, sempre attuale, torna sulle prime pagine a seguito della pubblicazione di una ricerca della Università di Cambridge che ha sviluppato una struttura cellulare di un topo con un cuore che batte regolarmente.</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image002-1.png?w=481\" alt=\"\" width=\"697\" height=\"430\" /><img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image003-1.png?w=906\" alt=\"\" /><p>Magdalena Zernicka-Goetz</p>\n<img src=\"https://ilblogdellasci.files.wordpress.com/2022/09/image004.jpg?w=474\" alt=\"\" width=\"622\" height=\"465\" /><p>Gianluca Amadei</p>\n<p>Del gruppo fa parte anche uno scienziato italiano Gianluca Amadei,che dinnanzi alle obiezioni di natura etica sulla realizzazione della vita artificiale si è affrettato a sostenere che non è creare nuove vite il fine primario della ricerca, ma quello di salvare quelle esistenti, di dare contributi essenziali alla medicina citando il caso del fallimento tuttora non interpretato di alcune gravidanze e di superare la sperimentazione animale, così contribuendo positivamente alla soluzione di un altro dilemma etico.</p>\n<p>Lembrione sintetico ha ovviamente come primo traguardo il contributo ai trapianti oggi drammaticamente carenti nellofferta rispetto alla domanda, con attese fino a 4 anni per i trapianti di cuore ed a 2 anni per quelli di fegato. Il lavoro dovrebbe adesso continuare presso lAteneo di Padova per creare nuovi organi e nuovi farmaci.</p>"
var unread = true
var starred = true
val thumbnail = null
val icon = "ba79e238383ce83c23a169929c8906ef.png"
val link =
"https://ilblogdellasci.wordpress.com/2022/09/09/etica-della-ricerca-sotto-i-riflettori/"
var sourcetitle = "La Chimica e la Società"
var tags = "Chimica, Testing"
}
+2 -1
View File
@@ -1,7 +1,7 @@
buildscript {
dependencies {
// SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
classpath("com.squareup.sqldelight:gradle-plugin:1.5.4")
}
}
@@ -12,6 +12,7 @@ plugins {
kotlin("android").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("com.mikepenz.aboutlibraries.plugin").version("10.5.1").apply(false)
}
apply(plugin = "org.sonarqube")
-1
View File
@@ -34,4 +34,3 @@ org.gradle.caching=true
ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none
pushCache=true
+4 -3
View File
@@ -1,7 +1,7 @@
object SqlDelight {
const val runtime = "com.squareup.sqldelight:runtime:1.5.3"
const val android = "com.squareup.sqldelight:android-driver:1.5.3"
const val native = "com.squareup.sqldelight:native-driver:1.5.3"
const val runtime = "com.squareup.sqldelight:runtime:1.5.4"
const val android = "com.squareup.sqldelight:android-driver:1.5.4"
const val native = "com.squareup.sqldelight:native-driver:1.5.4"
}
@@ -58,6 +58,7 @@ kotlin {
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-okhttp:2.1.1")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
// Sql
implementation(SqlDelight.android)
@@ -1,49 +1,29 @@
package bou.amine.apps.readerforselfossv2.utils
import android.os.Build
import android.text.format.DateUtils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import kotlinx.datetime.*
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
actual fun parseDate(dateString: String): Long {
val FORMATTERV1 = "yyyy-MM-dd HH:mm:ss"
return if (appSettingsService.getApiVersion() >= 4) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
OffsetDateTime.parse(dateString).toInstant().toEpochMilli()
} else {
TODO("VERSION.SDK_INT < O")
}
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(FORMATTERV1)).toInstant(
ZoneOffset.UTC).toEpochMilli()
} else {
TODO("VERSION.SDK_INT < O")
actual class DateUtils {
actual companion object {
actual fun parseDate(dateString: String): Long {
return try {
Instant.parse(dateString).toEpochMilliseconds()
} catch (e: Exception) {
LocalDateTime.parse(dateString.replace(" ", "T")).toInstant(TimeZone.currentSystemDefault()).toEpochMilliseconds()
}
}
}
actual fun parseRelativeDate(dateString: String): String {
actual fun parseRelativeDate(dateString: String): String {
val date = parseDate(dateString)
val date = parseDate(dateString)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
" " + DateUtils.getRelativeTimeSpanString(
return " " + DateUtils.getRelativeTimeSpanString(
date,
Instant.now().toEpochMilli(),
Clock.System.now().toEpochMilliseconds(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} else {
TODO("VERSION.SDK_INT < O")
}
}
}
@@ -108,8 +108,8 @@ class SelfossModel {
return stringUrl
}
fun sourceAndDateText(dateUtils: DateUtils): String =
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime)
fun sourceAndDateText(): String =
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
fun toggleStar(): Item {
this.starred = !this.starred
@@ -10,8 +10,8 @@ import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class Repository(private val api: SelfossApi, private val appSettingsService: AppSettingsService, val isConnectionAvailable: MutableStateFlow<Boolean>, private val db: ReaderForSelfossDB) {
@@ -19,7 +19,6 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var connectionMonitored = false
var baseUrl = appSettingsService.getBaseUrl()
lateinit var dateUtils: DateUtils
var displayedItems = ItemType.UNREAD
@@ -29,12 +28,12 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
var offlineOverride = false
var badgeUnread = 0
set(value) {field = if (value < 0) { 0 } else { value } }
var badgeAll = 0
set(value) {field = if (value < 0) { 0 } else { value } }
var badgeStarred = 0
set(value) {field = if (value < 0) { 0 } else { value } }
private val _badgeUnread = MutableStateFlow(0)
val badgeUnread = _badgeUnread.asStateFlow()
private val _badgeAll = MutableStateFlow(0)
val badgeAll = _badgeAll.asStateFlow()
private val _badgeStarred = MutableStateFlow(0)
val badgeStarred = _badgeStarred.asStateFlow()
private var fetchedSources = false
private var fetchedTags = false
@@ -75,7 +74,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (fetchedItems.success && fetchedItems.data != null) {
items = ArrayList(fetchedItems.data!!)
if (fromDB) {
items.sortByDescending { dateUtils.parseDate(it.datetime) }
items.sortByDescending { DateUtils.parseDate(it.datetime) }
}
}
return items
@@ -127,17 +126,17 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
if (isNetworkAvailable()) {
val response = api.stats()
if (response.success && response.data != null) {
badgeUnread = response.data.unread
badgeAll = response.data.total
badgeStarred = response.data.starred
_badgeUnread.value = response.data.unread
_badgeAll.value = response.data.total
_badgeStarred.value = response.data.starred
success = true
}
} else if (appSettingsService.isItemCachingEnabled()) {
// TODO: do this differently, because it's not efficient
val dbItems = getDBItems()
badgeUnread = dbItems.filter { item -> item.unread }.size
badgeStarred = dbItems.filter { item -> item.starred }.size
badgeAll = dbItems.size
_badgeUnread.value = dbItems.filter { item -> item.unread }.size
_badgeStarred.value = dbItems.filter { item -> item.starred }.size
_badgeAll.value = dbItems.size
success = true
}
return success
@@ -285,7 +284,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun markAsReadLocally(item: SelfossModel.Item) {
if (item.unread) {
item.unread = false
badgeUnread -= 1
_badgeUnread.value -= 1
}
CoroutineScope(Dispatchers.Main).launch {
@@ -296,7 +295,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unmarkAsReadLocally(item: SelfossModel.Item) {
if (!item.unread) {
item.unread = true
badgeUnread += 1
_badgeUnread.value += 1
}
CoroutineScope(Dispatchers.Main).launch {
@@ -307,7 +306,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun starrLocally(item: SelfossModel.Item) {
if (!item.starred) {
item.starred = true
badgeStarred += 1
_badgeStarred.value += 1
}
CoroutineScope(Dispatchers.Main).launch {
@@ -318,7 +317,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
private fun unstarrLocally(item: SelfossModel.Item) {
if (item.starred) {
item.starred = false
badgeStarred -= 1
_badgeStarred.value -= 1
}
CoroutineScope(Dispatchers.Main).launch {
@@ -394,7 +393,6 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
}
}
dateUtils = DateUtils(appSettingsService)
}
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride
@@ -1,27 +0,0 @@
package bou.amine.apps.readerforselfossv2.service
import bou.amine.apps.readerforselfossv2.utils.DateUtils
class SearchService(val dateUtils: DateUtils) {
var displayedItems: String = "unread"
set(value) {
field = when (value) {
"all" -> "all"
"unread" -> "unread"
"read" -> "read"
"starred" -> "starred"
else -> "all"
}
}
var position = 0
var searchFilter: String? = null
var sourceIDFilter: Long? = null
var sourceFilter: String? = null
var tagFilter: String? = null
var itemsCaching = false
var badgeUnread = -1
var badgeAll = -1
var badgeStarred = -1
}
@@ -1,16 +1,9 @@
package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
expect class DateUtils() {
companion object {
fun parseDate(dateString: String): Long
fun SelfossModel.Item.parseDate(dateUtils: DateUtils): Long =
dateUtils.parseDate(this.datetime)
expect class DateUtils constructor(appSettingsService: AppSettingsService) {
val appSettingsService: AppSettingsService // This is needed because of https://stackoverflow.com/a/65249085
fun parseDate(dateString: String): Long
fun parseRelativeDate(dateString: String): String
fun parseRelativeDate(dateString: String): String
}
}
@@ -1,14 +1,13 @@
package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
actual class DateUtils {
actual companion object {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
@@ -2,13 +2,15 @@ package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
actual class DateUtils {
actual companion object {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}