diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index 635b1081db25994b0bdaea5f4bbf48628855c6b8..c9feddb687cb0c3752b75b2fa2bccbfc7c0d016b 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -2,13 +2,6 @@ - ComplexCondition:AppPurchaseFragment.kt$AppPurchaseFragment.<no name provided>$url.contains("https://play.google.com/store/apps/details") && url.contains("raii") && url.contains("raboi") && url.contains("rasi") && url.contains("rapt") - CyclomaticComplexMethod:ApplicationApiImpl.kt$ApplicationApiImpl$private suspend fun generateCleanAPKHome(home: CleanApkHome, appType: String): List<Home> - CyclomaticComplexMethod:ApplicationDiffUtil.kt$ApplicationDiffUtil$override fun areContentsTheSame(oldItem: Application, newItem: Application): Boolean - CyclomaticComplexMethod:CategoryUtils.kt$CategoryUtils$fun provideAppsCategoryIconResource(categoryId: String): Int - CyclomaticComplexMethod:DownloadProgressLD.kt$DownloadProgressLD$override fun observe(owner: LifecycleOwner, observer: Observer<in DownloadProgress>) - CyclomaticComplexMethod:HomeChildFusedAppDiffUtil.kt$HomeChildFusedAppDiffUtil$override fun areContentsTheSame(oldItem: Application, newItem: Application): Boolean - CyclomaticComplexMethod:MainActivity.kt$MainActivity$override fun onCreate(savedInstanceState: Bundle?) EmptyCatchBlock:NativeDeviceInfoProviderModule.kt$NativeDeviceInfoProviderModule${ } EmptyFunctionBlock:CleanApkAuthenticator.kt$CleanApkAuthenticator${} ImplicitDefaultLocale:ApplicationFragment.kt$ApplicationFragment$String.format("%d%%", progressPercentage) @@ -21,11 +14,6 @@ InvalidPackageDeclaration:Trackers.kt$package foundation.e.apps.data.exodus LargeClass:ApplicationApiImpl.kt$ApplicationApiImpl : ApplicationApi LargeClass:ApplicationFragment.kt$ApplicationFragment : TimeoutFragment - LongMethod:ApplicationApiImpl.kt$ApplicationApiImpl$private suspend fun generateCleanAPKHome(home: CleanApkHome, appType: String): List<Home> - LongMethod:ApplicationFragment.kt$ApplicationFragment$private fun observeDownloadStatus(view: View) - LongMethod:CategoryUtils.kt$CategoryUtils$fun provideAppsCategoryIconResource(categoryId: String): Int - LongMethod:MainActivity.kt$MainActivity$override fun onCreate(savedInstanceState: Bundle?) - LongMethod:NativeDeviceInfoProviderModule.kt$NativeDeviceInfoProviderModule$@Singleton @Provides fun provideNativeDeviceProperties( @ApplicationContext context: Context, ): Properties LongParameterList:ApplicationApiImpl.kt$ApplicationApiImpl$( private val pkgManagerModule: PkgManagerModule, private val pwaManagerModule: PWAManagerModule, private val preferenceManagerModule: PreferenceManagerModule, @Named("gplayRepository") private val gplayRepository: PlayStoreRepository, @Named("cleanApkAppsRepository") private val cleanApkAppsRepository: CleanApkRepository, @Named("cleanApkPWARepository") private val cleanApkPWARepository: CleanApkRepository, @ApplicationContext private val context: Context ) LongParameterList:ApplicationDialogFragment.kt$ApplicationDialogFragment$( drawable: Int = -1, title: String, message: String, positiveButtonText: String = "", positiveButtonAction: (() -> Unit)? = null, cancelButtonText: String = "", cancelButtonAction: (() -> Unit)? = null, cancellable: Boolean = true, onDismissListener: (() -> Unit)? = null, ) LongParameterList:ApplicationListRVAdapter.kt$ApplicationListRVAdapter$( private val applicationInstaller: ApplicationInstaller, private val privacyInfoViewModel: PrivacyInfoViewModel, private val appInfoFetchViewModel: AppInfoFetchViewModel, private val mainActivityViewModel: MainActivityViewModel, private val currentDestinationId: Int, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((Application) -> Unit)? = null ) @@ -115,11 +103,6 @@ MaxLineLength:UpdatesViewModel.kt$UpdatesViewModel$return updatesList.value?.first?.any { it.status == Status.UPDATABLE || it.status == Status.INSTALLATION_ISSUE } == true MaxLineLength:UpdatesWorker.kt$UpdatesWorker$applicationContext.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED MayBeConst:EcloudApiInterface.kt$EcloudApiInterface.Companion$val BASE_URL = "https://eu.gtoken.ecloud.global/" - NestedBlockDepth:ApplicationApiImpl.kt$ApplicationApiImpl$private suspend fun generateCleanAPKHome(home: CleanApkHome, appType: String): List<Home> - NestedBlockDepth:DownloadManager.kt$DownloadManager$fun checkDownloadProgress( downloadId: Long, filePath: String = "", downloadCompleted: ((Boolean, String) -> Unit)? ) - NestedBlockDepth:PWAManagerModule.kt$PWAManagerModule$fun getPwaStatus(application: Application): Status - NestedBlockDepth:PkgManagerBR.kt$PkgManagerBR$override fun onReceive(context: Context?, intent: Intent?) - NestedBlockDepth:SettingsFragment.kt$SettingsFragment$override fun onViewCreated(view: View, savedInstanceState: Bundle?) PrintStackTrace:CommonUtilsModule.kt$CommonUtilsModule$e PrintStackTrace:EcloudRepository.kt$EcloudRepository$e PrintStackTrace:InstallWorkManager.kt$InstallWorkManager$e diff --git a/app/src/main/java/foundation/e/apps/MainActivity.kt b/app/src/main/java/foundation/e/apps/MainActivity.kt index a0042b28389b5c6b609d8917f576ae435568840e..e94784c0271bdf17868bd81b9dafd937e196c6ef 100644 --- a/app/src/main/java/foundation/e/apps/MainActivity.kt +++ b/app/src/main/java/foundation/e/apps/MainActivity.kt @@ -78,20 +78,50 @@ class MainActivity : AppCompatActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - val bottomNavigationView = binding.bottomNavigationView - val navHostFragment = - supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment - val navController = navHostFragment.navController - bottomNavigationView.setupWithNavController(navController) - setupBottomNavItemSelectedListener(bottomNavigationView, navHostFragment, navController) + val (bottomNavigationView, navController) = setupBootomNav() var hasInternet = true - viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java] - signInViewModel = ViewModelProvider(this)[SignInViewModel::class.java] - loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + setupViewModels() + + setupNavigations(navController) + + if (intent.hasExtra(UpdatesNotifier.UPDATES_NOTIFICATION_CLICK_EXTRA)) { + bottomNavigationView.selectedItemId = R.id.updatesFragment + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + viewModel.createNotificationChannels() + } + + viewModel.setupConnectivityManager(this.applicationContext) + + hasInternet = observeInternetConnections(hasInternet) + + observeAuthObjects(navController) + + setupDestinationChangedListener(navController, hasInternet, bottomNavigationView) + + observePurchaseAppPage() + + observeErrorMessage() + + observeErrorMessageString() + + observeIsAppPurchased() + + observePurchaseDeclined() - // navOptions and activityNavController for TOS and SignIn Fragments + if (viewModel.internetConnection.value != true) { + showNoInternet() + } + + viewModel.updateAppWarningList() + + observeEvents() + } + + private fun setupNavigations(navController: NavController) { val navOptions = NavOptions.Builder() .setPopUpTo(R.id.navigation_resource, true) .build() @@ -104,79 +134,63 @@ class MainActivity : AppCompatActivity() { loginViewModel.startLoginFlow() } } + } - viewModel.setupConnectivityManager(this.applicationContext) - - viewModel.internetConnection.observe(this) { isInternetAvailable -> - hasInternet = isInternetAvailable - if (isInternetAvailable) { - binding.noInternet.visibility = View.GONE - binding.fragment.visibility = View.VISIBLE - } - } + private fun observeEvents() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + observeInvalidAuth() + } - loginViewModel.authObjects.distinctUntilChanged().observe(this) { - when { - it == null -> return@observe - it.isEmpty() -> { - // No auth type defined means user has not logged in yet - // Pop back stack to prevent showing TOSFragment on pressing back button. - navController.popBackStack() - navController.navigate(R.id.signInFragment) + launch { + observeTooManyRequests() } - else -> {} - } + launch { + observeSignatureMissMatchError() + } - it.find { it is AuthObject.GPlayAuth }?.result?.run { - if (isSuccess()) { - viewModel.gPlayAuthData = data as AuthData - } else if (exception is GPlayValidationException) { - val email = otherPayload.toString() - viewModel.uploadFaultyTokenToEcloud( - email, - SystemInfoProvider.getAppBuildInfo() - ) - } else if (exception != null) { - Timber.e(exception, "Login failed! message: ${exception?.localizedMessage}") + launch { + observerErrorEvent() } - } - } - navController.addOnDestinationChangedListener { _, destination, _ -> - if (!hasInternet) { - showNoInternet() - } - when (destination.id) { - R.id.applicationFragment, - R.id.applicationListFragment, - R.id.screenshotFragment, - R.id.descriptionFragment, - R.id.TOSFragment, - R.id.googleSignInFragment, - R.id.signInFragment -> { - bottomNavigationView.visibility = View.GONE + launch { + observeAppPurchaseFragment() } - else -> { - bottomNavigationView.visibility = View.VISIBLE + launch { + observeNoInternetEvent() } } } + } - if (intent.hasExtra(UpdatesNotifier.UPDATES_NOTIFICATION_CLICK_EXTRA)) { - bottomNavigationView.selectedItemId = R.id.updatesFragment + private fun observePurchaseDeclined() { + viewModel.purchaseDeclined.observe(this) { + if (it.isNotEmpty()) { + lifecycleScope.launch { + viewModel.updateUnavailableForPurchaseDeclined(it) + } + } } + } - // Create notification channel on post-nougat devices - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - viewModel.createNotificationChannels() + private fun observeIsAppPurchased() { + viewModel.isAppPurchased.observe(this) { + if (it.isNotEmpty()) { + startInstallationOfPurchasedApp(viewModel, it) + } } + } - viewModel.purchaseAppLiveData.observe(this) { - goToAppPurchaseFragment(it) + private fun observeErrorMessageString() { + viewModel.errorMessageStringResource.observe(this) { + showSnackbarMessage(getString(it)) } + } + private fun observeErrorMessage() { viewModel.errorMessage.observe(this) { when (it) { is ApiException.AppNotPurchased -> showSnackbarMessage(getString(R.string.message_app_available_later)) @@ -185,60 +199,99 @@ class MainActivity : AppCompatActivity() { ) } } + } - viewModel.errorMessageStringResource.observe(this) { - showSnackbarMessage(getString(it)) + private fun observePurchaseAppPage() { + viewModel.purchaseAppLiveData.observe(this) { + goToAppPurchaseFragment(it) } + } - viewModel.isAppPurchased.observe(this) { - if (it.isNotEmpty()) { - startInstallationOfPurchasedApp(viewModel, it) + private fun observeInternetConnections(hasInternet: Boolean): Boolean { + var mutableHasInternet = hasInternet + viewModel.internetConnection.observe(this) { isInternetAvailable -> + mutableHasInternet = isInternetAvailable + if (isInternetAvailable) { + binding.noInternet.visibility = View.GONE + binding.fragment.visibility = View.VISIBLE } } + return mutableHasInternet + } - viewModel.purchaseDeclined.observe(this) { - if (it.isNotEmpty()) { - lifecycleScope.launch { - viewModel.updateUnavailableForPurchaseDeclined(it) - } + private fun setupDestinationChangedListener( + navController: NavController, + hasInternet: Boolean, + bottomNavigationView: BottomNavigationView + ) { + navController.addOnDestinationChangedListener { _, destination, _ -> + if (!hasInternet) { + showNoInternet() } - } - - if (viewModel.internetConnection.value != true) { - showNoInternet() - } - - viewModel.updateAppWarningList() - - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - observeInvalidAuth() - } - - launch { - observeTooManyRequests() + when (destination.id) { + R.id.applicationFragment, + R.id.applicationListFragment, + R.id.screenshotFragment, + R.id.descriptionFragment, + R.id.TOSFragment, + R.id.googleSignInFragment, + R.id.signInFragment -> { + bottomNavigationView.visibility = View.GONE } - launch { - observeSignatureMissMatchError() + else -> { + bottomNavigationView.visibility = View.VISIBLE } + } + } + } - launch { - observerErrorEvent() + private fun observeAuthObjects(navController: NavController) { + loginViewModel.authObjects.distinctUntilChanged().observe(this) { + when { + it == null -> return@observe + it.isEmpty() -> { + // No auth type defined means user has not logged in yet + // Pop back stack to prevent showing TOSFragment on pressing back button. + navController.popBackStack() + navController.navigate(R.id.signInFragment) } - launch { - observeAppPurchaseFragment() - } + else -> {} + } - launch { - observeNoInternetEvent() + it.find { it is AuthObject.GPlayAuth }?.result?.run { + if (isSuccess()) { + viewModel.gPlayAuthData = data as AuthData + } else if (exception is GPlayValidationException) { + val email = otherPayload.toString() + viewModel.uploadFaultyTokenToEcloud( + email, + SystemInfoProvider.getAppBuildInfo() + ) + } else if (exception != null) { + Timber.e(exception, "Login failed! message: ${exception?.localizedMessage}") } } } } + private fun setupViewModels() { + viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java] + signInViewModel = ViewModelProvider(this)[SignInViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + } + + private fun setupBootomNav(): Pair { + val bottomNavigationView = binding.bottomNavigationView + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.fragment) as NavHostFragment + val navController = navHostFragment.navController + bottomNavigationView.setupWithNavController(navController) + setupBottomNavItemSelectedListener(bottomNavigationView, navHostFragment, navController) + return Pair(bottomNavigationView, navController) + } + private suspend fun observeNoInternetEvent() { EventBus.events.filter { appEvent -> appEvent is AppEvent.NoInternetEvent diff --git a/app/src/main/java/foundation/e/apps/data/DownloadManager.kt b/app/src/main/java/foundation/e/apps/data/DownloadManager.kt index 56ba57137d23856dd0a36f81b9d203019824db3c..fabb01926cf74f2272ea27e58388e73845bd028f 100644 --- a/app/src/main/java/foundation/e/apps/data/DownloadManager.kt +++ b/app/src/main/java/foundation/e/apps/data/DownloadManager.kt @@ -123,36 +123,44 @@ class DownloadManager @Inject constructor( downloadCompleted: ((Boolean, String) -> Unit)? ) { try { - downloadManager.query(downloadManagerQuery.setFilterById(downloadId)) - .use { cursor -> - if (cursor.moveToFirst()) { - var status = - cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) - val totalSizeBytes = - getLong(cursor, DownloadManager.COLUMN_TOTAL_SIZE_BYTES) - val bytesDownloadedSoFar = - getLong(cursor, DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) - val reason = - cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)) - - status = sanitizeStatus(downloadId, status, reason) - - if (status == DownloadManager.STATUS_FAILED) { - Timber.d("Download Failed: $filePath=> $bytesDownloadedSoFar/$totalSizeBytes $status") - downloadsMaps[downloadId] = false - downloadCompleted?.invoke(false, filePath) - } else if (status == DownloadManager.STATUS_SUCCESSFUL) { - Timber.d("Download Successful: $filePath=> $bytesDownloadedSoFar/$totalSizeBytes $status") - downloadsMaps[downloadId] = false - downloadCompleted?.invoke(true, filePath) - } - } + downloadManager.query(downloadManagerQuery.setFilterById(downloadId)).use { cursor -> + if (cursor.moveToFirst()) { + handleDownloadStatus(cursor, downloadId, filePath, downloadCompleted) } + } } catch (e: Exception) { Timber.e(e) } } + private fun handleDownloadStatus( + cursor: Cursor, + downloadId: Long, + filePath: String, + downloadCompleted: ((Boolean, String) -> Unit)? + ) { + var status = + cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) + val totalSizeBytes = + getLong(cursor, DownloadManager.COLUMN_TOTAL_SIZE_BYTES) + val bytesDownloadedSoFar = + getLong(cursor, DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + val reason = + cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_REASON)) + + status = sanitizeStatus(downloadId, status, reason) + + if (status == DownloadManager.STATUS_FAILED) { + Timber.d("Download Failed: $filePath=> $bytesDownloadedSoFar/$totalSizeBytes $status") + downloadsMaps[downloadId] = false + downloadCompleted?.invoke(false, filePath) + } else if (status == DownloadManager.STATUS_SUCCESSFUL) { + Timber.d("Download Successful: $filePath=> $bytesDownloadedSoFar/$totalSizeBytes $status") + downloadsMaps[downloadId] = false + downloadCompleted?.invoke(true, filePath) + } + } + private fun tickerFlow( downloadId: Long, period: Duration, diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt index 869611db23798beaa9b113eb4507631db332f5ae..bcba66eeca0b737169ad4b140c97841fdfb95b6d 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationApiImpl.kt @@ -61,7 +61,7 @@ import foundation.e.apps.data.playstore.PlayStoreRepository import foundation.e.apps.data.preference.PreferenceManagerModule import foundation.e.apps.install.pkg.PWAManagerModule import foundation.e.apps.install.pkg.PkgManagerModule -import foundation.e.apps.ui.home.model.HomeChildFusedAppDiffUtil +import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil import foundation.e.apps.utils.eventBus.AppEvent import foundation.e.apps.utils.eventBus.EventBus import kotlinx.coroutines.Deferred @@ -1072,106 +1072,77 @@ class ApplicationApiImpl @Inject constructor( private suspend fun generateCleanAPKHome(home: CleanApkHome, appType: String): List { val list = mutableListOf() val headings = if (appType == APP_TYPE_OPEN) { - mapOf( - "top_updated_apps" to context.getString(R.string.top_updated_apps), - "top_updated_games" to context.getString(R.string.top_updated_games), - "popular_apps_in_last_24_hours" to context.getString(R.string.popular_apps_in_last_24_hours), - "popular_games_in_last_24_hours" to context.getString(R.string.popular_games_in_last_24_hours), - "discover" to context.getString(R.string.discover) - ) + getOpenSourceHomeCategories() } else { - mapOf( - "popular_apps" to context.getString(R.string.popular_apps), - "popular_games" to context.getString(R.string.popular_games), - "discover" to context.getString(R.string.discover_pwa) - ) + getPWAHomeCategories() } headings.forEach { (key, value) -> when (key) { "top_updated_apps" -> { - if (home.top_updated_apps.isNotEmpty()) { - home.top_updated_apps.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.top_updated_apps)) - } + prepareApps(home.top_updated_apps, list, value) } "top_updated_games" -> { - if (home.top_updated_games.isNotEmpty()) { - home.top_updated_games.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.top_updated_games)) - } + prepareApps(home.top_updated_games, list, value) } "popular_apps" -> { - if (home.popular_apps.isNotEmpty()) { - home.popular_apps.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.popular_apps)) - } + prepareApps(home.popular_apps, list, value) } "popular_games" -> { - if (home.popular_games.isNotEmpty()) { - home.popular_games.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.popular_games)) - } + prepareApps(home.popular_games, list, value) } "popular_apps_in_last_24_hours" -> { - if (home.popular_apps_in_last_24_hours.isNotEmpty()) { - home.popular_apps_in_last_24_hours.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.popular_apps_in_last_24_hours)) - } + prepareApps(home.popular_apps_in_last_24_hours, list, value) } "popular_games_in_last_24_hours" -> { - if (home.popular_games_in_last_24_hours.isNotEmpty()) { - home.popular_games_in_last_24_hours.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.popular_games_in_last_24_hours)) - } + prepareApps(home.popular_games_in_last_24_hours, list, value) } "discover" -> { - if (home.discover.isNotEmpty()) { - home.discover.forEach { - it.updateStatus() - it.updateType() - it.updateFilterLevel(null) - } - list.add(Home(value, home.discover)) - } + prepareApps(home.discover, list, value) } } } + return list.map { it.source = appType it } } + private suspend fun prepareApps( + appList: List, + list: MutableList, + value: String + ) { + if (appList.isNotEmpty()) { + appList.forEach { + it.updateStatus() + it.updateType() + it.updateFilterLevel(null) + } + list.add(Home(value, appList)) + } + } + + private fun getPWAHomeCategories() = mapOf( + "popular_apps" to context.getString(R.string.popular_apps), + "popular_games" to context.getString(R.string.popular_games), + "discover" to context.getString(R.string.discover_pwa) + ) + + private fun getOpenSourceHomeCategories() = mapOf( + "top_updated_apps" to context.getString(R.string.top_updated_apps), + "top_updated_games" to context.getString(R.string.top_updated_games), + "popular_apps_in_last_24_hours" to context.getString(R.string.popular_apps_in_last_24_hours), + "popular_games_in_last_24_hours" to context.getString(R.string.popular_games_in_last_24_hours), + "discover" to context.getString(R.string.discover) + ) + private suspend fun fetchGPlayHome(authData: AuthData): List { val list = mutableListOf() val gplayHomeData = gplayRepository.getHomeScreenData() as Map> @@ -1293,7 +1264,7 @@ class ApplicationApiImpl @Inject constructor( newApplications: List, oldApplications: List ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + val fusedAppDiffUtil = ApplicationDiffUtil() if (newApplications.size != oldApplications.size) { return true } diff --git a/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt b/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt index 03960b9e42fa10160665a2c6b15e5314d910f8e0..08d3c9816724fd8abd8eac59f23bdd4a63ac6ccc 100644 --- a/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt +++ b/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt @@ -21,160 +21,87 @@ package foundation.e.apps.data.application.utils import foundation.e.apps.R object CategoryUtils { - fun provideAppsCategoryIconResource(categoryId: String): Int { - return when (categoryId) { - "comics" -> - R.drawable.ic_cat_comics - "connectivity" -> - R.drawable.ic_cat_connectivity - "development" -> - R.drawable.ic_cat_development - "education" -> - R.drawable.ic_cat_education - "graphics" -> - R.drawable.ic_cat_graphics - "internet" -> - R.drawable.ic_cat_internet - "music_and_audio" -> - R.drawable.ic_cat_music_and_audio - "entertainment" -> - R.drawable.ic_cat_entertainment - "tools" -> - R.drawable.ic_cat_tools - "security" -> - R.drawable.ic_cat_security - "system" -> - R.drawable.ic_cat_system - "system_apps" -> - R.drawable.ic_cat_system - "communication" -> - R.drawable.ic_cat_communication - "medical" -> - R.drawable.ic_cat_medical - "lifestyle" -> - R.drawable.ic_cat_lifestyle - "video_players" -> - R.drawable.ic_cat_video_players - "video_players_and_editors" -> - R.drawable.ic_cat_video_players - "events" -> - R.drawable.ic_cat_events - "productivity" -> - R.drawable.ic_cat_productivity - "house_and_home" -> - R.drawable.ic_cat_house_and_home - "art_and_design" -> - R.drawable.ic_art_and_design - "photography" -> - R.drawable.ic_cat_photography - "auto_and_vehicles" -> - R.drawable.ic_auto_and_vehicles - "books_and_reference" -> - R.drawable.ic_books_and_reference - "social" -> - R.drawable.ic_cat_social - "travel_and_local" -> - R.drawable.ic_cat_travel_and_local - "beauty" -> - R.drawable.ic_beauty - "personalization" -> - R.drawable.ic_cat_personalization - "business" -> - R.drawable.ic_business - "health_and_fitness" -> - R.drawable.ic_cat_health_and_fitness - "dating" -> - R.drawable.ic_cat_dating - "news_and_magazines" -> - R.drawable.ic_cat_news_and_magazine - "finance" -> - R.drawable.ic_cat_finance - "food_and_drink" -> - R.drawable.ic_cat_food_and_drink - "shopping" -> - R.drawable.ic_cat_shopping - "libraries_and_demo" -> - R.drawable.ic_cat_libraries_and_demo - "sports" -> - R.drawable.ic_cat_sports - "maps_and_navigation" -> - R.drawable.ic_cat_maps_and_navigation - "parenting" -> - R.drawable.ic_cat_parenting - "weather" -> - R.drawable.ic_cat_weather - "topic/family" -> - R.drawable.ic_cat_family - "game_card" -> - R.drawable.ic_cat_game_card - "game_action" -> - R.drawable.ic_cat_game_action - "game_board" -> - R.drawable.ic_cat_game_board - "game_role_playing" -> - R.drawable.ic_cat_game_role_playing - "game_arcade" -> - R.drawable.ic_cat_game_arcade - "game_casino" -> - R.drawable.ic_cat_game_casino - "game_adventure" -> - R.drawable.ic_cat_game_adventure - "game_casual" -> - R.drawable.ic_cat_game_casual - "game_puzzle" -> - R.drawable.ic_cat_game_puzzle - "game_strategy" -> - R.drawable.ic_cat_game_strategy - "game_educational" -> - R.drawable.ic_cat_game_educational - "game_music" -> - R.drawable.ic_cat_game_music - "game_racing" -> - R.drawable.ic_cat_game_racing - "game_simulation" -> - R.drawable.ic_cat_game_simulation - "game_sports" -> - R.drawable.ic_cat_game_sports - "game_trivia" -> - R.drawable.ic_cat_game_trivia - "game_word" -> - R.drawable.ic_cat_game_word - "game_open_games" -> - R.drawable.ic_cat_open_games - "pwa_education" -> - R.drawable.ic_cat_education - "pwa_entertainment" -> - R.drawable.ic_cat_entertainment - "food & drink" -> - R.drawable.ic_cat_food_nd_drink - "pwa_lifestyle" -> - R.drawable.ic_cat_lifestyle - "music" -> - R.drawable.ic_cat_game_music - "news" -> - R.drawable.ic_cat_news - "pwa_games" -> - R.drawable.ic_cat_game_action - "reference" -> - R.drawable.ic_cat_reference - "pwa_shopping" -> - R.drawable.ic_cat_shopping - "pwa_social" -> - R.drawable.ic_cat_social - "pwa_sports" -> - R.drawable.ic_cat_sports - "travel" -> - R.drawable.ic_cat_travel - "pwa_business" -> - R.drawable.ic_business - "watch_face" -> - R.drawable.ic_watchface - "android_wear" -> - R.drawable.ic_watch_apps - else -> - R.drawable.ic_cat_default - } - } + + private val categoryIconMap = mapOf( + "comics" to R.drawable.ic_cat_comics, + "connectivity" to R.drawable.ic_cat_connectivity, + "development" to R.drawable.ic_cat_development, + "education" to R.drawable.ic_cat_education, + "graphics" to R.drawable.ic_cat_graphics, + "internet" to R.drawable.ic_cat_internet, + "music_and_audio" to R.drawable.ic_cat_music_and_audio, + "entertainment" to R.drawable.ic_cat_entertainment, + "tools" to R.drawable.ic_cat_tools, + "security" to R.drawable.ic_cat_security, + "system" to R.drawable.ic_cat_system, + "system_apps" to R.drawable.ic_cat_system, + "communication" to R.drawable.ic_cat_communication, + "medical" to R.drawable.ic_cat_medical, + "lifestyle" to R.drawable.ic_cat_lifestyle, + "video_players" to R.drawable.ic_cat_video_players, + "video_players_and_editors" to R.drawable.ic_cat_video_players, + "events" to R.drawable.ic_cat_events, + "productivity" to R.drawable.ic_cat_productivity, + "house_and_home" to R.drawable.ic_cat_house_and_home, + "art_and_design" to R.drawable.ic_art_and_design, + "photography" to R.drawable.ic_cat_photography, + "auto_and_vehicles" to R.drawable.ic_auto_and_vehicles, + "books_and_reference" to R.drawable.ic_books_and_reference, + "social" to R.drawable.ic_cat_social, + "travel_and_local" to R.drawable.ic_cat_travel_and_local, + "beauty" to R.drawable.ic_beauty, + "personalization" to R.drawable.ic_cat_personalization, + "business" to R.drawable.ic_business, + "health_and_fitness" to R.drawable.ic_cat_health_and_fitness, + "dating" to R.drawable.ic_cat_dating, + "news_and_magazines" to R.drawable.ic_cat_news_and_magazine, + "finance" to R.drawable.ic_cat_finance, + "food_and_drink" to R.drawable.ic_cat_food_and_drink, + "shopping" to R.drawable.ic_cat_shopping, + "libraries_and_demo" to R.drawable.ic_cat_libraries_and_demo, + "sports" to R.drawable.ic_cat_sports, + "maps_and_navigation" to R.drawable.ic_cat_maps_and_navigation, + "parenting" to R.drawable.ic_cat_parenting, + "weather" to R.drawable.ic_cat_weather, + "topic/family" to R.drawable.ic_cat_family, + "game_card" to R.drawable.ic_cat_game_card, + "game_action" to R.drawable.ic_cat_game_action, + "game_board" to R.drawable.ic_cat_game_board, + "game_role_playing" to R.drawable.ic_cat_game_role_playing, + "game_arcade" to R.drawable.ic_cat_game_arcade, + "game_casino" to R.drawable.ic_cat_game_casino, + "game_adventure" to R.drawable.ic_cat_game_adventure, + "game_casual" to R.drawable.ic_cat_game_casual, + "game_puzzle" to R.drawable.ic_cat_game_puzzle, + "game_strategy" to R.drawable.ic_cat_game_strategy, + "game_educational" to R.drawable.ic_cat_game_educational, + "game_music" to R.drawable.ic_cat_game_music, + "game_racing" to R.drawable.ic_cat_game_racing, + "game_simulation" to R.drawable.ic_cat_game_simulation, + "game_sports" to R.drawable.ic_cat_game_sports, + "game_trivia" to R.drawable.ic_cat_game_trivia, + "game_word" to R.drawable.ic_cat_game_word, + "game_open_games" to R.drawable.ic_cat_open_games, + "pwa_education" to R.drawable.ic_cat_education, + "pwa_entertainment" to R.drawable.ic_cat_entertainment, + "food & drink" to R.drawable.ic_cat_food_nd_drink, + "pwa_lifestyle" to R.drawable.ic_cat_lifestyle, + "music" to R.drawable.ic_cat_game_music, + "news" to R.drawable.ic_cat_news, + "pwa_games" to R.drawable.ic_cat_game_action, + "reference" to R.drawable.ic_cat_reference, + "pwa_shopping" to R.drawable.ic_cat_shopping, + "pwa_social" to R.drawable.ic_cat_social, + "pwa_sports" to R.drawable.ic_cat_sports, + "travel" to R.drawable.ic_cat_travel, + "pwa_business" to R.drawable.ic_business, + "watch_face" to R.drawable.ic_watchface, + "android_wear" to R.drawable.ic_watch_apps, + ) + + fun provideAppsCategoryIconResource(categoryId: String) = + categoryIconMap[categoryId] ?: R.drawable.ic_cat_default + } enum class CategoryType { diff --git a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt index ae3d2ef6f3dcebc30c0166a8a4bf8ff82cec4fa9..dde0fa5e1d9964488f8d88a8f922c49d83d57fcc 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt @@ -43,84 +43,91 @@ object NativeDeviceInfoProviderModule { @ApplicationContext context: Context, ): Properties { val properties = Properties().apply { - // Build Props - setProperty("UserReadableName", "${Build.DEVICE}-default") - setProperty("Build.HARDWARE", Build.HARDWARE) - setProperty( - "Build.RADIO", - if (Build.getRadioVersion() != null) - Build.getRadioVersion() - else - "unknown" - ) - setProperty("Build.FINGERPRINT", Build.FINGERPRINT) - setProperty("Build.BRAND", Build.BRAND) - setProperty("Build.DEVICE", Build.DEVICE) - setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") - setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) - setProperty("Build.MODEL", Build.MODEL) - setProperty("Build.MANUFACTURER", Build.MANUFACTURER) - setProperty("Build.PRODUCT", Build.PRODUCT) - setProperty("Build.ID", Build.ID) - setProperty("Build.BOOTLOADER", Build.BOOTLOADER) - - val config = context.resources.configuration - setProperty("TouchScreen", "${config.touchscreen}") - setProperty("Keyboard", "${config.keyboard}") - setProperty("Navigation", "${config.navigation}") - setProperty("ScreenLayout", "${config.screenLayout and 15}") - setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") - setProperty( - "HasFiveWayNavigation", - "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" - ) + setBuildProperties() + setConfigProperties(context) + setDisplayMetrics(context) + setGLExtensions(context) + setGoogleProperties(context) + setMiscProperties() + } + return properties + } - // Display Metrics - val metrics = context.resources.displayMetrics - setProperty("Screen.Density", "${metrics.densityDpi}") - setProperty("Screen.Width", "${metrics.widthPixels}") - setProperty("Screen.Height", "${metrics.heightPixels}") + private fun Properties.setMiscProperties() { + setProperty("Roaming", "mobile-notroaming") + setProperty("TimeZone", "UTC-10") + setProperty("CellOperator", "310") + setProperty("SimOperator", "38") + } - // Supported Platforms - setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) + private fun Properties.setGoogleProperties(context: Context) { + val gsfVersionProvider = NativeGsfVersionProvider(context) + setProperty("Client", "android-google") + setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") + setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode(true)}") + setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString(true)) + } - // Supported Features - setProperty("Features", getFeatures(context).joinToString(separator = ",")) - // Shared Locales - setProperty("Locales", getLocales(context).joinToString(separator = ",")) - // Shared Libraries - setProperty( - "SharedLibraries", - getSharedLibraries(context).joinToString(separator = ",") - ) - // GL Extensions - val activityManager = - context.getSystemService(ACTIVITY_SERVICE) as ActivityManager - setProperty( - "GL.Version", - activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() - ) - setProperty( - "GL.Extensions", - EglExtensionProvider.eglExtensions.joinToString(separator = ",") - ) + private fun Properties.setGLExtensions(context: Context) { + val activityManager = + context.getSystemService(ACTIVITY_SERVICE) as ActivityManager + setProperty( + "GL.Version", + activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() + ) + setProperty( + "GL.Extensions", + EglExtensionProvider.eglExtensions.joinToString(separator = ",") + ) + } - // Google Related Props - val gsfVersionProvider = NativeGsfVersionProvider(context) - setProperty("Client", "android-google") - setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") - setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode(true)}") - setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString(true)) + private fun Properties.setDisplayMetrics(context: Context) { + val metrics = context.resources.displayMetrics + setProperty("Screen.Density", "${metrics.densityDpi}") + setProperty("Screen.Width", "${metrics.widthPixels}") + setProperty("Screen.Height", "${metrics.heightPixels}") + setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) + setProperty("Features", getFeatures(context).joinToString(separator = ",")) + setProperty("Locales", getLocales(context).joinToString(separator = ",")) + setProperty( + "SharedLibraries", + getSharedLibraries(context).joinToString(separator = ",") + ) + } - // MISC - setProperty("Roaming", "mobile-notroaming") - setProperty("TimeZone", "UTC-10") + private fun Properties.setConfigProperties(context: Context) { + val config = context.resources.configuration + setProperty("TouchScreen", "${config.touchscreen}") + setProperty("Keyboard", "${config.keyboard}") + setProperty("Navigation", "${config.navigation}") + setProperty("ScreenLayout", "${config.screenLayout and 15}") + setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") + setProperty( + "HasFiveWayNavigation", + "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" + ) + } - // Telephony (USA 3650 AT&T) - setProperty("CellOperator", "310") - setProperty("SimOperator", "38") - } - return properties + private fun Properties.setBuildProperties() { + setProperty("UserReadableName", "${Build.DEVICE}-default") + setProperty("Build.HARDWARE", Build.HARDWARE) + setProperty( + "Build.RADIO", + if (Build.getRadioVersion() != null) + Build.getRadioVersion() + else + "unknown" + ) + setProperty("Build.FINGERPRINT", Build.FINGERPRINT) + setProperty("Build.BRAND", Build.BRAND) + setProperty("Build.DEVICE", Build.DEVICE) + setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") + setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) + setProperty("Build.MODEL", Build.MODEL) + setProperty("Build.MANUFACTURER", Build.MANUFACTURER) + setProperty("Build.PRODUCT", Build.PRODUCT) + setProperty("Build.ID", Build.ID) + setProperty("Build.BOOTLOADER", Build.BOOTLOADER) } private fun getFeatures(context: Context): List { diff --git a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt index d65168a241e3d52dee5418934db2d98c6530f8d1..1f6fd60f7b737aa5553def328728ed8f9895997d 100644 --- a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt +++ b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt @@ -19,6 +19,10 @@ package foundation.e.apps.install.download.data import android.app.DownloadManager +import android.app.DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR +import android.app.DownloadManager.COLUMN_ID +import android.app.DownloadManager.COLUMN_STATUS +import android.app.DownloadManager.COLUMN_TOTAL_SIZE_BYTES import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData @@ -61,46 +65,7 @@ class DownloadProgressLD @Inject constructor( continue } try { - downloadManager.query(downloadManagerQuery.setFilterById(*downloadingIds.toLongArray())) - .use { cursor -> - cursor.moveToFirst() - while (!cursor.isAfterLast) { - val id = - cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID)) - val status = - cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS)) - val totalSizeBytes = - cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) - val bytesDownloadedSoFar = - cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) - - downloadProgress.downloadId = id - - if (!downloadProgress.totalSizeBytes.containsKey(id) || - downloadProgress.totalSizeBytes[id] != totalSizeBytes - ) { - downloadProgress.totalSizeBytes[id] = totalSizeBytes - } - - if (!downloadProgress.bytesDownloadedSoFar.containsKey(id) || - downloadProgress.bytesDownloadedSoFar[id] != bytesDownloadedSoFar - ) { - downloadProgress.bytesDownloadedSoFar[id] = bytesDownloadedSoFar - } - - downloadProgress.status[id] = - status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED - - if (downloadingIds.size == cursor.count) { - postValue(downloadProgress) - } - - if (downloadingIds.isEmpty()) { - cancel() - } - cursor.moveToNext() - } - } + findDownloadProgress(downloadingIds) } catch (e: Exception) { Timber.e("downloading Ids: $downloadingIds ${e.localizedMessage}") } @@ -109,6 +74,49 @@ class DownloadProgressLD @Inject constructor( } } + private fun findDownloadProgress(downloadingIds: MutableList) { + downloadManager.query(downloadManagerQuery.setFilterById(*downloadingIds.toLongArray())) + .use { cursor -> + cursor.moveToFirst() + while (!cursor.isAfterLast) { + val id = + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)) + val status = + cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)) + val totalSizeBytes = + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_TOTAL_SIZE_BYTES)) + val bytesDownloadedSoFar = + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_BYTES_DOWNLOADED_SO_FAR)) + + downloadProgress.downloadId = id + + if (!downloadProgress.totalSizeBytes.containsKey(id) || + downloadProgress.totalSizeBytes[id] != totalSizeBytes + ) { + downloadProgress.totalSizeBytes[id] = totalSizeBytes + } + + if (!downloadProgress.bytesDownloadedSoFar.containsKey(id) || + downloadProgress.bytesDownloadedSoFar[id] != bytesDownloadedSoFar + ) { + downloadProgress.bytesDownloadedSoFar[id] = bytesDownloadedSoFar + } + + downloadProgress.status[id] = + status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED + + if (downloadingIds.size == cursor.count) { + postValue(downloadProgress) + } + + if (downloadingIds.isEmpty()) { + cancel() + } + cursor.moveToNext() + } + } + } + override fun onInactive() { super.onInactive() job.cancel() diff --git a/app/src/main/java/foundation/e/apps/install/pkg/PWAManagerModule.kt b/app/src/main/java/foundation/e/apps/install/pkg/PWAManagerModule.kt index f71649f52efb86028d887eaff4bd9ff17632c7a1..4179f6fbd0e878e6a16eec3627c71325e3adf4f1 100644 --- a/app/src/main/java/foundation/e/apps/install/pkg/PWAManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/install/pkg/PWAManagerModule.kt @@ -4,6 +4,7 @@ import android.content.ContentUris import android.content.ContentValues import android.content.Context import android.content.Intent +import android.database.Cursor import android.graphics.Bitmap import android.graphics.BitmapFactory import android.net.Uri @@ -62,21 +63,12 @@ class PWAManagerModule @Inject constructor( Uri.parse(PWA_PLAYER), null, null, null, null )?.let { cursor -> - if (cursor.count > 0) { - if (cursor.moveToFirst()) { - do { - try { - val pwaItemUrl = cursor.getString(cursor.columnNames.indexOf("url")) - val pwaItemDbId = cursor.getLong(cursor.columnNames.indexOf("_id")) - if (application.url == pwaItemUrl) { - application.pwaPlayerDbId = pwaItemDbId - return Status.INSTALLED - } - } catch (e: Exception) { - e.printStackTrace() - } - } while (cursor.moveToNext()) + cursor.moveToFirst() + while (!cursor.isAfterLast) { + if (isPwaInstalled(cursor, application)) { + return Status.INSTALLED } + cursor.moveToNext() } cursor.close() } @@ -84,6 +76,24 @@ class PWAManagerModule @Inject constructor( return Status.UNAVAILABLE } + private fun isPwaInstalled( + cursor: Cursor, + application: Application + ): Boolean { + try { + val pwaItemUrl = cursor.getString(cursor.columnNames.indexOf("url")) + val pwaItemDbId = cursor.getLong(cursor.columnNames.indexOf("_id")) + if (application.url == pwaItemUrl) { + application.pwaPlayerDbId = pwaItemDbId + return true + } + } catch (e: Exception) { + Timber.w(e) + } + + return false + } + /** * Launch PWA using PWA Player. */ diff --git a/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt index 918c93f1677d6b58e2ae928dbd45222843cc712a..a33cc1d9444fc7b8e7bb20ff3802b3fba6066206 100644 --- a/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt @@ -63,21 +63,32 @@ open class PkgManagerBR : BroadcastReceiver() { Timber.d("onReceive: $packageName $action $extra $status") packages?.let { pkgList -> - pkgList.forEach { pkgName -> - when (action) { - Intent.ACTION_PACKAGE_ADDED -> { - updateDownloadStatus(pkgName) - removeFaultyAppByPackageName(pkgName) - } - Intent.ACTION_PACKAGE_REMOVED -> { - if (!isUpdating) deleteDownload(pkgName) - removeFaultyAppByPackageName(pkgName) - } - PkgManagerModule.ERROR_PACKAGE_INSTALL -> { - Timber.e("Installation failed due to error: $extra") - updateInstallationIssue(pkgName) - } - } + handlePackageList(pkgList, action, isUpdating, extra) + } + } + } + + private fun handlePackageList( + pkgList: Array, + action: String, + isUpdating: Boolean, + extra: String? + ) { + pkgList.forEach { pkgName -> + when (action) { + Intent.ACTION_PACKAGE_ADDED -> { + updateDownloadStatus(pkgName) + removeFaultyAppByPackageName(pkgName) + } + + Intent.ACTION_PACKAGE_REMOVED -> { + if (!isUpdating) deleteDownload(pkgName) + removeFaultyAppByPackageName(pkgName) + } + + PkgManagerModule.ERROR_PACKAGE_INSTALL -> { + Timber.e("Installation failed due to error: $extra") + updateInstallationIssue(pkgName) } } } diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt index bacd5077f9983086c8a209eec6f8f4ad425986f7..d69cc60320cca6083b4a75471a36f6a3c3448f29 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationFragment.kt @@ -489,71 +489,80 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { private fun observeDownloadStatus(view: View) { applicationViewModel.appStatus.observe(viewLifecycleOwner) { status -> - val installButton = binding.downloadInclude.installButton - val downloadPB = binding.downloadInclude.progressLayout - val appSize = binding.downloadInclude.appSize val application = applicationViewModel.getFusedApp() ?: Application() - mainActivityViewModel.verifyUiFilter(application) { if (!application.filterLevel.isInitialized()) { return@verifyUiFilter } - when (status) { - Status.INSTALLED -> handleInstalled( - installButton, - view, - application, - downloadPB, - appSize - ) - Status.UPDATABLE -> handleUpdatable( - installButton, - view, - application, - downloadPB, - appSize - ) + handleInstallStatus(status, view, application) + } + } + } - Status.UNAVAILABLE -> handleUnavaiable( - installButton, - application, - downloadPB, - appSize - ) + private fun handleInstallStatus( + status: Status?, + view: View, + application: Application, + ) { + val installButton = binding.downloadInclude.installButton + val downloadPB = binding.downloadInclude.progressLayout + val appSize = binding.downloadInclude.appSize + + when (status) { + Status.INSTALLED -> handleInstalled( + installButton, + view, + application, + downloadPB, + appSize + ) - Status.QUEUED, Status.AWAITING, Status.DOWNLOADED -> handleQueued( - installButton, - application, - downloadPB, - appSize - ) + Status.UPDATABLE -> handleUpdatable( + installButton, + view, + application, + downloadPB, + appSize + ) - Status.DOWNLOADING -> handleDownloading( - installButton, - application, - downloadPB, - appSize - ) + Status.UNAVAILABLE -> handleUnavaiable( + installButton, + application, + downloadPB, + appSize + ) - Status.INSTALLING -> handleInstalling( - installButton, - downloadPB, - appSize - ) + Status.QUEUED, Status.AWAITING, Status.DOWNLOADED -> handleQueued( + installButton, + application, + downloadPB, + appSize + ) - Status.BLOCKED -> handleBlocked(installButton, view) - Status.INSTALLATION_ISSUE -> handleInstallingIssue( - installButton, - application, - downloadPB, - appSize - ) + Status.DOWNLOADING -> handleDownloading( + installButton, + application, + downloadPB, + appSize + ) - else -> { - Timber.d("Unknown status: $status") - } - } + Status.INSTALLING -> handleInstalling( + installButton, + downloadPB, + appSize + ) + + Status.BLOCKED -> handleBlocked(installButton, view) + Status.INSTALLATION_ISSUE -> handleInstallingIssue( + installButton, + application, + downloadPB, + appSize + ) + + else -> { + Timber.d("Unknown status: $status") } } } diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationDiffUtil.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationDiffUtil.kt index bd887d93850952370daab57b6d21c855242ff80a..aea640d79fa8e556d1f083facc251dddc70c6407 100644 --- a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationDiffUtil.kt +++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationDiffUtil.kt @@ -27,30 +27,6 @@ class ApplicationDiffUtil : DiffUtil.ItemCallback() { } override fun areContentsTheSame(oldItem: Application, newItem: Application): Boolean { - return oldItem._id == newItem._id && - oldItem.appSize.contentEquals(newItem.appSize) && - oldItem.author.contentEquals(newItem.author) && - oldItem.category.contentEquals(newItem.category) && - oldItem.description.contentEquals(newItem.description) && - oldItem.icon_image_path.contentEquals(newItem.icon_image_path) && - oldItem.last_modified.contentEquals(newItem.last_modified) && - oldItem.latest_version_code == newItem.latest_version_code && - oldItem.latest_version_number.contentEquals(newItem.latest_version_number) && - oldItem.licence.contentEquals(newItem.licence) && - oldItem.appSize.contentEquals(newItem.appSize) && - oldItem.name.contentEquals(newItem.name) && - oldItem.offer_type == newItem.offer_type && - oldItem.origin == newItem.origin && - oldItem.other_images_path == newItem.other_images_path && - oldItem.package_name.contentEquals(newItem.package_name) && - oldItem.perms == newItem.perms && - oldItem.ratings == newItem.ratings && - oldItem.shareUrl.contentEquals(newItem.shareUrl) && - oldItem.source.contentEquals(newItem.source) && - oldItem.status == newItem.status && - ((oldItem.trackers == LIST_OF_NULL && newItem.trackers.isEmpty()) || oldItem.trackers == newItem.trackers) && - oldItem.url.contentEquals(newItem.url) && - oldItem.isFree == newItem.isFree && - oldItem.is_pwa == newItem.is_pwa + return oldItem == newItem } } diff --git a/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt b/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt index b4197164cf082ebc83a8a00295f30f1ff1feeba3..afd4f8cd2dfb6224109c08fda60540f41fe97799 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt @@ -30,7 +30,7 @@ import foundation.e.apps.data.application.data.Home 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.ui.home.model.HomeChildFusedAppDiffUtil +import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil import foundation.e.apps.ui.parentFragment.LoadingViewModel import kotlinx.coroutines.launch import java.util.UUID @@ -146,7 +146,7 @@ class HomeViewModel @Inject constructor( oldHome: Home, newHome: Home, ): Boolean { - val fusedAppDiffUtil = HomeChildFusedAppDiffUtil() + val fusedAppDiffUtil = ApplicationDiffUtil() oldHome.list.forEach { oldFusedApp -> val indexOfOldFusedApp = oldHome.list.indexOf(oldFusedApp) diff --git a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildFusedAppDiffUtil.kt b/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildFusedAppDiffUtil.kt deleted file mode 100644 index 8f2be23a6d612d5fe7005c8f651f843132b83a2c..0000000000000000000000000000000000000000 --- a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildFusedAppDiffUtil.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2022 E FOUNDATION - * - * 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.ui.home.model - -import androidx.recyclerview.widget.DiffUtil -import foundation.e.apps.data.application.data.Application - -class HomeChildFusedAppDiffUtil : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Application, newItem: Application): Boolean { - return oldItem == newItem - } - - override fun areContentsTheSame(oldItem: Application, newItem: Application): Boolean { - return oldItem._id == newItem._id && - oldItem.appSize.contentEquals(newItem.appSize) && - oldItem.author.contentEquals(newItem.author) && - oldItem.category.contentEquals(newItem.category) && - oldItem.description.contentEquals(newItem.description) && - oldItem.icon_image_path.contentEquals(newItem.icon_image_path) && - oldItem.last_modified.contentEquals(newItem.last_modified) && - oldItem.latest_version_code == newItem.latest_version_code && - oldItem.latest_version_number.contentEquals(newItem.latest_version_number) && - oldItem.licence.contentEquals(newItem.licence) && - oldItem.appSize.contentEquals(newItem.appSize) && - oldItem.name.contentEquals(newItem.name) && - oldItem.offer_type == newItem.offer_type && - oldItem.origin == newItem.origin && - oldItem.other_images_path == newItem.other_images_path && - oldItem.package_name.contentEquals(newItem.package_name) && - oldItem.perms == newItem.perms && - oldItem.ratings == newItem.ratings && - oldItem.shareUrl.contentEquals(newItem.shareUrl) && - oldItem.source.contentEquals(newItem.source) && - oldItem.status == newItem.status && - oldItem.trackers == newItem.trackers && - oldItem.url.contentEquals(newItem.url) && - oldItem.isFree == newItem.isFree && - oldItem.is_pwa == newItem.is_pwa - } -} diff --git a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt index d26563217e076d087e1c5b218b87a32147df21a3..2dbbfe71a4d3626cc7b18bfaca274aee2066f728 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/model/HomeChildRVAdapter.kt @@ -41,6 +41,7 @@ import foundation.e.apps.data.application.data.Application import foundation.e.apps.databinding.HomeChildListItemBinding import foundation.e.apps.ui.AppInfoFetchViewModel import foundation.e.apps.ui.MainActivityViewModel +import foundation.e.apps.ui.applicationlist.ApplicationDiffUtil import foundation.e.apps.ui.home.HomeFragmentDirections import foundation.e.apps.utils.disableInstallButton import foundation.e.apps.utils.enableInstallButton @@ -51,7 +52,7 @@ class HomeChildRVAdapter( private val mainActivityViewModel: MainActivityViewModel, private var lifecycleOwner: LifecycleOwner?, private var paidAppHandler: ((Application) -> Unit)? = null -) : ListAdapter(HomeChildFusedAppDiffUtil()) { +) : ListAdapter(ApplicationDiffUtil()) { private val shimmer = Shimmer.ColorHighlightBuilder() .setDuration(500) diff --git a/app/src/main/java/foundation/e/apps/ui/purchase/AppPurchaseFragment.kt b/app/src/main/java/foundation/e/apps/ui/purchase/AppPurchaseFragment.kt index decd860dbcadb694d0a6f100aab4bce3068d9103..f63db522aee22eb8cc04d54a252210b8e64100c1 100644 --- a/app/src/main/java/foundation/e/apps/ui/purchase/AppPurchaseFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/purchase/AppPurchaseFragment.kt @@ -58,11 +58,25 @@ class AppPurchaseFragment : Fragment() { binding.playStoreWebView.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView, url: String) { - if (url.contains("https://play.google.com/store/apps/details") && url.contains("raii") && - url.contains("raboi") && url.contains("rasi") && url.contains("rapt") - ) { - isAppPurchased = true + isAppPurchased = isAppPurchased(url) + } + + private fun isAppPurchased(url: String): Boolean { + val urlElementsOfPurchasedApp = listOf( + "https://play.google.com/store/apps/details", + "raii", + "raboi", + "rasi", + "rapt" + ) + + urlElementsOfPurchasedApp.forEach { + if (!url.contains(it)) { + return false + } } + + return true } } diff --git a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt index 5b54866c6eb632a194fd93c099f494d7bc3ffd80..bf1150b329ae56f6ece8b950a059727bdb739f17 100644 --- a/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/settings/SettingsFragment.kt @@ -33,6 +33,7 @@ import androidx.preference.Preference.OnPreferenceChangeListener import androidx.preference.PreferenceFragmentCompat import androidx.work.ExistingPeriodicWorkPolicy import coil.load +import com.aurora.gplayapi.data.models.AuthData import com.google.android.material.snackbar.Snackbar import com.google.gson.Gson import dagger.hilt.android.AndroidEntryPoint @@ -166,23 +167,7 @@ class SettingsFragment : PreferenceFragmentCompat() { mainActivityViewModel.gPlayAuthData.let { authData -> mainActivityViewModel.getUser().name.let { user -> - when (user) { - User.ANONYMOUS.name -> { - binding.accountType.setText(R.string.user_anonymous) - binding.email.isVisible = false - } - User.GOOGLE.name -> { - if (!authData.isAnonymous) { - binding.accountType.text = authData.userProfile?.name - binding.email.text = mainActivityViewModel.getUserEmail() - binding.avatar.load(authData.userProfile?.artwork?.url) - } - } - User.NO_GOOGLE.name -> { - binding.accountType.setText(R.string.logged_out) - binding.email.isVisible = false - } - } + handleUser(user, authData) } } @@ -230,6 +215,28 @@ class SettingsFragment : PreferenceFragmentCompat() { } } + private fun handleUser(user: String, authData: AuthData) { + when (user) { + User.ANONYMOUS.name -> { + binding.accountType.setText(R.string.user_anonymous) + binding.email.isVisible = false + } + + User.GOOGLE.name -> { + if (!authData.isAnonymous) { + binding.accountType.text = authData.userProfile?.name + binding.email.text = mainActivityViewModel.getUserEmail() + binding.avatar.load(authData.userProfile?.artwork?.url) + } + } + + User.NO_GOOGLE.name -> { + binding.accountType.setText(R.string.logged_out) + binding.email.isVisible = false + } + } + } + private fun fetchCheckboxPreference(id: Int): CheckBoxPreference? { return findPreference(getString(id)) }