Loading app/src/main/java/foundation/e/apps/MainActivity.kt +49 −12 Original line number Diff line number Diff line Loading @@ -26,13 +26,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavOptions import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import com.aurora.gplayapi.exceptions.ApiException import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.databinding.ActivityMainBinding import foundation.e.apps.manager.workmanager.InstallWorkManager import foundation.e.apps.purchase.AppPurchaseFragmentDirections import foundation.e.apps.setup.signin.SignInViewModel import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.Status Loading Loading @@ -156,17 +159,6 @@ class MainActivity : AppCompatActivity() { // Observe and handle downloads viewModel.downloadList.observe(this) { list -> // val shouldDownload = list.any { // it.status == Status.INSTALLING || it.status == Status.DOWNLOADING || it.status == Status.INSTALLED // } // if (!shouldDownload && list.isNotEmpty()) { // for (item in list) { // if (item.status == Status.QUEUED) { // viewModel.downloadApp(item) // break // } // } // } list.forEach { if (it.status == Status.QUEUED) { lifecycleScope.launch { Loading @@ -178,10 +170,41 @@ class MainActivity : AppCompatActivity() { } } viewModel.purchaseAppLiveData.observe(this) { val action = AppPurchaseFragmentDirections.actionGlobalAppPurchaseFragment(it.package_name) findNavController(R.id.fragment).navigate(action) } viewModel.errorMessage.observe(this) { when (it) { is ApiException.AppNotPurchased -> showSnackbarMessage(getString(R.string.message_app_available_later)) else -> showSnackbarMessage(it.localizedMessage ?: getString(R.string.unknown_error)) else -> showSnackbarMessage( it.localizedMessage ?: getString(R.string.unknown_error) ) } } viewModel.errorMessageStringResource.observe(this) { showSnackbarMessage(getString(it)) } viewModel.isAppPurchased.observe(this) { if (it.isNotEmpty()) { startInstallationOfPurchasedApp(viewModel, it) ApplicationDialogFragment( title = "Purchase complete!", message = "Your app will automatically be downloaded in this device", positiveButtonText = "OK" ).show(supportFragmentManager, TAG) } } viewModel.purchaseDeclined.observe(this) { if (it.isNotEmpty()) { lifecycleScope.launch { viewModel.updateUnavailableForPurchaseDeclined(it) } } } Loading @@ -190,6 +213,20 @@ class MainActivity : AppCompatActivity() { } } private fun startInstallationOfPurchasedApp( viewModel: MainActivityViewModel, it: String ) { lifecycleScope.launch { val fusedDownload = viewModel.updateAwaitingForPurchasedApp(it) if (fusedDownload != null) { InstallWorkManager.enqueueWork(applicationContext, fusedDownload) } else { showSnackbarMessage(getString(R.string.paid_app_anonymous_message)) } } } private fun showSnackbarMessage(message: String) { Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() } Loading app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +82 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package foundation.e.apps import android.graphics.Bitmap import android.os.Build import android.util.Base64 import android.util.Log import android.widget.ImageView import androidx.annotation.RequiresApi import androidx.core.graphics.drawable.toBitmap Loading @@ -31,12 +32,14 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.exceptions.ApiException import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.modules.DataStoreModule Loading @@ -61,6 +64,10 @@ class MainActivityViewModel @Inject constructor( private var _authData: MutableLiveData<AuthData> = MutableLiveData() val authData: LiveData<AuthData> = _authData val authValidity: MutableLiveData<Boolean> = MutableLiveData() private val _purchaseAppLiveData: MutableLiveData<FusedDownload> = MutableLiveData() val purchaseAppLiveData: LiveData<FusedDownload> = _purchaseAppLiveData val isAppPurchased: MutableLiveData<String> = MutableLiveData() val purchaseDeclined: MutableLiveData<String> = MutableLiveData() var authRequestRunning = false // Downloads Loading @@ -68,10 +75,17 @@ class MainActivityViewModel @Inject constructor( var installInProgress = false private val _errorMessage = MutableLiveData<Exception>() val errorMessage: LiveData<Exception> = _errorMessage private val _errorMessageStringResource = MutableLiveData<Int>() val errorMessageStringResource: LiveData<Int> = _errorMessageStringResource /* * Authentication related functions */ companion object { private const val TAG = "MainActivityViewModel" } fun getAuthData() { if (!authRequestRunning) { authRequestRunning = true Loading Loading @@ -124,6 +138,10 @@ class MainActivityViewModel @Inject constructor( } fun getApplication(app: FusedApp, imageView: ImageView?) { if (!app.isFree && authData.value?.isAnonymous == true) { _errorMessageStringResource.value = R.string.paid_app_anonymous_message return } viewModelScope.launch { val fusedDownload: FusedDownload try { Loading @@ -141,9 +159,16 @@ class MainActivityViewModel @Inject constructor( mutableMapOf(), app.status, app.type, appIcon appIcon, app.latest_version_code, app.offer_type, app.isFree ) } catch (e: Exception) { if (e is ApiException.AppNotPurchased) { handleAppNotPurchased(imageView, app) return@launch } _errorMessage.value = e return@launch } Loading @@ -155,10 +180,66 @@ class MainActivityViewModel @Inject constructor( } } private fun handleAppNotPurchased( imageView: ImageView?, app: FusedApp ) { val appIcon = imageView?.let { getImageBase64(it) } ?: "" val fusedDownload = FusedDownload( app._id, app.origin, Status.PURCHASE_NEEDED, app.name, app.package_name, mutableListOf(), mutableMapOf(), app.status, app.type, appIcon, app.latest_version_code, app.offer_type, app.isFree ) viewModelScope.launch { fusedManagerRepository.addFusedDownloadPurchaseNeeded(fusedDownload) _purchaseAppLiveData.postValue(fusedDownload) } } suspend fun updateAwaiting(fusedDownload: FusedDownload) { fusedManagerRepository.updateAwaiting(fusedDownload) } suspend fun updateAwaitingForPurchasedApp(packageName: String): FusedDownload? { val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = packageName) authData.value?.let { if (!it.isAnonymous) { try { fusedDownload.downloadURLList = fusedAPIRepository.getDownloadLink( fusedDownload.id, fusedDownload.package_name, fusedDownload.versionCode, fusedDownload.offerType, it, Origin.GPLAY ).toMutableList() } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) _errorMessage.value = e return null } updateAwaiting(fusedDownload) return fusedDownload } } return null } suspend fun updateUnavailableForPurchaseDeclined(packageName: String) { val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = packageName) fusedManagerRepository.updateUnavailable(fusedDownload) } fun cancelDownload(app: FusedApp) { viewModelScope.launch { val fusedDownload = Loading app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +4 −3 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package foundation.e.apps.api.fused import android.content.Context import android.text.format.Formatter import android.util.Log import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork Loading Loading @@ -202,7 +201,8 @@ class FusedAPIImpl @Inject constructor( } return response?.apps } else { return gPlayAPIRepository.listApps(browseUrl, authData).map { app -> val listApps = gPlayAPIRepository.listApps(browseUrl, authData) return listApps.map { app -> app.transformToFusedApp() } } Loading Loading @@ -496,7 +496,8 @@ class FusedAPIImpl @Inject constructor( } private suspend fun getGplaySearchResults(query: String, authData: AuthData): List<FusedApp> { return gPlayAPIRepository.getSearchResults(query, authData).map { app -> val searchResults = gPlayAPIRepository.getSearchResults(query, authData) return searchResults.map { app -> app.transformToFusedApp() } } Loading app/src/main/java/foundation/e/apps/application/subFrags/ApplicationDialogFragment.kt +3 −1 Original line number Diff line number Diff line Loading @@ -52,10 +52,12 @@ class ApplicationDialogFragment( positiveButtonAction?.invoke() this.dismiss() } .setNegativeButton(cancelButtonText) { _, _ -> if (cancelButtonText.isNotEmpty()) { materialAlertDialogBuilder.setNegativeButton(cancelButtonText) { _, _ -> cancelButtonAction?.invoke() this.dismiss() } } if (drawable != -1) { materialAlertDialogBuilder.setIcon(drawable) } Loading app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +1 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package foundation.e.apps.applicationlist import android.os.Bundle import android.util.Log import android.view.View import android.widget.ImageView import androidx.fragment.app.Fragment Loading Loading @@ -121,6 +120,7 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu message = getString(R.string.dialog_paidapp_message, fusedApp.name, fusedApp.price), positiveButtonText = getString(R.string.dialog_confirm), positiveButtonAction = { getApplication(fusedApp) }, cancelButtonText = getString(R.string.dialog_cancel), ).show(childFragmentManager, "HomeFragment") Loading Loading @@ -171,10 +171,6 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu val progress = appProgressViewModel.calculateProgress(fusedApp, it) val downloadProgress = ((progress.second / progress.first.toDouble()) * 100).toInt() Log.d( "HomeParentAdapter", "download progress of ===> ${fusedApp.name} : $downloadProgress" ) val viewHolder = recyclerView.findViewHolderForAdapterPosition( adapter.currentList.indexOf(fusedApp) ) Loading Loading
app/src/main/java/foundation/e/apps/MainActivity.kt +49 −12 Original line number Diff line number Diff line Loading @@ -26,13 +26,16 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.navigation.NavOptions import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import com.aurora.gplayapi.exceptions.ApiException import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import foundation.e.apps.application.subFrags.ApplicationDialogFragment import foundation.e.apps.databinding.ActivityMainBinding import foundation.e.apps.manager.workmanager.InstallWorkManager import foundation.e.apps.purchase.AppPurchaseFragmentDirections import foundation.e.apps.setup.signin.SignInViewModel import foundation.e.apps.updates.UpdatesNotifier import foundation.e.apps.utils.enums.Status Loading Loading @@ -156,17 +159,6 @@ class MainActivity : AppCompatActivity() { // Observe and handle downloads viewModel.downloadList.observe(this) { list -> // val shouldDownload = list.any { // it.status == Status.INSTALLING || it.status == Status.DOWNLOADING || it.status == Status.INSTALLED // } // if (!shouldDownload && list.isNotEmpty()) { // for (item in list) { // if (item.status == Status.QUEUED) { // viewModel.downloadApp(item) // break // } // } // } list.forEach { if (it.status == Status.QUEUED) { lifecycleScope.launch { Loading @@ -178,10 +170,41 @@ class MainActivity : AppCompatActivity() { } } viewModel.purchaseAppLiveData.observe(this) { val action = AppPurchaseFragmentDirections.actionGlobalAppPurchaseFragment(it.package_name) findNavController(R.id.fragment).navigate(action) } viewModel.errorMessage.observe(this) { when (it) { is ApiException.AppNotPurchased -> showSnackbarMessage(getString(R.string.message_app_available_later)) else -> showSnackbarMessage(it.localizedMessage ?: getString(R.string.unknown_error)) else -> showSnackbarMessage( it.localizedMessage ?: getString(R.string.unknown_error) ) } } viewModel.errorMessageStringResource.observe(this) { showSnackbarMessage(getString(it)) } viewModel.isAppPurchased.observe(this) { if (it.isNotEmpty()) { startInstallationOfPurchasedApp(viewModel, it) ApplicationDialogFragment( title = "Purchase complete!", message = "Your app will automatically be downloaded in this device", positiveButtonText = "OK" ).show(supportFragmentManager, TAG) } } viewModel.purchaseDeclined.observe(this) { if (it.isNotEmpty()) { lifecycleScope.launch { viewModel.updateUnavailableForPurchaseDeclined(it) } } } Loading @@ -190,6 +213,20 @@ class MainActivity : AppCompatActivity() { } } private fun startInstallationOfPurchasedApp( viewModel: MainActivityViewModel, it: String ) { lifecycleScope.launch { val fusedDownload = viewModel.updateAwaitingForPurchasedApp(it) if (fusedDownload != null) { InstallWorkManager.enqueueWork(applicationContext, fusedDownload) } else { showSnackbarMessage(getString(R.string.paid_app_anonymous_message)) } } } private fun showSnackbarMessage(message: String) { Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() } Loading
app/src/main/java/foundation/e/apps/MainActivityViewModel.kt +82 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ package foundation.e.apps import android.graphics.Bitmap import android.os.Build import android.util.Base64 import android.util.Log import android.widget.ImageView import androidx.annotation.RequiresApi import androidx.core.graphics.drawable.toBitmap Loading @@ -31,12 +32,14 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.liveData import androidx.lifecycle.viewModelScope import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.exceptions.ApiException import com.google.gson.Gson import dagger.hilt.android.lifecycle.HiltViewModel import foundation.e.apps.api.fused.FusedAPIRepository import foundation.e.apps.api.fused.data.FusedApp import foundation.e.apps.manager.database.fusedDownload.FusedDownload import foundation.e.apps.manager.fused.FusedManagerRepository import foundation.e.apps.utils.enums.Origin import foundation.e.apps.utils.enums.Status import foundation.e.apps.utils.enums.Type import foundation.e.apps.utils.modules.DataStoreModule Loading @@ -61,6 +64,10 @@ class MainActivityViewModel @Inject constructor( private var _authData: MutableLiveData<AuthData> = MutableLiveData() val authData: LiveData<AuthData> = _authData val authValidity: MutableLiveData<Boolean> = MutableLiveData() private val _purchaseAppLiveData: MutableLiveData<FusedDownload> = MutableLiveData() val purchaseAppLiveData: LiveData<FusedDownload> = _purchaseAppLiveData val isAppPurchased: MutableLiveData<String> = MutableLiveData() val purchaseDeclined: MutableLiveData<String> = MutableLiveData() var authRequestRunning = false // Downloads Loading @@ -68,10 +75,17 @@ class MainActivityViewModel @Inject constructor( var installInProgress = false private val _errorMessage = MutableLiveData<Exception>() val errorMessage: LiveData<Exception> = _errorMessage private val _errorMessageStringResource = MutableLiveData<Int>() val errorMessageStringResource: LiveData<Int> = _errorMessageStringResource /* * Authentication related functions */ companion object { private const val TAG = "MainActivityViewModel" } fun getAuthData() { if (!authRequestRunning) { authRequestRunning = true Loading Loading @@ -124,6 +138,10 @@ class MainActivityViewModel @Inject constructor( } fun getApplication(app: FusedApp, imageView: ImageView?) { if (!app.isFree && authData.value?.isAnonymous == true) { _errorMessageStringResource.value = R.string.paid_app_anonymous_message return } viewModelScope.launch { val fusedDownload: FusedDownload try { Loading @@ -141,9 +159,16 @@ class MainActivityViewModel @Inject constructor( mutableMapOf(), app.status, app.type, appIcon appIcon, app.latest_version_code, app.offer_type, app.isFree ) } catch (e: Exception) { if (e is ApiException.AppNotPurchased) { handleAppNotPurchased(imageView, app) return@launch } _errorMessage.value = e return@launch } Loading @@ -155,10 +180,66 @@ class MainActivityViewModel @Inject constructor( } } private fun handleAppNotPurchased( imageView: ImageView?, app: FusedApp ) { val appIcon = imageView?.let { getImageBase64(it) } ?: "" val fusedDownload = FusedDownload( app._id, app.origin, Status.PURCHASE_NEEDED, app.name, app.package_name, mutableListOf(), mutableMapOf(), app.status, app.type, appIcon, app.latest_version_code, app.offer_type, app.isFree ) viewModelScope.launch { fusedManagerRepository.addFusedDownloadPurchaseNeeded(fusedDownload) _purchaseAppLiveData.postValue(fusedDownload) } } suspend fun updateAwaiting(fusedDownload: FusedDownload) { fusedManagerRepository.updateAwaiting(fusedDownload) } suspend fun updateAwaitingForPurchasedApp(packageName: String): FusedDownload? { val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = packageName) authData.value?.let { if (!it.isAnonymous) { try { fusedDownload.downloadURLList = fusedAPIRepository.getDownloadLink( fusedDownload.id, fusedDownload.package_name, fusedDownload.versionCode, fusedDownload.offerType, it, Origin.GPLAY ).toMutableList() } catch (e: Exception) { Log.e(TAG, e.stackTraceToString()) _errorMessage.value = e return null } updateAwaiting(fusedDownload) return fusedDownload } } return null } suspend fun updateUnavailableForPurchaseDeclined(packageName: String) { val fusedDownload = fusedManagerRepository.getFusedDownload(packageName = packageName) fusedManagerRepository.updateUnavailable(fusedDownload) } fun cancelDownload(app: FusedApp) { viewModelScope.launch { val fusedDownload = Loading
app/src/main/java/foundation/e/apps/api/fused/FusedAPIImpl.kt +4 −3 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ package foundation.e.apps.api.fused import android.content.Context import android.text.format.Formatter import android.util.Log import com.aurora.gplayapi.SearchSuggestEntry import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.Artwork Loading Loading @@ -202,7 +201,8 @@ class FusedAPIImpl @Inject constructor( } return response?.apps } else { return gPlayAPIRepository.listApps(browseUrl, authData).map { app -> val listApps = gPlayAPIRepository.listApps(browseUrl, authData) return listApps.map { app -> app.transformToFusedApp() } } Loading Loading @@ -496,7 +496,8 @@ class FusedAPIImpl @Inject constructor( } private suspend fun getGplaySearchResults(query: String, authData: AuthData): List<FusedApp> { return gPlayAPIRepository.getSearchResults(query, authData).map { app -> val searchResults = gPlayAPIRepository.getSearchResults(query, authData) return searchResults.map { app -> app.transformToFusedApp() } } Loading
app/src/main/java/foundation/e/apps/application/subFrags/ApplicationDialogFragment.kt +3 −1 Original line number Diff line number Diff line Loading @@ -52,10 +52,12 @@ class ApplicationDialogFragment( positiveButtonAction?.invoke() this.dismiss() } .setNegativeButton(cancelButtonText) { _, _ -> if (cancelButtonText.isNotEmpty()) { materialAlertDialogBuilder.setNegativeButton(cancelButtonText) { _, _ -> cancelButtonAction?.invoke() this.dismiss() } } if (drawable != -1) { materialAlertDialogBuilder.setIcon(drawable) } Loading
app/src/main/java/foundation/e/apps/applicationlist/ApplicationListFragment.kt +1 −5 Original line number Diff line number Diff line Loading @@ -19,7 +19,6 @@ package foundation.e.apps.applicationlist import android.os.Bundle import android.util.Log import android.view.View import android.widget.ImageView import androidx.fragment.app.Fragment Loading Loading @@ -121,6 +120,7 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu message = getString(R.string.dialog_paidapp_message, fusedApp.name, fusedApp.price), positiveButtonText = getString(R.string.dialog_confirm), positiveButtonAction = { getApplication(fusedApp) }, cancelButtonText = getString(R.string.dialog_cancel), ).show(childFragmentManager, "HomeFragment") Loading Loading @@ -171,10 +171,6 @@ class ApplicationListFragment : Fragment(R.layout.fragment_application_list), Fu val progress = appProgressViewModel.calculateProgress(fusedApp, it) val downloadProgress = ((progress.second / progress.first.toDouble()) * 100).toInt() Log.d( "HomeParentAdapter", "download progress of ===> ${fusedApp.name} : $downloadProgress" ) val viewHolder = recyclerView.findViewHolderForAdapterPosition( adapter.currentList.indexOf(fusedApp) ) Loading