From 37fa4a1a8ea0c27b55f961cb6d1516251370803a Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 14:51:00 +0200 Subject: [PATCH 01/28] Add multiplatform connectivity check --- .../apps/readerforselfossv2/android/MyApp.kt | 3 ++- shared/build.gradle.kts | 3 +++ .../readerforselfossv2/utils/NetworkStatus.kt | 17 ++++++++++++ .../repository/RepositoryImpl.kt | 5 +++- .../readerforselfossv2/utils/NetworkStatus.kt | 9 +++++++ .../readerforselfossv2/utils/NetworkStatus.kt | 27 +++++++++++++++++++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt create mode 100644 shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt create mode 100644 shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index a59b0d4..622017c 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -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.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.repository.Repository +import bou.amine.apps.readerforselfossv2.utils.NetworkStatus import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop @@ -27,7 +28,7 @@ class MyApp : MultiDexApplication(), DIAware { override val di by DI.lazy { import(networkModule) - bind() with singleton { Repository(instance(), instance()) } + bind() with singleton { Repository(instance(), instance(), NetworkStatus(applicationContext)) } } private lateinit var config: Config diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index ec4c4b3..9a5992d 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -36,6 +36,9 @@ kotlin { //Logging 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 { diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt new file mode 100644 index 0000000..03ba29e --- /dev/null +++ b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt @@ -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() + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 2dcaa46..97c83d2 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -5,16 +5,18 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.ItemType +import bou.amine.apps.readerforselfossv2.utils.NetworkStatus import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers 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() var items = ArrayList() + private val isConnectionAvailable = networkStatus.current var baseUrl = apiDetails.getBaseUrl() 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 } } init { + networkStatus.start() // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found CoroutineScope(Dispatchers.Main).launch { updateApiVersion() diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt new file mode 100644 index 0000000..da7b9b9 --- /dev/null +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt @@ -0,0 +1,9 @@ +package bou.amine.apps.readerforselfossv2.utils + +import kotlinx.coroutines.flow.MutableStateFlow + +expect class NetworkStatus { + val current: MutableStateFlow + fun start() + fun stop() +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt new file mode 100644 index 0000000..fbd45a0 --- /dev/null +++ b/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt @@ -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 = 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) + } + } + } +} \ No newline at end of file -- 2.34.1 From 4f32097821184dce6291d7bfc83ac93427834cf6 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 15:14:58 +0200 Subject: [PATCH 02/28] Perform network connectivity checks in the repository --- .../repository/RepositoryImpl.kt | 219 ++++++++++++------ 1 file changed, 142 insertions(+), 77 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 97c83d2..0944cbd 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -48,40 +48,65 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun getNewerItems(): ArrayList { - // TODO: Check connectivity, use the updatedSince parameter - val fetchedItems = api.getItems(displayedItems.type, - settings.getString("prefer_api_items_number", "200").toInt(), - offset = 0, - tagFilter?.tag, - sourceFilter?.id?.toLong(), - searchFilter, - null) + // TODO: Use the updatedSince parameter + if (isConnectionAvailable.value) { + val fetchedItems = api.getItems( + displayedItems.type, + settings.getString("prefer_api_items_number", "200").toInt(), + offset = 0, + tagFilter?.tag, + sourceFilter?.id?.toLong(), + searchFilter, + null + ) - if (fetchedItems != null) { - items = ArrayList(fetchedItems) + if (fetchedItems != null) { + items = ArrayList(fetchedItems) + } + } else { + // TODO: Provide an error message if the connection is not available. + // TODO: Get items from the database } return items } suspend fun getOlderItems(): ArrayList { - // TODO: Check connectivity - val offset = items.size - val fetchedItems = api.getItems(displayedItems.type, - settings.getString("prefer_api_items_number", "200").toInt(), - offset, - tagFilter?.tag, - sourceFilter?.id?.toLong(), - searchFilter, - null) + if (isConnectionAvailable.value) { + val offset = items.size + val fetchedItems = api.getItems( + displayedItems.type, + settings.getString("prefer_api_items_number", "200").toInt(), + offset, + tagFilter?.tag, + sourceFilter?.id?.toLong(), + searchFilter, + null + ) - if (fetchedItems != null) { - appendItems(fetchedItems) + if (fetchedItems != null) { + appendItems(fetchedItems) + } + } else { + // TODO: Provide an error message } return items } - suspend fun allItems(itemType: ItemType): List? = - api.getItems(itemType.type, 200, 0, tagFilter?.tag, sourceFilter?.id?.toLong(), searchFilter, null) + suspend fun allItems(itemType: ItemType): List? { + return if (isConnectionAvailable.value) { + api.getItems( + itemType.type, + 200, + 0, + tagFilter?.tag, + sourceFilter?.id?.toLong(), + searchFilter, + null + ) + } else { + null + } + } private fun appendItems(fetchedItems: List) { // TODO: Store in DB if enabled by user @@ -97,35 +122,52 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun reloadBadges(): Boolean { - // TODO: Check connectivity, calculate from DB var success = false - val response = api.stats() - if (response != null) { - badgeUnread = response.unread - badgeAll = response.total - badgeStarred = response.starred - success = true + if (isConnectionAvailable.value) { + val response = api.stats() + if (response != null) { + badgeUnread = response.unread + badgeAll = response.total + badgeStarred = response.starred + success = true + } + } else { + // TODO: Compute badges from database } return success } suspend fun getTags(): List? { - // TODO: Check success, store in DB - return api.tags() + // TODO: Store in DB + return if (isConnectionAvailable.value) { + api.tags() + } else { + // TODO: Compute from database + null + } } suspend fun getSpouts(): Map? { - // TODO: Check success, store in DB - return api.spouts() + // TODO: Store in DB + return if (isConnectionAvailable.value) { + api.spouts() + } else { + // TODO: Compute from database + null + } } suspend fun getSources(): ArrayList? { - // TODO: Check success - return api.sources() + // TODO: Store in DB + return if (isConnectionAvailable.value) { + api.sources() + } else { + // TODO: Compute from database + null + } } suspend fun markAsRead(item: SelfossModel.Item): Boolean { - // TODO: Check internet connection val success = markAsReadById(item.id) if (success) { @@ -135,11 +177,15 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun markAsReadById(id: Int): Boolean { - // TODO: Check internet connection - return api.markAsRead(id.toString())?.isSuccess == true + var success = false + if (isConnectionAvailable.value) { + success = api.markAsRead(id.toString())?.isSuccess == true + } + return success } suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean { + // TODO: Check internet connection val success = unmarkAsReadById(item.id) if (success) { @@ -150,7 +196,11 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun unmarkAsReadById(id: Int): Boolean { // TODO: Check internet connection - return api.unmarkAsRead(id.toString())?.isSuccess == true + var success = false + if (isConnectionAvailable.value) { + success = api.unmarkAsRead(id.toString())?.isSuccess == true + } + return success } suspend fun starr(item: SelfossModel.Item): Boolean { @@ -163,8 +213,11 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun starrById(id: Int): Boolean { - // TODO: Check success, store in DB - return api.starr(id.toString())?.isSuccess == true + var success = false + if (isConnectionAvailable.value) { + success = api.starr(id.toString())?.isSuccess == true + } + return success } suspend fun unstarr(item: SelfossModel.Item): Boolean { @@ -177,14 +230,19 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun unstarrById(id: Int): Boolean { - // TODO: Check internet connection - return api.unstarr(id.toString())?.isSuccess == true + var success = false + if (isConnectionAvailable.value) { + success = api.unstarr(id.toString())?.isSuccess == true + } + return success } - suspend fun markAllAsRead(items: ArrayList): Boolean { - // TODO: Check Internet connectivity, store in DB - val success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true + suspend fun markAllAsRead(items: ArrayList): Boolean { + var success = false + if (isConnectionAvailable) { + success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true + } if (success) { for (item in items) { @@ -233,45 +291,51 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails tags: String, filter: String ): Boolean { - // TODO: Check connectivity - val response = api.createSourceForVersion( - title, - url, - spout, - tags, - filter, - apiMajorVersion - ) + var response = false + if (isConnectionAvailable.value) { + response = api.createSourceForVersion( + title, + url, + spout, + tags, + filter, + apiMajorVersion + )?.isSuccess == true + } - return response != null + return response } suspend fun deleteSource(id: Int): Boolean { - // TODO: Check connectivity, store in DB + // TODO: Store in DB var success = false - val response = api.deleteSource(id) - if (response != null) { - success = response.isSuccess + if (isConnectionAvailable.value) { + val response = api.deleteSource(id) + if (response != null) { + success = response.isSuccess + } } return success } suspend fun updateRemote(): Boolean { - // TODO: Handle connectivity issues - val response = api.update() - return response?.isSuccess ?: false + var response = false + if (isConnectionAvailable.value) { + response = api.update()?.isSuccess == true + } + return response } suspend fun login(): Boolean { var result = false - try { - val response = api.login() - if (response != null && response.isSuccess) { - result = true + if (isConnectionAvailable.value) { + try { + val response = api.login() + result = response?.isSuccess == true + } catch (cause: Throwable) { + Napier.e(cause.stackTraceToString(), tag = "RepositoryImpl.updateRemote") } - } catch (cause: Throwable) { - Napier.e(cause.stackTraceToString(),tag = "RepositoryImpl.updateRemote") } return result } @@ -290,13 +354,14 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } private suspend fun updateApiVersion() { - // TODO: Handle connectivity issues - val fetchedVersion = api.version() - if (fetchedVersion != null) { - apiMajorVersion = fetchedVersion.getApiMajorVersion() - settings.putInt("apiVersionMajor", apiMajorVersion) - } else { - apiMajorVersion = settings.getInt("apiVersionMajor", 0) + apiMajorVersion = settings.getInt("apiVersionMajor", 0) + + if (isConnectionAvailable.value) { + val fetchedVersion = api.version() + if (fetchedVersion != null) { + apiMajorVersion = fetchedVersion.getApiMajorVersion() + settings.putInt("apiVersionMajor", apiMajorVersion) + } } } -- 2.34.1 From d4c2373baca2c211eed1323907e529c8641db29b Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 19:56:24 +0200 Subject: [PATCH 03/28] Simplify network connectivity status check --- androidApp/build.gradle.kts | 3 +++ .../apps/readerforselfossv2/android/MyApp.kt | 4 +-- .../readerforselfossv2/utils/NetworkStatus.kt | 17 ------------ .../repository/RepositoryImpl.kt | 10 +++---- .../readerforselfossv2/utils/NetworkStatus.kt | 9 ------- .../readerforselfossv2/utils/NetworkStatus.kt | 27 ------------------- 6 files changed, 10 insertions(+), 60 deletions(-) delete mode 100644 shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt delete mode 100644 shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt delete mode 100644 shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 72c3fa7..73b0d74 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -202,4 +202,7 @@ dependencies { kapt("androidx.room:room-compiler:2.4.0-beta01") implementation("android.arch.work:work-runtime-ktx:1.0.1") + + // Network information + implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index 622017c..c241176 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -13,10 +13,10 @@ import bou.amine.apps.readerforselfossv2.DI.networkModule import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth import bou.amine.apps.readerforselfossv2.repository.Repository -import bou.amine.apps.readerforselfossv2.utils.NetworkStatus import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop +import com.github.`ln-12`.library.ConnectivityStatus import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.russhwolf.settings.Settings @@ -28,7 +28,7 @@ class MyApp : MultiDexApplication(), DIAware { override val di by DI.lazy { import(networkModule) - bind() with singleton { Repository(instance(), instance(), NetworkStatus(applicationContext)) } + bind() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) } } private lateinit var config: Config diff --git a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt deleted file mode 100644 index 03ba29e..0000000 --- a/shared/src/androidMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt +++ /dev/null @@ -1,17 +0,0 @@ -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() - } -} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 0944cbd..fc92c4e 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -5,18 +5,18 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.ItemType -import bou.amine.apps.readerforselfossv2.utils.NetworkStatus +import com.github.`ln-12`.library.ConnectivityStatus import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, networkStatus: NetworkStatus) { +class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, connectivityStatus: ConnectivityStatus) { val settings = Settings() var items = ArrayList() - private val isConnectionAvailable = networkStatus.current + private val isConnectionAvailable = connectivityStatus.isNetworkConnected var baseUrl = apiDetails.getBaseUrl() lateinit var dateUtils: DateUtils @@ -38,7 +38,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails set(value) {field = if (value < 0) { 0 } else { value } } init { - networkStatus.start() + connectivityStatus.start() // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found CoroutineScope(Dispatchers.Main).launch { updateApiVersion() @@ -240,7 +240,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun markAllAsRead(items: ArrayList): Boolean { var success = false - if (isConnectionAvailable) { + if (isConnectionAvailable.value) { success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true } diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt deleted file mode 100644 index da7b9b9..0000000 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt +++ /dev/null @@ -1,9 +0,0 @@ -package bou.amine.apps.readerforselfossv2.utils - -import kotlinx.coroutines.flow.MutableStateFlow - -expect class NetworkStatus { - val current: MutableStateFlow - fun start() - fun stop() -} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt b/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt deleted file mode 100644 index fbd45a0..0000000 --- a/shared/src/iosMain/kotlin/bou/amine/apps/readerforselfossv2/utils/NetworkStatus.kt +++ /dev/null @@ -1,27 +0,0 @@ -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 = 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) - } - } - } -} \ No newline at end of file -- 2.34.1 From 0f3c48dd8e4102472708f7b74b90b1358fe4f52a Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 20:04:31 +0200 Subject: [PATCH 04/28] Handle the offline override in the repository --- .../android/HomeActivity.kt | 4 +-- .../repository/RepositoryImpl.kt | 35 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index c77bbfe..69dde5c 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -153,7 +153,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar val view = binding.root fromTabShortcut = intent.getIntExtra("shortcutTab", -1) != -1 - offlineShortcut = intent.getBooleanExtra("startOffline", false) + repository.offlineOverride = intent.getBooleanExtra("startOffline", false) if (fromTabShortcut) { elementsShown = ItemType.fromInt(intent.getIntExtra("shortcutTab", ItemType.UNREAD.position)) @@ -197,7 +197,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar R.color.refresh_progress_3 ) binding.swipeRefreshLayout.setOnRefreshListener { - offlineShortcut = false + repository.offlineOverride = false lastFetchDone = false handleDrawerItems() CoroutineScope(Dispatchers.Main).launch { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index fc92c4e..2ab5bf7 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -28,6 +28,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails var searchFilter: String? = null var itemsCaching = settings.getBoolean("items_caching", false) + var offlineOverride = false var apiMajorVersion = 0 var badgeUnread = 0 @@ -49,7 +50,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getNewerItems(): ArrayList { // TODO: Use the updatedSince parameter - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { val fetchedItems = api.getItems( displayedItems.type, settings.getString("prefer_api_items_number", "200").toInt(), @@ -71,7 +72,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun getOlderItems(): ArrayList { - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { val offset = items.size val fetchedItems = api.getItems( displayedItems.type, @@ -93,7 +94,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun allItems(itemType: ItemType): List? { - return if (isConnectionAvailable.value) { + return if (isConnectionAvailable.value && !offlineOverride) { api.getItems( itemType.type, 200, @@ -123,7 +124,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun reloadBadges(): Boolean { var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { val response = api.stats() if (response != null) { badgeUnread = response.unread @@ -139,7 +140,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getTags(): List? { // TODO: Store in DB - return if (isConnectionAvailable.value) { + return if (isConnectionAvailable.value && !offlineOverride) { api.tags() } else { // TODO: Compute from database @@ -149,7 +150,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getSpouts(): Map? { // TODO: Store in DB - return if (isConnectionAvailable.value) { + return if (isConnectionAvailable.value && !offlineOverride) { api.spouts() } else { // TODO: Compute from database @@ -159,7 +160,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getSources(): ArrayList? { // TODO: Store in DB - return if (isConnectionAvailable.value) { + return if (isConnectionAvailable.value && !offlineOverride) { api.sources() } else { // TODO: Compute from database @@ -178,7 +179,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun markAsReadById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { success = api.markAsRead(id.toString())?.isSuccess == true } return success @@ -197,7 +198,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun unmarkAsReadById(id: Int): Boolean { // TODO: Check internet connection var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { success = api.unmarkAsRead(id.toString())?.isSuccess == true } return success @@ -214,7 +215,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun starrById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { success = api.starr(id.toString())?.isSuccess == true } return success @@ -231,7 +232,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun unstarrById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { success = api.unstarr(id.toString())?.isSuccess == true } return success @@ -240,7 +241,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun markAllAsRead(items: ArrayList): Boolean { var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true } @@ -292,7 +293,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails filter: String ): Boolean { var response = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { response = api.createSourceForVersion( title, url, @@ -309,7 +310,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun deleteSource(id: Int): Boolean { // TODO: Store in DB var success = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { val response = api.deleteSource(id) if (response != null) { success = response.isSuccess @@ -321,7 +322,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun updateRemote(): Boolean { var response = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { response = api.update()?.isSuccess == true } return response @@ -329,7 +330,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun login(): Boolean { var result = false - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { try { val response = api.login() result = response?.isSuccess == true @@ -356,7 +357,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails private suspend fun updateApiVersion() { apiMajorVersion = settings.getInt("apiVersionMajor", 0) - if (isConnectionAvailable.value) { + if (isConnectionAvailable.value && !offlineOverride) { val fetchedVersion = api.version() if (fetchedVersion != null) { apiMajorVersion = fetchedVersion.getApiMajorVersion() -- 2.34.1 From 13ea7a693b8c857cfda0ca28521b210229bd1eb8 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 20:12:25 +0200 Subject: [PATCH 05/28] Do not fake offline mode when updating remote --- .../amine/apps/readerforselfossv2/repository/RepositoryImpl.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 2ab5bf7..d6b1f16 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -322,7 +322,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun updateRemote(): Boolean { var response = false - if (isConnectionAvailable.value && !offlineOverride) { + offlineOverride = false + if (isConnectionAvailable.value) { response = api.update()?.isSuccess == true } return response -- 2.34.1 From c224b8a0b38db3525b1b0ce8d7fcfc054184f039 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 20:12:45 +0200 Subject: [PATCH 06/28] Remove network checks from the home activity --- .../android/HomeActivity.kt | 141 ++++++++---------- 1 file changed, 63 insertions(+), 78 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index 69dde5c..fc49968 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -44,7 +44,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.bottombar.maybeShow import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView import bou.amine.apps.readerforselfossv2.repository.Repository @@ -126,7 +125,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar private var recyclerAdapter: RecyclerView.Adapter<*>? = null private var fromTabShortcut: Boolean = false - private var offlineShortcut: Boolean = false private lateinit var tagsBadge: Map @@ -689,30 +687,26 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar var sources: List? fun sourcesApiCall() { - if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) { - CoroutineScope(Dispatchers.Main).launch { - val response = repository.getSources() - if (response != null) { - sources = response - val apiDrawerData = DrawerData(tags, sources) - if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { - handleDrawerData(apiDrawerData) - } - } else { - val apiDrawerData = DrawerData(tags, null) - if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { - handleDrawerData(apiDrawerData) - } + CoroutineScope(Dispatchers.Main).launch { + val response = repository.getSources() + if (response != null) { + sources = response + val apiDrawerData = DrawerData(tags, sources) + if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { + handleDrawerData(apiDrawerData) + } + } else { + val apiDrawerData = DrawerData(tags, null) + if ((maybeDrawerData != null && maybeDrawerData != apiDrawerData) || maybeDrawerData == null) { + handleDrawerData(apiDrawerData) } } } } - if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut) && updateSources) { - CoroutineScope(Dispatchers.IO).launch { - tags = repository.getTags() - sourcesApiCall() - } + CoroutineScope(Dispatchers.IO).launch { + tags = repository.getTags() + sourcesApiCall() } } @@ -944,10 +938,8 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar private fun reloadBadges() { if (displayUnreadCount || displayAllCount) { CoroutineScope(Dispatchers.Main).launch { - if (applicationContext.isNetworkAvailable()) { - repository.reloadBadges() - reloadBadgeContent() - } + repository.reloadBadges() + reloadBadgeContent() } } } @@ -1021,61 +1013,56 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { R.id.refresh -> { - if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) { - needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { - Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() - // TODO: Use Dispatchers.IO - CoroutineScope(Dispatchers.Main).launch { - val updatedRemote = repository.updateRemote() - if (updatedRemote) { - Toast.makeText( - this@HomeActivity, - R.string.refresh_success_response, Toast.LENGTH_LONG - ) - .show() - } else { - Toast.makeText( - this@HomeActivity, - R.string.refresh_failer_message, - Toast.LENGTH_SHORT - ).show() - } + needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { + Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() + // TODO: Use Dispatchers.IO + CoroutineScope(Dispatchers.Main).launch { + val updatedRemote = repository.updateRemote() + if (updatedRemote) { + // TODO: Send toast messages from the repository + Toast.makeText( + this@HomeActivity, + R.string.refresh_success_response, Toast.LENGTH_LONG + ) + .show() + } else { + Toast.makeText( + this@HomeActivity, + R.string.refresh_failer_message, + Toast.LENGTH_SHORT + ).show() } } - return true - } else { - return false } + return true } R.id.readAll -> { if (elementsShown == ItemType.UNREAD) { needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { binding.swipeRefreshLayout.isRefreshing = true - if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) { - CoroutineScope(Dispatchers.Main).launch { - val success = repository.markAllAsRead(items) - if (success) { - Toast.makeText( - this@HomeActivity, - R.string.all_posts_read, - Toast.LENGTH_SHORT - ).show() - tabNewBadge.removeBadge() + CoroutineScope(Dispatchers.Main).launch { + val success = repository.markAllAsRead(items) + if (success) { + Toast.makeText( + this@HomeActivity, + R.string.all_posts_read, + Toast.LENGTH_SHORT + ).show() + tabNewBadge.removeBadge() - handleDrawerItems() + handleDrawerItems() - getElementsAccordingToTab() - } else { - Toast.makeText( - this@HomeActivity, - R.string.all_posts_not_read, - Toast.LENGTH_SHORT - ).show() - } - handleListResult() - binding.swipeRefreshLayout.isRefreshing = false + getElementsAccordingToTab() + } else { + Toast.makeText( + this@HomeActivity, + R.string.all_posts_not_read, + Toast.LENGTH_SHORT + ).show() } + handleListResult() + binding.swipeRefreshLayout.isRefreshing = false } } } @@ -1127,17 +1114,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar } } - if (this@HomeActivity.isNetworkAvailable(null, offlineShortcut)) { - CoroutineScope(Dispatchers.Main).launch { - val actions = db.actionsDao().actions() + CoroutineScope(Dispatchers.Main).launch { + val actions = db.actionsDao().actions() - actions.forEach { action -> - when { - action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action) - action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action) - action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action) - action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action) - } + actions.forEach { action -> + when { + action.read -> doAndReportOnFail(repository.markAsReadById(action.articleId.toInt()), action) + action.unread -> doAndReportOnFail(repository.unmarkAsReadById(action.articleId.toInt()), action) + action.starred -> doAndReportOnFail(repository.starrById(action.articleId.toInt()), action) + action.unstarred -> doAndReportOnFail(repository.unstarrById(action.articleId.toInt()), action) } } } -- 2.34.1 From 551a3e3caa6920300dff0203c5fdf98c692b5769 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 20:20:44 +0200 Subject: [PATCH 07/28] Remove all connectivity checks outside the repository --- .../android/LoginActivity.kt | 17 +-- .../android/SourcesActivity.kt | 37 +++-- .../android/adapters/ItemCardAdapter.kt | 29 ++-- .../android/adapters/SourcesListAdapter.kt | 29 ++-- .../android/background/background.kt | 98 ++++++------ .../android/fragments/ArticleFragment.kt | 143 +++++++++--------- 6 files changed, 166 insertions(+), 187 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 3ef0544..544325f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -15,7 +15,6 @@ import androidx.appcompat.app.AppCompatActivity import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.repository.Repository import com.mikepenz.aboutlibraries.LibsBuilder import com.russhwolf.settings.Settings @@ -202,15 +201,13 @@ class LoginActivity() : AppCompatActivity(), DIAware { repository.refreshLoginInformation(url, login, password, httpLogin, httpPassword, isWithSelfSignedCert) - if (this@LoginActivity.isNetworkAvailable(this@LoginActivity.findViewById(R.id.loginForm))) { - CoroutineScope(Dispatchers.IO).launch { - val result = repository.login() - if (result) { - goToMain() - } else { - CoroutineScope(Dispatchers.Main).launch { - preferenceError(Exception("Not success")) - } + CoroutineScope(Dispatchers.IO).launch { + val result = repository.login() + if (result) { + goToMain() + } else { + CoroutineScope(Dispatchers.Main).launch { + preferenceError(Exception("Not success")) } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt index fe94cf3..10988e9 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt @@ -10,7 +10,6 @@ import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import com.ftinc.scoop.Scoop @@ -64,30 +63,28 @@ class SourcesActivity : AppCompatActivity(), DIAware { binding.recyclerView.setHasFixedSize(true) binding.recyclerView.layoutManager = mLayoutManager - if (this@SourcesActivity.isNetworkAvailable(binding.recyclerView)) { - CoroutineScope(Dispatchers.Main).launch { - val response = repository.getSources() - if (response != null) { - 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 { + CoroutineScope(Dispatchers.Main).launch { + val response = repository.getSources() + if (response != null) { + items = response + val mAdapter = SourcesListAdapter( + this@SourcesActivity, items + ) + binding.recyclerView.adapter = mAdapter + mAdapter.notifyDataSetChanged() + if (items.isEmpty()) { Toast.makeText( this@SourcesActivity, - R.string.cant_get_sources, + R.string.nothing_here, Toast.LENGTH_SHORT ).show() } + } else { + Toast.makeText( + this@SourcesActivity, + R.string.cant_get_sources, + Toast.LENGTH_SHORT + ).show() } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt index c4a32fe..aa87278 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/ItemCardAdapter.kt @@ -16,7 +16,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper 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.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import com.amulyakhare.textdrawable.TextDrawable @@ -110,22 +109,20 @@ class ItemCardAdapter( binding.favButton.setOnClickListener { val item = items[bindingAdapterPosition] - if (c.isNetworkAvailable()) { - if (item.starred) { - CoroutineScope(Dispatchers.IO).launch { - repository.unstarr(item) - // TODO: Handle failure - } - item.starred = false - binding.favButton.isSelected = false - } else { - CoroutineScope(Dispatchers.IO).launch { - repository.starr(item) - // TODO: Handle failure - } - item.starred = true - binding.favButton.isSelected = true + if (item.starred) { + CoroutineScope(Dispatchers.IO).launch { + repository.unstarr(item) + // TODO: Handle failure } + item.starred = false + binding.favButton.isSelected = false + } else { + CoroutineScope(Dispatchers.IO).launch { + repository.starr(item) + // TODO: Handle failure + } + item.starred = true + binding.favButton.isSelected = true } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt index 30c9260..dd7d9e0 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/adapters/SourcesListAdapter.kt @@ -14,7 +14,6 @@ import bou.amine.apps.readerforselfossv2.android.model.getIcon import bou.amine.apps.readerforselfossv2.android.model.getTitleDecoded import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.glide.circularBitmapDrawable -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.android.utils.toTextDrawableString import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel @@ -78,21 +77,19 @@ class SourcesListAdapter( val deleteBtn: Button = mView.findViewById(R.id.deleteBtn) deleteBtn.setOnClickListener { - if (c.isNetworkAvailable(null)) { - val (id) = items[adapterPosition] - CoroutineScope(Dispatchers.IO).launch { - val successfullyDeletedSource = repository.deleteSource(id) - if (successfullyDeletedSource) { - items.removeAt(adapterPosition) - notifyItemRemoved(adapterPosition) - notifyItemRangeChanged(adapterPosition, itemCount) - } else { - Toast.makeText( - app, - R.string.can_delete_source, - Toast.LENGTH_SHORT - ).show() - } + val (id) = items[adapterPosition] + CoroutineScope(Dispatchers.IO).launch { + val successfullyDeletedSource = repository.deleteSource(id) + if (successfullyDeletedSource) { + items.removeAt(adapterPosition) + notifyItemRemoved(adapterPosition) + notifyItemRangeChanged(adapterPosition, itemCount) + } else { + Toast.makeText( + app, + R.string.can_delete_source, + Toast.LENGTH_SHORT + ).show() } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt index ddbf1d8..2bf2fa5 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt @@ -21,7 +21,6 @@ import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATIO import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3 import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4 import bou.amine.apps.readerforselfossv2.android.utils.Config -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.ItemType @@ -46,65 +45,60 @@ override fun doWork(): Result { val periodicRefresh = settings.getBoolean("periodic_refresh", false) if (periodicRefresh) { - if (context.isNetworkAvailable()) { + CoroutineScope(Dispatchers.IO).launch { + val notificationManager = + applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - CoroutineScope(Dispatchers.IO).launch { - val notificationManager = - applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val notification = + NotificationCompat.Builder(applicationContext, Config.syncChannelId) + .setContentTitle(context.getString(R.string.loading_notification_title)) + .setContentText(context.getString(R.string.loading_notification_text)) + .setOngoing(true) + .setPriority(PRIORITY_LOW) + .setChannelId(Config.syncChannelId) + .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) - val notification = - NotificationCompat.Builder(applicationContext, Config.syncChannelId) - .setContentTitle(context.getString(R.string.loading_notification_title)) - .setContentText(context.getString(R.string.loading_notification_text)) - .setOngoing(true) - .setPriority(PRIORITY_LOW) - .setChannelId(Config.syncChannelId) - .setSmallIcon(R.drawable.ic_stat_cloud_download_black_24dp) + notificationManager.notify(1, notification.build()) - notificationManager.notify(1, notification.build()) + val notifyNewItems = settings.getBoolean("notify_new_items", false) - val notifyNewItems = settings.getBoolean("notify_new_items", false) + db = Room.databaseBuilder( + applicationContext, + AppDatabase::class.java, "selfoss-database" + ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3) + .addMigrations(MIGRATION_3_4).build() - db = Room.databaseBuilder( - applicationContext, - AppDatabase::class.java, "selfoss-database" - ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3) - .addMigrations(MIGRATION_3_4).build() + val actions = db.actionsDao().actions() - val actions = db.actionsDao().actions() - - actions.forEach { action -> - when { - action.read -> doAndReportOnFail( - repository.markAsReadById(action.articleId.toInt()), - action - ) - action.unread -> doAndReportOnFail( - repository.unmarkAsReadById(action.articleId.toInt()), - action - ) - action.starred -> doAndReportOnFail( - repository.starrById(action.articleId.toInt()), - action - ) - action.unstarred -> doAndReportOnFail( - repository.unstarrById(action.articleId.toInt()), - action - ) - } + actions.forEach { action -> + when { + action.read -> doAndReportOnFail( + repository.markAsReadById(action.articleId.toInt()), + action + ) + action.unread -> doAndReportOnFail( + repository.unmarkAsReadById(action.articleId.toInt()), + action + ) + action.starred -> doAndReportOnFail( + repository.starrById(action.articleId.toInt()), + action + ) + action.unstarred -> doAndReportOnFail( + repository.unstarrById(action.articleId.toInt()), + action + ) } + } - if (context.isNetworkAvailable()) { - launch { - try { - val newItems = repository.allItems(ItemType.UNREAD) - handleNewItemsNotification(newItems, notifyNewItems, notificationManager) - val readItems = repository.allItems(ItemType.ALL) - val starredItems = repository.allItems(ItemType.STARRED) - // TODO: save all to DB - } catch (e: Throwable) {} - } - } + launch { + try { + val newItems = repository.allItems(ItemType.UNREAD) + handleNewItemsNotification(newItems, notifyNewItems, notificationManager) + val readItems = repository.allItems(ItemType.ALL) + val starredItems = repository.allItems(ItemType.STARRED) + // TODO: save all to DB + } catch (e: Throwable) {} } } } diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 2b9d6b2..3fca8d8 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -35,7 +35,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth -import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAvailable import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString @@ -276,89 +275,87 @@ class ArticleFragment : Fragment(), DIAware { } private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) { - if ((context != null && requireContext().isNetworkAvailable(null)) || context == null) { - binding.progressBar.visibility = View.VISIBLE - val parser = MercuryApi() + binding.progressBar.visibility = View.VISIBLE + val parser = MercuryApi() - parser.parseUrl(url).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - // TODO: clean all the following after finding the mercury content issue - try { - if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { + parser.parseUrl(url).enqueue( + object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + // TODO: clean all the following after finding the mercury content issue + try { + if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { + try { + binding.titleView.text = response.body()!!.title + if (typeface != null) { + binding.titleView.typeface = typeface + } try { - binding.titleView.text = response.body()!!.title - if (typeface != null) { - binding.titleView.typeface = typeface - } + // Note: Mercury may return relative urls... If it does the url val will not be changed. + URL(response.body()!!.url) + url = response.body()!!.url + } catch (e: MalformedURLException) { + // Mercury returned a relative url. We do nothing. + } + } catch (e: Exception) { + } + + try { + contentText = response.body()!!.content.orEmpty() + htmlToWebview() + } catch (e: Exception) { + } + + try { + if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { + binding.imageView.visibility = View.VISIBLE try { - // Note: Mercury may return relative urls... If it does the url val will not be changed. - URL(response.body()!!.url) - url = response.body()!!.url - } catch (e: MalformedURLException) { - // Mercury returned a relative url. We do nothing. + Glide + .with(requireContext()) + .asBitmap() + .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty()) + .apply(RequestOptions.fitCenterTransform()) + .into(binding.imageView) + } catch (e: Exception) { } - } catch (e: Exception) { + } else { + binding.imageView.visibility = View.GONE } - - try { - contentText = response.body()!!.content.orEmpty() - htmlToWebview() - } catch (e: Exception) { - } - - try { - if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { - binding.imageView.visibility = View.VISIBLE - try { - Glide - .with(requireContext()) - .asBitmap() - .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty()) - .apply(RequestOptions.fitCenterTransform()) - .into(binding.imageView) - } catch (e: Exception) { - } - } else { - binding.imageView.visibility = View.GONE - } - } catch (e: Exception) { - if (context != null) { - } - } - - try { - binding.nestedScrollView.scrollTo(0, 0) - - binding.progressBar.visibility = View.GONE - } catch (e: Exception) { - if (context != null) { - } - } - } else { - try { - openInBrowserAfterFailing(customTabsIntent) - } catch (e: Exception) { - if (context != null) { - } + } catch (e: Exception) { + if (context != null) { } } - } catch (e: Exception) { - if (context != null) { + + try { + binding.nestedScrollView.scrollTo(0, 0) + + binding.progressBar.visibility = View.GONE + } catch (e: Exception) { + if (context != null) { + } + } + } else { + try { + openInBrowserAfterFailing(customTabsIntent) + } catch (e: Exception) { + if (context != null) { + } } } + } catch (e: Exception) { + if (context != null) { + } } - - override fun onFailure( - call: Call, - t: Throwable - ) = openInBrowserAfterFailing(customTabsIntent) } - ) - } + + override fun onFailure( + call: Call, + t: Throwable + ) = openInBrowserAfterFailing(customTabsIntent) + } + ) } private fun htmlToWebview() { -- 2.34.1 From 492e7e4aeda190588094d17f73d4844aa646833c Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 20:26:02 +0200 Subject: [PATCH 08/28] Update todo comments --- .../apps/readerforselfossv2/repository/RepositoryImpl.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index d6b1f16..fd921ee 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -65,7 +65,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails items = ArrayList(fetchedItems) } } else { - // TODO: Provide an error message if the connection is not available. // TODO: Get items from the database } return items @@ -88,7 +87,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails appendItems(fetchedItems) } } else { - // TODO: Provide an error message + // TODO: Get items from the database } return items } @@ -105,6 +104,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails null ) } else { + // TODO: Provide an error message null } } @@ -186,7 +186,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean { - // TODO: Check internet connection val success = unmarkAsReadById(item.id) if (success) { @@ -196,7 +195,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun unmarkAsReadById(id: Int): Boolean { - // TODO: Check internet connection var success = false if (isConnectionAvailable.value && !offlineOverride) { success = api.unmarkAsRead(id.toString())?.isSuccess == true -- 2.34.1 From f56861a3c20a3b07f13d90b0bb2f690d90fcb528 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Wed, 17 Aug 2022 21:17:50 +0200 Subject: [PATCH 09/28] Show a message when the network connection is lost --- .../apps/readerforselfossv2/android/HomeActivity.kt | 7 +++++++ .../readerforselfossv2/repository/RepositoryImpl.kt | 13 +++++++++++++ 2 files changed, 20 insertions(+) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index fc49968..2cb4e70 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -17,6 +17,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.room.Room import androidx.work.Constraints @@ -178,6 +179,12 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar dataBase = AndroidDeviceDatabase(applicationContext) + lifecycleScope.launch { + repository.toastMessageState.collect { + Toast.makeText(baseContext, it, Toast.LENGTH_SHORT).show() + } + } + handleBottomBar() handleDrawer() diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index fd921ee..78b49de 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -10,6 +10,9 @@ import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, connectivityStatus: ConnectivityStatus) { @@ -18,6 +21,9 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails var items = ArrayList() private val isConnectionAvailable = connectivityStatus.isNetworkConnected + private val _toastMessageState = MutableSharedFlow(0) + val toastMessageState = _toastMessageState.asSharedFlow() + var baseUrl = apiDetails.getBaseUrl() lateinit var dateUtils: DateUtils @@ -45,6 +51,13 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails updateApiVersion() dateUtils = DateUtils(apiMajorVersion) reloadBadges() + + isConnectionAvailable.asStateFlow().collect { connectionAvailable -> + if (!connectionAvailable) { + // TODO: Localize this string + _toastMessageState.emit("Network connection lost") + } + } } } -- 2.34.1 From d654b1b0bde44e6733a25a4aad5689e7bab2bc07 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Thu, 18 Aug 2022 14:45:10 +0200 Subject: [PATCH 10/28] Refactor connectivity check --- .../repository/RepositoryImpl.kt | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 78b49de..ff83fac 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -63,7 +63,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getNewerItems(): ArrayList { // TODO: Use the updatedSince parameter - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { val fetchedItems = api.getItems( displayedItems.type, settings.getString("prefer_api_items_number", "200").toInt(), @@ -84,7 +84,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun getOlderItems(): ArrayList { - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { val offset = items.size val fetchedItems = api.getItems( displayedItems.type, @@ -106,7 +106,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } suspend fun allItems(itemType: ItemType): List? { - return if (isConnectionAvailable.value && !offlineOverride) { + return if (isNetworkAvailable()) { api.getItems( itemType.type, 200, @@ -137,7 +137,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun reloadBadges(): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { val response = api.stats() if (response != null) { badgeUnread = response.unread @@ -153,7 +153,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getTags(): List? { // TODO: Store in DB - return if (isConnectionAvailable.value && !offlineOverride) { + return if (isNetworkAvailable()) { api.tags() } else { // TODO: Compute from database @@ -163,7 +163,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getSpouts(): Map? { // TODO: Store in DB - return if (isConnectionAvailable.value && !offlineOverride) { + return if (isNetworkAvailable()) { api.spouts() } else { // TODO: Compute from database @@ -173,7 +173,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getSources(): ArrayList? { // TODO: Store in DB - return if (isConnectionAvailable.value && !offlineOverride) { + return if (isNetworkAvailable()) { api.sources() } else { // TODO: Compute from database @@ -192,7 +192,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun markAsReadById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { success = api.markAsRead(id.toString())?.isSuccess == true } return success @@ -209,7 +209,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun unmarkAsReadById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { success = api.unmarkAsRead(id.toString())?.isSuccess == true } return success @@ -226,7 +226,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun starrById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { success = api.starr(id.toString())?.isSuccess == true } return success @@ -243,7 +243,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun unstarrById(id: Int): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { success = api.unstarr(id.toString())?.isSuccess == true } return success @@ -252,7 +252,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun markAllAsRead(items: ArrayList): Boolean { var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true } @@ -304,7 +304,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails filter: String ): Boolean { var response = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { response = api.createSourceForVersion( title, url, @@ -321,7 +321,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun deleteSource(id: Int): Boolean { // TODO: Store in DB var success = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { val response = api.deleteSource(id) if (response != null) { success = response.isSuccess @@ -342,7 +342,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun login(): Boolean { var result = false - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { try { val response = api.login() result = response?.isSuccess == true @@ -369,7 +369,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails private suspend fun updateApiVersion() { apiMajorVersion = settings.getInt("apiVersionMajor", 0) - if (isConnectionAvailable.value && !offlineOverride) { + if (isNetworkAvailable()) { val fetchedVersion = api.version() if (fetchedVersion != null) { apiMajorVersion = fetchedVersion.getApiMajorVersion() @@ -378,5 +378,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } } + private fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride + // TODO: Handle offline actions } \ No newline at end of file -- 2.34.1 From d1481a1db61ce2995ed1e0e9ceb9721dd94d24f7 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Thu, 18 Aug 2022 14:48:27 +0200 Subject: [PATCH 11/28] Reintroduce network checks where required --- .../android/background/background.kt | 2 +- .../android/fragments/ArticleFragment.kt | 139 +++++++++--------- .../repository/RepositoryImpl.kt | 2 +- 3 files changed, 74 insertions(+), 69 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt index 2bf2fa5..3fb4d84 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt @@ -43,7 +43,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con override fun doWork(): Result { val settings = Settings() val periodicRefresh = settings.getBoolean("periodic_refresh", false) - if (periodicRefresh) { + if (periodicRefresh && repository.isNetworkAvailable()) { CoroutineScope(Dispatchers.IO).launch { val notificationManager = diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 3fca8d8..17c441e 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -278,84 +278,89 @@ class ArticleFragment : Fragment(), DIAware { binding.progressBar.visibility = View.VISIBLE val parser = MercuryApi() - parser.parseUrl(url).enqueue( - object : Callback { - override fun onResponse( - call: Call, - response: Response - ) { - // TODO: clean all the following after finding the mercury content issue - try { - if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { - try { - binding.titleView.text = response.body()!!.title - if (typeface != null) { - binding.titleView.typeface = typeface - } + if (repository.isNetworkAvailable()) { + parser.parseUrl(url).enqueue( + object : Callback { + override fun onResponse( + call: Call, + response: Response + ) { + // TODO: clean all the following after finding the mercury content issue + try { + if (response.body() != null && response.body()!!.content != null && !response.body()!!.content.isNullOrEmpty()) { try { - // Note: Mercury may return relative urls... If it does the url val will not be changed. - URL(response.body()!!.url) - url = response.body()!!.url - } catch (e: MalformedURLException) { - // Mercury returned a relative url. We do nothing. - } - } catch (e: Exception) { - } - - try { - contentText = response.body()!!.content.orEmpty() - htmlToWebview() - } catch (e: Exception) { - } - - try { - if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { - binding.imageView.visibility = View.VISIBLE - try { - Glide - .with(requireContext()) - .asBitmap() - .loadMaybeBasicAuth(config, response.body()!!.lead_image_url.orEmpty()) - .apply(RequestOptions.fitCenterTransform()) - .into(binding.imageView) - } catch (e: Exception) { + binding.titleView.text = response.body()!!.title + if (typeface != null) { + binding.titleView.typeface = typeface } - } else { - binding.imageView.visibility = View.GONE + try { + // Note: Mercury may return relative urls... If it does the url val will not be changed. + URL(response.body()!!.url) + url = response.body()!!.url + } catch (e: MalformedURLException) { + // Mercury returned a relative url. We do nothing. + } + } catch (e: Exception) { } - } catch (e: Exception) { - if (context != null) { - } - } - try { - binding.nestedScrollView.scrollTo(0, 0) + try { + contentText = response.body()!!.content.orEmpty() + htmlToWebview() + } catch (e: Exception) { + } - binding.progressBar.visibility = View.GONE - } catch (e: Exception) { - if (context != null) { + try { + if (response.body()!!.lead_image_url != null && !response.body()!!.lead_image_url.isNullOrEmpty() && context != null) { + binding.imageView.visibility = View.VISIBLE + try { + Glide + .with(requireContext()) + .asBitmap() + .loadMaybeBasicAuth( + config, + response.body()!!.lead_image_url.orEmpty() + ) + .apply(RequestOptions.fitCenterTransform()) + .into(binding.imageView) + } catch (e: Exception) { + } + } else { + binding.imageView.visibility = View.GONE + } + } catch (e: Exception) { + if (context != null) { + } + } + + try { + binding.nestedScrollView.scrollTo(0, 0) + + binding.progressBar.visibility = View.GONE + } catch (e: Exception) { + if (context != null) { + } + } + } else { + try { + openInBrowserAfterFailing(customTabsIntent) + } catch (e: Exception) { + if (context != null) { + } } } - } else { - try { - openInBrowserAfterFailing(customTabsIntent) - } catch (e: Exception) { - if (context != null) { - } + } catch (e: Exception) { + if (context != null) { } } - } catch (e: Exception) { - if (context != null) { - } } - } - override fun onFailure( - call: Call, - t: Throwable - ) = openInBrowserAfterFailing(customTabsIntent) - } - ) + override fun onFailure( + call: Call, + t: Throwable + ) = openInBrowserAfterFailing(customTabsIntent) + } + ) + } } private fun htmlToWebview() { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index ff83fac..078e206 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -378,7 +378,7 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails } } - private fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride + fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride // TODO: Handle offline actions } \ No newline at end of file -- 2.34.1 From 3c5b606a02764a84878440acb536a896c8461816 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Thu, 18 Aug 2022 14:50:19 +0200 Subject: [PATCH 12/28] Do not change the network override from within the repository --- .../bou/amine/apps/readerforselfossv2/android/HomeActivity.kt | 1 + .../amine/apps/readerforselfossv2/repository/RepositoryImpl.kt | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index 2cb4e70..e92b4ab 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -1024,6 +1024,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() // TODO: Use Dispatchers.IO CoroutineScope(Dispatchers.Main).launch { + repository.offlineOverride = false val updatedRemote = repository.updateRemote() if (updatedRemote) { // TODO: Send toast messages from the repository diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 078e206..f0f7839 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -333,7 +333,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun updateRemote(): Boolean { var response = false - offlineOverride = false if (isConnectionAvailable.value) { response = api.update()?.isSuccess == true } -- 2.34.1 From d838f509d4fccb1e6fe5dca53f27cf88f0a77ff4 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 12:15:36 +0200 Subject: [PATCH 13/28] Stop monitoring the network when the app goes in background --- androidApp/build.gradle.kts | 8 +++++--- .../apps/readerforselfossv2/android/MyApp.kt | 19 +++++++++++++++++++ .../android/background/background.kt | 3 ++- .../android/utils/network/NetworkUtils.kt | 2 +- .../repository/RepositoryImpl.kt | 11 +++++++++-- 5 files changed, 36 insertions(+), 7 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 73b0d74..654a4a5 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -193,10 +193,12 @@ dependencies { //PhotoView implementation("com.github.chrisbanes:PhotoView:2.3.0") - implementation("androidx.core:core-ktx:1.7.0") + implementation("androidx.core:core-ktx:1.8.0") - implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.4.0") - implementation("androidx.lifecycle:lifecycle-common-java8:2.4.0") + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1") + implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1") + implementation("androidx.lifecycle:lifecycle-runtime:2.5.1") + implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") implementation("androidx.room:room-ktx:2.4.0-beta01") kapt("androidx.room:room-compiler:2.4.0-beta01") diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index c241176..2974263 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -7,6 +7,9 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.widget.ImageView +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDexApplication import androidx.preference.PreferenceManager import bou.amine.apps.readerforselfossv2.DI.networkModule @@ -31,6 +34,7 @@ class MyApp : MultiDexApplication(), DIAware { bind() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) } } + private val repository: Repository by instance() private lateinit var config: Config private lateinit var settings : Settings @@ -47,6 +51,8 @@ class MyApp : MultiDexApplication(), DIAware { tryToHandleBug() handleNotificationChannels() + + ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(repository)) } private fun handleNotificationChannels() { @@ -106,4 +112,17 @@ class MyApp : MultiDexApplication(), DIAware { } } } + + class AppLifeCycleObserver(val repository: Repository) : DefaultLifecycleObserver { + + override fun onResume(owner: LifecycleOwner) { + super.onResume(owner) + repository.startNetwork() + } + + override fun onPause(owner: LifecycleOwner) { + repository.stopNetwork() + super.onPause(owner) + } + } } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt index 3fb4d84..03a0097 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/background/background.kt @@ -21,6 +21,7 @@ import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATIO import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_2_3 import bou.amine.apps.readerforselfossv2.android.persistence.migrations.MIGRATION_3_4 import bou.amine.apps.readerforselfossv2.android.utils.Config +import bou.amine.apps.readerforselfossv2.android.utils.network.isNetworkAccessible import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.ItemType @@ -43,7 +44,7 @@ class LoadingWorker(val context: Context, params: WorkerParameters) : Worker(con override fun doWork(): Result { val settings = Settings() val periodicRefresh = settings.getBoolean("periodic_refresh", false) - if (periodicRefresh && repository.isNetworkAvailable()) { + if (periodicRefresh && isNetworkAccessible(context)) { CoroutineScope(Dispatchers.IO).launch { val notificationManager = diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt index 2edfc1c..4463626 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt @@ -46,7 +46,7 @@ fun Context.isNetworkAvailable( return if(overrideOffline) overrideOffline else networkIsAccessible } -private fun isNetworkAccessible(context: Context): Boolean { +fun isNetworkAccessible(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index f0f7839..2ea43c4 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, connectivityStatus: ConnectivityStatus) { +class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus) { val settings = Settings() var items = ArrayList() @@ -45,7 +45,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails set(value) {field = if (value < 0) { 0 } else { value } } init { - connectivityStatus.start() // TODO: Dispatchers.IO not available in KMM, an alternative solution should be found CoroutineScope(Dispatchers.Main).launch { updateApiVersion() @@ -379,5 +378,13 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride + fun startNetwork() { + connectivityStatus.start() + } + + fun stopNetwork() { + connectivityStatus.stop() + } + // TODO: Handle offline actions } \ No newline at end of file -- 2.34.1 From 1258ed3ad38efef436ac0a96fcebaf81dfd83d11 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 12:17:15 +0200 Subject: [PATCH 14/28] Don't create the mercury api if not connection is available --- .../readerforselfossv2/android/fragments/ArticleFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 17c441e..abc8f2c 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -275,10 +275,10 @@ class ArticleFragment : Fragment(), DIAware { } private fun getContentFromMercury(customTabsIntent: CustomTabsIntent) { + if (repository.isNetworkAvailable()) { binding.progressBar.visibility = View.VISIBLE val parser = MercuryApi() - if (repository.isNetworkAvailable()) { parser.parseUrl(url).enqueue( object : Callback { override fun onResponse( -- 2.34.1 From b9497ca93904c5de43d49792b9a6b44315ac851f Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 12:37:11 +0200 Subject: [PATCH 15/28] Prepare the repository functions for DB implementation --- .../repository/RepositoryImpl.kt | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 2ea43c4..d2d6600 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -62,8 +62,9 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails suspend fun getNewerItems(): ArrayList { // TODO: Use the updatedSince parameter + var fetchedItems: List? = null if (isNetworkAvailable()) { - val fetchedItems = api.getItems( + fetchedItems = api.getItems( displayedItems.type, settings.getString("prefer_api_items_number", "200").toInt(), offset = 0, @@ -72,20 +73,21 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails searchFilter, null ) - - if (fetchedItems != null) { - items = ArrayList(fetchedItems) - } } else { // TODO: Get items from the database } + + if (fetchedItems != null) { + items = ArrayList(fetchedItems) + } return items } suspend fun getOlderItems(): ArrayList { + var fetchedItems: List? = null if (isNetworkAvailable()) { val offset = items.size - val fetchedItems = api.getItems( + fetchedItems = api.getItems( displayedItems.type, settings.getString("prefer_api_items_number", "200").toInt(), offset, @@ -94,13 +96,13 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails searchFilter, null ) - - if (fetchedItems != null) { - appendItems(fetchedItems) - } } else { // TODO: Get items from the database } + + if (fetchedItems != null) { + appendItems(fetchedItems) + } return items } -- 2.34.1 From 47b7062e168f37d4e65554cf8117f9d6fb4a6119 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 12:40:38 +0200 Subject: [PATCH 16/28] Remove unused function --- .../android/utils/network/NetworkUtils.kt | 38 ------------------- 1 file changed, 38 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt index 4463626..3ed5c53 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/utils/network/NetworkUtils.kt @@ -1,51 +1,13 @@ package bou.amine.apps.readerforselfossv2.android.utils.network import android.content.Context -import android.graphics.Color import android.net.ConnectivityManager import android.net.NetworkCapabilities import android.os.Build -import android.view.View -import android.widget.TextView -import bou.amine.apps.readerforselfossv2.android.R import com.google.android.material.snackbar.Snackbar -var snackBarShown = false -var view: View? = null lateinit var s: Snackbar -fun Context.isNetworkAvailable( - v: View? = null, - overrideOffline: Boolean = false -): Boolean { - val networkIsAccessible = isNetworkAccessible(this) - - if (v != null && (!networkIsAccessible || overrideOffline) && (!snackBarShown || v != view)) { - view = v - s = Snackbar - .make( - v, - R.string.no_network_connectivity, - Snackbar.LENGTH_INDEFINITE - ) - - s.setAction(android.R.string.ok) { - snackBarShown = false - s.dismiss() - } - - val view = s.view - val tv: TextView = view.findViewById(com.google.android.material.R.id.snackbar_text) - tv.setTextColor(Color.WHITE) - s.show() - snackBarShown = true - } - if (snackBarShown && networkIsAccessible && !overrideOffline) { - s.dismiss() - } - return if(overrideOffline) overrideOffline else networkIsAccessible -} - fun isNetworkAccessible(context: Context): Boolean { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager -- 2.34.1 From 82c4a5a1f9d4ceb507b51579b1ec806d83ffa4c0 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 12:42:24 +0200 Subject: [PATCH 17/28] Don't send toast messages from the repository --- .../apps/readerforselfossv2/android/HomeActivity.kt | 7 ------- .../readerforselfossv2/repository/RepositoryImpl.kt | 13 ------------- 2 files changed, 20 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index e92b4ab..b39639b 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -17,7 +17,6 @@ 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.room.Room import androidx.work.Constraints @@ -179,12 +178,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar dataBase = AndroidDeviceDatabase(applicationContext) - lifecycleScope.launch { - repository.toastMessageState.collect { - Toast.makeText(baseContext, it, Toast.LENGTH_SHORT).show() - } - } - handleBottomBar() handleDrawer() diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index d2d6600..a29b924 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -10,9 +10,6 @@ import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class Repository(private val api: SelfossApi, private val apiDetails: ApiDetailsService, val connectivityStatus: ConnectivityStatus) { @@ -21,9 +18,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails var items = ArrayList() private val isConnectionAvailable = connectivityStatus.isNetworkConnected - private val _toastMessageState = MutableSharedFlow(0) - val toastMessageState = _toastMessageState.asSharedFlow() - var baseUrl = apiDetails.getBaseUrl() lateinit var dateUtils: DateUtils @@ -50,13 +44,6 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails updateApiVersion() dateUtils = DateUtils(apiMajorVersion) reloadBadges() - - isConnectionAvailable.asStateFlow().collect { connectionAvailable -> - if (!connectionAvailable) { - // TODO: Localize this string - _toastMessageState.emit("Network connection lost") - } - } } } -- 2.34.1 From 7c37b183d70f87eee75e0432525b2b19d72ff5f9 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 21:11:04 +0200 Subject: [PATCH 18/28] Refactor functions --- .../repository/RepositoryImpl.kt | 43 +++++-------------- 1 file changed, 11 insertions(+), 32 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index a29b924..a8ba4c3 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -178,13 +178,9 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return success } - suspend fun markAsReadById(id: Int): Boolean { - var success = false - if (isNetworkAvailable()) { - success = api.markAsRead(id.toString())?.isSuccess == true - } - return success - } + suspend fun markAsReadById(id: Int): Boolean = + isNetworkAvailable() && api.markAsRead(id.toString())?.isSuccess == true + suspend fun unmarkAsRead(item: SelfossModel.Item): Boolean { val success = unmarkAsReadById(item.id) @@ -195,13 +191,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return success } - suspend fun unmarkAsReadById(id: Int): Boolean { - var success = false - if (isNetworkAvailable()) { - success = api.unmarkAsRead(id.toString())?.isSuccess == true - } - return success - } + suspend fun unmarkAsReadById(id: Int): Boolean = + isNetworkAvailable() && api.unmarkAsRead(id.toString())?.isSuccess == true suspend fun starr(item: SelfossModel.Item): Boolean { val success = starrById(item.id) @@ -212,13 +203,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return success } - suspend fun starrById(id: Int): Boolean { - var success = false - if (isNetworkAvailable()) { - success = api.starr(id.toString())?.isSuccess == true - } - return success - } + suspend fun starrById(id: Int): Boolean = + isNetworkAvailable() && api.starr(id.toString())?.isSuccess == true suspend fun unstarr(item: SelfossModel.Item): Boolean { val success = unstarrById(item.id) @@ -229,22 +215,15 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return success } - suspend fun unstarrById(id: Int): Boolean { - var success = false - if (isNetworkAvailable()) { - success = api.unstarr(id.toString())?.isSuccess == true - } - return success - } + suspend fun unstarrById(id: Int): Boolean = + isNetworkAvailable() && api.unstarr(id.toString())?.isSuccess == true suspend fun markAllAsRead(items: ArrayList): Boolean { var success = false - if (isNetworkAvailable()) { - success = api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true - } - if (success) { + if (isNetworkAvailable() && api.markAllAsRead(items.map { it.id.toString() })?.isSuccess == true) { + success = true for (item in items) { markAsReadLocally(item) } -- 2.34.1 From 97d506333975d260d68cfd23f2661f4ca9fa5aa7 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 21:12:39 +0200 Subject: [PATCH 19/28] Consider offline override before updating remote --- .../apps/readerforselfossv2/repository/RepositoryImpl.kt | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index a8ba4c3..a12bcb2 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -298,13 +298,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails return success } - suspend fun updateRemote(): Boolean { - var response = false - if (isConnectionAvailable.value) { - response = api.update()?.isSuccess == true - } - return response - } + suspend fun updateRemote(): Boolean = + isNetworkAvailable() && api.update()?.isSuccess == true suspend fun login(): Boolean { var result = false -- 2.34.1 From 0c8e49214f32df37817e5cdea8095ebd01d02329 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 21:14:13 +0200 Subject: [PATCH 20/28] Don't reset offline override before updating remote --- .../bou/amine/apps/readerforselfossv2/android/HomeActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index b39639b..fc49968 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -1017,7 +1017,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() // TODO: Use Dispatchers.IO CoroutineScope(Dispatchers.Main).launch { - repository.offlineOverride = false val updatedRemote = repository.updateRemote() if (updatedRemote) { // TODO: Send toast messages from the repository -- 2.34.1 From 20588aab8147357f3d42cb7dc135fe1a43cbf1ac Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sat, 20 Aug 2022 21:28:21 +0200 Subject: [PATCH 21/28] Add comment to remember the problem with the connectivity-status library --- androidApp/build.gradle.kts | 1 + .../apps/readerforselfossv2/repository/RepositoryImpl.kt | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 654a4a5..28281e1 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -206,5 +206,6 @@ dependencies { implementation("android.arch.work:work-runtime-ktx:1.0.1") // Network information + // TODO: When updating this library, check if the todo in RepositoryImpl.startNetwork can be resolved implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index a12bcb2..57c702b 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -341,6 +341,11 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride + // TODO: Start and stop the monitoring within Android and iOS code + // This should be performed in the architecture specific code + // However it is not possible on android due to the dash contained in the name of the library + // com.github.ln-12:multiplatform-connectivity-status:1.1.0 + // https://github.com/ln-12/multiplatform-connectivity-status/issues/2 fun startNetwork() { connectivityStatus.start() } -- 2.34.1 From 2a44162c5a171ba9ca1dc988e774fda674c2e532 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 14:11:28 +0200 Subject: [PATCH 22/28] Send toast messages to the home activity on connectivity changes --- androidApp/build.gradle.kts | 5 ++-- .../android/HomeActivity.kt | 12 +++++++++ .../apps/readerforselfossv2/android/MyApp.kt | 2 ++ .../android/viewmodel/AppViewModel.kt | 25 +++++++++++++++++++ .../repository/RepositoryImpl.kt | 5 +++- 5 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 28281e1..9e42a73 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -181,8 +181,9 @@ dependencies { implementation("androidx.viewpager2:viewpager2:1.1.0-beta01") //Dependency Injection - implementation("org.kodein.di:kodein-di:7.12.0") - implementation("org.kodein.di:kodein-di-framework-android-x:7.12.0") + implementation("org.kodein.di:kodein-di:7.14.0") + implementation("org.kodein.di:kodein-di-framework-android-x:7.14.0") + implementation("org.kodein.di:kodein-di-framework-android-x-viewmodel:7.14.0") //Settings implementation("com.russhwolf:multiplatform-settings-no-arg:0.9") diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index fc49968..ea1d756 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -17,6 +17,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.room.Room import androidx.work.Constraints @@ -46,6 +47,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.bottombar.removeBadge import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.persistence.toEntity import bou.amine.apps.readerforselfossv2.android.utils.persistence.toView +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.ItemType @@ -134,6 +136,7 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar override val di by closestDI() private val repository : Repository by instance() + private val viewModel : AppViewModel by instance() data class DrawerData(val tags: List?, val sources: List?) @@ -173,6 +176,15 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar AppDatabase::class.java, "selfoss-database" ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() + lifecycleScope.launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + this@HomeActivity, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } customTabActivityHelper = CustomTabActivityHelper() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index 2974263..22192f4 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -15,6 +15,7 @@ import androidx.preference.PreferenceManager import bou.amine.apps.readerforselfossv2.DI.networkModule import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions @@ -32,6 +33,7 @@ class MyApp : MultiDexApplication(), DIAware { override val di by DI.lazy { import(networkModule) bind() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) } + bindProvider { AppViewModel(repository = instance()) } } private val repository: Repository by instance() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt new file mode 100644 index 0000000..2bd2d2e --- /dev/null +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt @@ -0,0 +1,25 @@ +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 _toastMessageProvider = MutableSharedFlow() + val toastMessageProvider = _toastMessageProvider.asSharedFlow() + + init { + viewModelScope.launch { + repository.isConnectionAvailable.collect { isConnected -> + if (isConnected && repository.connectionMonitored) { + _toastMessageProvider.emit("Network connection is now available") + } else if (repository.connectionMonitored){ + _toastMessageProvider.emit("Network connection lost") + } + } + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 57c702b..06639c9 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -16,7 +16,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails val settings = Settings() var items = ArrayList() - private val isConnectionAvailable = connectivityStatus.isNetworkConnected + val isConnectionAvailable = connectivityStatus.isNetworkConnected + var connectionMonitored = false var baseUrl = apiDetails.getBaseUrl() lateinit var dateUtils: DateUtils @@ -348,9 +349,11 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails // https://github.com/ln-12/multiplatform-connectivity-status/issues/2 fun startNetwork() { connectivityStatus.start() + connectionMonitored = true } fun stopNetwork() { + connectionMonitored = false connectivityStatus.stop() } -- 2.34.1 From 9203012a9726d0e47a3aa771a93fca2002125e38 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 19:47:24 +0200 Subject: [PATCH 23/28] Include a local copy of the connectivity-status library to solve a bug --- androidApp/build.gradle.kts | 4 +- .../apps/readerforselfossv2/android/MyApp.kt | 4 +- .../android/viewmodel/AppViewModel.kt | 7 +- connectionstatus/.gitignore | 1 + connectionstatus/build.gradle.kts | 65 ++++++++++++++ .../src/androidMain/AndroidManifest.xml | 4 + .../github/ln12/library/ConnectivityStatus.kt | 85 +++++++++++++++++++ .../github/ln12/library/ConnectivityStatus.kt | 12 +++ .../github/ln12/library/ConnectivityStatus.kt | 67 +++++++++++++++ settings.gradle.kts | 3 +- shared/build.gradle.kts | 4 +- .../repository/RepositoryImpl.kt | 4 +- 12 files changed, 251 insertions(+), 9 deletions(-) create mode 100644 connectionstatus/.gitignore create mode 100644 connectionstatus/build.gradle.kts create mode 100644 connectionstatus/src/androidMain/AndroidManifest.xml create mode 100644 connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt create mode 100644 connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt create mode 100644 connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 9e42a73..6d80981 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -208,5 +208,7 @@ dependencies { // Network information // TODO: When updating this library, check if the todo in RepositoryImpl.startNetwork can be resolved - implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") + // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 + // implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") + implementation(project(":connectionstatus")) } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index 22192f4..2e95d26 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -20,7 +20,7 @@ import bou.amine.apps.readerforselfossv2.repository.Repository import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop -import com.github.`ln-12`.library.ConnectivityStatus +import com.github.ln12.library.ConnectivityStatus import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.russhwolf.settings.Settings @@ -33,7 +33,7 @@ class MyApp : MultiDexApplication(), DIAware { override val di by DI.lazy { import(networkModule) bind() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) } - bindProvider { AppViewModel(repository = instance()) } + bind() with singleton { AppViewModel(repository = instance()) } } private val repository: Repository by instance() diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt index 2bd2d2e..cc7c21d 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt @@ -10,14 +10,17 @@ import kotlinx.coroutines.launch class AppViewModel(private val repository: Repository) : ViewModel() { private val _toastMessageProvider = MutableSharedFlow() val toastMessageProvider = _toastMessageProvider.asSharedFlow() + private var wasConnected = true init { viewModelScope.launch { repository.isConnectionAvailable.collect { isConnected -> - if (isConnected && repository.connectionMonitored) { + if (isConnected && !wasConnected && repository.connectionMonitored) { _toastMessageProvider.emit("Network connection is now available") - } else if (repository.connectionMonitored){ + wasConnected = true + } else if (!isConnected && wasConnected && repository.connectionMonitored){ _toastMessageProvider.emit("Network connection lost") + wasConnected = false } } } diff --git a/connectionstatus/.gitignore b/connectionstatus/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/connectionstatus/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/connectionstatus/build.gradle.kts b/connectionstatus/build.gradle.kts new file mode 100644 index 0000000..045698e --- /dev/null +++ b/connectionstatus/build.gradle.kts @@ -0,0 +1,65 @@ +plugins { + id("com.android.library") + kotlin("multiplatform") +} + +group = "com.github.ln-12" +version = "1.1.0" + +repositories { + google() + mavenCentral() +} + +kotlin { + android { + compilations.all { + kotlinOptions.jvmTarget = "1.8" + } + + publishLibraryVariants("release", "debug") + } + ios() + + sourceSets { + val commonMain by getting { + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) + } + } + + val androidMain by getting + val androidTest by getting { + dependencies { + implementation(kotlin("test-junit")) + implementation("junit:junit:4.13.2") + } + } + + val iosMain by getting + val iosTest by getting + } +} + +android { + compileSdk = 31 + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + defaultConfig { + minSdk = 21 + targetSdk = 31 + } +} + +// metadata is currently not supported for iOS +// https://youtrack.jetbrains.com/issue/KT-44459#focus=Comments-27-4645829.0-0 +kotlin.metadata { + compilations.matching { it.name == "iosMain" }.all { + compileKotlinTaskProvider.configure { enabled = false } + } +} \ No newline at end of file diff --git a/connectionstatus/src/androidMain/AndroidManifest.xml b/connectionstatus/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000..f3a3bcb --- /dev/null +++ b/connectionstatus/src/androidMain/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt new file mode 100644 index 0000000..027abed --- /dev/null +++ b/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt @@ -0,0 +1,85 @@ +package com.github.ln12.library + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Build +import android.util.Log +import kotlinx.coroutines.flow.MutableStateFlow + +// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 +// https://github.com/ln-12/multiplatform-connectivity-status +// Copyright 2021 Lorenzo Neumann +// Edited by davidoskky as here: https://github.com/ln-12/multiplatform-connectivity-status/pull/4 +actual class ConnectivityStatus(private val context: Context) { + actual val isNetworkConnected = MutableStateFlow(false) + + private var connectivityManager: ConnectivityManager? = null + private val networkCallback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + Log.d("Connectivity status", "Connected") + isNetworkConnected.value = true + } + + override fun onLost(network: Network) { + Log.d("Connectivity status", "Disconnected") + isNetworkConnected.value = false + } + } + + actual fun start() { + try { + if (connectivityManager == null) { + connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // API 24 and above + connectivityManager!!.registerDefaultNetworkCallback(networkCallback) + + val currentNetwork = connectivityManager!!.activeNetwork + + if(currentNetwork == null) { + isNetworkConnected.value = false + + Log.d("Connectivity status", "Disconnected") + } + } else { + // API 23 and below + val networkRequest = NetworkRequest.Builder().apply { + addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } + }.build() + + connectivityManager!!.registerNetworkCallback(networkRequest, networkCallback) + + val currentNetwork = connectivityManager!!.activeNetworkInfo + + if(currentNetwork == null || ( + currentNetwork.type != ConnectivityManager.TYPE_ETHERNET && + currentNetwork.type != ConnectivityManager.TYPE_WIFI && + currentNetwork.type != ConnectivityManager.TYPE_MOBILE + )) { + isNetworkConnected.value = false + + Log.d("Connectivity status", "Disconnected") + } + } + + Log.d("Connectivity status", "Started") + } catch (e: Exception) { + Log.d("Connectivity status", "Failed to start: ${e.message.toString()}") + e.printStackTrace() + isNetworkConnected.value = false + } + } + + actual fun stop() { + connectivityManager?.unregisterNetworkCallback(networkCallback) + Log.d("Connectivity status", "Stopped") + } +} \ No newline at end of file diff --git a/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt new file mode 100644 index 0000000..3b25c82 --- /dev/null +++ b/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt @@ -0,0 +1,12 @@ +package com.github.ln12.library + +import kotlinx.coroutines.flow.MutableStateFlow + +// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 +// https://github.com/ln-12/multiplatform-connectivity-status +// Copyright 2021 Lorenzo Neumann +expect class ConnectivityStatus { + val isNetworkConnected: MutableStateFlow + fun start() + fun stop() +} \ No newline at end of file diff --git a/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt new file mode 100644 index 0000000..e3c8970 --- /dev/null +++ b/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt @@ -0,0 +1,67 @@ +package com.github.ln12.library + +import kotlinx.coroutines.flow.MutableStateFlow +import cocoapods.Reachability.* +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import platform.Foundation.NSLog +import platform.darwin.dispatch_async +import platform.darwin.dispatch_get_main_queue +import kotlin.native.concurrent.freeze + + +// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 +// https://github.com/ln-12/multiplatform-connectivity-status +// Copyright 2021 Lorenzo Neumann +actual class ConnectivityStatus { + actual val isNetworkConnected = MutableStateFlow(false) + + private var reachability: Reachability? = null + + // Swift can't directly use a MutableStateFlow, so the status + // is exposed via a lambda/closure + fun getStatus(success: (Boolean) -> Unit) { + MainScope().launch { + isNetworkConnected.collect { status -> + success(status) + } + } + } + + actual fun start() { + dispatch_async(dispatch_get_main_queue()) { + reachability = Reachability.reachabilityForInternetConnection() + + val reachableCallback = { reach: Reachability? -> + dispatch_async(dispatch_get_main_queue(), { + NSLog("Connected") + + isNetworkConnected.value = true + }.freeze()) + }.freeze() + reachability?.reachableBlock = reachableCallback + + val unreachableCallback = { reach: Reachability? -> + dispatch_async(dispatch_get_main_queue(), { + NSLog("Disconnected") + + isNetworkConnected.value = false + }.freeze()) + }.freeze() + reachability?.unreachableBlock = unreachableCallback + + reachability?.startNotifier() + + dispatch_async(dispatch_get_main_queue(), { + isNetworkConnected.value = reachability?.isReachable() ?: false + + NSLog("Initial reachability: ${reachability?.isReachable()}") + }.freeze()) + } + } + + actual fun stop() { + reachability?.stopNotifier() + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 0b32ec1..9c56c94 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,4 +8,5 @@ pluginManagement { rootProject.name = "ReaderForSelfossV2" include(":androidApp") -include(":shared") \ No newline at end of file +include(":shared") +include(":connectionstatus") \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 9a5992d..ca82dbd 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -38,7 +38,9 @@ kotlin { implementation("io.github.aakira:napier:2.6.1") // Network information - implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") + // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 + //implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") + implementation(project(":connectionstatus")) } } val commonTest by getting { diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 06639c9..d3876bf 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -5,7 +5,7 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.ItemType -import com.github.`ln-12`.library.ConnectivityStatus +import com.github.ln12.library.ConnectivityStatus import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope @@ -348,8 +348,8 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails // com.github.ln-12:multiplatform-connectivity-status:1.1.0 // https://github.com/ln-12/multiplatform-connectivity-status/issues/2 fun startNetwork() { - connectivityStatus.start() connectionMonitored = true + connectivityStatus.start() } fun stopNetwork() { -- 2.34.1 From 8c69bb8c3c6e16faaccd74dddbee6f005cd3c33e Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 19:52:53 +0200 Subject: [PATCH 24/28] Send a message regarding connectivity loss/retrieval on all activities --- .../android/AddSourceActivity.kt | 13 +++++++++++++ .../readerforselfossv2/android/LoginActivity.kt | 14 ++++++++++++++ .../readerforselfossv2/android/SourcesActivity.kt | 13 +++++++++++++ .../android/fragments/ArticleFragment.kt | 13 +++++++++++++ 4 files changed, 53 insertions(+) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt index 0edd1c2..f4d79df 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt @@ -6,11 +6,13 @@ import android.view.View import android.widget.* import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout +import androidx.lifecycle.lifecycleScope import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import com.ftinc.scoop.Scoop import kotlinx.coroutines.CoroutineScope @@ -30,6 +32,7 @@ class AddSourceActivity : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() + private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@AddSourceActivity) @@ -76,6 +79,16 @@ class AddSourceActivity : AppCompatActivity(), DIAware { binding.sourceUri.text.toString() ) } + + lifecycleScope.launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + this@AddSourceActivity, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } } override fun onResume() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 544325f..6c75f57 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -10,11 +10,14 @@ import android.view.MenuItem import android.view.View import android.view.inputmethod.EditorInfo import android.widget.TextView +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import com.mikepenz.aboutlibraries.LibsBuilder import com.russhwolf.settings.Settings @@ -38,6 +41,7 @@ class LoginActivity() : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() + private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@LoginActivity) @@ -57,6 +61,16 @@ class LoginActivity() : AppCompatActivity(), DIAware { } handleActions() + + lifecycleScope.launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + this@LoginActivity, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } } private fun handleActions() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt index 10988e9..2761ae6 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt @@ -5,11 +5,13 @@ import android.content.res.ColorStateList import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import com.ftinc.scoop.Scoop @@ -27,6 +29,7 @@ class SourcesActivity : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() + private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@SourcesActivity) @@ -47,6 +50,16 @@ class SourcesActivity : AppCompatActivity(), DIAware { binding.fab.rippleColor = appColors.colorAccentDark binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) + + lifecycleScope.launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + this@SourcesActivity, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } } override fun onStop() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index abc8f2c..74d5d03 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -19,6 +19,7 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.res.ResourcesCompat import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.room.Room import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.R @@ -35,6 +36,7 @@ import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth +import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString @@ -79,6 +81,7 @@ class ArticleFragment : Fragment(), DIAware { override val di : DI by closestDI() private val repository: Repository by instance() + private val viewModel : AppViewModel by instance() private var settings = Settings() @@ -108,6 +111,16 @@ class ArticleFragment : Fragment(), DIAware { requireContext(), AppDatabase::class.java, "selfoss-database" ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() + + lifecycleScope.launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + context, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } } override fun onCreateView( -- 2.34.1 From 2a78be69b5f41896d84cd26755561602ea93f944 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 20:38:51 +0200 Subject: [PATCH 25/28] Send toast messages at the application level and not on a per activity basis --- .../android/AddSourceActivity.kt | 13 ------------- .../readerforselfossv2/android/HomeActivity.kt | 12 ------------ .../readerforselfossv2/android/LoginActivity.kt | 14 -------------- .../apps/readerforselfossv2/android/MyApp.kt | 16 ++++++++++++++++ .../android/SourcesActivity.kt | 13 ------------- .../android/fragments/ArticleFragment.kt | 13 ------------- 6 files changed, 16 insertions(+), 65 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt index f4d79df..0edd1c2 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/AddSourceActivity.kt @@ -6,13 +6,11 @@ import android.view.View import android.widget.* import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout -import androidx.lifecycle.lifecycleScope import bou.amine.apps.readerforselfossv2.android.databinding.ActivityAddSourceBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings import bou.amine.apps.readerforselfossv2.android.utils.Config import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid -import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import com.ftinc.scoop.Scoop import kotlinx.coroutines.CoroutineScope @@ -32,7 +30,6 @@ class AddSourceActivity : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() - private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@AddSourceActivity) @@ -79,16 +76,6 @@ class AddSourceActivity : AppCompatActivity(), DIAware { binding.sourceUri.text.toString() ) } - - lifecycleScope.launch { - viewModel.toastMessageProvider.collect { toastMessage -> - Toast.makeText( - this@AddSourceActivity, - toastMessage, - Toast.LENGTH_SHORT - ).show() - } - } } override fun onResume() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt index ea1d756..4934d5f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/HomeActivity.kt @@ -17,7 +17,6 @@ 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.room.Room import androidx.work.Constraints @@ -136,7 +135,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar override val di by closestDI() private val repository : Repository by instance() - private val viewModel : AppViewModel by instance() data class DrawerData(val tags: List?, val sources: List?) @@ -176,16 +174,6 @@ class HomeActivity : AppCompatActivity(), SearchView.OnQueryTextListener, DIAwar AppDatabase::class.java, "selfoss-database" ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() - lifecycleScope.launch { - viewModel.toastMessageProvider.collect { toastMessage -> - Toast.makeText( - this@HomeActivity, - toastMessage, - Toast.LENGTH_SHORT - ).show() - } - } - customTabActivityHelper = CustomTabActivityHelper() dataBase = AndroidDeviceDatabase(applicationContext) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt index 6c75f57..544325f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/LoginActivity.kt @@ -10,14 +10,11 @@ import android.view.MenuItem import android.view.View import android.view.inputmethod.EditorInfo import android.widget.TextView -import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope import bou.amine.apps.readerforselfossv2.android.databinding.ActivityLoginBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.utils.isBaseUrlValid -import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import com.mikepenz.aboutlibraries.LibsBuilder import com.russhwolf.settings.Settings @@ -41,7 +38,6 @@ class LoginActivity() : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() - private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@LoginActivity) @@ -61,16 +57,6 @@ class LoginActivity() : AppCompatActivity(), DIAware { } handleActions() - - lifecycleScope.launch { - viewModel.toastMessageProvider.collect { toastMessage -> - Toast.makeText( - this@LoginActivity, - toastMessage, - Toast.LENGTH_SHORT - ).show() - } - } } private fun handleActions() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index 2e95d26..50a857b 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -7,9 +7,11 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.widget.ImageView +import android.widget.Toast import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner +import androidx.lifecycle.lifecycleScope import androidx.multidex.MultiDexApplication import androidx.preference.PreferenceManager import bou.amine.apps.readerforselfossv2.DI.networkModule @@ -24,6 +26,9 @@ import com.github.ln12.library.ConnectivityStatus import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.russhwolf.settings.Settings +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier import org.kodein.di.* @@ -37,6 +42,7 @@ class MyApp : MultiDexApplication(), DIAware { } private val repository: Repository by instance() + private val viewModel: AppViewModel by instance() private lateinit var config: Config private lateinit var settings : Settings @@ -55,6 +61,16 @@ class MyApp : MultiDexApplication(), DIAware { handleNotificationChannels() ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(repository)) + + CoroutineScope(Dispatchers.Main).launch { + viewModel.toastMessageProvider.collect { toastMessage -> + Toast.makeText( + applicationContext, + toastMessage, + Toast.LENGTH_SHORT + ).show() + } + } } private fun handleNotificationChannels() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt index 2761ae6..10988e9 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/SourcesActivity.kt @@ -5,13 +5,11 @@ import android.content.res.ColorStateList import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import bou.amine.apps.readerforselfossv2.android.adapters.SourcesListAdapter import bou.amine.apps.readerforselfossv2.android.databinding.ActivitySourcesBinding import bou.amine.apps.readerforselfossv2.android.themes.AppColors import bou.amine.apps.readerforselfossv2.android.themes.Toppings -import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import com.ftinc.scoop.Scoop @@ -29,7 +27,6 @@ class SourcesActivity : AppCompatActivity(), DIAware { override val di by closestDI() private val repository : Repository by instance() - private val viewModel : AppViewModel by instance() override fun onCreate(savedInstanceState: Bundle?) { appColors = AppColors(this@SourcesActivity) @@ -50,16 +47,6 @@ class SourcesActivity : AppCompatActivity(), DIAware { binding.fab.rippleColor = appColors.colorAccentDark binding.fab.backgroundTintList = ColorStateList.valueOf(appColors.colorAccent) - - lifecycleScope.launch { - viewModel.toastMessageProvider.collect { toastMessage -> - Toast.makeText( - this@SourcesActivity, - toastMessage, - Toast.LENGTH_SHORT - ).show() - } - } } override fun onStop() { diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt index 74d5d03..abc8f2c 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/fragments/ArticleFragment.kt @@ -19,7 +19,6 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.res.ResourcesCompat import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment -import androidx.lifecycle.lifecycleScope import androidx.room.Room import bou.amine.apps.readerforselfossv2.android.ImageActivity import bou.amine.apps.readerforselfossv2.android.R @@ -36,7 +35,6 @@ import bou.amine.apps.readerforselfossv2.android.utils.* import bou.amine.apps.readerforselfossv2.android.utils.customtabs.CustomTabActivityHelper import bou.amine.apps.readerforselfossv2.android.utils.glide.getBitmapInputStream import bou.amine.apps.readerforselfossv2.android.utils.glide.loadMaybeBasicAuth -import bou.amine.apps.readerforselfossv2.android.viewmodel.AppViewModel import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.utils.isEmptyOrNullOrNullString @@ -81,7 +79,6 @@ class ArticleFragment : Fragment(), DIAware { override val di : DI by closestDI() private val repository: Repository by instance() - private val viewModel : AppViewModel by instance() private var settings = Settings() @@ -111,16 +108,6 @@ class ArticleFragment : Fragment(), DIAware { requireContext(), AppDatabase::class.java, "selfoss-database" ).addMigrations(MIGRATION_1_2).addMigrations(MIGRATION_2_3).addMigrations(MIGRATION_3_4).build() - - lifecycleScope.launch { - viewModel.toastMessageProvider.collect { toastMessage -> - Toast.makeText( - context, - toastMessage, - Toast.LENGTH_SHORT - ).show() - } - } } override fun onCreateView( -- 2.34.1 From df4903cae57c76d7616690f7f04070d32b80cd76 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 21:30:07 +0200 Subject: [PATCH 26/28] Include the connectivity status library as a aar file --- androidApp/build.gradle.kts | 2 +- .../apps/readerforselfossv2/android/MyApp.kt | 2 +- connectionstatus/.gitignore | 1 - connectionstatus/build.gradle.kts | 65 -------------- .../src/androidMain/AndroidManifest.xml | 4 - .../github/ln12/library/ConnectivityStatus.kt | 85 ------------------ .../github/ln12/library/ConnectivityStatus.kt | 12 --- .../github/ln12/library/ConnectivityStatus.kt | 67 -------------- settings.gradle.kts | 3 +- shared/build.gradle.kts | 2 +- .../multiplatform-connectivity-status.aar | Bin 0 -> 5113 bytes .../repository/RepositoryImpl.kt | 2 +- 12 files changed, 5 insertions(+), 240 deletions(-) delete mode 100644 connectionstatus/.gitignore delete mode 100644 connectionstatus/build.gradle.kts delete mode 100644 connectionstatus/src/androidMain/AndroidManifest.xml delete mode 100644 connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt delete mode 100644 connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt delete mode 100644 connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt create mode 100644 shared/libs/multiplatform-connectivity-status.aar diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 6d80981..527731a 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -210,5 +210,5 @@ dependencies { // TODO: When updating this library, check if the todo in RepositoryImpl.startNetwork can be resolved // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 // implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") - implementation(project(":connectionstatus")) + implementation(files("../shared/libs/multiplatform-connectivity-status.aar")) } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index 50a857b..dbd036f 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -22,7 +22,7 @@ import bou.amine.apps.readerforselfossv2.repository.Repository import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop -import com.github.ln12.library.ConnectivityStatus +import com.github.`ln-12`.library.ConnectivityStatus import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.russhwolf.settings.Settings diff --git a/connectionstatus/.gitignore b/connectionstatus/.gitignore deleted file mode 100644 index 42afabf..0000000 --- a/connectionstatus/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/connectionstatus/build.gradle.kts b/connectionstatus/build.gradle.kts deleted file mode 100644 index 045698e..0000000 --- a/connectionstatus/build.gradle.kts +++ /dev/null @@ -1,65 +0,0 @@ -plugins { - id("com.android.library") - kotlin("multiplatform") -} - -group = "com.github.ln-12" -version = "1.1.0" - -repositories { - google() - mavenCentral() -} - -kotlin { - android { - compilations.all { - kotlinOptions.jvmTarget = "1.8" - } - - publishLibraryVariants("release", "debug") - } - ios() - - sourceSets { - val commonMain by getting { - dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - } - } - - val androidMain by getting - val androidTest by getting { - dependencies { - implementation(kotlin("test-junit")) - implementation("junit:junit:4.13.2") - } - } - - val iosMain by getting - val iosTest by getting - } -} - -android { - compileSdk = 31 - sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") - defaultConfig { - minSdk = 21 - targetSdk = 31 - } -} - -// metadata is currently not supported for iOS -// https://youtrack.jetbrains.com/issue/KT-44459#focus=Comments-27-4645829.0-0 -kotlin.metadata { - compilations.matching { it.name == "iosMain" }.all { - compileKotlinTaskProvider.configure { enabled = false } - } -} \ No newline at end of file diff --git a/connectionstatus/src/androidMain/AndroidManifest.xml b/connectionstatus/src/androidMain/AndroidManifest.xml deleted file mode 100644 index f3a3bcb..0000000 --- a/connectionstatus/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt deleted file mode 100644 index 027abed..0000000 --- a/connectionstatus/src/androidMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.ln12.library - -import android.content.Context -import android.net.ConnectivityManager -import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest -import android.os.Build -import android.util.Log -import kotlinx.coroutines.flow.MutableStateFlow - -// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 -// https://github.com/ln-12/multiplatform-connectivity-status -// Copyright 2021 Lorenzo Neumann -// Edited by davidoskky as here: https://github.com/ln-12/multiplatform-connectivity-status/pull/4 -actual class ConnectivityStatus(private val context: Context) { - actual val isNetworkConnected = MutableStateFlow(false) - - private var connectivityManager: ConnectivityManager? = null - private val networkCallback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - Log.d("Connectivity status", "Connected") - isNetworkConnected.value = true - } - - override fun onLost(network: Network) { - Log.d("Connectivity status", "Disconnected") - isNetworkConnected.value = false - } - } - - actual fun start() { - try { - if (connectivityManager == null) { - connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // API 24 and above - connectivityManager!!.registerDefaultNetworkCallback(networkCallback) - - val currentNetwork = connectivityManager!!.activeNetwork - - if(currentNetwork == null) { - isNetworkConnected.value = false - - Log.d("Connectivity status", "Disconnected") - } - } else { - // API 23 and below - val networkRequest = NetworkRequest.Builder().apply { - addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) - } - }.build() - - connectivityManager!!.registerNetworkCallback(networkRequest, networkCallback) - - val currentNetwork = connectivityManager!!.activeNetworkInfo - - if(currentNetwork == null || ( - currentNetwork.type != ConnectivityManager.TYPE_ETHERNET && - currentNetwork.type != ConnectivityManager.TYPE_WIFI && - currentNetwork.type != ConnectivityManager.TYPE_MOBILE - )) { - isNetworkConnected.value = false - - Log.d("Connectivity status", "Disconnected") - } - } - - Log.d("Connectivity status", "Started") - } catch (e: Exception) { - Log.d("Connectivity status", "Failed to start: ${e.message.toString()}") - e.printStackTrace() - isNetworkConnected.value = false - } - } - - actual fun stop() { - connectivityManager?.unregisterNetworkCallback(networkCallback) - Log.d("Connectivity status", "Stopped") - } -} \ No newline at end of file diff --git a/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt deleted file mode 100644 index 3b25c82..0000000 --- a/connectionstatus/src/commonMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.github.ln12.library - -import kotlinx.coroutines.flow.MutableStateFlow - -// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 -// https://github.com/ln-12/multiplatform-connectivity-status -// Copyright 2021 Lorenzo Neumann -expect class ConnectivityStatus { - val isNetworkConnected: MutableStateFlow - fun start() - fun stop() -} \ No newline at end of file diff --git a/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt b/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt deleted file mode 100644 index e3c8970..0000000 --- a/connectionstatus/src/iosMain/kotlin/com/github/ln12/library/ConnectivityStatus.kt +++ /dev/null @@ -1,67 +0,0 @@ -package com.github.ln12.library - -import kotlinx.coroutines.flow.MutableStateFlow -import cocoapods.Reachability.* -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch -import platform.Foundation.NSLog -import platform.darwin.dispatch_async -import platform.darwin.dispatch_get_main_queue -import kotlin.native.concurrent.freeze - - -// From library com.github.ln-12:multiplatform-connectivity-status:1.1.0 -// https://github.com/ln-12/multiplatform-connectivity-status -// Copyright 2021 Lorenzo Neumann -actual class ConnectivityStatus { - actual val isNetworkConnected = MutableStateFlow(false) - - private var reachability: Reachability? = null - - // Swift can't directly use a MutableStateFlow, so the status - // is exposed via a lambda/closure - fun getStatus(success: (Boolean) -> Unit) { - MainScope().launch { - isNetworkConnected.collect { status -> - success(status) - } - } - } - - actual fun start() { - dispatch_async(dispatch_get_main_queue()) { - reachability = Reachability.reachabilityForInternetConnection() - - val reachableCallback = { reach: Reachability? -> - dispatch_async(dispatch_get_main_queue(), { - NSLog("Connected") - - isNetworkConnected.value = true - }.freeze()) - }.freeze() - reachability?.reachableBlock = reachableCallback - - val unreachableCallback = { reach: Reachability? -> - dispatch_async(dispatch_get_main_queue(), { - NSLog("Disconnected") - - isNetworkConnected.value = false - }.freeze()) - }.freeze() - reachability?.unreachableBlock = unreachableCallback - - reachability?.startNotifier() - - dispatch_async(dispatch_get_main_queue(), { - isNetworkConnected.value = reachability?.isReachable() ?: false - - NSLog("Initial reachability: ${reachability?.isReachable()}") - }.freeze()) - } - } - - actual fun stop() { - reachability?.stopNotifier() - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 9c56c94..0b32ec1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,5 +8,4 @@ pluginManagement { rootProject.name = "ReaderForSelfossV2" include(":androidApp") -include(":shared") -include(":connectionstatus") \ No newline at end of file +include(":shared") \ No newline at end of file diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index ca82dbd..ad4c8a8 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -40,7 +40,7 @@ kotlin { // Network information // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 //implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") - implementation(project(":connectionstatus")) + implementation(files("libs/multiplatform-connectivity-status.aar")) } } val commonTest by getting { diff --git a/shared/libs/multiplatform-connectivity-status.aar b/shared/libs/multiplatform-connectivity-status.aar new file mode 100644 index 0000000000000000000000000000000000000000..2af7712ab4a997267cdfe5f5a42e0a739ab320ce GIT binary patch literal 5113 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 literal 0 HcmV?d00001 diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index d3876bf..44fc676 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -5,7 +5,7 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.ItemType -import com.github.ln12.library.ConnectivityStatus +import com.github.`ln-12`.library.ConnectivityStatus import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope -- 2.34.1 From 1d5ab3205ea80918df4520dfec5a9340a99e0055 Mon Sep 17 00:00:00 2001 From: davidoskky Date: Sun, 21 Aug 2022 23:32:41 +0200 Subject: [PATCH 27/28] Localize strings --- .../readerforselfossv2/android/viewmodel/AppViewModel.kt | 7 ++++--- androidApp/src/main/res/values/strings.xml | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt index cc7c21d..9320762 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/viewmodel/AppViewModel.kt @@ -2,13 +2,14 @@ package bou.amine.apps.readerforselfossv2.android.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import bou.amine.apps.readerforselfossv2.android.R 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 _toastMessageProvider = MutableSharedFlow() + private val _toastMessageProvider = MutableSharedFlow() val toastMessageProvider = _toastMessageProvider.asSharedFlow() private var wasConnected = true @@ -16,10 +17,10 @@ class AppViewModel(private val repository: Repository) : ViewModel() { viewModelScope.launch { repository.isConnectionAvailable.collect { isConnected -> if (isConnected && !wasConnected && repository.connectionMonitored) { - _toastMessageProvider.emit("Network connection is now available") + _toastMessageProvider.emit(R.string.network_connectivity_retrieved) wasConnected = true } else if (!isConnected && wasConnected && repository.connectionMonitored){ - _toastMessageProvider.emit("Network connection lost") + _toastMessageProvider.emit(R.string.network_connectivity_lost) wasConnected = false } } diff --git a/androidApp/src/main/res/values/strings.xml b/androidApp/src/main/res/values/strings.xml index fc20983..a83ab53 100644 --- a/androidApp/src/main/res/values/strings.xml +++ b/androidApp/src/main/res/values/strings.xml @@ -143,6 +143,8 @@ Check for new sources and tags Disable this if your server is receiving excessive amounts of database queries. Not connected ! + "Network connection lost" + "Network connection is now available" Sync articles Articles will not be synced in the background Articles will periodically be synced -- 2.34.1 From 4c78b22614ee2a9f588722073d80daf213ea1c4f Mon Sep 17 00:00:00 2001 From: davidoskky Date: Mon, 22 Aug 2022 19:33:58 +0200 Subject: [PATCH 28/28] Use the connectivity-status library from the repository rather than the local copy --- androidApp/build.gradle.kts | 5 +---- .../apps/readerforselfossv2/android/MyApp.kt | 16 ++++++++++------ shared/build.gradle.kts | 4 +--- .../libs/multiplatform-connectivity-status.aar | Bin 5113 -> 0 bytes .../repository/RepositoryImpl.kt | 17 +---------------- 5 files changed, 13 insertions(+), 29 deletions(-) delete mode 100644 shared/libs/multiplatform-connectivity-status.aar diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 527731a..b548d33 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -207,8 +207,5 @@ dependencies { implementation("android.arch.work:work-runtime-ktx:1.0.1") // Network information - // TODO: When updating this library, check if the todo in RepositoryImpl.startNetwork can be resolved - // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 - // implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") - implementation(files("../shared/libs/multiplatform-connectivity-status.aar")) + implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") } \ No newline at end of file diff --git a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt index dbd036f..be77f62 100644 --- a/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt +++ b/androidApp/src/main/java/bou/amine/apps/readerforselfossv2/android/MyApp.kt @@ -22,7 +22,7 @@ import bou.amine.apps.readerforselfossv2.repository.Repository import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.ftinc.scoop.Scoop -import com.github.`ln-12`.library.ConnectivityStatus +import com.github.ln_12.library.ConnectivityStatus import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.russhwolf.settings.Settings @@ -37,12 +37,14 @@ class MyApp : MultiDexApplication(), DIAware { override val di by DI.lazy { import(networkModule) - bind() with singleton { Repository(instance(), instance(), ConnectivityStatus(applicationContext)) } + bind() with singleton { Repository(instance(), instance(), connectivityStatus) } + bind() with singleton { ConnectivityStatus(applicationContext) } bind() with singleton { AppViewModel(repository = instance()) } } private val repository: Repository by instance() private val viewModel: AppViewModel by instance() + private val connectivityStatus: ConnectivityStatus by instance() private lateinit var config: Config private lateinit var settings : Settings @@ -60,7 +62,7 @@ class MyApp : MultiDexApplication(), DIAware { handleNotificationChannels() - ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(repository)) + ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifeCycleObserver(connectivityStatus, repository)) CoroutineScope(Dispatchers.Main).launch { viewModel.toastMessageProvider.collect { toastMessage -> @@ -131,15 +133,17 @@ class MyApp : MultiDexApplication(), DIAware { } } - class AppLifeCycleObserver(val repository: Repository) : DefaultLifecycleObserver { + class AppLifeCycleObserver(val connectivityStatus: ConnectivityStatus, val repository: Repository) : DefaultLifecycleObserver { override fun onResume(owner: LifecycleOwner) { super.onResume(owner) - repository.startNetwork() + repository.connectionMonitored = true + connectivityStatus.start() } override fun onPause(owner: LifecycleOwner) { - repository.stopNetwork() + repository.connectionMonitored = false + connectivityStatus.stop() super.onPause(owner) } } diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index ad4c8a8..f8242cd 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -38,9 +38,7 @@ kotlin { implementation("io.github.aakira:napier:2.6.1") // Network information - // TODO: Include this library once this is merged https://github.com/ln-12/multiplatform-connectivity-status/pull/4 - //implementation("com.github.ln-12:multiplatform-connectivity-status:1.1.0") - implementation(files("libs/multiplatform-connectivity-status.aar")) + implementation("com.github.ln-12:multiplatform-connectivity-status:1.3.0") } } val commonTest by getting { diff --git a/shared/libs/multiplatform-connectivity-status.aar b/shared/libs/multiplatform-connectivity-status.aar deleted file mode 100644 index 2af7712ab4a997267cdfe5f5a42e0a739ab320ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5113 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 diff --git a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt index 44fc676..287ef1c 100644 --- a/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/bou/amine/apps/readerforselfossv2/repository/RepositoryImpl.kt @@ -5,7 +5,7 @@ import bou.amine.apps.readerforselfossv2.rest.SelfossModel import bou.amine.apps.readerforselfossv2.service.ApiDetailsService import bou.amine.apps.readerforselfossv2.utils.DateUtils import bou.amine.apps.readerforselfossv2.utils.ItemType -import com.github.`ln-12`.library.ConnectivityStatus +import com.github.ln_12.library.ConnectivityStatus import com.russhwolf.settings.Settings import io.github.aakira.napier.Napier import kotlinx.coroutines.CoroutineScope @@ -342,20 +342,5 @@ class Repository(private val api: SelfossApi, private val apiDetails: ApiDetails fun isNetworkAvailable() = isConnectionAvailable.value && !offlineOverride - // TODO: Start and stop the monitoring within Android and iOS code - // This should be performed in the architecture specific code - // However it is not possible on android due to the dash contained in the name of the library - // com.github.ln-12:multiplatform-connectivity-status:1.1.0 - // https://github.com/ln-12/multiplatform-connectivity-status/issues/2 - fun startNetwork() { - connectionMonitored = true - connectivityStatus.start() - } - - fun stopNetwork() { - connectionMonitored = false - connectivityStatus.stop() - } - // TODO: Handle offline actions } \ No newline at end of file -- 2.34.1