chore: new connectivity dep. Closes #84.
All checks were successful
Check PR code / Lint (pull_request) Successful in 1m2s
Check PR code / translations (pull_request) Successful in 1m24s
Check PR code / build (pull_request) Successful in 14m10s

This commit is contained in:
2025-03-10 21:11:39 +01:00
parent ef13e300f0
commit 3bf60f1146
8 changed files with 152 additions and 144 deletions

View File

@ -12,28 +12,38 @@ plugins {
id("app.cash.sqldelight") version "2.0.2"
}
fun Project.execWithOutput(cmd: String, ignore: Boolean = false): String {
val result: String = ByteArrayOutputStream().use { outputStream ->
project.exec {
commandLine = cmd.split(" ")
standardOutput = outputStream
isIgnoreExitValue = ignore
fun Project.execWithOutput(
cmd: String,
ignore: Boolean = false,
): String {
val result: String =
ByteArrayOutputStream().use { outputStream ->
project.exec {
commandLine = cmd.split(" ")
standardOutput = outputStream
isIgnoreExitValue = ignore
}
outputStream.toString()
}
outputStream.toString()
}
return result
}
fun gitVersion(): String {
val maybeTagOfCurrentCommit = execWithOutput("git -C ../ describe --contains HEAD", true)
val process = if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.")
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
} else {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process.replace("^0", "").replace("'", "").substring(1).replace("\\.", "").trim()
val process =
if (maybeTagOfCurrentCommit.isEmpty()) {
println("No tag on current commit. Will take the latest one.")
execWithOutput("git -C ../ for-each-ref refs/tags --sort=-refname --format='%(refname:short)' --count=1")
} else {
println("Tag found on current commit")
execWithOutput("git -C ../ describe --contains HEAD")
}
return process
.replace("^0", "")
.replace("'", "")
.substring(1)
.replace("\\.", "")
.trim()
}
fun versionCodeFromGit(): Int {
@ -116,7 +126,6 @@ android {
isIncludeAndroidResources = true
}
}
}
dependencies {
@ -141,7 +150,7 @@ dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.2.0")
implementation("org.jsoup:jsoup:1.18.3")
//multidex
// multidex
implementation("androidx.multidex:multidex:2.0.1")
// About
@ -162,31 +171,28 @@ dependencies {
implementation("me.relex:circleindicator:2.1.6")
implementation("androidx.viewpager2:viewpager2:1.1.0")
//Dependency Injection
// Dependency Injection
implementation("org.kodein.di:kodein-di:7.23.1")
implementation("org.kodein.di:kodein-di-framework-android-x:7.23.1")
implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.23.1")
//Settings
// Settings
implementation("com.russhwolf:multiplatform-settings-no-arg:1.3.0")
//Logging
// Logging
implementation("io.github.aakira:napier:2.7.1")
//PhotoView
// PhotoView
implementation("com.github.chrisbanes:PhotoView:2.3.0")
implementation("androidx.core:core-ktx:1.15.0")
implementation("androidx.lifecycle:lifecycle-extensions:2.2.0")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0")
// SQLDELIGHT
implementation("app.cash.sqldelight:android-driver:2.0.2")
//test
// test
testImplementation("junit:junit:4.13.2")
testImplementation("io.mockk:mockk:1.13.14")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
@ -210,11 +216,12 @@ tasks.withType<Test> {
useJUnit()
testLogging {
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
events = setOf(
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR
)
events =
setOf(
org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED,
org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED,
org.gradle.api.tasks.testing.logging.TestLogEvent.STANDARD_ERROR,
)
showStandardStreams = true
}
}
@ -227,4 +234,4 @@ aboutLibraries {
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
duplicationMode = com.mikepenz.aboutlibraries.plugin.DuplicateMode.MERGE
duplicationRule = com.mikepenz.aboutlibraries.plugin.DuplicateRule.GROUP
}
}

View File

@ -10,18 +10,16 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.multidex.MultiDexApplication
import bou.amine.apps.readerforselfossv2.android.testing.TestingHelper
import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel
import bou.amine.apps.readerforselfossv2.dao.DriverFactory
import bou.amine.apps.readerforselfossv2.dao.ReaderForSelfossDB
import bou.amine.apps.readerforselfossv2.di.networkModule
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import com.github.ln_12.library.ConnectivityStatus
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
import io.github.aakira.napier.DebugAntilog
import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.acra.ACRA
import org.acra.ReportField
@ -44,27 +42,21 @@ class MyApp :
import(networkModule)
bind<DriverFactory>() with singleton { DriverFactory(applicationContext) }
bind<ReaderForSelfossDB>() with singleton { ReaderForSelfossDB(driverFactory.createDriver()) }
bind<ConnectivityService>() with singleton { ConnectivityService() }
bind<Repository>() with
singleton {
Repository(
instance(),
instance(),
isConnectionAvailable,
instance(),
instance(),
)
}
bind<ConnectivityStatus>() with singleton { ConnectivityStatus(applicationContext) }
bind<AppViewModel>() with singleton { AppViewModel(repository = instance()) }
}
private val repository: Repository by instance()
private val viewModel: AppViewModel by instance()
private val connectivityStatus: ConnectivityStatus by instance()
private val driverFactory: DriverFactory by instance()
@Suppress("detekt:ForbiddenComment")
// TODO: handle with the "previous" way
private val isConnectionAvailable: MutableStateFlow<Boolean> = MutableStateFlow(true)
private val connectivityService: ConnectivityService by instance()
override fun onCreate() {
super.onCreate()
@ -77,13 +69,12 @@ class MyApp :
ProcessLifecycleOwner.get().lifecycle.addObserver(
AppLifeCycleObserver(
connectivityStatus,
repository,
connectivityService,
),
)
CoroutineScope(Dispatchers.Main).launch {
viewModel.networkAvailableProvider.collect { networkAvailable ->
connectivityService.networkAvailableProvider.collect { networkAvailable ->
val toastMessage =
if (networkAvailable) {
repository.handleDBActions()
@ -189,18 +180,15 @@ class MyApp :
}
class AppLifeCycleObserver(
val connectivityStatus: ConnectivityStatus,
val repository: Repository,
val connectivityService: ConnectivityService,
) : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
repository.connectionMonitored = true
connectivityStatus.start()
connectivityService.start()
}
override fun onPause(owner: LifecycleOwner) {
repository.connectionMonitored = false
connectivityStatus.stop()
connectivityService.stop()
super.onPause(owner)
}
}

View File

@ -42,6 +42,7 @@ import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.MercuryApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
import bou.amine.apps.readerforselfossv2.utils.getHtmlDecoded
import bou.amine.apps.readerforselfossv2.utils.getImages
import bou.amine.apps.readerforselfossv2.utils.getThumbnail
@ -88,6 +89,7 @@ class ArticleFragment :
override val di: DI by closestDI()
private val repository: Repository by instance()
private val appSettingsService: AppSettingsService by instance()
private val connectivityService: ConnectivityService by instance()
private var typeface: Typeface? = null
private var resId: Int = 0
@ -168,7 +170,7 @@ class ArticleFragment :
private fun handleContent() {
if (contentText.isEmptyOrNullOrNullString()) {
if (repository.isNetworkAvailable() && url.isUrlValid()) {
if (connectivityService.isNetworkAvailable() == true && url.isUrlValid()) {
getContentFromMercury(url!!)
}
} else {

View File

@ -1,32 +0,0 @@
package bou.amine.apps.readerforselfossv2.android.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import bou.amine.apps.readerforselfossv2.repository.Repository
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class AppViewModel(
private val repository: Repository,
) : ViewModel() {
private val _networkAvailableProvider = MutableSharedFlow<Boolean>()
val networkAvailableProvider = _networkAvailableProvider.asSharedFlow()
private var wasConnected = true
init {
viewModelScope.launch {
repository.isConnectionAvailable.collect { isConnected ->
if (repository.connectionMonitored) {
if (isConnected && !wasConnected && repository.connectionMonitored) {
_networkAvailableProvider.emit(true)
wasConnected = true
} else if (!isConnected && wasConnected && repository.connectionMonitored) {
_networkAvailableProvider.emit(false)
wasConnected = false
}
}
}
}
}
}

View File

@ -11,6 +11,7 @@ import bou.amine.apps.readerforselfossv2.model.SuccessResponse
import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.rest.SelfossApi
import bou.amine.apps.readerforselfossv2.service.AppSettingsService
import bou.amine.apps.readerforselfossv2.service.ConnectivityService
import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.toView
import io.mockk.clearAllMocks
@ -24,7 +25,6 @@ import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNotSame
import junit.framework.TestCase.assertSame
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertNotEquals
import org.junit.Before
@ -52,15 +52,12 @@ class RepositoryTest {
private val db = mockk<ReaderForSelfossDB>(relaxed = true)
private val appSettingsService = mockk<AppSettingsService>()
private val api = mockk<SelfossApi>()
private val connectivityService = mockk<ConnectivityService>()
private lateinit var repository: Repository
private fun initializeRepository(
isConnectionAvailable: MutableStateFlow<Boolean> =
MutableStateFlow(
true,
),
) {
repository = Repository(api, appSettingsService, isConnectionAvailable, db)
private fun initializeRepository(isNetworkAvailable: Boolean? = true) {
every { connectivityService.isNetworkAvailable() } returns isNetworkAvailable
repository = Repository(api, appSettingsService, connectivityService, db)
runBlocking {
repository.updateApiInformation()
@ -110,7 +107,7 @@ class RepositoryTest {
fun instantiate_repository_without_api_version() {
every { appSettingsService.getApiVersion() } returns -1
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
coVerify(exactly = 0) { api.apiInformation() }
coVerify(exactly = 0) { api.stats() }
@ -287,7 +284,7 @@ class RepositoryTest {
fun get_newer_items_without_connectivity() {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
runBlocking {
repository.getNewerItems()
}
@ -314,7 +311,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
repository.setTagFilter(SelfossModel.Tag("Test", "red", 3))
runBlocking {
repository.getNewerItems()
@ -342,7 +339,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
repository.setSourceFilter(
SelfossModel.SourceDetail(
1,
@ -457,7 +454,7 @@ class RepositoryTest {
var success: Boolean
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
runBlocking {
success = repository.reloadBadges()
}
@ -477,7 +474,7 @@ class RepositoryTest {
var success: Boolean
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
runBlocking {
success = repository.reloadBadges()
}
@ -572,7 +569,7 @@ class RepositoryTest {
every { appSettingsService.isUpdateSourcesEnabled() } returns true
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
@ -590,7 +587,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
@ -607,7 +604,7 @@ class RepositoryTest {
every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
@ -625,7 +622,7 @@ class RepositoryTest {
every { appSettingsService.isUpdateSourcesEnabled() } returns false
every { appSettingsService.isItemCachingEnabled() } returns false
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testTags: List<SelfossModel.Tag>
runBlocking {
testTags = repository.getTags()
@ -775,7 +772,7 @@ class RepositoryTest {
@Test
fun get_sources_without_connection() {
val (_, sourcesDB) = prepareSources()
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testSources: List<SelfossModel.Source>
runBlocking {
testSources = repository.getSourcesDetails()
@ -792,7 +789,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns true
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testSources: List<SelfossModel.Source>
runBlocking {
testSources = repository.getSourcesDetails()
@ -809,7 +806,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns true
every { appSettingsService.isUpdateSourcesEnabled() } returns false
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testSources: List<SelfossModel.Source>
runBlocking {
testSources = repository.getSourcesDetails()
@ -826,7 +823,7 @@ class RepositoryTest {
every { appSettingsService.isItemCachingEnabled() } returns false
every { appSettingsService.isUpdateSourcesEnabled() } returns false
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var testSources: List<SelfossModel.Source>
runBlocking {
testSources = repository.getSourcesDetails()
@ -898,7 +895,7 @@ class RepositoryTest {
coEvery { api.createSourceForVersion(any(), any(), any(), any()) } returns
SuccessResponse(true)
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var response: Boolean
runBlocking {
response =
@ -955,7 +952,7 @@ class RepositoryTest {
fun delete_source_without_connection() {
coEvery { api.deleteSource(any()) } returns SuccessResponse(false)
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var response: Boolean
runBlocking {
response = repository.deleteSource(5, "src")
@ -1028,7 +1025,7 @@ class RepositoryTest {
data = "undocumented...",
)
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var response: Boolean
runBlocking {
response = repository.updateRemote()
@ -1070,7 +1067,7 @@ class RepositoryTest {
fun login_but_without_connection() {
coEvery { api.login() } returns SuccessResponse(success = true)
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
var response: Boolean
runBlocking {
response = repository.login()
@ -1150,7 +1147,7 @@ class RepositoryTest {
coEvery { api.getItems(any(), any(), any(), any(), any(), any(), any()) } returns
StatusAndData(success = false, data = generateTestApiItem())
initializeRepository(MutableStateFlow(false))
initializeRepository(false)
prepareSearch()
runBlocking {
repository.tryToCacheItemsAndGetNewOnes()