From 78fec893e1bc10a423302893507a5ce8180fdb2a Mon Sep 17 00:00:00 2001 From: Sayantan Roychowdhury Date: Thu, 31 Aug 2023 16:57:47 +0530 Subject: [PATCH 1/5] new strings --- app/src/main/res/values/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 48ff9b26b..18b0ca91f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -191,6 +191,10 @@ Open Settings More info + + Too many requests + The current Google account is being rate limited for too many network requests.\n\n\"Common apps\" category will be disabled. Kindly login again after some time. + Error occurred while loading apps. This can be because server is not responding or other server error.\n\nPlease retry to try again, or try later. -- GitLab From 4d2faeabe588df62b805224267b39e33ddf367c8 Mon Sep 17 00:00:00 2001 From: Sayantan Roychowdhury Date: Fri, 1 Sep 2023 11:01:58 +0530 Subject: [PATCH 2/5] commit everything --- .../java/foundation/e/apps/MainActivity.kt | 37 +++++++++++++++++++ .../apps/data/gplay/utils/GPlayHttpClient.kt | 10 ++++- .../e/apps/data/login/LoginViewModel.kt | 3 ++ .../preference/PreferenceManagerModule.kt | 2 + .../subFrags/ApplicationDialogFragment.kt | 13 +++++++ .../e/apps/utils/eventBus/AppEvent.kt | 1 + 6 files changed, 64 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 874f049aa..04715a759 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -42,6 +42,7 @@ import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.login.LoginViewModel import foundation.e.apps.data.login.exceptions.GPlayValidationException +import foundation.e.apps.data.preference.PreferenceManagerModule import foundation.e.apps.databinding.ActivityMainBinding import foundation.e.apps.install.updates.UpdatesNotifier import foundation.e.apps.ui.MainActivityViewModel @@ -52,6 +53,7 @@ import foundation.e.apps.ui.setup.signin.SignInViewModel import foundation.e.apps.utils.SystemInfoProvider import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus +import javax.inject.Inject import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -65,6 +67,11 @@ class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.java.simpleName private lateinit var viewModel: MainActivityViewModel + private var isShowingDialog = false + + @Inject + lateinit var preferenceManagerModule: PreferenceManagerModule + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -207,6 +214,10 @@ class MainActivity : AppCompatActivity() { observeInvalidAuth() } + launch { + observeTooManyRequests() + } + launch { observeSignatureMissMatchError() } @@ -284,6 +295,32 @@ class MainActivity : AppCompatActivity() { } } + private suspend fun observeTooManyRequests() { + EventBus.events.filter { appEvent -> + appEvent is AppEvent.TooManyRequests + }.collectLatest { + if (isShowingDialog) return@collectLatest + ApplicationDialogFragment( + title = getString(R.string.too_many_requests), + message = getString(R.string.too_many_requests_desc), + positiveButtonText = getString(R.string.ok), + positiveButtonAction = { + preferenceManagerModule.disableGplay() + loginViewModel.startLoginFlow() + }, + cancelButtonText = getString(R.string.logout), + cancelButtonAction = { + loginViewModel.logout() + }, + cancellable = false, + onDismissListener = { + isShowingDialog = false + }, + ).show(supportFragmentManager, TAG) + isShowingDialog = true + } + } + private fun setupBottomNavItemSelectedListener( bottomNavigationView: BottomNavigationView, navHostFragment: NavHostFragment, 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 9a30a62a9..e0e5de374 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 @@ -195,12 +195,18 @@ class GPlayHttpClient @Inject constructor( code = response.code Timber.d("$TAG: Url: ${response.request.url}\nStatus: $code") - if (code == 401) { - MainScope().launch { + when (code) { + 401 -> MainScope().launch { EventBus.invokeEvent( AppEvent.InvalidAuthEvent(AuthObject.GPlayAuth::class.java.simpleName) ) } + + 429 -> MainScope().launch { + EventBus.invokeEvent( + AppEvent.TooManyRequests() + ) + } } // TODO: exception will be thrown for all apis when all gplay api implementation diff --git a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt index 25c54362e..976f0453e 100644 --- a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt +++ b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt @@ -25,6 +25,7 @@ import foundation.e.apps.data.enums.User import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.launch import javax.inject.Inject +import okhttp3.Cache /** * ViewModel to handle all login related operations. @@ -33,6 +34,7 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( private val loginSourceRepository: LoginSourceRepository, + private val cache: Cache, ) : ViewModel() { /** @@ -127,6 +129,7 @@ class LoginViewModel @Inject constructor( */ fun logout() { viewModelScope.launch { + cache.evictAll() loginSourceRepository.logout() authObjects.postValue(listOf()) } diff --git a/app/src/main/java/foundation/e/apps/data/preference/PreferenceManagerModule.kt b/app/src/main/java/foundation/e/apps/data/preference/PreferenceManagerModule.kt index 428daf195..0dba23101 100644 --- a/app/src/main/java/foundation/e/apps/data/preference/PreferenceManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/data/preference/PreferenceManagerModule.kt @@ -52,6 +52,8 @@ class PreferenceManagerModule @Inject constructor( fun isPWASelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_PWA, true) fun isGplaySelected() = preferenceManager.getBoolean(PREFERENCE_SHOW_GPLAY, true) + fun disableGplay() = preferenceManager.edit().putBoolean(PREFERENCE_SHOW_GPLAY, false).apply() + fun autoUpdatePreferred(): Boolean { return preferenceManager.getBoolean("updateInstallAuto", false) } diff --git a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt index f13657eeb..c5702ec9d 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt @@ -19,6 +19,7 @@ package foundation.e.apps.ui.application.subFrags import android.app.Dialog +import android.content.DialogInterface import android.os.Bundle import android.text.Html import android.text.SpannableString @@ -41,6 +42,8 @@ class ApplicationDialogFragment() : DialogFragment() { private var positiveButtonAction: (() -> Unit)? = null private var cancelButtonText: String? = null private var cancelButtonAction: (() -> Unit)? = null + private var cancellable: Boolean = true + private var onDismissListener: (() -> Unit)? = null constructor( drawable: Int = -1, @@ -50,6 +53,8 @@ class ApplicationDialogFragment() : DialogFragment() { positiveButtonAction: (() -> Unit)? = null, cancelButtonText: String = "", cancelButtonAction: (() -> Unit)? = null, + cancellable: Boolean = true, + onDismissListener: (() -> Unit)? = null, ) : this() { this.drawable = drawable this.title = title @@ -58,6 +63,8 @@ class ApplicationDialogFragment() : DialogFragment() { this.positiveButtonAction = positiveButtonAction this.cancelButtonText = cancelButtonText this.cancelButtonAction = cancelButtonAction + this.cancellable = cancellable + this.onDismissListener = onDismissListener } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { @@ -70,6 +77,7 @@ class ApplicationDialogFragment() : DialogFragment() { positiveButtonAction?.invoke() this.dismiss() } + .setCancelable(cancellable) if (cancelButtonText?.isNotEmpty() == true) { materialAlertDialogBuilder.setNegativeButton(cancelButtonText) { _, _ -> cancelButtonAction?.invoke() @@ -91,6 +99,11 @@ class ApplicationDialogFragment() : DialogFragment() { } } + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDismissListener?.invoke() + } + private fun TextView.removeUnderlineFromLinks() { val spannable = SpannableString(text) for (urlSpan in spannable.getSpans(0, spannable.length, URLSpan::class.java)) { diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index 1074bbdbb..a069b55a6 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -32,4 +32,5 @@ sealed class AppEvent(val data: Any) { class ErrorMessageEvent(stringResourceId: Int) : AppEvent(stringResourceId) class AppPurchaseEvent(fusedDownload: FusedDownload) : AppEvent(fusedDownload) class NoInternetEvent(isInternetAvailable: Boolean) : AppEvent(isInternetAvailable) + class TooManyRequests: AppEvent(Unit) } -- GitLab From c844de84a5e1924c9132693f26e78731d76ea4ec Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Sat, 2 Sep 2023 17:24:54 +0600 Subject: [PATCH 3/5] handled status 429 and login failure --- .../java/foundation/e/apps/MainActivity.kt | 46 ++++++++--------- .../e/apps/data/fused/FusedApiImpl.kt | 3 +- .../apps/data/gplay/utils/GPlayHttpClient.kt | 8 ++- .../e/apps/data/login/LoginViewModel.kt | 3 ++ .../ui/parentFragment/LoadingViewModel.kt | 2 +- .../e/apps/ui/search/SearchViewModel.kt | 4 +- .../e/apps/utils/eventBus/AppEvent.kt | 2 +- app/src/main/res/layout/activity_main.xml | 50 +++++++++++++++++++ app/src/main/res/values/strings.xml | 3 +- 9 files changed, 87 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 04715a759..df476b2e4 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -67,8 +67,6 @@ class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.java.simpleName private lateinit var viewModel: MainActivityViewModel - private var isShowingDialog = false - @Inject lateinit var preferenceManagerModule: PreferenceManagerModule @@ -137,6 +135,12 @@ class MainActivity : AppCompatActivity() { email, SystemInfoProvider.getAppBuildInfo() ) + } else if (exception != null) { + ApplicationDialogFragment( + title = getString(R.string.sign_in_failed_title), + message = getString(R.string.sign_in_failed_desc), + positiveButtonText = getString(R.string.ok) + ).show(supportFragmentManager, TAG) } } } @@ -288,36 +292,26 @@ class MainActivity : AppCompatActivity() { }.distinctUntilChanged { old, new -> ((old.data is String) && (new.data is String) && old.data == new.data) }.collectLatest { - val data = it.data as String - if (data.isNotBlank()) { - loginViewModel.markInvalidAuthObject(data) - } + validatedAuthObject(it) + } + } + + private fun validatedAuthObject(appEvent: AppEvent) { + val data = appEvent.data as String + if (data.isNotBlank()) { + loginViewModel.markInvalidAuthObject(data) } } private suspend fun observeTooManyRequests() { EventBus.events.filter { appEvent -> appEvent is AppEvent.TooManyRequests - }.collectLatest { - if (isShowingDialog) return@collectLatest - ApplicationDialogFragment( - title = getString(R.string.too_many_requests), - message = getString(R.string.too_many_requests_desc), - positiveButtonText = getString(R.string.ok), - positiveButtonAction = { - preferenceManagerModule.disableGplay() - loginViewModel.startLoginFlow() - }, - cancelButtonText = getString(R.string.logout), - cancelButtonAction = { - loginViewModel.logout() - }, - cancellable = false, - onDismissListener = { - isShowingDialog = false - }, - ).show(supportFragmentManager, TAG) - isShowingDialog = true + }.collectLatest { appEvent -> + binding.sessionErrorLayout.visibility = View.VISIBLE + binding.retrySessionButton.setOnClickListener { + binding.sessionErrorLayout.visibility = View.GONE + validatedAuthObject(appEvent = appEvent) + } } } 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 cb4f227a6..cb398a78c 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 @@ -280,7 +280,6 @@ class FusedApiImpl @Inject constructor( packageSpecificResults ) } - return finalSearchResult } @@ -427,7 +426,7 @@ class FusedApiImpl @Inject constructor( ): FusedApp? { try { getApplicationDetails(query, query, authData, Origin.GPLAY).let { - if (it.second == ResultStatus.OK) { + if (it.second == ResultStatus.OK && it.first.package_name.isNotEmpty()) { return it.first } } 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 e0e5de374..ab0b62285 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 @@ -155,9 +155,11 @@ class GPlayHttpClient @Inject constructor( } private fun processRequest(request: Request): PlayResponse { + var response: Response? = null return try { val call = okHttpClient.newCall(request) - buildPlayResponse(call.execute()) + response = call.execute() + buildPlayResponse(response) } 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. @@ -171,6 +173,8 @@ class GPlayHttpClient @Inject constructor( is SocketTimeoutException -> handleExceptionOnGooglePlayRequest(e) else -> handleExceptionOnGooglePlayRequest(e) } + } finally { + response?.close() } } @@ -204,7 +208,7 @@ class GPlayHttpClient @Inject constructor( 429 -> MainScope().launch { EventBus.invokeEvent( - AppEvent.TooManyRequests() + AppEvent.TooManyRequests(AuthObject.GPlayAuth::class.java.simpleName) ) } } diff --git a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt index 976f0453e..7ff728eae 100644 --- a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt +++ b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt @@ -17,10 +17,12 @@ package foundation.e.apps.data.login +import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.enums.User import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.launch @@ -122,6 +124,7 @@ class LoginViewModel @Inject constructor( } authObjects.postValue(authObjectsLocal) + cache.evictAll() } /** diff --git a/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt b/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt index a59e502ca..64e235199 100644 --- a/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt @@ -60,7 +60,7 @@ abstract class LoadingViewModel : ViewModel() { it is GPlayValidationException }?.run { if (!autoRetried && retryBlock(failedAuthList)) { - autoRetried = true + autoRetried = false exceptionsList.clear() return } 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 6ecc4c0a3..6f35e840b 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 @@ -42,6 +42,7 @@ import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.coroutineContext + @HiltViewModel class SearchViewModel @Inject constructor( private val fusedAPIRepository: FusedAPIRepository, @@ -61,6 +62,7 @@ class SearchViewModel @Inject constructor( companion object { private const val DATA_LOAD_ERROR = "Data load error" + private const val MIN_SEARCH_ITEM_PER_PAGE = 8 } fun getSearchSuggestions(query: String, gPlayAuth: AuthObject.GPlayAuth) { @@ -160,7 +162,7 @@ class SearchViewModel @Inject constructor( nextSubBundle = gplaySearchResult.data?.second // if first page has less data, then fetch next page data without waiting for users' scroll - if (isFirstFetch && gplaySearchResult.data?.first?.size!! < 4) { + if (isFirstFetch && (gplaySearchResult.data?.first?.size ?: 0) < MIN_SEARCH_ITEM_PER_PAGE) { CoroutineScope(coroutineContext).launch { fetchGplayData(query) } diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index a069b55a6..d38ef387d 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -32,5 +32,5 @@ sealed class AppEvent(val data: Any) { class ErrorMessageEvent(stringResourceId: Int) : AppEvent(stringResourceId) class AppPurchaseEvent(fusedDownload: FusedDownload) : AppEvent(fusedDownload) class NoInternetEvent(isInternetAvailable: Boolean) : AppEvent(isInternetAvailable) - class TooManyRequests: AppEvent(Unit) + class TooManyRequests(authName: String): AppEvent(authName) } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 99610ac83..38aa95c20 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -23,6 +23,56 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + + + + + + + + + Too many requests - The current Google account is being rate limited for too many network requests.\n\n\"Common apps\" category will be disabled. Kindly login again after some time. + The anonymous account you\'re currently using is unavailable. Refresh the session to get another one. Error occurred while loading apps. @@ -231,4 +231,5 @@ Request Exodus report Clicking on \"%1$s\" will open a tab in your browser with the app\'s package name prefilled.<br /><br />Click on \"Perform analysis\" to start the analysis by Exodus.<br /><br />When the button \"See the report\" is displayed (it can take a while depending on the app) you can close the tab and go back to the app description in %2$s where you should see the Privacy Score. Sometimes Exodus can fail to analyze the app.<br /><br />NB: it can take up to 10 min for the score to be displayed in the app description. + REFRESH SESSION -- GitLab From 55b1a4f8a1f5cba7cb90de78df9cbdae00268813 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Sat, 2 Sep 2023 22:35:37 +0600 Subject: [PATCH 4/5] removed unused resources --- app/src/main/java/foundation/e/apps/MainActivity.kt | 2 ++ app/src/main/res/values/strings.xml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index df476b2e4..d9e15e765 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -58,6 +58,7 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +import timber.log.Timber @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -136,6 +137,7 @@ class MainActivity : AppCompatActivity() { SystemInfoProvider.getAppBuildInfo() ) } else if (exception != null) { + Timber.e(exception, "Login failed! message: ${exception?.localizedMessage}") ApplicationDialogFragment( title = getString(R.string.sign_in_failed_title), message = getString(R.string.sign_in_failed_desc), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 395d518c4..14b52a407 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,7 +192,6 @@ More info - Too many requests The anonymous account you\'re currently using is unavailable. Refresh the session to get another one. -- GitLab From 2dc501ba97649fd812113104404df48c1bc867ce Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Mon, 4 Sep 2023 18:38:24 +0600 Subject: [PATCH 5/5] improved handling status 429 --- .../main/java/foundation/e/apps/MainActivity.kt | 5 +++-- .../e/apps/data/gplay/utils/GPlayHttpClient.kt | 16 ++++++++++++---- .../e/apps/data/login/LoginViewModel.kt | 2 -- .../e/apps/ui/parentFragment/LoadingViewModel.kt | 2 +- .../e/apps/ui/parentFragment/TimeoutFragment.kt | 9 ++++++++- .../e/apps/ui/search/SearchViewModel.kt | 8 +++----- .../foundation/e/apps/utils/eventBus/AppEvent.kt | 2 +- app/src/main/res/values/strings.xml | 4 ++-- 8 files changed, 30 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index d9e15e765..c4d95fcea 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -40,6 +40,7 @@ import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.login.AuthObject +import foundation.e.apps.data.login.LoginSourceGPlay import foundation.e.apps.data.login.LoginViewModel import foundation.e.apps.data.login.exceptions.GPlayValidationException import foundation.e.apps.data.preference.PreferenceManagerModule @@ -308,11 +309,11 @@ class MainActivity : AppCompatActivity() { private suspend fun observeTooManyRequests() { EventBus.events.filter { appEvent -> appEvent is AppEvent.TooManyRequests - }.collectLatest { appEvent -> + }.collectLatest { binding.sessionErrorLayout.visibility = View.VISIBLE binding.retrySessionButton.setOnClickListener { binding.sessionErrorLayout.visibility = View.GONE - validatedAuthObject(appEvent = appEvent) + loginViewModel.startLoginFlow(listOf(LoginSourceGPlay::class.java.simpleName)) } } } 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 ab0b62285..b502cd756 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 @@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit import javax.inject.Inject class GPlayHttpClient @Inject constructor( - cache: Cache, + private val cache: Cache, ) : IHttpClient { private val POST = "POST" @@ -55,6 +55,9 @@ class GPlayHttpClient @Inject constructor( private const val TAG = "GPlayHttpClient" private const val HTTP_TIMEOUT_IN_SECOND = 10L private const val SEARCH = "search" + private const val SEARCH_SUGGEST = "searchSuggest" + private const val STATUS_CODE_UNAUTHORIZED = 401 + private const val STATUS_CODE_TOO_MANY_REQUESTS = 429 } private val okHttpClient = OkHttpClient().newBuilder() @@ -200,15 +203,20 @@ class GPlayHttpClient @Inject constructor( Timber.d("$TAG: Url: ${response.request.url}\nStatus: $code") when (code) { - 401 -> MainScope().launch { + STATUS_CODE_UNAUTHORIZED -> MainScope().launch { EventBus.invokeEvent( AppEvent.InvalidAuthEvent(AuthObject.GPlayAuth::class.java.simpleName) ) } - 429 -> MainScope().launch { + STATUS_CODE_TOO_MANY_REQUESTS -> MainScope().launch { + cache.evictAll() + if (response.request.url.toString().contains(SEARCH_SUGGEST)) { + return@launch + } + EventBus.invokeEvent( - AppEvent.TooManyRequests(AuthObject.GPlayAuth::class.java.simpleName) + AppEvent.TooManyRequests() ) } } diff --git a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt index 7ff728eae..3fbf6c10e 100644 --- a/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt +++ b/app/src/main/java/foundation/e/apps/data/login/LoginViewModel.kt @@ -17,12 +17,10 @@ package foundation.e.apps.data.login -import android.content.Context import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import dagger.hilt.android.qualifiers.ApplicationContext import foundation.e.apps.data.enums.User import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.launch diff --git a/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt b/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt index 64e235199..a59e502ca 100644 --- a/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/parentFragment/LoadingViewModel.kt @@ -60,7 +60,7 @@ abstract class LoadingViewModel : ViewModel() { it is GPlayValidationException }?.run { if (!autoRetried && retryBlock(failedAuthList)) { - autoRetried = false + autoRetried = true exceptionsList.clear() return } 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 5173da6fe..1557c14dd 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 @@ -54,6 +54,10 @@ import timber.log.Timber */ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { + companion object { + private const val STATUS_TOO_MANY_REQUESTS = "Status: 429" + } + val loginViewModel: LoginViewModel by lazy { ViewModelProvider(requireActivity())[LoginViewModel::class.java] } @@ -353,7 +357,6 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { * is shown to the user. */ fun showDataLoadError(exception: Exception) { - val dialogView = DialogErrorLogBinding.inflate(requireActivity().layoutInflater) dialogView.apply { moreInfo.setOnClickListener { @@ -406,6 +409,10 @@ abstract class TimeoutFragment(@LayoutRes layoutId: Int) : Fragment(layoutId) { } val unknownSourceException = exceptions.find { it is UnknownSourceException } + if (gPlayException?.message?.contains(STATUS_TOO_MANY_REQUESTS) == true) { + return + } + /* * Take caution altering the cases. * Cases to be defined from most restrictive to least restrictive. 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 6f35e840b..90e07f32b 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 @@ -52,8 +52,7 @@ class SearchViewModel @Inject constructor( val searchResult: MutableLiveData, Boolean>>> = MutableLiveData() - private var searchResultLiveData: LiveData, Boolean>>> = - MutableLiveData() + private var lastAuthObjects: List? = null private var nextSubBundle: Set? = null @@ -62,7 +61,6 @@ class SearchViewModel @Inject constructor( companion object { private const val DATA_LOAD_ERROR = "Data load error" - private const val MIN_SEARCH_ITEM_PER_PAGE = 8 } fun getSearchSuggestions(query: String, gPlayAuth: AuthObject.GPlayAuth) { @@ -161,8 +159,8 @@ class SearchViewModel @Inject constructor( val isFirstFetch = nextSubBundle == null nextSubBundle = gplaySearchResult.data?.second - // if first page has less data, then fetch next page data without waiting for users' scroll - if (isFirstFetch && (gplaySearchResult.data?.first?.size ?: 0) < MIN_SEARCH_ITEM_PER_PAGE) { + //first page has less data, then fetch next page data without waiting for users' scroll + if (isFirstFetch) { CoroutineScope(coroutineContext).launch { fetchGplayData(query) } diff --git a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt index d38ef387d..f7fbf663a 100644 --- a/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt +++ b/app/src/main/java/foundation/e/apps/utils/eventBus/AppEvent.kt @@ -32,5 +32,5 @@ sealed class AppEvent(val data: Any) { class ErrorMessageEvent(stringResourceId: Int) : AppEvent(stringResourceId) class AppPurchaseEvent(fusedDownload: FusedDownload) : AppEvent(fusedDownload) class NoInternetEvent(isInternetAvailable: Boolean) : AppEvent(isInternetAvailable) - class TooManyRequests(authName: String): AppEvent(authName) + class TooManyRequests : AppEvent(Unit) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 14b52a407..66c4effc7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -192,7 +192,8 @@ More info - The anonymous account you\'re currently using is unavailable. Refresh the session to get another one. + The anonymous account you\'re currently using is unavailable. Please refresh the session to get another one. + REFRESH SESSION Error occurred while loading apps. @@ -230,5 +231,4 @@ Request Exodus report Clicking on \"%1$s\" will open a tab in your browser with the app\'s package name prefilled.<br /><br />Click on \"Perform analysis\" to start the analysis by Exodus.<br /><br />When the button \"See the report\" is displayed (it can take a while depending on the app) you can close the tab and go back to the app description in %2$s where you should see the Privacy Score. Sometimes Exodus can fail to analyze the app.<br /><br />NB: it can take up to 10 min for the score to be displayed in the app description. - REFRESH SESSION -- GitLab