Compare commits

..

No commits in common. "af8969ce4acb25c3b1a0838afa2848daa0d065b6" and "6076eb1cee0156d0a86d9b86e6ad9afe0feadb19" have entirely different histories.

21 changed files with 230 additions and 221 deletions

View File

@ -6,19 +6,16 @@ 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
- ./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\""
- echo "---------------------------------------------------------"
- echo "Building..."
- ./gradlew build
- ./gradlew build -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Testing..."
- echo "---------------------------------------------------------"
- ./gradlew test
- ./gradlew test -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
environment:
SONAR_HOST_URL:
from_secret: sonarScannerHostUrl
@ -88,12 +85,8 @@ 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 -P pushCache=false
- ./gradlew :androidApp:assembleGithubConfigRelease -PignoreGitVersion=true -P appLoginUrl="\"URL\"" -P appLoginUsername="\"LOGIN\"" -P appLoginPassword="\"PASS\"" -P pushCache=false
- echo "---------------------------------------------------------"
- echo "Get Key"
- wget https://amine-louveau.fr/key

View File

@ -54,13 +54,9 @@ fun versionNameFromGit(): String {
android {
compileOptions {
// Flag to enable support for the new language APIs
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
// For Kotlin projects
kotlinOptions {
jvmTarget = "11"
isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
compileSdk = 32
buildToolsVersion = "31.0.0"
@ -134,6 +130,8 @@ 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")
@ -142,6 +140,9 @@ dependencies {
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")
// Retrofit + http logging + okhttp
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
@ -188,13 +189,12 @@ dependencies {
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("com.squareup.sqldelight:android-driver:1.5.4")
implementation("com.squareup.sqldelight:android-driver:1.5.3")
//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> {

View File

@ -94,7 +94,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
try {
val items = repository.getSpouts()
if (items.isNotEmpty()) {
if (items != null) {
val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key

View File

@ -101,7 +101,7 @@ class LoginActivity : AppCompatActivity(), DIAware {
finish()
}
private fun preferenceError() {
private fun preferenceError(t: Throwable) {
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()
preferenceError(Exception("Not success"))
}
}
}

View File

@ -56,7 +56,7 @@ class SourcesActivity : AppCompatActivity(), DIAware {
CoroutineScope(Dispatchers.Main).launch {
val response = repository.getSources()
if (response.isNotEmpty()) {
if (response != null) {
items = response
val mAdapter = SourcesListAdapter(
this@SourcesActivity, items

View File

@ -10,12 +10,9 @@ 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.LinkOnTouchListener
import bou.amine.apps.readerforselfossv2.android.utils.*
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
@ -62,7 +59,7 @@ class ItemCardAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
if (!appSettingsService.isFullHeightCardsEnabled()) {
binding.itemImage.maxHeight = imageMaxHeight

View File

@ -51,7 +51,7 @@ class ItemListAdapter(
binding.title.setLinkTextColor(c.resources.getColor(R.color.colorAccent))
binding.sourceTitleAndDate.text = itm.sourceAndDateText()
binding.sourceTitleAndDate.text = itm.sourceAndDateText(repository.dateUtils)
if (itm.getThumbnail(repository.baseUrl).isEmpty()) {

View File

@ -101,7 +101,7 @@ class ArticleFragment : Fragment(), DIAware {
contentText = item.content
contentTitle = item.title.getHtmlDecoded()
contentImage = item.getThumbnail(repository.baseUrl)
contentSource = item.sourceAndDateText()
contentSource = item.sourceAndDateText(repository.dateUtils)
allImages = item.getImages()
fontSize = appSettingsService.getFontSize()

View File

@ -1,33 +0,0 @@
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)
}
}

View File

@ -1,5 +1,6 @@
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
@ -63,14 +64,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))
@ -80,7 +81,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())
@ -96,7 +97,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()
@ -116,7 +117,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())
@ -131,7 +132,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())
@ -147,7 +148,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())
@ -163,7 +164,7 @@ class RepositoryTest {
}
@Test
fun get_newer_items_without_connectivity() {
fun Get_newer_items_without_connectivity() {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
@ -177,7 +178,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()
@ -205,7 +206,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()
@ -240,7 +241,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())
@ -256,7 +257,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())
@ -273,7 +274,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())
@ -290,8 +291,8 @@ class RepositoryTest {
}
@Test
fun reload_badges() {
var success: Boolean
fun Reload_badges() {
var success = false
initializeRepository()
runBlocking {
@ -307,10 +308,10 @@ class RepositoryTest {
}
@Test
fun reload_badges_without_response() {
fun Reload_badges_without_response() {
coEvery { api.stats() } returns SelfossModel.StatusAndData(success = false, data = null)
var success: Boolean
var success = false
initializeRepository()
runBlocking {
@ -326,11 +327,11 @@ class RepositoryTest {
}
@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: Boolean
var success = false
initializeRepository(MutableStateFlow(false))
runBlocking {
@ -346,11 +347,11 @@ class RepositoryTest {
}
@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: Boolean
var success = false
initializeRepository(MutableStateFlow(false))
runBlocking {
@ -366,7 +367,7 @@ class RepositoryTest {
}
@Test
fun get_tags() {
fun Get_tags() {
val tags = listOf(
SelfossModel.Tag("test", "red", 6),
SelfossModel.Tag("second", "yellow", 0)
@ -382,7 +383,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository()
var testTags: List<SelfossModel.Tag>?
var testTags: List<SelfossModel.Tag>? = null
runBlocking {
testTags = repository.getTags()
}
@ -393,7 +394,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)
@ -409,7 +410,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository()
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
// Tags will be fetched from the database on the second call, thus testTags != tags
@ -423,7 +424,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)
@ -439,7 +440,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository()
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
@ -450,7 +451,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)
@ -466,7 +467,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository()
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
testTags = repository.getTags()
@ -479,7 +480,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)
@ -495,7 +496,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
@ -507,7 +508,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)
@ -523,7 +524,7 @@ class RepositoryTest {
every { appSettingsService.isUpdateSourcesEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
@ -534,7 +535,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)
@ -550,7 +551,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
@ -562,7 +563,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)
@ -578,7 +579,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository(MutableStateFlow(false))
var testTags: List<SelfossModel.Tag>
var testTags: List<SelfossModel.Tag> = emptyList()
runBlocking {
testTags = repository.getTags()
}
@ -630,7 +631,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -684,7 +685,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
// Sources will be fetched from the database on the second call, thus testSources != sources
@ -741,7 +742,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -795,7 +796,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -847,7 +848,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -901,7 +902,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -955,7 +956,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -1009,7 +1010,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>?
var testSources: List<SelfossModel.Source>? = null
runBlocking {
testSources = repository.getSources()
}
@ -1025,7 +1026,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(true)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.createSource(
"test",
@ -1055,7 +1056,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(false)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.createSource(
"test",
@ -1085,7 +1086,7 @@ class RepositoryTest {
SelfossModel.SuccessResponse(true)
initializeRepository(MutableStateFlow(false))
var response: Boolean
var response = false
runBlocking {
response = repository.createSource(
"test",
@ -1114,7 +1115,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(true)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.deleteSource(5)
}
@ -1128,7 +1129,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.deleteSource(5)
}
@ -1142,7 +1143,7 @@ class RepositoryTest {
coEvery { api.deleteSource(any()) } returns SelfossModel.SuccessResponse(false)
initializeRepository(MutableStateFlow(false))
var response: Boolean
var response = false
runBlocking {
response = repository.deleteSource(5)
}
@ -1159,7 +1160,7 @@ class RepositoryTest {
)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.updateRemote()
}
@ -1176,7 +1177,7 @@ class RepositoryTest {
)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.updateRemote()
}
@ -1193,7 +1194,7 @@ class RepositoryTest {
)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.updateRemote()
}
@ -1210,7 +1211,7 @@ class RepositoryTest {
)
initializeRepository(MutableStateFlow(false))
var response: Boolean
var response = false
runBlocking {
response = repository.updateRemote()
}
@ -1224,7 +1225,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.login()
}
@ -1238,7 +1239,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = false)
initializeRepository()
var response: Boolean
var response = false
runBlocking {
response = repository.login()
}
@ -1252,7 +1253,7 @@ class RepositoryTest {
coEvery { api.login() } returns SelfossModel.SuccessResponse(success = true)
initializeRepository(MutableStateFlow(false))
var response: Boolean
var response = false
runBlocking {
response = repository.login()
}
@ -1366,3 +1367,55 @@ 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"
}

View File

@ -1,57 +0,0 @@
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"
}

View File

@ -1,7 +1,7 @@
buildscript {
dependencies {
// SqlDelight
classpath("com.squareup.sqldelight:gradle-plugin:1.5.4")
classpath("com.squareup.sqldelight:gradle-plugin:1.5.3")
}
}

View File

@ -34,3 +34,4 @@ org.gradle.caching=true
ignoreGitVersion=false
kotlin.native.cacheKind.iosX64=none
pushCache=true

View File

@ -1,7 +1,7 @@
object SqlDelight {
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"
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"
}
@ -58,7 +58,6 @@ 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)

View File

@ -1,16 +1,33 @@
package bou.amine.apps.readerforselfossv2.utils
import android.os.Build
import android.text.format.DateUtils
import kotlinx.datetime.*
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
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
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()
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")
}
}
}
@ -18,12 +35,15 @@ actual class DateUtils {
val date = parseDate(dateString)
return " " + DateUtils.getRelativeTimeSpanString(
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
" " + DateUtils.getRelativeTimeSpanString(
date,
Clock.System.now().toEpochMilliseconds(),
Instant.now().toEpochMilli(),
DateUtils.MINUTE_IN_MILLIS,
DateUtils.FORMAT_ABBREV_RELATIVE
)
} else {
TODO("VERSION.SDK_INT < O")
}
}
}

View File

@ -108,8 +108,8 @@ class SelfossModel {
return stringUrl
}
fun sourceAndDateText(): String =
this.sourcetitle.getHtmlDecoded() + DateUtils.parseRelativeDate(this.datetime)
fun sourceAndDateText(dateUtils: DateUtils): String =
this.sourcetitle.getHtmlDecoded() + dateUtils.parseRelativeDate(this.datetime)
fun toggleStar(): Item {
this.starred = !this.starred

View File

@ -11,6 +11,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
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) {
@ -18,6 +19,7 @@ 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
@ -73,7 +75,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
@ -392,6 +394,7 @@ class Repository(private val api: SelfossApi, private val appSettingsService: Ap
appSettingsService.updateApiVersion(fetchedVersion.data.getApiMajorVersion())
}
}
dateUtils = DateUtils(appSettingsService)
}
fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride

View File

@ -0,0 +1,27 @@
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
}

View File

@ -1,9 +1,16 @@
package bou.amine.apps.readerforselfossv2.utils
expect class DateUtils() {
companion object {
import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
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
}
}

View File

@ -1,7 +1,8 @@
package bou.amine.apps.readerforselfossv2.utils
actual class DateUtils {
actual companion object {
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")
}
@ -9,5 +10,5 @@ actual class DateUtils {
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}

View File

@ -2,8 +2,7 @@ package bou.amine.apps.readerforselfossv2.utils
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
actual class DateUtils {
actual companion object {
actual class DateUtils actual constructor(actual val appSettingsService: AppSettingsService) {
actual fun parseDate(dateString: String): Long {
TODO("Not yet implemented")
}
@ -11,6 +10,5 @@ actual class DateUtils {
actual fun parseRelativeDate(dateString: String): String {
TODO("Not yet implemented")
}
}
}