diff --git a/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt index f5c5cdd50a9099331500746b5aa7f51ba3e1757e..c31a64f465832d216ac37ca910479fe5631bb02d 100644 --- a/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt +++ b/app/src/main/java/foundation/e/apps/data/NetworkHandler.kt @@ -75,6 +75,7 @@ private fun extractErrorMessage(e: Exception): String { 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/application/ApplicationRepository.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt index 30f068df2bd0443c994e0d827f1551be055add05..9e32ed8171b02fedb71971da981719f9410a2b80 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt @@ -39,7 +39,7 @@ import foundation.e.apps.data.application.search.GplaySearchResult import foundation.e.apps.data.application.search.SearchApi import foundation.e.apps.data.application.utils.CategoryType import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.ui.search.SearchResult +import foundation.e.apps.data.application.search.SearchResult import javax.inject.Inject import javax.inject.Singleton diff --git a/app/src/main/java/foundation/e/apps/data/application/search/SearchApi.kt b/app/src/main/java/foundation/e/apps/data/application/search/SearchApi.kt index 34898eefdafd415022b3908f65b125427ed713ce..4ea26f61b2c74d0654a83cef62715404f73e3341 100644 --- a/app/src/main/java/foundation/e/apps/data/application/search/SearchApi.kt +++ b/app/src/main/java/foundation/e/apps/data/application/search/SearchApi.kt @@ -23,10 +23,11 @@ import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.SearchBundle import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.data.Application -import foundation.e.apps.ui.search.SearchResult typealias GplaySearchResult = ResultSupreme, Set>> +typealias SearchResult = ResultSupreme, Boolean>> + interface SearchApi { companion object { const val APP_TYPE_ANY = "any" diff --git a/app/src/main/java/foundation/e/apps/data/application/search/SearchApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/search/SearchApiImpl.kt index 0c16bb68f3282b2a06131e2a4dff153459453a6b..b040cd66242813d8b6da002fcce9b1470fa5fab7 100644 --- a/app/src/main/java/foundation/e/apps/data/application/search/SearchApiImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/search/SearchApiImpl.kt @@ -38,8 +38,9 @@ import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.enums.ResultStatus import foundation.e.apps.data.handleNetworkResult import foundation.e.apps.data.login.AuthObject +import foundation.e.apps.data.login.exceptions.CleanApkIOException +import foundation.e.apps.data.login.exceptions.GPlayIOException import foundation.e.apps.data.preference.AppLoungePreference -import foundation.e.apps.ui.search.SearchResult import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import kotlinx.coroutines.Deferred @@ -94,7 +95,10 @@ class SearchApiImpl @Inject constructor( query: String, authData: AuthData ): SearchResult { - var finalSearchResult: SearchResult = ResultSupreme.Error() + var finalSearchResult: SearchResult = ResultSupreme.Error( + message = "", + exception = CleanApkIOException("Unable to reach CleanAPK API") + ) val packageSpecificResults = fetchPackageSpecificResult(authData, query).data?.first ?: emptyList() @@ -158,7 +162,8 @@ class SearchApiImpl @Inject constructor( query ), appLoungePreference.isGplaySelected() - ) + ), + exception = result.exception ) } @@ -187,7 +192,8 @@ class SearchApiImpl @Inject constructor( query ), appLoungePreference.isGplaySelected() || appLoungePreference.isPWASelected() - ) + ), + exception = result.exception ) } @@ -333,7 +339,7 @@ class SearchApiImpl @Inject constructor( query: String, nextPageSubBundle: Set? ): GplaySearchResult { - return handleNetworkResult { + val result = handleNetworkResult { coroutineScope { launch(Dispatchers.IO) { doDummySearch() } } val searchResults = @@ -354,6 +360,11 @@ class SearchApiImpl @Inject constructor( return@handleNetworkResult Pair(fusedAppList.toList(), searchResults.second.toSet()) } + + return if (result.isSuccess()) result else ResultSupreme.Error( + message = "", + exception = GPlayIOException("Unable to reach Google Play API") + ) } // Initiate a dummy search to ensure Google Play returns enough results for the search query @@ -369,7 +380,8 @@ class SearchApiImpl @Inject constructor( val dummySearchPackageNames = DUMMY_SEARCH_EXPECTED_APPS.map { it.second } val searchedAppsPackageNames = searchedApps.map { it.packageName } - val isSearchContainingResults = searchedAppsPackageNames.containsAll(dummySearchPackageNames) + val isSearchContainingResults = + searchedAppsPackageNames.containsAll(dummySearchPackageNames) if (!isSearchContainingResults) { Timber.d("Search didn't return enough results, refreshing token...") @@ -383,19 +395,23 @@ class SearchApiImpl @Inject constructor( * else will show the GPlay app itself. */ private suspend fun replaceWithFDroid(gPlayApps: List): List { + try { + if (gPlayApps.isEmpty()) return emptyList() - if (gPlayApps.isEmpty()) return emptyList() + val packageNames = gPlayApps.map { it.packageName } + val response = appSources.cleanApkAppsRepo.checkAvailablePackages(packageNames) - val packageNames = gPlayApps.map { it.packageName } - val response = appSources.cleanApkAppsRepo.checkAvailablePackages(packageNames) + val availableApps = response.body()?.apps ?: emptyList() - val availableApps = response.body()?.apps ?: emptyList() - - return gPlayApps.map { gPlayApp -> - availableApps.find { it.package_name == gPlayApp.packageName }?.apply { - isGplayReplaced = true - updateSource(context) - } ?: gPlayApp.toApplication(context) + return gPlayApps.map { gPlayApp -> + availableApps.find { it.package_name == gPlayApp.packageName }?.apply { + isGplayReplaced = true + updateSource(context) + } ?: gPlayApp.toApplication(context) + } + } catch (e: Exception) { + Timber.w(e, "Failed to replace Google apps with their F-Droid counterparts.") + return gPlayApps.map { it.toApplication(context) } } } diff --git a/app/src/main/java/foundation/e/apps/data/login/exceptions/CleanApkException.kt b/app/src/main/java/foundation/e/apps/data/login/exceptions/CleanApkException.kt index 293819914aac64751e60c5da12cf5b94caa2a5c2..bc48392273fabb7817eb1580d9c137c2e548be38 100644 --- a/app/src/main/java/foundation/e/apps/data/login/exceptions/CleanApkException.kt +++ b/app/src/main/java/foundation/e/apps/data/login/exceptions/CleanApkException.kt @@ -20,7 +20,9 @@ package foundation.e.apps.data.login.exceptions /** * This exception is for all CleanApk data loading exceptions. */ -class CleanApkException( +open class CleanApkException( val isTimeout: Boolean, message: String? = null, ) : LoginException(message) + +class CleanApkIOException(message: String) : CleanApkException(false, message) diff --git a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayException.kt b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayException.kt index 345208778528b1d12c330fe7477111c98c7e7501..2c39dc76e151acac151de1a0838534adb14224c6 100644 --- a/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayException.kt +++ b/app/src/main/java/foundation/e/apps/data/login/exceptions/GPlayException.kt @@ -24,3 +24,5 @@ open class GPlayException( val isTimeout: Boolean, message: String? = null, ) : LoginException(message) + +open class GPlayIOException(message: String): GPlayException(false, message) diff --git a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt index c42a9f0fbd75f3e7905f7fb0a5a0d42e4b2871c6..b01b87c553a63560ffb5845dabb98fd2b753a1a6 100644 --- a/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt +++ b/app/src/main/java/foundation/e/apps/di/network/InterceptorModule.kt @@ -25,13 +25,7 @@ import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import foundation.e.apps.BuildConfig import okhttp3.Interceptor -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.logging.HttpLoggingInterceptor -import timber.log.Timber -import java.io.IOException import java.util.Locale import javax.inject.Singleton @@ -42,8 +36,6 @@ class InterceptorModule { companion object { private const val HEADER_USER_AGENT = "User-Agent" private const val HEADER_ACCEPT_LANGUAGE = "Accept-Language" - const val ERROR_RESPONSE_CODE = 999 // Arbitrary value, not to mix with HTTP status code - const val ERROR_RESPONSE_MESSAGE = "IOException occurred." val HEADER_USER_AGENT_VALUE = "Dalvik/2.1.0 (Linux; U; Android ${Build.VERSION.RELEASE};)" } @@ -52,7 +44,7 @@ class InterceptorModule { @Provides fun provideInterceptor(): Interceptor { return Interceptor { chain -> - val builder = + val request = chain .request() .newBuilder() @@ -61,26 +53,12 @@ class InterceptorModule { HEADER_USER_AGENT_VALUE ) .header(HEADER_ACCEPT_LANGUAGE, Locale.getDefault().language) + .build() - val response = try { - chain.proceed(builder.build()) - } catch (ioException: IOException) { - Timber.e(ioException) - return@Interceptor createResponseForIOException(chain.request()) - } - - return@Interceptor response + return@Interceptor chain.proceed(request) } } - private fun createResponseForIOException(request: Request) = Response.Builder() - .request(request) - .protocol(Protocol.HTTP_1_1) - .code(ERROR_RESPONSE_CODE) - .message(ERROR_RESPONSE_MESSAGE) - .body(ERROR_RESPONSE_MESSAGE.toResponseBody()) - .build() - @Provides @Singleton fun provideLoggingInterceptor(): HttpLoggingInterceptor { diff --git a/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt b/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt index 6d3d162b8f2e58896832a8560047033049972e28..b720fca17c8748a2589a1e8ff905fd66cec3e7cb 100644 --- a/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/parentFragment/TimeoutFragment.kt @@ -22,6 +22,7 @@ import android.graphics.Paint import android.net.Uri import android.widget.TextView import androidx.annotation.LayoutRes +import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -34,13 +35,15 @@ import foundation.e.apps.R import foundation.e.apps.data.enums.User import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.login.PlayStoreAuthenticator -import foundation.e.apps.ui.LoginViewModel import foundation.e.apps.data.login.exceptions.CleanApkException +import foundation.e.apps.data.login.exceptions.CleanApkIOException import foundation.e.apps.data.login.exceptions.GPlayException +import foundation.e.apps.data.login.exceptions.GPlayIOException import foundation.e.apps.data.login.exceptions.GPlayLoginException import foundation.e.apps.data.login.exceptions.GPlayValidationException import foundation.e.apps.data.login.exceptions.UnknownSourceException import foundation.e.apps.databinding.DialogErrorLogBinding +import foundation.e.apps.ui.LoginViewModel import foundation.e.apps.ui.MainActivityViewModel import timber.log.Timber @@ -356,7 +359,11 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { * instance if it deems fit. Else it may return null, at which case no error dialog * is shown to the user. */ - fun showDataLoadError(exception: Exception) { + private fun showDataLoadError( + exception: Exception, + @StringRes dialogTitle: Int = R.string.data_load_error, + @StringRes dialogMessage: Int = R.string.data_load_error_desc + ) { val dialogView = DialogErrorLogBinding.inflate(requireActivity().layoutInflater) dialogView.apply { moreInfo.setOnClickListener { @@ -376,8 +383,8 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { } val predefinedDialog = AlertDialog.Builder(requireActivity()).apply { - setTitle(R.string.data_load_error) - setMessage(R.string.data_load_error_desc) + setTitle(dialogTitle) + setMessage(dialogMessage) setView(dialogView.root) setPositiveButton(R.string.retry) { _, _ -> showLoadingUI() @@ -407,6 +414,7 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { val gPlayException = exceptions.find { it is GPlayException }?.run { this as GPlayException } + val unknownSourceException = exceptions.find { it is UnknownSourceException } if (gPlayException?.message?.contains(STATUS_TOO_MANY_REQUESTS) == true) { @@ -418,6 +426,19 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { * Cases to be defined from most restrictive to least restrictive. */ when { + // Handle IOException + cleanApkException is CleanApkIOException -> showDataLoadError( + exception = cleanApkException, + dialogTitle = R.string.open_source_apps_unavailable, + dialogMessage = R.string.open_source_apps_data_load_error_description, + ) + + gPlayException is GPlayIOException -> showDataLoadError( + exception = gPlayException, + dialogTitle = R.string.common_apps_unavailable, + dialogMessage = R.string.common_apps_data_load_error_description + ) + // Handle timeouts cleanApkException?.isTimeout == true -> showTimeout(cleanApkException) gPlayException?.isTimeout == true -> showTimeout(gPlayException) 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 9d89d8f31ae6f51d828cf8944ea1dbac5b991ac6..f4562656a69bc54945e4279db4e9b33673019f5e 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 @@ -28,28 +28,24 @@ import com.aurora.gplayapi.data.models.SearchBundle import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.data.ResultSupreme import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.application.search.GplaySearchResult import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.application.search.GplaySearchResult +import foundation.e.apps.data.application.search.SearchResult import foundation.e.apps.data.enums.Origin import foundation.e.apps.data.exodus.repositories.IAppPrivacyInfoRepository import foundation.e.apps.data.exodus.repositories.PrivacyScoreRepository import foundation.e.apps.data.login.AuthObject -import foundation.e.apps.data.login.exceptions.CleanApkException -import foundation.e.apps.data.login.exceptions.GPlayException -import foundation.e.apps.data.login.exceptions.UnknownSourceException import foundation.e.apps.di.CommonUtilsModule.LIST_OF_NULL import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext - -typealias SearchResult = ResultSupreme, Boolean>> +import timber.log.Timber +import javax.inject.Inject @HiltViewModel class SearchViewModel @Inject constructor( @@ -64,7 +60,7 @@ class SearchViewModel @Inject constructor( MutableLiveData() val searchResult: LiveData = _searchResult - val gplaySearchLoaded : MutableLiveData = MutableLiveData(false) + val gplaySearchLoaded: MutableLiveData = MutableLiveData(false) private var lastAuthObjects: List? = null @@ -119,16 +115,15 @@ class SearchViewModel @Inject constructor( this.lastAuthObjects = authObjectList super.onLoadData(authObjectList, { successAuthList, _ -> + successAuthList.find { it is AuthObject.CleanApk }?.run { + fetchCleanApkData(query, null) + } successAuthList.find { it is AuthObject.GPlayAuth }?.run { - getSearchResults(query, result.data!! as AuthData) - return@onLoadData + nextSubBundle = null + fetchGplayData(query) } - successAuthList.find { it is AuthObject.CleanApk }?.run { - getSearchResults(query, null) - return@onLoadData - } }, retryBlock) } @@ -138,7 +133,7 @@ class SearchViewModel @Inject constructor( * without having to wait for all of the apps. * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5171 */ - private fun getSearchResults( + private fun fetchCleanApkData( query: String, authData: AuthData? ) { @@ -152,28 +147,8 @@ class SearchViewModel @Inject constructor( emitFilteredResults(searchResultSupreme) if (!searchResultSupreme.isSuccess()) { - val exception = - if (authData != null) { - GPlayException( - searchResultSupreme.isTimeout(), - searchResultSupreme.message.ifBlank { DATA_LOAD_ERROR } - ) - } else { - CleanApkException( - searchResultSupreme.isTimeout(), - searchResultSupreme.message.ifBlank { DATA_LOAD_ERROR } - ) - } - - handleException(exception) - } - - if (authData == null) { - return@launch + searchResultSupreme.exception?.let { handleException(it) } } - - nextSubBundle = null - fetchGplayData(query) } } @@ -191,25 +166,30 @@ class SearchViewModel @Inject constructor( } } - private suspend fun fetchGplayData(query: String) { - isLoading = true - val gplaySearchResult = applicationRepository.getGplaySearchResults(query, nextSubBundle) + private fun fetchGplayData(query: String) { + viewModelScope.launch(Dispatchers.IO) { + isLoading = true + val gplaySearchResult = + applicationRepository.getGplaySearchResults(query, nextSubBundle) - if (!gplaySearchResult.isSuccess()) { - handleException(gplaySearchResult.exception ?: UnknownSourceException()) - } + if (!gplaySearchResult.isSuccess()) { + gplaySearchResult.exception?.let { + handleException(it) + } + } - nextSubBundle = gplaySearchResult.data?.second + nextSubBundle = gplaySearchResult.data?.second - val currentAppList = updateCurrentAppList(gplaySearchResult) - val finalResult = ResultSupreme.Success( - Pair(currentAppList.toList(), nextSubBundle?.isNotEmpty() ?: false) - ) + val currentAppList = updateCurrentAppList(gplaySearchResult) + val finalResult = ResultSupreme.Success( + Pair(currentAppList.toList(), nextSubBundle?.isNotEmpty() ?: false) + ) - hasGPlayBeenFetched = true - emitFilteredResults(finalResult) + hasGPlayBeenFetched = true + emitFilteredResults(finalResult) - isLoading = false + isLoading = false + } } private fun updateCurrentAppList(gplaySearchResult: GplaySearchResult): List { diff --git a/app/src/main/java/foundation/e/apps/utils/NetworkStatusManager.kt b/app/src/main/java/foundation/e/apps/utils/NetworkStatusManager.kt index 0473a1bcec98dbbaba0b8b5f11912235bd83a5ae..3b769604a8d6c178b7d34d66c2990a5e35823ce9 100644 --- a/app/src/main/java/foundation/e/apps/utils/NetworkStatusManager.kt +++ b/app/src/main/java/foundation/e/apps/utils/NetworkStatusManager.kt @@ -68,8 +68,11 @@ object NetworkStatusManager { ) { super.onCapabilitiesChanged(network, networkCapabilities) val hasInternet = - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_INTERNET + ) && networkCapabilities.hasCapability( + NetworkCapabilities.NET_CAPABILITY_VALIDATED + ) Timber.d("Network: onCapabilitiesChanged: ${network.networkHandle}, hasInternet: $hasInternet") sendInternetStatus(hasInternet) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 261a9270e025ca93826ed2ec60df0c9703f5ed35..399766b085eb054f25369a92188179da417d9bce 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -205,4 +205,10 @@ System-App Achtung – Aktualisierung! App Lounge wird vom System beendet, um eine Aktualisierung durchzuführen. Bitte führen Sie keine anderen Aktionen aus, bis App Lounge aktualisert und beendet ist. - \ No newline at end of file + + + Gemeinsame Apps nicht verfügbar + Open-Source-Apps und PWA nicht verfügbar + Beim Laden der gemeinsamen Apps ist ein Fehler aufgetreten. Nur Open-Source-Apps und PWA sind momentan verfügbar. + Beim Laden von PWA und Open-Source-Apps ist ein Fehler aufgetreten. Nur gemeinsame Apps sind momentan verfügbar. + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ac9f8f58659b636eafdd00914d8ae24164f5a6a9..6d29329f5f66403f546d14b0018ef46facd2d382 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -203,4 +203,10 @@ Recopilando la clasificación de contenido de todas las aplicaciones que has instalado. Advertencia del contenido La aplicación puede contener desnudos, blasfemias, insultos, violencia, sexualidad intensa, incorrección política u otros temas potencialmente perturbadores. Esto es especialmente importante en entornos como lugares de trabajo, escuelas, entornos religiosos y familiares. - \ No newline at end of file + + + Aplicaciones comunes no disponibles + Aplicaciones de código abierto y PWA no disponibles + Se produjo un error al cargar las aplicaciones comunes. Solo las aplicaciones de código abierto y PWA están disponibles por ahora. + Se produjo un error al cargar las aplicaciones PWA y de código abierto. Solo las aplicaciones comunes están disponibles por ahora. + diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4540074cb786664817228f9b38b9c73a4f52652c..f818464111489a1e983f04d87aa8d5756a77074e 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -203,4 +203,10 @@ Cliquer sur \"%1$s\" ouvrira un onglet dans votre navigateur avec le nom du paquet de l\'application pré-rempli.<br /><br /> Cliquez sur \"Perform analysis\" pour lancer l\'analyse par Exodus.<br /><br /> Quand le bouton \"See the report\" apparaît (cela peut prendre un moment selon l\'application) vous pouvez fermer l\'onglet et retourner sur la description de l\'application dans %2$s où vous devriez voir la note de Confidentialité. Parfois Exodus peut échouer à analyser l\'application.<br /><br />NB : cela peut prendre jusqu\'à 10 min pour que le score apparaisse dans la description de l\'application. RENOUVELER LA SESSION Application système - \ No newline at end of file + + + Applications communes indisponibles + Applications Open Source et PWA indisponibles + Une erreur est survenue lors du chargement des applications communes. Seules les applications Open Source et PWA sont disponibles pour l’instant. + Une erreur est survenue lors du chargement des applications PWA et Open Source. Seules les applications communes sont disponibles pour l\'instant. + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index d5505806e1d19b2075318f6b11152fac47740507..34663ceda0918a1ccf432a9a8821d97bc7e76cef 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -203,4 +203,10 @@ Avviso sui contenuti App Lounge verrà chiusa dal sistema mentre installa l\'aggiornamento. Si prega di non effettuare alcuna attività su App Lounge finchè non sarà aggiornata e chiusa. Avviso sull\'aggiornamento! - \ No newline at end of file + + + App comuni non disponibili + App Open Source e PWA non disponibili + Si è verificato un errore durante il caricamento delle app comuni. Solo le app Open Source e PWA sono disponibili per ora. + Si è verificato un errore durante il caricamento delle app PWA e Open Source. Solo le app comuni sono disponibili per ora. + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa41bcdf6112f70981e43b1cec523b8900253c62..2ab9f4a3a597433eb8c6321890ccff2e0b4a73c1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -236,4 +236,10 @@ The app may contain nudity, profanity, slurs, violence, intense sexuality, political incorrectness, or other potentially disturbing subject matter. This is especially relevant in environments like workplaces, schools, religious and family settings. Gathering content rating for all the apps you\'ve installed. + + Common apps unavailable + Open Source apps and PWA unavailable + An error occurred while loading Common apps. Only Open Source apps and PWA are available for now. + An error occurred while loading PWA and Open Source apps. Only Common apps are available for now. + diff --git a/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt b/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt index cf7afdbe8d7aa14ddfb3454aae0ae3b9d7a6d2ac..929874288c5477fdb9bf7f11dace5c242e7ca23e 100644 --- a/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt +++ b/app/src/test/java/foundation/e/apps/di/network/InterceptorModuleTest.kt @@ -19,8 +19,6 @@ package foundation.e.apps.di.network import junit.framework.TestCase.assertEquals -import junit.framework.TestCase.assertNotNull -import junit.framework.TestCase.assertTrue import okhttp3.Interceptor import okhttp3.Protocol import okhttp3.Request @@ -33,7 +31,6 @@ import org.mockito.Mockito.verify import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.whenever -import java.io.IOException import java.util.Locale class InterceptorModuleTest { @@ -87,26 +84,4 @@ class InterceptorModuleTest { assertEquals(Locale.getDefault().language, capturedRequest.header("Accept-Language")) assertEquals(response, interceptedResponse) } - - @Test - fun `provideInterceptor should return custom response on IOException`() { - // Mock IOException when proceeding with chain - whenever(chain.proceed(any())).thenThrow(IOException()) - - val interceptor = module.provideInterceptor() - - // Intercept the request, should catch IOException and return custom response - val interceptedResponse = interceptor.intercept(chain) - - assertNotNull(interceptedResponse) - assertEquals(InterceptorModule.ERROR_RESPONSE_CODE, interceptedResponse.code) - assertEquals( - InterceptorModule.ERROR_RESPONSE_MESSAGE, - interceptedResponse.message - ) - assertTrue( - interceptedResponse.body?.string() - ?.contains(InterceptorModule.ERROR_RESPONSE_MESSAGE) == true - ) - } }