diff --git a/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt
new file mode 100644
index 0000000000000000000000000000000000000000..78985de5fb13bfdcf071fe6dcce939a5abcb6ebe
--- /dev/null
+++ b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright MURENA SAS 2023
+ * Apps Quickly and easily install Android apps onto your device!
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package foundation.e.apps.data
+
+import foundation.e.apps.data.gplay.utils.GPlayHttpClient
+import foundation.e.apps.data.gplay.utils.GplayHttpRequestException
+import foundation.e.apps.data.login.exceptions.GPlayException
+import java.net.SocketTimeoutException
+
+private const val TIMEOUT = "Timeout"
+private const val UNKNOWN = "Unknown"
+private const val STATUS = "Status:"
+private const val ERROR_GPLAY_API = "Gplay api has faced error!"
+
+suspend fun handleNetworkResult(call: suspend () -> T): ResultSupreme {
+ return try {
+ ResultSupreme.Success(call())
+ } catch (e: SocketTimeoutException) {
+ handleSocketTimeoutException(e)
+ } catch (e: GplayHttpRequestException) {
+ resultSupremeGplayHttpRequestException(e)
+ } catch (e: Exception) {
+ handleOthersException(e)
+ }
+}
+
+private fun handleSocketTimeoutException(e: SocketTimeoutException): ResultSupreme.Timeout {
+ val message = extractErrorMessage(e)
+ val resultTimeout = ResultSupreme.Timeout(exception = e)
+ resultTimeout.message = message
+ return resultTimeout
+}
+
+private fun resultSupremeGplayHttpRequestException(e: GplayHttpRequestException): ResultSupreme {
+ val message = extractErrorMessage(e)
+ val exception = GPlayException(e.status == GPlayHttpClient.STATUS_CODE_TIMEOUT, message)
+
+ return if (exception.isTimeout) {
+ ResultSupreme.Timeout(exception = exception)
+ } else {
+ ResultSupreme.Error(message, exception)
+ }
+}
+
+private fun handleOthersException(e: Exception): ResultSupreme.Error {
+ val message = extractErrorMessage(e)
+ return ResultSupreme.Error(message, e)
+}
+
+private fun extractErrorMessage(e: Exception): String {
+ val status = when (e) {
+ is GplayHttpRequestException -> e.status.toString()
+ is SocketTimeoutException -> TIMEOUT
+ else -> UNKNOWN
+ }
+ return (e.localizedMessage?.ifBlank { ERROR_GPLAY_API } ?: ERROR_GPLAY_API) + " $STATUS $status"
+}
diff --git a/app/src/main/java/foundation/e/apps/data/ResultSupreme.kt b/app/src/main/java/foundation/e/apps/data/ResultSupreme.kt
index 14695702f15924b607ee449c8f66958bbe95ddad..a7a773f60ae33131d12c9ec6f68cae8adda48356 100644
--- a/app/src/main/java/foundation/e/apps/data/ResultSupreme.kt
+++ b/app/src/main/java/foundation/e/apps/data/ResultSupreme.kt
@@ -20,6 +20,8 @@ package foundation.e.apps.data
import foundation.e.apps.data.enums.ResultStatus
import java.util.concurrent.TimeoutException
+private const val UNKNOWN_ERROR = "Unknown error!"
+
/**
* Another implementation of Result class.
* This removes the use of [ResultStatus] class for different status.
@@ -52,10 +54,12 @@ sealed class ResultSupreme {
* Example can be an empty list.
* @param exception Optional exception from try-catch block.
*/
- class Timeout(data: T, exception: Exception = TimeoutException()) :
+ class Timeout(data: T? = null, exception: Exception = TimeoutException()) :
ResultSupreme() {
init {
- setData(data)
+ data?.let {
+ setData(it)
+ }
this.exception = exception
}
}
@@ -119,6 +123,16 @@ sealed class ResultSupreme {
this.data = data
}
+ fun getResultStatus(): ResultStatus {
+ return when (this) {
+ is Success -> ResultStatus.OK
+ is Timeout -> ResultStatus.TIMEOUT
+ else -> ResultStatus.UNKNOWN.apply {
+ message = this@ResultSupreme.exception?.localizedMessage ?: UNKNOWN_ERROR
+ }
+ }
+ }
+
companion object {
/**
diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/RetrofitModule.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/RetrofitModule.kt
index 43a505137c32b44c1c88c655cf7a8c55faf04e19..c924f27c7ad5412351e3f3f40ce9b58b530ddb12 100644
--- a/app/src/main/java/foundation/e/apps/data/cleanapk/RetrofitModule.kt
+++ b/app/src/main/java/foundation/e/apps/data/cleanapk/RetrofitModule.kt
@@ -48,6 +48,7 @@ import retrofit2.converter.moshi.MoshiConverterFactory
import timber.log.Timber
import java.net.ConnectException
import java.util.Locale
+import java.util.concurrent.TimeUnit
import javax.inject.Named
import javax.inject.Singleton
@@ -55,6 +56,8 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object RetrofitModule {
+ private const val HTTP_TIMEOUT_IN_SECOND = 10L
+
/**
* Provides an instance of Retrofit to work with CleanAPK API
* @return instance of [CleanApkRetrofit]
@@ -208,6 +211,7 @@ object RetrofitModule {
fun provideOkHttpClient(cache: Cache, interceptor: Interceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
+ .callTimeout(HTTP_TIMEOUT_IN_SECOND, TimeUnit.SECONDS)
.cache(cache)
.build()
}
diff --git a/app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt b/app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt
index cb398a78c124c0e8ff6e03156570ac61a1ac0aae..e08bb6deda41e6b4cf580bedfabd29f15180e6e7 100644
--- a/app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt
+++ b/app/src/main/java/foundation/e/apps/data/fused/FusedApiImpl.kt
@@ -22,7 +22,6 @@ import android.content.Context
import android.text.format.Formatter
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
-import androidx.lifecycle.map
import com.aurora.gplayapi.Constants
import com.aurora.gplayapi.SearchSuggestEntry
import com.aurora.gplayapi.data.models.App
@@ -63,8 +62,7 @@ import foundation.e.apps.data.fused.utils.CategoryType
import foundation.e.apps.data.fused.utils.CategoryUtils
import foundation.e.apps.data.fusedDownload.models.FusedDownload
import foundation.e.apps.data.gplay.GplayStoreRepository
-import foundation.e.apps.data.gplay.utils.GplayHttpRequestException
-import foundation.e.apps.data.login.exceptions.GPlayException
+import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.preference.PreferenceManagerModule
import foundation.e.apps.install.pkg.PWAManagerModule
import foundation.e.apps.install.pkg.PkgManagerModule
@@ -73,11 +71,9 @@ import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withTimeout
import retrofit2.Response
import timber.log.Timber
-import java.net.SocketTimeoutException
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
@@ -100,8 +96,6 @@ class FusedApiImpl @Inject constructor(
private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&"
private const val CATEGORY_OPEN_GAMES_ID = "game_open_games"
private const val CATEGORY_OPEN_GAMES_TITLE = "Open games"
- private const val ERROR_GPLAY_SEARCH = "Gplay search has failed!"
- private const val ERROR_GPLAY_SOURCE_NOT_SELECTED = "Gplay apps are not selected!"
}
/**
@@ -167,29 +161,32 @@ class FusedApiImpl @Inject constructor(
authData: AuthData,
): ResultSupreme> {
- val apiStatus = when (source) {
- Source.GPLAY -> runCodeWithTimeout({
+ val result = when (source) {
+ Source.GPLAY -> handleNetworkResult> {
priorList.addAll(fetchGPlayHome(authData))
- })
+ priorList
+ }
- Source.OPEN -> runCodeWithTimeout({
+ Source.OPEN -> handleNetworkResult {
val response =
(cleanApkAppsRepository.getHomeScreenData() as Response).body()
response?.home?.let {
priorList.addAll(generateCleanAPKHome(it, APP_TYPE_OPEN))
}
- })
+ priorList
+ }
- Source.PWA -> runCodeWithTimeout({
+ Source.PWA -> handleNetworkResult {
val response =
(cleanApkPWARepository.getHomeScreenData() as Response).body()
response?.home?.let {
priorList.addAll(generateCleanAPKHome(it, APP_TYPE_PWA))
}
- })
+ priorList
+ }
}
- setHomeErrorMessage(apiStatus, source)
+ setHomeErrorMessage(result.getResultStatus(), source)
priorList.sortByDescending {
when (it.source) {
APP_TYPE_OPEN -> 2
@@ -197,7 +194,7 @@ class FusedApiImpl @Inject constructor(
else -> 3
}
}
- return ResultSupreme.create(apiStatus, priorList)
+ return ResultSupreme.create(result.getResultStatus(), priorList)
}
private fun setHomeErrorMessage(apiStatus: ResultStatus, source: Source) {
@@ -289,7 +286,7 @@ class FusedApiImpl @Inject constructor(
packageSpecificResults: ArrayList
): ResultSupreme, Boolean>> {
val pwaApps: MutableList = mutableListOf()
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
val apps =
cleanApkPWARepository.getSearchResult(query).body()?.apps
apps?.apply {
@@ -297,14 +294,14 @@ class FusedApiImpl @Inject constructor(
pwaApps.addAll(this)
}
}
- })
+ }
- if (pwaApps.isNotEmpty() || status != ResultStatus.OK) {
+ if (pwaApps.isNotEmpty() || result.getResultStatus() != ResultStatus.OK) {
searchResult.addAll(pwaApps)
}
return ResultSupreme.create(
- status,
+ result.getResultStatus(),
Pair(
filterWithKeywordSearch(
searchResult,
@@ -322,16 +319,17 @@ class FusedApiImpl @Inject constructor(
searchResult: MutableList,
packageSpecificResults: ArrayList
): ResultSupreme, Boolean>> {
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
cleanApkResults.addAll(getCleanAPKSearchResults(query))
- })
+ cleanApkResults
+ }
if (cleanApkResults.isNotEmpty()) {
searchResult.addAll(cleanApkResults)
}
return ResultSupreme.create(
- status,
+ result.getResultStatus(),
Pair(
filterWithKeywordSearch(
searchResult,
@@ -351,7 +349,7 @@ class FusedApiImpl @Inject constructor(
var gplayPackageResult: FusedApp? = null
var cleanapkPackageResult: FusedApp? = null
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
if (preferenceManagerModule.isGplaySelected()) {
gplayPackageResult = getGplayPackagResult(query, authData)
}
@@ -359,7 +357,7 @@ class FusedApiImpl @Inject constructor(
if (preferenceManagerModule.isOpenSourceSelected()) {
cleanapkPackageResult = getCleanApkPackageResult(query)
}
- })
+ }
/*
* Currently only show open source package result if exists in both fdroid and gplay.
@@ -378,10 +376,13 @@ class FusedApiImpl @Inject constructor(
* If there was a timeout, return it and don't try to fetch anything else.
* Also send true in the pair to signal more results being loaded.
*/
- if (status != ResultStatus.OK) {
- return ResultSupreme.create(status, Pair(packageSpecificResults, false))
+ if (result.getResultStatus() != ResultStatus.OK) {
+ return ResultSupreme.create(
+ result.getResultStatus(),
+ Pair(packageSpecificResults, false)
+ )
}
- return ResultSupreme.create(status, Pair(packageSpecificResults, true))
+ return ResultSupreme.create(result.getResultStatus(), Pair(packageSpecificResults, true))
}
/*
@@ -446,7 +447,7 @@ class FusedApiImpl @Inject constructor(
*/
private suspend fun getCleanapkSearchResult(packageName: String): ResultSupreme {
var fusedApp = FusedApp()
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
val result = cleanApkAppsRepository.getSearchResult(
packageName,
"package_name"
@@ -455,15 +456,15 @@ class FusedApiImpl @Inject constructor(
if (result?.apps?.isNotEmpty() == true && result.numberOfResults == 1) {
fusedApp = result.apps[0]
}
- })
- return ResultSupreme.create(status, fusedApp)
+ }
+ return ResultSupreme.create(result.getResultStatus(), fusedApp)
}
override suspend fun getSearchSuggestions(query: String): List {
var searchSuggesions = listOf()
- runCodeWithTimeout({
+ handleNetworkResult {
searchSuggesions = gplayRepository.getSearchSuggestions(query)
- })
+ }
return searchSuggesions
}
@@ -523,7 +524,7 @@ class FusedApiImpl @Inject constructor(
override suspend fun getPWAApps(category: String): ResultSupreme, String>> {
val list = mutableListOf()
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
val response = getPWAAppsResponse(category)
response?.apps?.forEach {
it.updateStatus()
@@ -531,13 +532,13 @@ class FusedApiImpl @Inject constructor(
it.updateFilterLevel(null)
list.add(it)
}
- })
- return ResultSupreme.create(status, Pair(list, ""))
+ }
+ return ResultSupreme.create(result.getResultStatus(), Pair(list, ""))
}
override suspend fun getOpenSourceApps(category: String): ResultSupreme, String>> {
val list = mutableListOf()
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
val response = getOpenSourceAppsResponse(category)
response?.apps?.forEach {
it.updateStatus()
@@ -545,8 +546,8 @@ class FusedApiImpl @Inject constructor(
it.updateFilterLevel(null)
list.add(it)
}
- })
- return ResultSupreme.create(status, Pair(list, ""))
+ }
+ return ResultSupreme.create(result.getResultStatus(), Pair(list, ""))
}
/*
@@ -557,7 +558,7 @@ class FusedApiImpl @Inject constructor(
*/
override suspend fun getCleanapkAppDetails(packageName: String): Pair {
var fusedApp = FusedApp()
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
val result = cleanApkAppsRepository.getSearchResult(
packageName,
"package_name"
@@ -569,8 +570,8 @@ class FusedApiImpl @Inject constructor(
?: FusedApp()
}
fusedApp.updateFilterLevel(null)
- })
- return Pair(fusedApp, status)
+ }
+ return Pair(fusedApp, result.getResultStatus())
}
override suspend fun getApplicationDetails(
@@ -614,7 +615,7 @@ class FusedApiImpl @Inject constructor(
* i.e. check timeout for individual package query.
*/
for (packageName in packageNameList) {
- status = runCodeWithTimeout({
+ val result = handleNetworkResult {
cleanApkAppsRepository.getSearchResult(
packageName,
"package_name"
@@ -627,7 +628,9 @@ class FusedApiImpl @Inject constructor(
)
}
}
- })
+ }
+
+ status = result.getResultStatus()
/*
* If status is not ok, immediately return.
@@ -653,7 +656,7 @@ class FusedApiImpl @Inject constructor(
/*
* Old code moved from getApplicationDetails()
*/
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
gplayRepository.getAppsDetails(packageNameList).forEach { app ->
/*
* Some apps are restricted to locations. Example "com.skype.m2".
@@ -670,9 +673,9 @@ class FusedApiImpl @Inject constructor(
)
}
}
- })
+ }
- return Pair(fusedAppList, status)
+ return Pair(fusedAppList, result.getResultStatus())
}
/**
@@ -691,7 +694,7 @@ class FusedApiImpl @Inject constructor(
appList: List,
): ResultSupreme> {
val filteredFusedApps = mutableListOf()
- val status = runCodeWithTimeout({
+ return handleNetworkResult {
appList.forEach {
val filter = getAppFilterLevel(it, authData)
if (filter.isUnFiltered()) {
@@ -702,9 +705,8 @@ class FusedApiImpl @Inject constructor(
)
}
}
- })
-
- return ResultSupreme.create(status, filteredFusedApps)
+ filteredFusedApps
+ }
}
/**
@@ -783,7 +785,7 @@ class FusedApiImpl @Inject constructor(
var response: FusedApp? = null
- val status = runCodeWithTimeout({
+ val result = handleNetworkResult {
response = if (origin == Origin.CLEANAPK) {
(cleanApkAppsRepository.getAppDetails(id) as Response).body()?.app
} else {
@@ -796,9 +798,10 @@ class FusedApiImpl @Inject constructor(
it.updateSource()
it.updateFilterLevel(authData)
}
- })
+ response
+ }
- return Pair(response ?: FusedApp(), status)
+ return Pair(result.data ?: FusedApp(), result.getResultStatus())
}
/*
@@ -836,9 +839,9 @@ class FusedApiImpl @Inject constructor(
val gplayCategoryResult = fetchGplayCategories(
type,
)
- categoriesList.addAll(gplayCategoryResult.second)
- apiStatus = gplayCategoryResult.first
- errorApplicationCategory = gplayCategoryResult.third
+ categoriesList.addAll(gplayCategoryResult.data ?: listOf())
+ apiStatus = gplayCategoryResult.getResultStatus()
+ errorApplicationCategory = APP_TYPE_ANY
}
return Pair(apiStatus, errorApplicationCategory)
@@ -846,34 +849,25 @@ class FusedApiImpl @Inject constructor(
private suspend fun fetchGplayCategories(
type: CategoryType,
- ): Triple, String> {
- var errorApplicationCategory = ""
- var apiStatus = ResultStatus.OK
+ ): ResultSupreme> {
val categoryList = mutableListOf()
- runCodeWithTimeout({
+
+ return handleNetworkResult {
val playResponse = gplayRepository.getCategories(type).map { app ->
val category = app.transformToFusedCategory()
updateCategoryDrawable(category)
category
}
categoryList.addAll(playResponse)
- }, {
- errorApplicationCategory = APP_TYPE_ANY
- apiStatus = ResultStatus.TIMEOUT
- }, {
- errorApplicationCategory = APP_TYPE_ANY
- apiStatus = ResultStatus.UNKNOWN
- })
- return Triple(apiStatus, categoryList, errorApplicationCategory)
+ categoryList
+ }
}
private suspend fun fetchPWACategories(
type: CategoryType,
): Triple, String> {
- var errorApplicationCategory = ""
- var apiStatus: ResultStatus = ResultStatus.OK
val fusedCategoriesList = mutableListOf()
- runCodeWithTimeout({
+ val result = handleNetworkResult {
getPWAsCategories()?.let {
fusedCategoriesList.addAll(
getFusedCategoryBasedOnCategoryType(
@@ -881,23 +875,16 @@ class FusedApiImpl @Inject constructor(
)
)
}
- }, {
- errorApplicationCategory = APP_TYPE_PWA
- apiStatus = ResultStatus.TIMEOUT
- }, {
- errorApplicationCategory = APP_TYPE_PWA
- apiStatus = ResultStatus.UNKNOWN
- })
- return Triple(apiStatus, fusedCategoriesList, errorApplicationCategory)
+ }
+
+ return Triple(result.getResultStatus(), fusedCategoriesList, APP_TYPE_PWA)
}
private suspend fun fetchOpenSourceCategories(
type: CategoryType,
): Triple, String> {
- var errorApplicationCategory = ""
- var apiStatus: ResultStatus = ResultStatus.OK
val fusedCategoryList = mutableListOf()
- runCodeWithTimeout({
+ val result = handleNetworkResult {
getOpenSourceCategories()?.let {
fusedCategoryList.addAll(
getFusedCategoryBasedOnCategoryType(
@@ -907,14 +894,9 @@ class FusedApiImpl @Inject constructor(
)
)
}
- }, {
- errorApplicationCategory = APP_TYPE_OPEN
- apiStatus = ResultStatus.TIMEOUT
- }, {
- errorApplicationCategory = APP_TYPE_OPEN
- apiStatus = ResultStatus.UNKNOWN
- })
- return Triple(apiStatus, fusedCategoryList, errorApplicationCategory)
+ }
+
+ return Triple(result.getResultStatus(), fusedCategoryList, APP_TYPE_OPEN)
}
/**
@@ -956,9 +938,8 @@ class FusedApiImpl @Inject constructor(
}
private fun getCategoryIconName(category: FusedCategory): String {
- var categoryTitle = if (category.tag.getOperationalTag()
- .contentEquals(AppTag.GPlay().getOperationalTag())
- ) category.id else category.title
+ var categoryTitle = if (category.tag.getOperationalTag().contentEquals(AppTag.GPlay().getOperationalTag()))
+ category.id else category.title
if (categoryTitle.contains(CATEGORY_TITLE_REPLACEABLE_CONJUNCTION)) {
categoryTitle = categoryTitle.replace(CATEGORY_TITLE_REPLACEABLE_CONJUNCTION, "and")
@@ -1082,12 +1063,12 @@ class FusedApiImpl @Inject constructor(
query: String,
nextPageSubBundle: Set?
): GplaySearchResult {
- try {
+ return handleNetworkResult {
val searchResults =
gplayRepository.getSearchResult(query, nextPageSubBundle?.toMutableSet())
if (!preferenceManagerModule.isGplaySelected()) {
- return ResultSupreme.Error(ERROR_GPLAY_SOURCE_NOT_SELECTED)
+ return@handleNetworkResult Pair(listOf(), setOf())
}
val fusedAppList =
@@ -1097,20 +1078,7 @@ class FusedApiImpl @Inject constructor(
fusedAppList.add(FusedApp(isPlaceHolder = true))
}
- return ResultSupreme.Success(Pair(fusedAppList.toList(), searchResults.second.toSet()))
- } catch (e: GplayHttpRequestException) {
- val message = (
- e.localizedMessage?.ifBlank { ERROR_GPLAY_SEARCH }
- ?: ERROR_GPLAY_SEARCH
- ) + "Status: ${e.status}"
-
- val exception = GPlayException(e.status == 408, message)
- return ResultSupreme.Error(message, exception)
- } catch (e: Exception) {
- val exception =
- GPlayException(e is SocketTimeoutException, e.localizedMessage)
-
- return ResultSupreme.Error(e.localizedMessage ?: "", exception)
+ return@handleNetworkResult Pair(fusedAppList.toList(), searchResults.second.toSet())
}
}
@@ -1416,7 +1384,7 @@ class FusedApiImpl @Inject constructor(
var fusedAppList: MutableList = mutableListOf()
var nextPageUrl = ""
- val status = runCodeWithTimeout({
+ return handleNetworkResult {
val streamCluster =
gplayRepository.getAppsByCategory(category, pageUrl) as StreamCluster
@@ -1429,8 +1397,7 @@ class FusedApiImpl @Inject constructor(
if (!nextPageUrl.isNullOrEmpty()) {
fusedAppList.add(FusedApp(isPlaceHolder = true))
}
- })
-
- return ResultSupreme.create(status, Pair(fusedAppList, nextPageUrl))
+ Pair(fusedAppList, nextPageUrl)
+ }
}
}
diff --git a/app/src/main/java/foundation/e/apps/data/gplay/utils/GPlayHttpClient.kt b/app/src/main/java/foundation/e/apps/data/gplay/utils/GPlayHttpClient.kt
index 839d7c168229231301718c71f0d6289a27dd0abf..38fb0ad2277b2460d6531b276f3f8b1705781cf6 100644
--- a/app/src/main/java/foundation/e/apps/data/gplay/utils/GPlayHttpClient.kt
+++ b/app/src/main/java/foundation/e/apps/data/gplay/utils/GPlayHttpClient.kt
@@ -40,7 +40,6 @@ import okhttp3.Response
import timber.log.Timber
import java.io.IOException
import java.net.SocketTimeoutException
-import java.net.UnknownHostException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -58,6 +57,7 @@ class GPlayHttpClient @Inject constructor(
private const val SEARCH_SUGGEST = "searchSuggest"
private const val STATUS_CODE_UNAUTHORIZED = 401
private const val STATUS_CODE_TOO_MANY_REQUESTS = 429
+ const val STATUS_CODE_TIMEOUT = 408
}
private val okHttpClient = OkHttpClient().newBuilder()
@@ -163,32 +163,16 @@ class GPlayHttpClient @Inject constructor(
val call = okHttpClient.newCall(request)
response = call.execute()
buildPlayResponse(response)
+ } catch (e: GplayHttpRequestException) {
+ throw e
} catch (e: Exception) {
- // TODO: exception will be thrown for all apis when all gplay api implementation
- // will handle the exceptions. this will be done in following issue.
- // Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1483
- if (request.url.toString().contains(SEARCH)) {
- throw e
- }
-
- when (e) {
- is UnknownHostException,
- is SocketTimeoutException -> handleExceptionOnGooglePlayRequest(e)
-
- else -> handleExceptionOnGooglePlayRequest(e)
- }
+ val status = if (e is SocketTimeoutException) STATUS_CODE_TIMEOUT else -1
+ throw GplayHttpRequestException(status, e.localizedMessage ?: "")
} finally {
response?.close()
}
}
- private fun handleExceptionOnGooglePlayRequest(e: Exception): PlayResponse {
- Timber.e("processRequest: ${e.localizedMessage}")
- return PlayResponse().apply {
- errorString = "${this@GPlayHttpClient::class.java.simpleName}: ${e.localizedMessage}"
- }
- }
-
private fun buildUrl(url: String, params: Map): HttpUrl {
val urlBuilder = url.toHttpUrl().newBuilder()
params.forEach {
@@ -222,10 +206,7 @@ class GPlayHttpClient @Inject constructor(
}
}
- // TODO: exception will be thrown for all apis when all gplay api implementation
- // will handle the exceptions. this will be done in following issue.
- // Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/1483
- if (response.request.url.toString().contains(SEARCH) && code != 200) {
+ if (code != 200) {
throw GplayHttpRequestException(code, response.message)
}
diff --git a/app/src/main/java/foundation/e/apps/data/login/api/LoginApiRepository.kt b/app/src/main/java/foundation/e/apps/data/login/api/LoginApiRepository.kt
index 5d97b64870aa92b261af2e29cf8470a2bc47d110..b2fbd026c9919c4b62a0e97c0736fa367b786048 100644
--- a/app/src/main/java/foundation/e/apps/data/login/api/LoginApiRepository.kt
+++ b/app/src/main/java/foundation/e/apps/data/login/api/LoginApiRepository.kt
@@ -19,13 +19,11 @@ package foundation.e.apps.data.login.api
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.PlayResponse
-import foundation.e.apps.data.Constants.timeoutDurationInMillis
import foundation.e.apps.data.ResultSupreme
import foundation.e.apps.data.enums.User
import foundation.e.apps.data.gplay.utils.AC2DMUtil
+import foundation.e.apps.data.handleNetworkResult
import foundation.e.apps.data.login.exceptions.GPlayLoginException
-import kotlinx.coroutines.TimeoutCancellationException
-import kotlinx.coroutines.withTimeout
import java.util.Locale
/**
@@ -52,9 +50,9 @@ class LoginApiRepository constructor(
* else blank for Anonymous login.
*/
suspend fun fetchAuthData(email: String, aasToken: String, locale: Locale): ResultSupreme {
- val result = runCodeWithTimeout({
+ val result = handleNetworkResult {
gPlayLoginInterface.fetchAuthData(email, aasToken)
- })
+ }
return result.apply {
this.data?.locale = locale
this.exception = when (result) {
@@ -76,13 +74,13 @@ class LoginApiRepository constructor(
*/
suspend fun login(authData: AuthData): ResultSupreme {
var response = PlayResponse()
- val result = runCodeWithTimeout({
+ val result = handleNetworkResult {
response = gPlayLoginInterface.login(authData)
if (response.code != 200) {
throw Exception("Validation network code: ${response.code}")
}
response
- })
+ }
return ResultSupreme.replicate(result, response).apply {
this.exception = when (result) {
is ResultSupreme.Timeout -> GPlayLoginException(true, "GPlay API timeout", user)
@@ -109,8 +107,8 @@ class LoginApiRepository constructor(
googleLoginApi: GoogleLoginApi,
email: String,
oauthToken: String
- ): ResultSupreme {
- val result = runCodeWithTimeout({
+ ): ResultSupreme {
+ val result = handleNetworkResult {
var aasToken = ""
val response = googleLoginApi.getAC2DMResponse(email, oauthToken)
var error = response.errorString
@@ -129,7 +127,7 @@ class LoginApiRepository constructor(
throw Exception(error)
}
aasToken
- })
+ }
return result.apply {
this.exception = when (result) {
is ResultSupreme.Timeout -> GPlayLoginException(true, "GPlay API timeout", User.GOOGLE)
@@ -138,26 +136,4 @@ class LoginApiRepository constructor(
}
}
}
-
- /**
- * Utility method to run a specified code block in a fixed amount of time.
- */
- private suspend fun runCodeWithTimeout(
- block: suspend () -> T,
- timeoutBlock: (() -> T?)? = null,
- exceptionBlock: ((e: Exception) -> T?)? = null,
- ): ResultSupreme {
- return try {
- withTimeout(timeoutDurationInMillis) {
- return@withTimeout ResultSupreme.Success(block())
- }
- } catch (e: TimeoutCancellationException) {
- ResultSupreme.Timeout(timeoutBlock?.invoke()).apply {
- message = e.message ?: ""
- }
- } catch (e: Exception) {
- e.printStackTrace()
- ResultSupreme.Error(exceptionBlock?.invoke(e), message = e.message ?: "")
- }
- }
}
diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListViewModel.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListViewModel.kt
index 79c8cc52d37bbb50296571b448cd0093d120a758..86be6867d85fb6e9970d9bbdec2faf078de10a32 100644
--- a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListViewModel.kt
+++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListViewModel.kt
@@ -42,9 +42,11 @@ class ApplicationListViewModel @Inject constructor(
val appListLiveData: MutableLiveData>?> = MutableLiveData()
- var isLoading = false
+ private var isLoading = false
- var nextPageUrl: String? = null
+ private var nextPageUrl: String? = null
+
+ private var currentAuthListObject: List? = null
fun loadData(
category: String,
@@ -54,11 +56,18 @@ class ApplicationListViewModel @Inject constructor(
) {
super.onLoadData(authObjectList, { successAuthList, _ ->
- if (appListLiveData.value?.data?.isNotEmpty() == true) {
+ // if token is refreshed, then reset all data
+ if (currentAuthListObject != null && currentAuthListObject != authObjectList) {
+ appListLiveData.postValue(ResultSupreme.Success(emptyList()))
+ nextPageUrl = null
+ }
+
+ if (appListLiveData.value?.data?.isNotEmpty() == true && currentAuthListObject == authObjectList) {
appListLiveData.postValue(appListLiveData.value)
return@onLoadData
}
+ this.currentAuthListObject = authObjectList
successAuthList.find { it is AuthObject.GPlayAuth }?.run {
getList(category, result.data!! as AuthData, source)
return@onLoadData
@@ -163,18 +172,9 @@ class ApplicationListViewModel @Inject constructor(
private fun appendAppList(it: Pair, String>): List? {
val currentAppList = appListLiveData.value?.data?.toMutableList()
currentAppList?.removeIf { item -> item.isPlaceHolder }
- val appList = currentAppList?.plus(it.first)
- return appList
+ return currentAppList?.plus(it.first)
}
- /**
- * @return returns true if there is changes in data, otherwise false
- */
- fun isAnyAppUpdated(
- newFusedApps: List,
- oldFusedApps: List
- ) = fusedAPIRepository.isAnyFusedAppUpdated(newFusedApps, oldFusedApps)
-
fun hasAnyAppInstallStatusChanged(currentList: List) =
fusedAPIRepository.isAnyAppInstallStatusChanged(currentList)
}
diff --git a/app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt b/app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt
index 6a2e807747c86fcebf1f983a67549c602ace0207..a2bc710807f7e60f1d9d0e8e69d45bbf02b466b8 100644
--- a/app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt
+++ b/app/src/main/java/foundation/e/apps/ui/search/SearchViewModel.kt
@@ -158,7 +158,7 @@ class SearchViewModel @Inject constructor(
nextSubBundle = gplaySearchResult.data?.second
// first page has less data, then fetch next page data without waiting for users' scroll
- if (isFirstFetch) {
+ if (isFirstFetch && gplaySearchResult.isSuccess()) {
CoroutineScope(coroutineContext).launch {
fetchGplayData(query)
}