From 12a30d2d4dbd0ac1541745cb5ef9d47bd98c2ac1 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Thu, 3 Oct 2024 17:10:19 +0600 Subject: [PATCH 1/2] fix: install button for paidapp --- .../ui/application/ApplicationFragment.kt | 60 ++++++++++++------- .../layout/fragment_application_download.xml | 11 ++++ 2 files changed, 48 insertions(+), 23 deletions(-) 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 27ee47abb..9591bee4a 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 @@ -18,6 +18,7 @@ package foundation.e.apps.ui.application +import android.annotation.SuppressLint import android.content.Intent import android.graphics.Color import android.graphics.drawable.Drawable @@ -782,6 +783,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } } + @SuppressLint("SetTextI18n") private fun handleUnavaiable( installButton: MaterialButton, application: Application, @@ -789,38 +791,32 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { appSize: MaterialTextView ) { installButton.apply { - enableInstallButton(R.string.install) - text = when { - mainActivityViewModel.checkUnsupportedApplication(application) -> - getString(R.string.not_available) - - application.isFree -> getString(R.string.install) - else -> application.price + installButton.disableInstallButton(R.string.install) + installButton.text = "" + binding.downloadInclude.progressBarInstall.visibility = View.VISIBLE + + appInfoFetchViewModel.isAppPurchased(application).observe(viewLifecycleOwner) { + binding.downloadInclude.progressBarInstall.visibility = View.GONE + enableInstallButton(R.string.install) + text = when { + mainActivityViewModel.checkUnsupportedApplication(application) -> + getString(R.string.not_available) + + application.isFree || application.isPurchased -> getString(R.string.install) + else -> application.price + } } + setOnClickListener { if (mainActivityViewModel.checkUnsupportedApplication(application, activity)) { return@setOnClickListener } applicationIcon?.let { - if (application.isFree) { + if (application.isFree || application.isPurchased) { disableInstallButton(R.string.cancel) installApplication(application) } else { - if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(application)) { - ApplicationDialogFragment( - title = getString(R.string.dialog_title_paid_app, application.name), - message = getString( - R.string.dialog_paidapp_message, - application.name, - application.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - installApplication(application) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "ApplicationFragment") - } + handleInstallClickForPaidApp(application) } } } @@ -829,6 +825,24 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { appSize.visibility = View.VISIBLE } + private fun handleInstallClickForPaidApp(application: Application) { + if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(application)) { + ApplicationDialogFragment( + title = getString(R.string.dialog_title_paid_app, application.name), + message = getString( + R.string.dialog_paidapp_message, + application.name, + application.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + installApplication(application) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "ApplicationFragment") + } + } + private fun MaterialButton.disableInstallButton(buttonStringID: Int) { isEnabled = false text = context.getString(buttonStringID) diff --git a/app/src/main/res/layout/fragment_application_download.xml b/app/src/main/res/layout/fragment_application_download.xml index 072f0cc5c..4f48a648d 100644 --- a/app/src/main/res/layout/fragment_application_download.xml +++ b/app/src/main/res/layout/fragment_application_download.xml @@ -93,4 +93,15 @@ app:cornerRadius="4dp" /> + + \ No newline at end of file -- GitLab From 7e6e418bcfa538dee017e0f26dc12e9cb08b33e9 Mon Sep 17 00:00:00 2001 From: Hasib Prince Date: Thu, 3 Oct 2024 23:15:01 +0600 Subject: [PATCH 2/2] refactor: optimized fetching purchase info --- .../ui/application/ApplicationFragment.kt | 55 +++++++++++------- .../ui/application/ApplicationViewModel.kt | 57 ++++++++++++++----- .../ApplicationListRVAdapter.kt | 9 ++- .../apps/ui/home/model/HomeChildRVAdapter.kt | 3 +- .../layout/fragment_application_download.xml | 1 + .../res/navigation/navigation_resource.xml | 4 ++ 6 files changed, 93 insertions(+), 36 deletions(-) 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 9591bee4a..3d25bf9ee 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 @@ -232,12 +232,32 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { fetchAppTracker(it) observeDownloadList() - observeDownloadStatus(binding.root) + updateInstallButton(it) stopLoadingUI() collectState() } + @SuppressLint("SetTextI18n") + private fun updateInstallButton(application: Application) { + binding.downloadInclude.apply { + installButton.disableInstallButton(R.string.install) + installButton.text = "" + progressBarInstall.visibility = View.VISIBLE + } + + if (!isFreeOrAlreadyPurchased(application)) { + appInfoFetchViewModel.isAppPurchased(application).observe(viewLifecycleOwner) { + binding.downloadInclude.progressBarInstall.visibility = View.GONE + observeDownloadStatus(binding.root) + } + return + } + + binding.downloadInclude.progressBarInstall.visibility = View.GONE + observeDownloadStatus(binding.root) + } + private fun collectState() { collectShareVisibilityState() collectAppContentRatingState() @@ -585,13 +605,15 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { /* Remove trailing slash (if present) that can become part of the packageName */ val packageName = args.packageName.run { if (endsWith('/')) dropLast(1) else this } - applicationViewModel.loadData( + val applicationLoadingParams = ApplicationLoadingParams( args.id, packageName, origin, isFdroidDeepLink, - authObjectList - ) { + authObjectList, + args.isPurchased + ) + applicationViewModel.loadData(applicationLoadingParams) { clearAndRestartGPlayLogin() true } @@ -783,7 +805,6 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { } } - @SuppressLint("SetTextI18n") private fun handleUnavaiable( installButton: MaterialButton, application: Application, @@ -791,20 +812,13 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { appSize: MaterialTextView ) { installButton.apply { - installButton.disableInstallButton(R.string.install) - installButton.text = "" - binding.downloadInclude.progressBarInstall.visibility = View.VISIBLE - - appInfoFetchViewModel.isAppPurchased(application).observe(viewLifecycleOwner) { - binding.downloadInclude.progressBarInstall.visibility = View.GONE - enableInstallButton(R.string.install) - text = when { - mainActivityViewModel.checkUnsupportedApplication(application) -> - getString(R.string.not_available) + enableInstallButton(R.string.install) + text = when { + mainActivityViewModel.checkUnsupportedApplication(application) -> + getString(R.string.not_available) - application.isFree || application.isPurchased -> getString(R.string.install) - else -> application.price - } + isFreeOrAlreadyPurchased(application) -> getString(R.string.install) + else -> application.price } setOnClickListener { @@ -812,7 +826,7 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { return@setOnClickListener } applicationIcon?.let { - if (application.isFree || application.isPurchased) { + if (isFreeOrAlreadyPurchased(application)) { disableInstallButton(R.string.cancel) installApplication(application) } else { @@ -825,6 +839,9 @@ class ApplicationFragment : TimeoutFragment(R.layout.fragment_application) { appSize.visibility = View.VISIBLE } + private fun isFreeOrAlreadyPurchased(application: Application) = + application.isFree || application.isPurchased + private fun handleInstallClickForPaidApp(application: Application) { if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(application)) { ApplicationDialogFragment( diff --git a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt index e9a0980ae..363198426 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/ApplicationViewModel.kt @@ -72,45 +72,66 @@ class ApplicationViewModel @Inject constructor( val appContentRatingState = _appContentRatingState.asStateFlow() fun loadData( - id: String, - packageName: String, - origin: Origin, - isFdroidLink: Boolean, - authObjectList: List, + params: ApplicationLoadingParams, retryBlock: (failedObjects: List) -> Boolean, ) { - if (isFdroidLink) { - getCleanapkAppDetails(packageName) + if (params.isFdroidDeepLink) { + getCleanapkAppDetails(params.packageName) return } - val gPlayObj = authObjectList.find { it is AuthObject.GPlayAuth } + val gPlayObj = params.authObjectList.find { it is AuthObject.GPlayAuth } /* * If user is viewing only open source apps, auth object list will not have * GPlayAuth, it will only have CleanApkAuth. */ - if (gPlayObj == null && origin == Origin.GPLAY) { + if (gPlayObj == null && params.origin == Origin.GPLAY) { _errorMessageLiveData.postValue(R.string.gplay_data_for_oss) return } - super.onLoadData(authObjectList, { successAuthList, _ -> + super.onLoadData(params.authObjectList, { successAuthList, _ -> successAuthList.find { it is AuthObject.GPlayAuth }?.run { - getApplicationDetails(id, packageName, result.data!! as AuthData, origin) + val authData = result.data as? AuthData + // Usually authdata won't be null, null check is added to avoid forcefully unwrapping + if (authData == null) { + _errorMessageLiveData.postValue(R.string.data_load_error) + return@onLoadData + } + + getApplicationDetails( + params.appId, + params.packageName, + params.isPurchased, + authData, + params.origin + ) return@onLoadData } successAuthList.find { it is AuthObject.CleanApk }?.run { - getApplicationDetails(id, packageName, AuthData("", ""), origin) + getApplicationDetails( + params.appId, + params.packageName, + params.isPurchased, + AuthData("", ""), + params.origin + ) return@onLoadData } }, retryBlock) } - fun getApplicationDetails(id: String, packageName: String, authData: AuthData, origin: Origin) { + fun getApplicationDetails( + id: String, + packageName: String, + isPurchased: Boolean, + authData: AuthData, + origin: Origin + ) { viewModelScope.launch(Dispatchers.IO) { try { val appData = @@ -120,6 +141,7 @@ class ApplicationViewModel @Inject constructor( authData, origin ) + appData.first.isPurchased = isPurchased applicationLiveData.postValue(appData) updateShareVisibilityState(appData.first.shareUri.toString()) @@ -247,3 +269,12 @@ sealed class ShareButtonVisibilityState { object Visible : ShareButtonVisibilityState() object Hidden : ShareButtonVisibilityState() } + +data class ApplicationLoadingParams( + val appId: String, + val packageName: String, + val origin: Origin, + val isFdroidDeepLink: Boolean, + val authObjectList: List, + val isPurchased: Boolean +) diff --git a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt index db6b9e8aa..4987b29ec 100644 --- a/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/applicationlist/ApplicationListRVAdapter.kt @@ -258,7 +258,8 @@ class ApplicationListRVAdapter( searchApp._id, searchApp.origin, catText, - searchApp.isGplayReplaced + searchApp.isGplayReplaced, + searchApp.isPurchased ) } R.id.searchFragment -> { @@ -267,7 +268,8 @@ class ApplicationListRVAdapter( searchApp._id, searchApp.origin, catText, - searchApp.isGplayReplaced + searchApp.isGplayReplaced, + searchApp.isPurchased ) } R.id.updatesFragment -> { @@ -276,7 +278,8 @@ class ApplicationListRVAdapter( searchApp._id, searchApp.origin, catText, - searchApp.isGplayReplaced + searchApp.isGplayReplaced, + searchApp.isPurchased ) } else -> null 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 faa437312..62886ab2d 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 @@ -97,7 +97,8 @@ class HomeChildRVAdapter( homeApp._id, homeApp.origin, homeApp.category, - homeApp.isGplayReplaced + homeApp.isGplayReplaced, + homeApp.isPurchased ) holder.itemView.findNavController().navigate(action) } diff --git a/app/src/main/res/layout/fragment_application_download.xml b/app/src/main/res/layout/fragment_application_download.xml index 4f48a648d..e34b6ecab 100644 --- a/app/src/main/res/layout/fragment_application_download.xml +++ b/app/src/main/res/layout/fragment_application_download.xml @@ -31,6 +31,7 @@ android:id="@+id/progressLayout" android:layout_width="0dp" android:layout_height="wrap_content" + android:visibility="invisible" app:layout_constraintBottom_toBottomOf="@id/installButton" app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toLeftOf="parent" diff --git a/app/src/main/res/navigation/navigation_resource.xml b/app/src/main/res/navigation/navigation_resource.xml index 9077ebf27..90fae739d 100644 --- a/app/src/main/res/navigation/navigation_resource.xml +++ b/app/src/main/res/navigation/navigation_resource.xml @@ -114,6 +114,10 @@ android:name="isGplayReplaced" app:argType="boolean" android:defaultValue="false"/> + -- GitLab