diff --git a/app/build.gradle b/app/build.gradle index 836e600a5e7a14b140daa37a34eb41b0d195bf53..fc8c76283ea4b41c8fad34d51f51ed35c2e4e123 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -254,4 +254,8 @@ dependencies { // elib implementation 'foundation.e:elib:0.0.1-alpha11' + + // androidx.activity + def activity_version = "1.6.1" + implementation("androidx.activity:activity-ktx:$activity_version") } diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 2068eb0f931916634db37034e8a2c47305a8ff2c..4cf9c3c7f5618d9b4772d20f7c4060b4fd19df08 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -1,4 +1,22 @@ + + @@ -19,7 +37,7 @@ LongParameterList:CleanApkRetrofit.kt$CleanApkRetrofit$( @Query("keyword") keyword: String, @Query("source") source: String = APP_SOURCE_FOSS, @Query("type") type: String = APP_TYPE_ANY, @Query("nres") nres: Int = 20, @Query("page") page: Int = 1, @Query("by") by: String? = null, ) LongParameterList:EglExtensionProvider.kt$EglExtensionProvider$( egl10: EGL10, eglDisplay: EGLDisplay, eglConfig: EGLConfig?, ai: IntArray, ai1: IntArray?, set: MutableSet<String> ) LongParameterList:FusedManagerImpl.kt$FusedManagerImpl$( @Named("cacheDir") private val cacheDir: String, private val downloadManager: DownloadManager, private val notificationManager: NotificationManager, private val fusedDownloadRepository: FusedDownloadRepository, private val pwaManager: PWAManager, private val appLoungePackageManager: AppLoungePackageManager, @Named("download") private val downloadNotificationChannel: NotificationChannel, @Named("update") private val updateNotificationChannel: NotificationChannel, @ApplicationContext private val context: Context ) - LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val fusedManagerRepository: FusedManagerRepository, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PWAManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val appInstallProcessor: AppInstallProcessor ) + LongParameterList:MainActivityViewModel.kt$MainActivityViewModel$( private val appLoungeDataStore: AppLoungeDataStore, private val applicationRepository: ApplicationRepository, private val fusedManagerRepository: FusedManagerRepository, private val appLoungePackageManager: AppLoungePackageManager, private val pwaManager: PWAManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, private val appInstallProcessor: AppInstallProcessor, private val sessionPreference: SessionPreference ) LongParameterList:UpdatesManagerImpl.kt$UpdatesManagerImpl$( @ApplicationContext private val context: Context, private val appLoungePackageManager: AppLoungePackageManager, private val applicationRepository: ApplicationRepository, private val faultyAppRepository: FaultyAppRepository, private val appLoungePreference: AppLoungePreference, private val fdroidRepository: FdroidRepository, private val blockedAppRepository: BlockedAppRepository, ) LongParameterList:UpdatesWorker.kt$UpdatesWorker$( @Assisted private val context: Context, @Assisted private val params: WorkerParameters, private val updatesManagerRepository: UpdatesManagerRepository, private val dataStoreManager: DataStoreManager, private val authenticatorRepository: AuthenticatorRepository, private val appInstallProcessor: AppInstallProcessor, private val blockedAppRepository: BlockedAppRepository, ) MagicNumber:AnonymousLoginManager.kt$AnonymousLoginManager$200 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6eb46c7219e9d7c2c821cb46ed7869330492165e..30d15f1110e602cb945e8b635e46d7a556aef1b0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,4 +1,22 @@ + + @@ -42,7 +60,9 @@ android:restoreNeedsApplication="true" android:supportsRtl="true" android:theme="@style/Theme.Apps" - android:usesCleartextTraffic="true"> + android:enableOnBackInvokedCallback="true" + android:usesCleartextTraffic="true" + tools:targetApi="tiramisu"> . + * */ package foundation.e.apps @@ -29,8 +29,8 @@ import dagger.hilt.android.HiltAndroidApp import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.AppLoungePreference -import foundation.e.apps.install.pkg.PkgManagerBR import foundation.e.apps.install.pkg.AppLoungePackageManager +import foundation.e.apps.install.pkg.PkgManagerBR import foundation.e.apps.install.updates.UpdatesWorkManager import foundation.e.apps.install.workmanager.InstallWorkManager import foundation.e.apps.ui.setup.tos.TOS_VERSION @@ -38,13 +38,13 @@ import foundation.e.apps.utils.CustomUncaughtExceptionHandler import foundation.e.lib.telemetry.Telemetry import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import timber.log.Timber import timber.log.Timber.Forest.plant import java.util.concurrent.Executors import javax.inject.Inject -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.runBlocking @HiltAndroidApp @DelicateCoroutinesApi diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index 51757f7ba202d5c74724b01ffa0fe04428e944b9..9ec898a0b769c65d60f5aa2ff86cd878f6c2d2b5 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -1,6 +1,5 @@ /* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021-2024 MURENA SAS * * 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 @@ -14,13 +13,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ package foundation.e.apps -import android.os.Build +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES import android.os.Bundle import android.view.View +import android.window.OnBackInvokedDispatcher.PRIORITY_DEFAULT import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider @@ -43,7 +45,6 @@ import foundation.e.apps.data.login.AuthObject import foundation.e.apps.data.login.LoginViewModel import foundation.e.apps.data.login.PlayStoreAuthenticator import foundation.e.apps.data.login.exceptions.GPlayValidationException -import foundation.e.apps.data.preference.AppLoungePreference import foundation.e.apps.databinding.ActivityMainBinding import foundation.e.apps.install.updates.UpdatesNotifier import foundation.e.apps.ui.MainActivityViewModel @@ -59,23 +60,25 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch import timber.log.Timber -import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { private lateinit var signInViewModel: SignInViewModel private lateinit var loginViewModel: LoginViewModel private lateinit var binding: ActivityMainBinding - private val TAG = MainActivity::class.java.simpleName private lateinit var viewModel: MainActivityViewModel - @Inject - lateinit var appLoungePreference: AppLoungePreference + companion object { + private val TAG = MainActivity::class.java.simpleName + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) + + setupBackPressHandlingForTiramisuAndAbove() + setContentView(binding.root) val (bottomNavigationView, navController) = setupBootomNav() @@ -88,7 +91,7 @@ class MainActivity : AppCompatActivity() { bottomNavigationView.selectedItemId = R.id.updatesFragment } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + if (VERSION.SDK_INT >= VERSION_CODES.O) { viewModel.createNotificationChannels() } @@ -120,6 +123,54 @@ class MainActivity : AppCompatActivity() { observeEvents() } + override fun onStart() { + super.onStart() + checkSessionRefresh() + } + + private fun checkSessionRefresh() { + if (viewModel.shouldRefreshSession()) { + refreshSession() + } + } + + private fun refreshSession() { + loginViewModel.startLoginFlow(listOf(PlayStoreAuthenticator::class.java.simpleName)) + } + + // In Android 12 (API level 32) and lower, onBackPressed is always called, + // regardless of any registered instances of OnBackPressedCallback. + // https://developer.android.com/guide/navigation/navigation-custom-back#onbackpressed + @Deprecated("Deprecated in Java") + override fun onBackPressed() { + if (isInitialScreen()) { + resetIgnoreStatusForSessionRefresh() + } + super.onBackPressed() + } + + private fun isInitialScreen(): Boolean { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment + + val navController = navHostFragment.navController + + return navController.currentDestination?.id == navController.graph.startDestinationId + } + + private fun resetIgnoreStatusForSessionRefresh() { + viewModel.updateIgnoreRefreshPreference(ignore = false) + } + + @Suppress("DEPRECATION") + private fun setupBackPressHandlingForTiramisuAndAbove() { + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { + onBackInvokedDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT) { + resetIgnoreStatusForSessionRefresh() + super.onBackPressed() // Deprecated for Android 13+ + } + } + } private fun setupNavigations(navController: NavController) { val navOptions = NavOptions.Builder() @@ -355,14 +406,25 @@ class MainActivity : AppCompatActivity() { EventBus.events.filter { appEvent -> appEvent is AppEvent.TooManyRequests }.collectLatest { - binding.sessionErrorLayout.visibility = View.VISIBLE - binding.retrySessionButton.setOnClickListener { - binding.sessionErrorLayout.visibility = View.GONE - loginViewModel.startLoginFlow(listOf(PlayStoreAuthenticator::class.java.simpleName)) + val shouldShowDialog = viewModel.shouldRefreshSession() + if (shouldShowDialog) { + binding.sessionErrorLayout.visibility = View.VISIBLE + binding.retrySessionButton.setOnClickListener { onRefreshSessionClick() } + binding.ignoreSessionButton.setOnClickListener { onIgnoreSessionClick() } } } } + private fun onIgnoreSessionClick() { + viewModel.updateIgnoreRefreshPreference(true) + binding.sessionErrorLayout.visibility = View.GONE + } + + private fun onRefreshSessionClick() { + binding.sessionErrorLayout.visibility = View.GONE + refreshSession() + } + private fun setupBottomNavItemSelectedListener( bottomNavigationView: BottomNavigationView, navHostFragment: NavHostFragment, diff --git a/app/src/main/java/foundation/e/apps/data/Constants.kt b/app/src/main/java/foundation/e/apps/data/Constants.kt index 20e9c0bc81421192e74dc7d8959f6c7efc554739..78ac855a4eb7f72b6b38536c644da2bcf11e7d89 100644 --- a/app/src/main/java/foundation/e/apps/data/Constants.kt +++ b/app/src/main/java/foundation/e/apps/data/Constants.kt @@ -1,3 +1,21 @@ +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * 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 object Constants { @@ -9,4 +27,6 @@ object Constants { const val ACTION_AUTHDATA_DUMP = "foundation.e.apps.action.DUMP_GACCOUNT_INFO" const val TAG_AUTHDATA_DUMP = "AUTHDATA_DUMP" + + const val PREFERENCE_IGNORE_SESSION_REFRESH = "ignoreSessionRefresh" } diff --git a/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt b/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt index 415c52cd0ff03c496dc5449daf7f94bfb66d421c..aca4e21888e16459e4153c0404cb68611271326b 100644 --- a/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt +++ b/app/src/main/java/foundation/e/apps/data/preference/AppLoungePreference.kt @@ -1,6 +1,5 @@ /* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021-2024 MURENA SAS * * 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 @@ -14,6 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ package foundation.e.apps.data.preference diff --git a/app/src/main/java/foundation/e/apps/data/preference/SessionPreference.kt b/app/src/main/java/foundation/e/apps/data/preference/SessionPreference.kt new file mode 100644 index 0000000000000000000000000000000000000000..c5a50d3d2ac5f71480250e5fc4ef1667760c1c84 --- /dev/null +++ b/app/src/main/java/foundation/e/apps/data/preference/SessionPreference.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * 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.preference + +import android.content.Context +import androidx.preference.PreferenceManager +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.Constants.PREFERENCE_IGNORE_SESSION_REFRESH +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SessionPreference @Inject constructor( + @ApplicationContext private val context: Context +) { + private val preferenceManager = PreferenceManager.getDefaultSharedPreferences(context) + + fun shouldIgnoreSessionRefresh(): Boolean { + return preferenceManager.getBoolean(PREFERENCE_IGNORE_SESSION_REFRESH, false) + } + + fun updateIgnoreSessionRefreshPreference(ignore: Boolean) { + preferenceManager.edit().putBoolean(PREFERENCE_IGNORE_SESSION_REFRESH, ignore).apply() + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index 57bd34bfe9fcf85113868a35d26f9bd35b5c21aa..3401e3c12504fa54786debb0c36a6a82c58ca4cd 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -1,6 +1,5 @@ /* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2021-2024 MURENA SAS * * 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 @@ -14,6 +13,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * */ package foundation.e.apps.ui @@ -32,20 +32,21 @@ import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.R +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.blockedApps.BlockedAppRepository import foundation.e.apps.data.ecloud.EcloudRepository import foundation.e.apps.data.enums.User import foundation.e.apps.data.enums.isInitialized import foundation.e.apps.data.enums.isUnFiltered -import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.application.data.Application import foundation.e.apps.data.fusedDownload.FusedManagerRepository import foundation.e.apps.data.fusedDownload.models.FusedDownload import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.install.pkg.PWAManager +import foundation.e.apps.data.preference.SessionPreference +import foundation.e.apps.data.preference.getSync import foundation.e.apps.install.pkg.AppLoungePackageManager +import foundation.e.apps.install.pkg.PWAManager import foundation.e.apps.install.workmanager.AppInstallProcessor -import foundation.e.apps.data.preference.getSync import foundation.e.apps.utils.NetworkStatusManager import kotlinx.coroutines.launch import javax.inject.Inject @@ -59,7 +60,8 @@ class MainActivityViewModel @Inject constructor( private val pwaManager: PWAManager, private val ecloudRepository: EcloudRepository, private val blockedAppRepository: BlockedAppRepository, - private val appInstallProcessor: AppInstallProcessor + private val appInstallProcessor: AppInstallProcessor, + private val sessionPreference: SessionPreference ) : ViewModel() { val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() @@ -238,4 +240,12 @@ class MainActivityViewModel @Inject constructor( fun launchPwa(application: Application) { pwaManager.launchPwa(application) } + + fun updateIgnoreRefreshPreference(ignore: Boolean) { + sessionPreference.updateIgnoreSessionRefreshPreference(ignore) + } + + fun shouldRefreshSession(): Boolean { + return !sessionPreference.shouldIgnoreSessionRefresh() + } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 2c2cf3bd90753a6610d99b16bd665fd9fcaca85a..66ff2e512436f8b55b6ff87da7ae1381124c453c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ + +