Compare commits

...

2 Commits

Author SHA1 Message Date
b7b4ff1485 Perform network connectivity checks in the repository 2022-08-17 15:14:58 +02:00
davidoskky
728eb747c3 Add multiplatform connectivity check 2022-08-17 14:51:00 +02:00
6 changed files with 214 additions and 90 deletions

View File

@ -13,6 +13,7 @@ import bou.amine.apps.readerforselfossv2.DI.networkModule
import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.Config
import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
import bou.amine.apps.readerforselfossv2.utils.NetworkStatus
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions import com.bumptech.glide.request.RequestOptions
import com.ftinc.scoop.Scoop import com.ftinc.scoop.Scoop
@ -25,7 +26,7 @@ class MyApp : MultiDexApplication(), DIAware {
override val di by DI.lazy { override val di by DI.lazy {
import(networkModule) import(networkModule)
bind<Repository>() with singleton { Repository(instance(), instance()) } bind<Repository>() with singleton { Repository(instance(), instance(), NetworkStatus(applicationContext)) }
} }
private lateinit var config: Config private lateinit var config: Config

View File

@ -36,6 +36,9 @@ kotlin {
//Logging //Logging
implementation("io.github.aakira:napier:2.6.1") implementation("io.github.aakira:napier:2.6.1")
// Network information
implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0")
} }
} }
val commonTest by getting { val commonTest by getting {

View File

@ -0,0 +1,17 @@
package bou.amine.apps.readerforselfossv2.utils
import android.content.Context
import com.github.`ln-12`.library.ConnectivityStatus
actual class NetworkStatus(context: Context) {
private val connectivityStatus = ConnectivityStatus(context)
actual val current = connectivityStatus.isNetworkConnected
actual fun start() {
connectivityStatus.start()
}
actual fun stop() {
connectivityStatus.stop()
}
}

View File

@ -5,16 +5,18 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel
import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.service.ApiDetailsService
import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.DateUtils
import bou.amine.apps.readerforselfossv2.utils.ItemType import bou.amine.apps.readerforselfossv2.utils.ItemType
import bou.amine.apps.readerforselfossv2.utils.NetworkStatus
import com.russhwolf.settings.Settings import com.russhwolf.settings.Settings
import io.github.aakira.napier.Napier import io.github.aakira.napier.Napier
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService) { class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, networkStatus: NetworkStatus) {
val settings = Settings() val settings = Settings()
var items = ArrayList<SelfossModel.Item>() var items = ArrayList<SelfossModel.Item>()
private val isConnectionAvailable = networkStatus.current
var baseUrl = apiDetails.getBaseUrl() var baseUrl = apiDetails.getBaseUrl()
lateinit var dateUtils: DateUtils lateinit var dateUtils: DateUtils
@ -36,6 +38,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
set(value) {field = if (value < 0) { 0 } else { value } } set(value) {field = if (value < 0) { 0 } else { value } }
init { init {
networkStatus.start()
// TODO: Dispatchers.IO not available in KMM, an alternative solution should be found // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
updateApiVersion() updateApiVersion()
@ -45,40 +48,65 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
} }
suspend fun getNewerItems(): ArrayList<SelfossModel.Item> { suspend fun getNewerItems(): ArrayList<SelfossModel.Item> {
// TODO: Check connectivity, use the updatedSince parameter // TODO: Use the updatedSince parameter
val fetchedItems = api.getItems(displayedItems.type, if (isConnectionAvailable.value) {
val fetchedItems = api.getItems(
displayedItems.type,
settings.getString("prefer_api_items_number", "200").toInt(), settings.getString("prefer_api_items_number", "200").toInt(),
offset = 0, offset = 0,
tagFilter?.tag, tagFilter?.tag,
sourceFilter?.id?.toLong(), sourceFilter?.id?.toLong(),
searchFilter, searchFilter,
null) null
)
if (fetchedItems != null) { if (fetchedItems != null) {
items = ArrayList(fetchedItems) items = ArrayList(fetchedItems)
} }
} else {
// TODO: Provide an error message if the connection is not available.
// TODO: Get items from the database
}
return items return items
} }
suspend fun getOlderItems(): ArrayList<SelfossModel.Item> { suspend fun getOlderItems(): ArrayList<SelfossModel.Item> {
// TODO: Check connectivity if (isConnectionAvailable.value) {
val offset = items.size val offset = items.size
val fetchedItems = api.getItems(displayedItems.type, val fetchedItems = api.getItems(
displayedItems.type,
settings.getString("prefer_api_items_number", "200").toInt(), settings.getString("prefer_api_items_number", "200").toInt(),
offset, offset,
tagFilter?.tag, tagFilter?.tag,
sourceFilter?.id?.toLong(), sourceFilter?.id?.toLong(),
searchFilter, searchFilter,
null) null
)
if (fetchedItems != null) { if (fetchedItems != null) {
appendItems(fetchedItems) appendItems(fetchedItems)
} }
} else {
// TODO: Provide an error message
}
return items return items
} }
suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? = suspend fun allItems(itemType: ItemType): List<SelfossModel.Item>? {
api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null) return if (isConnectionAvailable.value) {
api.getItems(
itemType.type,
200,
0,
tagFilter?.tag,
sourceFilter?.id?.toLong(),
searchFilter,
null
)
} else {
null
}
}
private fun appendItems(fetchedItems: List<SelfossModel.Item>) { private fun appendItems(fetchedItems: List<SelfossModel.Item>) {
// TODO: Store in DB if enabled by user // TODO: Store in DB if enabled by user
@ -94,8 +122,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
} }
suspend fun reloadBadges(): Boolean { suspend fun reloadBadges(): Boolean {
// TODO: Check connectivity, calculate from DB
var success = false var success = false
if (isConnectionAvailable.value) {
val response = api.stats() val response = api.stats()
if (response != null) { if (response != null) {
badgeUnread = response.unread badgeUnread = response.unread
@ -103,68 +131,99 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
badgeStarred = response.starred badgeStarred = response.starred
success = true success = true
} }
} else {
// TODO: Compute badges from database
}
return success return success
} }
suspend fun getTags(): List<SelfossModel.Tag>? { suspend fun getTags(): List<SelfossModel.Tag>? {
// TODO: Check success, store in DB // TODO: Store in DB
return api.tags() return if (isConnectionAvailable.value) {
api.tags()
} else {
// TODO: Compute from database
null
}
} }
suspend fun getSpouts(): Map<String, SelfossModel.Spout>? { suspend fun getSpouts(): Map<String, SelfossModel.Spout>? {
// TODO: Check success, store in DB // TODO: Store in DB
return api.spouts() return if (isConnectionAvailable.value) {
api.spouts()
} else {
// TODO: Compute from database
null
}
} }
suspend fun getSources(): ArrayList<SelfossModel.Source>? { suspend fun getSources(): ArrayList<SelfossModel.Source>? {
// TODO: Check success // TODO: Store in DB
return api.sources() return if (isConnectionAvailable.value) {
api.sources()
} else {
// TODO: Compute from database
null
}
} }
suspend fun markAsRead(id: Int): Boolean { suspend fun markAsRead(id: Int): Boolean {
// TODO: Check internet connection var success = false
val success = api.markAsRead(id.toString())?.isSuccess == true
if (isConnectionAvailable.value) {
success = api.markAsRead(id.toString())?.isSuccess == true
if (success) { if (success) {
markAsReadLocally(items.first {it.id == id}) markAsReadLocally(items.first { it.id == id })
}
} }
return success return success
} }
suspend fun unmarkAsRead(id: Int): Boolean { suspend fun unmarkAsRead(id: Int): Boolean {
// TODO: Check internet connection var success = false
val success = api.unmarkAsRead(id.toString())?.isSuccess == true
if (isConnectionAvailable.value) {
success = api.unmarkAsRead(id.toString())?.isSuccess == true
if (success) { if (success) {
unmarkAsReadLocally(items.first {it.id == id}) unmarkAsReadLocally(items.first { it.id == id })
}
} }
return success return success
} }
suspend fun starr(id: Int): Boolean { suspend fun starr(id: Int): Boolean {
// TODO: Check success, store in DB var success = false
val success = api.starr(id.toString())?.isSuccess == true
if (isConnectionAvailable.value) {
success = api.starr(id.toString())?.isSuccess == true
if (success) { if (success) {
starrLocally(items.first {it.id == id}) starrLocally(items.first { it.id == id })
}
} }
return success return success
} }
suspend fun unstarr(id: Int): Boolean { suspend fun unstarr(id: Int): Boolean {
// TODO: Check internet connection var success = false
val success = api.unstarr(id.toString())?.isSuccess == true
if (isConnectionAvailable.value) {
success = api.unstarr(id.toString())?.isSuccess == true
if (success) { if (success) {
unstarrLocally(items.first {it.id == id}) unstarrLocally(items.first { it.id == id })
}
} }
return success return success
} }
suspend fun markAllAsRead(ids: List<Int>): Boolean { suspend fun markAllAsRead(ids: List<Int>): Boolean {
// TODO: Check Internet connectivity, store in DB var success = false
val success = api.markAllAsRead(ids.map { it.toString() })?.isSuccess == true if (isConnectionAvailable.value) {
success = api.markAllAsRead(ids.map { it.toString() })?.isSuccess == true
if (success) { if (success) {
val itemsToMark = items.filter { it.id in ids } val itemsToMark = items.filter { it.id in ids }
@ -172,6 +231,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
markAsReadLocally(item) markAsReadLocally(item)
} }
} }
}
return success return success
} }
@ -214,45 +274,51 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
tags: String, tags: String,
filter: String filter: String
): Boolean { ): Boolean {
// TODO: Check connectivity var response = false
val response = api.createSourceForVersion( if (isConnectionAvailable.value) {
response = api.createSourceForVersion(
title, title,
url, url,
spout, spout,
tags, tags,
filter, filter,
apiMajorVersion apiMajorVersion
) )?.isSuccess == true
}
return response != null return response
} }
suspend fun deleteSource(id: Int): Boolean { suspend fun deleteSource(id: Int): Boolean {
// TODO: Check connectivity, store in DB // TODO: Store in DB
var success = false var success = false
if (isConnectionAvailable.value) {
val response = api.deleteSource(id) val response = api.deleteSource(id)
if (response != null) { if (response != null) {
success = response.isSuccess success = response.isSuccess
} }
}
return success return success
} }
suspend fun updateRemote(): Boolean { suspend fun updateRemote(): Boolean {
// TODO: Handle connectivity issues var response = false
val response = api.update() if (isConnectionAvailable.value) {
return response?.isSuccess ?: false response = api.update()?.isSuccess == true
}
return response
} }
suspend fun login(): Boolean { suspend fun login(): Boolean {
var result = false var result = false
if (isConnectionAvailable.value) {
try { try {
val response = api.login() val response = api.login()
if (response != null && response.isSuccess) { result = response?.isSuccess == true
result = true
}
} catch (cause: Throwable) { } catch (cause: Throwable) {
Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote") Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote")
}
} }
return result return result
} }
@ -271,13 +337,14 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails
} }
private suspend fun updateApiVersion() { private suspend fun updateApiVersion() {
// TODO: Handle connectivity issues apiMajorVersion = settings.getInt("apiVersionMajor", 0)
if (isConnectionAvailable.value) {
val fetchedVersion = api.version() val fetchedVersion = api.version()
if (fetchedVersion != null) { if (fetchedVersion != null) {
apiMajorVersion = fetchedVersion.getApiMajorVersion() apiMajorVersion = fetchedVersion.getApiMajorVersion()
settings.putInt("apiVersionMajor", apiMajorVersion) settings.putInt("apiVersionMajor", apiMajorVersion)
} else { }
apiMajorVersion = settings.getInt("apiVersionMajor", 0)
} }
} }

View File

@ -0,0 +1,9 @@
package bou.amine.apps.readerforselfossv2.utils
import kotlinx.coroutines.flow.MutableStateFlow
expect class NetworkStatus {
val current: MutableStateFlow<Boolean>
fun start()
fun stop()
}

View File

@ -0,0 +1,27 @@
package bou.amine.apps.readerforselfossv2.utils
import com.github.`ln-12`.library.ConnectivityStatus
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
actual class NetworkStatus {
private val connectivityStatus: ConnectivityStatus = ConnectivityStatus()
actual val current: MutableStateFlow<Boolean> = connectivityStatus.isNetworkConnected
actual fun start() {
connectivityStatus.start()
}
actual fun stop() {
connectivityStatus.stop()
}
fun getStatus(success: (Boolean) -> Unit) {
MainScope().launch {
connectivityStatus.isNetworkConnected.collect { status ->
success(status)
}
}
}
}