ViewModel: Switch to Hilt for accessing application context

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-03-19 13:13:01 +05:30
parent 13a88afa09
commit c3bae480c4
28 changed files with 421 additions and 712 deletions

View File

@@ -28,12 +28,6 @@ sealed class ViewState {
data class Success<T>(val data: T) : ViewState()
}
sealed class RequestState {
object Init : RequestState()
object Pending : RequestState()
object Complete : RequestState()
}
sealed class AuthState {
object Available : AuthState()
object Unavailable : AuthState()

View File

@@ -20,25 +20,23 @@
package com.aurora.store.view.ui.commons
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.Category
import com.aurora.store.R
import com.aurora.store.databinding.FragmentGenericRecyclerBinding
import com.aurora.store.view.epoxy.views.CategoryViewModel_
import com.aurora.store.viewmodel.category.AppCategoryViewModel
import com.aurora.store.viewmodel.category.BaseCategoryViewModel
import com.aurora.store.viewmodel.category.GameCategoryViewModel
import com.aurora.store.viewmodel.category.CategoryViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class CategoryFragment : BaseFragment() {
class CategoryFragment : BaseFragment(R.layout.fragment_generic_recycler) {
private lateinit var B: FragmentGenericRecyclerBinding
private lateinit var VM: BaseCategoryViewModel
private var _binding: FragmentGenericRecyclerBinding? = null
private val binding get() = _binding!!
private val viewModel: CategoryViewModel by viewModels()
private var pageType = 0
@@ -53,18 +51,9 @@ class CategoryFragment : BaseFragment() {
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
B = FragmentGenericRecyclerBinding.bind(
inflater.inflate(
R.layout.fragment_generic_recycler,
container,
false
)
)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentGenericRecyclerBinding.bind(view)
val bundle = arguments
if (bundle != null) {
@@ -72,31 +61,27 @@ class CategoryFragment : BaseFragment() {
}
when (pageType) {
0 -> VM = ViewModelProvider(this)[AppCategoryViewModel::class.java]
1 -> VM = ViewModelProvider(this)[GameCategoryViewModel::class.java]
0 -> viewModel.getCategoryList(Category.Type.APPLICATION)
1 -> viewModel.getCategoryList(Category.Type.GAME)
}
return B.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
VM.liveData.observe(viewLifecycleOwner) {
updateController(it)
}
}
private fun updateController(categoryList: List<Category>) {
B.recycler.withModels {
setFilterDuplicates(true)
categoryList.forEach {
add(
CategoryViewModel_()
.id(it.title)
.category(it)
.click { _ -> openCategoryBrowseFragment(it) }
)
viewModel.liveData.observe(viewLifecycleOwner) { categoryList ->
binding.recycler.withModels {
setFilterDuplicates(true)
categoryList.forEach {
add(
CategoryViewModel_()
.id(it.title)
.category(it)
.click { _ -> openCategoryBrowseFragment(it) }
)
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -20,27 +20,25 @@
package com.aurora.store.view.ui.commons
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.fragment.app.viewModels
import com.aurora.Constants
import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle
import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.R
import com.aurora.store.databinding.FragmentForYouBinding
import com.aurora.store.view.epoxy.controller.EditorChoiceController
import com.aurora.store.viewmodel.editorschoice.AppEditorChoiceViewModel
import com.aurora.store.viewmodel.editorschoice.BaseEditorChoiceViewModel
import com.aurora.store.viewmodel.editorschoice.GameEditorChoiceViewModel
import com.aurora.store.viewmodel.editorschoice.EditorChoiceViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class EditorChoiceFragment : BaseFragment(), EditorChoiceController.Callbacks {
class EditorChoiceFragment : BaseFragment(R.layout.fragment_for_you),
EditorChoiceController.Callbacks {
private lateinit var B: FragmentForYouBinding
private lateinit var C: EditorChoiceController
private lateinit var VM: BaseEditorChoiceViewModel
private var _binding: FragmentForYouBinding? = null
private val binding get() = _binding!!
private val viewModel: EditorChoiceViewModel by viewModels()
private var pageType = 0
@@ -55,46 +53,31 @@ class EditorChoiceFragment : BaseFragment(), EditorChoiceController.Callbacks {
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
B = FragmentForYouBinding.bind(
inflater.inflate(
R.layout.fragment_for_you,
container,
false
)
)
C = EditorChoiceController(this)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentForYouBinding.bind(view)
val bundle = arguments
if (bundle != null) {
pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
}
val editorChoiceController = EditorChoiceController(this)
binding.recycler.setController(editorChoiceController)
when (pageType) {
0 -> VM = ViewModelProvider(this)[AppEditorChoiceViewModel::class.java]
1 -> VM = ViewModelProvider(this)[GameEditorChoiceViewModel::class.java]
0 -> viewModel.getEditorChoiceStream(StreamHelper.Category.APPLICATION)
1 -> viewModel.getEditorChoiceStream(StreamHelper.Category.GAME)
}
return B.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
B.recycler.setController(C)
VM.liveData.observe(viewLifecycleOwner) {
updateController(it)
viewModel.liveData.observe(viewLifecycleOwner) {
editorChoiceController.setData(it)
}
}
private fun updateController(editorChoiceBundles: List<EditorChoiceBundle>) {
C.setData(editorChoiceBundles)
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onClick(editorChoiceCluster: EditorChoiceCluster) {

View File

@@ -28,11 +28,7 @@ import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.SearchBundle
@@ -124,32 +120,6 @@ class SearchResultsFragment : BaseFragment(R.layout.fragment_search_result),
query = requireArguments().getString("query")
query?.let { updateQuery(it) }
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
VM.responseCode.collect {
when (it) {
429 -> {
if (VM.liveData.value?.appList?.isEmpty() == true) {
attachErrorLayout(getString(R.string.rate_limited))
} else {
snackbar = Snackbar.make(
binding.root,
getString(R.string.rate_limited),
Snackbar.LENGTH_INDEFINITE
)
snackbar?.show()
}
}
else -> {
if (binding.errorLayout.isVisible) detachErrorLayout()
if (snackbar != null && snackbar?.isShown == true) snackbar?.dismiss()
}
}
}
}
}
}
override fun onDestroyView() {
@@ -163,19 +133,6 @@ class SearchResultsFragment : BaseFragment(R.layout.fragment_search_result),
_binding = null
}
private fun attachErrorLayout(message: String) {
binding.recycler.visibility = View.GONE
binding.filterFab.visibility = View.GONE
binding.errorLayout.visibility = View.VISIBLE
binding.errorMessage.text = message
}
private fun detachErrorLayout() {
binding.recycler.visibility = View.VISIBLE
binding.filterFab.visibility = View.VISIBLE
binding.errorLayout.visibility = View.GONE
}
private fun updateController(searchBundle: SearchBundle?) {
if (searchBundle == null) {
shimmerAnimationVisible = true

View File

@@ -1,64 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.util.Log
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import kotlinx.coroutines.flow.launchIn
import java.lang.reflect.Modifier
abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel(application) {
val responseCode = HttpClient.getPreferredClient(application).responseCode
protected val gson: Gson = GsonBuilder()
.excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC)
.create()
protected var requestState: RequestState
init {
Log.i("${javaClass.simpleName} Created")
requestState = RequestState.Init
// Start collecting response code for requests
responseCode.launchIn(viewModelScope)
}
abstract fun observe()
private fun redoLastNetworkTask() {
when (requestState) {
RequestState.Pending -> {
observe()
}
else -> {
}
}
}
}

View File

@@ -1,70 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.viewmodel.all
import android.app.Application
import android.content.pm.PackageInfo
import androidx.lifecycle.MutableLiveData
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil
import com.aurora.store.viewmodel.BaseAndroidViewModel
abstract class BaseAppsViewModel(application: Application) : BaseAndroidViewModel(application) {
private val authData = AuthProvider
.with(application)
.getAuthData()
private val appDetailsHelper = AppDetailsHelper(authData)
.using(HttpClient.getPreferredClient(application))
var blacklistProvider = BlacklistProvider
.with(application)
val liveData: MutableLiveData<List<App>> = MutableLiveData()
var appList: MutableList<App> = mutableListOf()
var packageInfoMap: MutableMap<String, PackageInfo> = mutableMapOf()
fun getFilteredApps(): List<App> {
val blackList = blacklistProvider.getBlackList()
packageInfoMap.clear()
packageInfoMap = PackageUtil.getPackageInfoMap(getApplication())
packageInfoMap.keys.let { packages ->
/*Filter black list*/
val filtersPackages = packages
.filter { !blackList.contains(it) }
return appDetailsHelper
.getAppByPackageName(filtersPackages)
.filter { it.displayName.isNotEmpty() }
.map {
it.isInstalled = true
it
}
}
}
}

View File

@@ -19,25 +19,32 @@
package com.aurora.store.viewmodel.all
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.store.data.RequestState
import com.aurora.store.data.model.Black
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import java.util.Locale
import javax.inject.Inject
class BlacklistViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class BlacklistViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val packageManager: PackageManager = application.packageManager
private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(application)
private val packageManager: PackageManager = context.packageManager
private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(context)
var blackList: MutableList<Black> = mutableListOf()
var selected: MutableSet<String> = mutableSetOf()
@@ -45,16 +52,15 @@ class BlacklistViewModel(application: Application) : BaseAndroidViewModel(applic
val liveData: MutableLiveData<List<Black>> = MutableLiveData()
init {
requestState = RequestState.Init
selected = blacklistProvider.getBlackList()
observe()
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val packageInfoMap = PackageUtil.getPackageInfoMap(getApplication())
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
packageInfoMap.values
.filter {
it.packageName != null
@@ -72,10 +78,8 @@ class BlacklistViewModel(application: Application) : BaseAndroidViewModel(applic
blackList.add(black)
}
liveData.postValue(blackList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
requestState = RequestState.Complete
} catch (e: Exception) {
e.printStackTrace()
requestState = RequestState.Pending
}
}
}

View File

@@ -19,34 +19,49 @@
package com.aurora.store.viewmodel.all
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.extensions.flushAndAdd
import com.aurora.store.data.RequestState
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.event.BusEvent
import com.aurora.store.util.AppUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.Locale
import javax.inject.Inject
class InstalledViewModel(application: Application) : BaseAppsViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class InstalledViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName
var appList: MutableList<App> = mutableListOf()
val liveData: MutableLiveData<List<App>> = MutableLiveData()
init {
EventBus.getDefault().register(this)
requestState = RequestState.Init
observe()
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
requestState = try {
appList = AppUtil.getFilteredInstalledApps(getApplication()).toMutableList()
try {
appList = AppUtil.getFilteredInstalledApps(context).toMutableList()
liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
RequestState.Complete
} catch (e: Exception) {
RequestState.Pending
} catch (exception: Exception) {
Log.e(TAG, "Failed to get installed apps", exception)
}
}
}

View File

@@ -19,34 +19,37 @@
package com.aurora.store.viewmodel.all
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.ClusterHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class LibraryAppsViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val clusterHelper: ClusterHelper =
ClusterHelper(authData).using(HttpClient.getPreferredClient(application))
ClusterHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()
init {
requestState = RequestState.Init
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
@@ -62,12 +65,9 @@ class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(appl
updateCluster(newCluster)
liveData.postValue(streamCluster)
}
else -> {
requestState = RequestState.Complete
}
else -> {}
}
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
@@ -79,4 +79,4 @@ class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(appl
clusterNextPageUrl = newCluster.clusterNextPageUrl
}
}
}
}

View File

@@ -19,15 +19,18 @@
package com.aurora.store.viewmodel.all
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
@@ -37,24 +40,24 @@ data class PaginatedAppList(
var hasMore: Boolean
)
class PurchasedViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class PurchasedViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData = AuthProvider.with(application).getAuthData()
private val authData = AuthProvider.with(context).getAuthData()
private val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient(application))
private val purchaseHelper = PurchaseHelper(authData).using(HttpClient.getPreferredClient(context))
private var appList: MutableList<App> = mutableListOf()
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
init {
requestState = RequestState.Init
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
requestState = try {
try {
val nextAppList = purchaseHelper.getPurchaseHistory(appList.size)
.filter { it.displayName.isNotEmpty() }
@@ -72,11 +75,9 @@ class PurchasedViewModel(application: Application) : BaseAndroidViewModel(applic
)
)
}
RequestState.Complete
} catch (e: Exception) {
RequestState.Pending
} catch (_: Exception) {
}
}
}
}
}
}

View File

@@ -19,14 +19,18 @@
package com.aurora.store.viewmodel.all
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.util.AppUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.Preferences
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
@@ -35,10 +39,12 @@ import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class UpdatesViewModel @Inject constructor(
application: Application,
@ApplicationContext private val context: Context,
private val gson: Gson,
private val downloadWorkerUtil: DownloadWorkerUtil
) : BaseAppsViewModel(application) {
) : ViewModel() {
private val TAG = UpdatesViewModel::class.java.simpleName
@@ -49,14 +55,14 @@ class UpdatesViewModel @Inject constructor(
val downloadsList get() = downloadWorkerUtil.downloadsList
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
try {
val isExtendedUpdateEnabled = Preferences.getBoolean(
getApplication(), Preferences.PREFERENCE_UPDATES_EXTENDED
context, Preferences.PREFERENCE_UPDATES_EXTENDED
)
val updates =
AppUtil.getUpdatableApps(getApplication(), gson, !isExtendedUpdateEnabled)
AppUtil.getUpdatableApps(context, gson, !isExtendedUpdateEnabled)
.sortedBy { it.displayName.lowercase(Locale.getDefault()) }
_updates.emit(updates)
} catch (exception: Exception) {

View File

@@ -19,10 +19,11 @@
package com.aurora.store.viewmodel.auth
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.Constants
import com.aurora.gplayapi.data.models.AuthData
@@ -33,7 +34,6 @@ import com.aurora.gplayapi.helpers.AuthValidator
import com.aurora.store.AccountType
import com.aurora.store.R
import com.aurora.store.data.AuthState
import com.aurora.store.data.RequestState
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.model.InsecureAuth
import com.aurora.store.data.network.HttpClient
@@ -43,7 +43,9 @@ import com.aurora.store.data.providers.SpoofProvider
import com.aurora.store.util.AC2DMTask
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA
import com.aurora.store.viewmodel.BaseAndroidViewModel
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
@@ -51,22 +53,24 @@ import org.greenrobot.eventbus.EventBus
import java.net.ConnectException
import java.net.UnknownHostException
import java.util.*
import javax.inject.Inject
class AuthViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class AuthViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val gson: Gson
) : ViewModel() {
private val TAG = AuthViewModel::class.java.simpleName
private val spoofProvider = SpoofProvider(getApplication())
private val spoofProvider = SpoofProvider(context)
val liveData: MutableLiveData<AuthState> = MutableLiveData()
init {
requestState = RequestState.Init
}
override fun observe() {
fun observe() {
if (liveData.value != AuthState.Fetching) {
val signedIn = Preferences.getBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN)
val signedIn = Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN)
if (signedIn) {
liveData.postValue(AuthState.Available)
buildSavedAuthData()
@@ -80,7 +84,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
liveData.postValue(AuthState.Fetching)
viewModelScope.launch(Dispatchers.IO) {
try {
var properties = NativeDeviceInfoProvider(getApplication()).getNativeDeviceProperties()
var properties = NativeDeviceInfoProvider(context).getNativeDeviceProperties()
if (spoofProvider.isDeviceSpoofEnabled())
properties = spoofProvider.getSpoofDeviceProperties()
@@ -88,9 +92,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
verifyAndSaveAuth(authData, AccountType.GOOGLE)
} catch (exception: Exception) {
liveData.postValue(
AuthState.Failed(
(getApplication() as Context).getString(R.string.failed_to_generate_session)
)
AuthState.Failed(context.getString(R.string.failed_to_generate_session))
)
Log.e(TAG, "Failed to generate Session", exception)
}
@@ -98,12 +100,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
}
fun buildAnonymousAuthData() {
val insecure = Preferences.getBoolean(
getApplication(),
Preferences.PREFERENCE_INSECURE_ANONYMOUS
)
if (insecure) {
if (Preferences.getBoolean(context, Preferences.PREFERENCE_INSECURE_ANONYMOUS)) {
buildInSecureAnonymousAuthData()
} else {
buildSecureAnonymousAuthData()
@@ -114,14 +111,13 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
liveData.postValue(AuthState.Fetching)
viewModelScope.launch(Dispatchers.IO) {
try {
var properties = NativeDeviceInfoProvider(getApplication())
.getNativeDeviceProperties()
var properties = NativeDeviceInfoProvider(context).getNativeDeviceProperties()
if (spoofProvider.isDeviceSpoofEnabled())
properties = spoofProvider.getSpoofDeviceProperties()
val playResponse = HttpClient
.getPreferredClient(getApplication())
.getPreferredClient(context)
.postAuth(
Constants.URL_DISPENSER,
gson.toJson(properties).toByteArray()
@@ -137,7 +133,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
authData.isAnonymous = true
verifyAndSaveAuth(authData, AccountType.ANONYMOUS)
} else {
throwError(playResponse, getApplication())
throwError(playResponse, context)
}
} catch (exception: Exception) {
liveData.postValue(AuthState.Failed(exception.message.toString()))
@@ -150,17 +146,15 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
liveData.postValue(AuthState.Fetching)
viewModelScope.launch(Dispatchers.IO) {
try {
var properties = NativeDeviceInfoProvider(getApplication())
var properties = NativeDeviceInfoProvider(context)
.getNativeDeviceProperties()
if (spoofProvider.isDeviceSpoofEnabled())
properties = spoofProvider.getSpoofDeviceProperties()
val playResponse = HttpClient
.getPreferredClient(getApplication())
.getAuth(
Constants.URL_DISPENSER
)
.getPreferredClient(context)
.getAuth(Constants.URL_DISPENSER)
if (playResponse.isSuccessful) {
val insecureAuth = gson.fromJson(
@@ -181,7 +175,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
authData.isAnonymous = true
verifyAndSaveAuth(authData, AccountType.ANONYMOUS)
} else {
throwError(playResponse, getApplication())
throwError(playResponse, context)
}
} catch (exception: Exception) {
liveData.postValue(AuthState.Failed(exception.message.toString()))
@@ -224,18 +218,17 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
if (isValid(savedAuthData)) {
liveData.postValue(AuthState.Valid)
requestState = RequestState.Complete
} else {
//Generate and validate new auth
val type = AccountProvider.with(getApplication()).getAccountType()
val type = AccountProvider.with(context).getAccountType()
when (type) {
AccountType.GOOGLE -> {
val email = Preferences.getString(
getApplication(),
context,
Constants.ACCOUNT_EMAIL_PLAIN
)
val aasToken = Preferences.getString(
getApplication(),
context,
Constants.ACCOUNT_AAS_PLAIN
)
buildGoogleAuthData(email, aasToken)
@@ -248,24 +241,23 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
} catch (e: Exception) {
val error = when (e) {
is UnknownHostException -> {
(getApplication() as Context).getString(R.string.title_no_network)
context.getString(R.string.title_no_network)
}
is ConnectException -> {
(getApplication() as Context).getString(R.string.server_unreachable)
context.getString(R.string.server_unreachable)
}
else -> {
(getApplication() as Context).getString(R.string.bad_request)
context.getString(R.string.bad_request)
}
}
liveData.postValue(AuthState.Failed(error))
requestState = RequestState.Pending
}
}
}
}
private fun getSavedAuthData(): AuthData {
val rawAuth: String = Preferences.getString(getApplication(), PREFERENCE_AUTH_DATA)
val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA)
return if (rawAuth.isNotBlank())
gson.fromJson(rawAuth, AuthData::class.java)
else
@@ -275,7 +267,7 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
private fun isValid(authData: AuthData): Boolean {
return try {
AuthValidator(authData)
.using(HttpClient.getPreferredClient(getApplication()))
.using(HttpClient.getPreferredClient(context))
.isValid()
} catch (e: Exception) {
false
@@ -292,9 +284,9 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
}
val versionId =
Preferences.getInteger(getApplication(), Preferences.PREFERENCE_VENDING_VERSION)
Preferences.getInteger(context, Preferences.PREFERENCE_VENDING_VERSION)
if (versionId != 0) {
val resources = (getApplication() as Context).resources
val resources = context.resources
authData.deviceInfoProvider?.properties?.let {
it.setProperty(
@@ -312,16 +304,12 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
if (authData.authToken.isNotEmpty() && authData.deviceConfigToken.isNotEmpty()) {
configAuthPref(authData, type, true)
liveData.postValue(AuthState.SignedIn)
requestState = RequestState.Complete
} else {
configAuthPref(authData, type, false)
liveData.postValue(AuthState.SignedOut)
requestState = RequestState.Pending
liveData.postValue(
AuthState.Failed(
(getApplication() as Context).getString(R.string.failed_to_generate_session)
)
AuthState.Failed(context.getString(R.string.failed_to_generate_session))
)
}
}
@@ -329,22 +317,14 @@ class AuthViewModel(application: Application) : BaseAndroidViewModel(application
private fun configAuthPref(authData: AuthData, type: AccountType, signedIn: Boolean) {
if (signedIn) {
//Save Auth Data
Preferences.putString(
getApplication(),
PREFERENCE_AUTH_DATA,
gson.toJson(authData)
)
Preferences.putString(context, PREFERENCE_AUTH_DATA, gson.toJson(authData))
}
//Save Auth Type
Preferences.putString(
getApplication(),
Constants.ACCOUNT_TYPE,
type.name // ANONYMOUS OR GOOGLE
)
Preferences.putString(context, Constants.ACCOUNT_TYPE, type.name)
//Save Auth Status
Preferences.putBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN, signedIn)
Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, signedIn)
}
@Throws(Exception::class)

View File

@@ -19,34 +19,37 @@
package com.aurora.store.viewmodel.browse
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.ExpandedBrowseHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class ExpandedStreamBrowseViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val streamHelper: ExpandedBrowseHelper = ExpandedBrowseHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()
override fun observe() {
requestState = RequestState.Init
}
fun getInitialCluster(expandedStreamUrl: String) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
@@ -57,10 +60,8 @@ class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewM
streamHelper.getExpandedBrowseClusters(browseResponse.browseTab.listUrl)
liveData.postValue(streamCluster)
} else {
requestState = RequestState.Complete
}
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
@@ -84,10 +85,8 @@ class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewM
if (!streamCluster.hasNext()) {
Log.i("End of Bundle")
requestState = RequestState.Complete
}
} catch (e: Exception) {
requestState = RequestState.Pending
}
}
}

View File

@@ -19,26 +19,33 @@
package com.aurora.store.viewmodel.browse
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class StreamBrowseViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()
@@ -49,12 +56,7 @@ class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(app
}
}
private fun getInitialCluster(
browseUrl: String
): StreamCluster {
requestState = RequestState.Init
private fun getInitialCluster(browseUrl: String): StreamCluster {
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
if (browseResponse.contentsUrl.isNotEmpty())
@@ -86,16 +88,10 @@ class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(app
liveData.postValue(streamCluster)
} else {
Log.i("End of Bundle")
requestState = RequestState.Complete
}
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
}
override fun observe() {
requestState = RequestState.Init
}
}

View File

@@ -1,58 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.viewmodel.category
import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.Category
import com.aurora.gplayapi.helpers.CategoryHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseCategoryViewModel(application: Application) : BaseAndroidViewModel(application) {
private val authData: AuthData = AuthProvider
.with(application)
.getAuthData()
private val streamHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient(application))
val liveData: MutableLiveData<List<Category>> = MutableLiveData()
lateinit var type: Category.Type
override fun observe() {
viewModelScope.launch(Dispatchers.IO) {
try {
liveData.postValue(streamHelper.getAllCategoriesList(type))
requestState = RequestState.Complete
} catch (e: Exception) {
requestState = RequestState.Pending
}
}
}
}

View File

@@ -19,24 +19,47 @@
package com.aurora.store.viewmodel.category
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.Category
import com.aurora.store.data.RequestState
import com.aurora.gplayapi.helpers.CategoryHelper
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class AppCategoryViewModel(application: Application) : BaseCategoryViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class CategoryViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
init {
type = Category.Type.APPLICATION
requestState = RequestState.Init
observe()
private val TAG = CategoryViewModel::class.java.simpleName
private val authData: AuthData = AuthProvider
.with(context)
.getAuthData()
private val streamHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<List<Category>> = MutableLiveData()
fun getCategoryList(type: Category.Type) {
viewModelScope.launch(Dispatchers.IO) {
try {
liveData.postValue(streamHelper.getAllCategoriesList(type))
} catch (exception: Exception) {
Log.e(TAG, "Failed fetching list of categories", exception)
}
}
}
}
class GameCategoryViewModel(application: Application) : BaseCategoryViewModel(application) {
init {
type = Category.Type.GAME
requestState = RequestState.Init
observe()
}
}

View File

@@ -19,28 +19,35 @@
package com.aurora.store.viewmodel.details
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class DetailsClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class DetailsClusterViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private var authData: AuthData = AuthProvider.with(application).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application))
private var authData: AuthData = AuthProvider.with(context).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData()
@@ -49,21 +56,13 @@ class DetailsClusterViewModel(application: Application) : BaseAndroidViewModel(a
lateinit var type: StreamHelper.Type
lateinit var category: StreamHelper.Category
override fun observe() {
}
fun getStreamBundle(
streamUrl: String
) {
fun getStreamBundle(streamUrl: String) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
streamBundle = appDetailsHelper.getDetailsStream(streamUrl)
liveData.postValue(ViewState.Success(streamBundle))
requestState = RequestState.Complete
} catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message))
}
}

View File

@@ -19,8 +19,10 @@
package com.aurora.store.viewmodel.details
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle
@@ -28,20 +30,25 @@ import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.data.models.details.DevStream
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class DevProfileViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class DevProfileViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private var authData: AuthData = AuthProvider.with(application).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application))
private var authData: AuthData = AuthProvider.with(context).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData()
@@ -51,22 +58,14 @@ class DevProfileViewModel(application: Application) : BaseAndroidViewModel(appli
lateinit var type: StreamHelper.Type
lateinit var category: StreamHelper.Category
override fun observe() {
}
fun getStreamBundle(
devId: String
) {
fun getStreamBundle(devId: String) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
devStream = appDetailsHelper.getDeveloperStream(devId)
streamBundle = devStream.streamBundle
liveData.postValue(ViewState.Success(devStream))
requestState = RequestState.Complete
} catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message))
}
}

View File

@@ -1,62 +0,0 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store 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 2 of the License, or
* (at your option) any later version.
*
* Aurora Store 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 Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.viewmodel.editorschoice
import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
open class BaseEditorChoiceViewModel(application: Application) : BaseAndroidViewModel(application) {
private val authData: AuthData = AuthProvider
.with(application)
.getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(application))
lateinit var category: StreamHelper.Category
val liveData: MutableLiveData<List<EditorChoiceBundle>> = MutableLiveData()
override fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val editorChoiceBundle = streamHelper.getEditorChoiceStream(category)
liveData.postValue(editorChoiceBundle)
requestState = RequestState.Complete
} catch (e: Exception) {
requestState = RequestState.Pending
}
}
}
}
}

View File

@@ -19,42 +19,40 @@
package com.aurora.store.viewmodel.editorschoice
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class EditorBrowseViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class EditorBrowseViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<MutableList<App>> = MutableLiveData()
val appList: MutableList<App> = mutableListOf()
override fun observe() {
requestState = RequestState.Init
}
fun getEditorStreamBundle(
browseUrl: String
) {
fun getEditorStreamBundle(browseUrl: String) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
requestState = RequestState.Init
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
val listResponse =
streamHelper.getNextStreamResponse(browseResponse.browseTab.listUrl)
@@ -68,10 +66,8 @@ class EditorBrowseViewModel(application: Application) : BaseAndroidViewModel(app
}
liveData.postValue(appList)
requestState = RequestState.Complete
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}

View File

@@ -19,20 +19,51 @@
package com.aurora.store.viewmodel.editorschoice
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class AppEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) {
init {
category = StreamHelper.Category.APPLICATION
observe()
}
}
class GameEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) {
init {
category = StreamHelper.Category.GAME
observe()
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class EditorChoiceViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val TAG = EditorChoiceViewModel::class.java.simpleName
private val authData: AuthData = AuthProvider
.with(context)
.getAuthData()
private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<List<EditorChoiceBundle>> = MutableLiveData()
fun getEditorChoiceStream(category: StreamHelper.Category) {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val editorChoiceBundle = streamHelper.getEditorChoiceStream(category)
liveData.postValue(editorChoiceBundle)
} catch (exception: Exception) {
Log.e(TAG, "Failed fetching list of categories", exception)
}
}
}
}
}

View File

@@ -19,29 +19,36 @@
package com.aurora.store.viewmodel.homestream
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class BaseClusterViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
var authData: AuthData = AuthProvider.with(application).getAuthData()
var authData: AuthData = AuthProvider.with(context).getAuthData()
var streamHelper: StreamHelper =
StreamHelper(authData).using(HttpClient.getPreferredClient(application))
StreamHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()
@@ -56,7 +63,7 @@ class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(appl
observe()
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
@@ -79,10 +86,8 @@ class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(appl
liveData.postValue(ViewState.Success(streamBundle))
} else {
Log.i("End of Bundle")
requestState = RequestState.Complete
}
} catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message))
}
}

View File

@@ -19,29 +19,36 @@
package com.aurora.store.viewmodel.review
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.Review
import com.aurora.gplayapi.data.models.ReviewCluster
import com.aurora.gplayapi.helpers.ReviewsHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class ReviewViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class ReviewViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
var authData: AuthData = AuthProvider
.with(application)
.with(context)
.getAuthData()
var reviewsHelper: ReviewsHelper = ReviewsHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
@@ -53,8 +60,7 @@ class ReviewViewModel(application: Application) : BaseAndroidViewModel(applicati
try {
reviewsCluster = reviewsHelper.getReviews(packageName, filter)
liveData.postValue(reviewsCluster)
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
@@ -72,14 +78,9 @@ class ReviewViewModel(application: Application) : BaseAndroidViewModel(applicati
}
liveData.postValue(reviewsCluster)
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
}
override fun observe() {
}
}
}

View File

@@ -19,26 +19,33 @@
package com.aurora.store.viewmodel.sale
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppSalesHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import com.aurora.store.viewmodel.all.PaginatedAppList
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class AppSalesViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class AppSalesViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val appSalesHelper: AppSalesHelper =
AppSalesHelper(authData).using(HttpClient.getPreferredClient(application))
AppSalesHelper(authData).using(HttpClient.getPreferredClient(context))
private var page: Int = 0
private val appList: MutableList<App> = mutableListOf()
@@ -46,14 +53,13 @@ class AppSalesViewModel(application: Application) : BaseAndroidViewModel(applica
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
init {
requestState = RequestState.Init
observe()
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
requestState = try {
try {
val nextAppList = getSearchResults()
if (nextAppList.isEmpty()) {
@@ -73,16 +79,13 @@ class AppSalesViewModel(application: Application) : BaseAndroidViewModel(applica
)
}
RequestState.Complete
} catch (e: Exception) {
RequestState.Pending
} catch (_: Exception) {
}
}
}
}
private fun getSearchResults(
): List<App> {
private fun getSearchResults(): List<App> {
return try {
appSalesHelper.getAppsOnSale(page = page++, offer = 100)
} catch (e: Exception) {

View File

@@ -19,33 +19,40 @@
package com.aurora.store.viewmodel.search
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.extensions.flushAndAdd
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.SearchBundle
import com.aurora.gplayapi.helpers.SearchHelper
import com.aurora.gplayapi.helpers.WebSearchHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class SearchResultViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class SearchResultViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val TAG = SearchResultViewModel::class.java.simpleName
private val authData: AuthData = AuthProvider
.with(application)
.with(context)
.getAuthData()
private val webSearchHelper: WebSearchHelper = WebSearchHelper(authData)
private val searchHelper: SearchHelper = SearchHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<SearchBundle> = MutableLiveData()
@@ -87,7 +94,7 @@ class SearchResultViewModel(application: Application) : BaseAndroidViewModel(app
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
if (nextSubBundleSet.isNotEmpty() && responseCode.value != 429) {
if (nextSubBundleSet.isNotEmpty()) {
val newSearchBundle = helper().next(nextSubBundleSet)
if (newSearchBundle.appList.isNotEmpty()) {
searchBundle.apply {
@@ -99,13 +106,9 @@ class SearchResultViewModel(application: Application) : BaseAndroidViewModel(app
}
}
} catch (e: Exception) {
Log.d(TAG, "Response code: ${responseCode.value}", e)
Log.d(TAG, "Failed to get next bundle", e)
}
}
}
}
override fun observe() {
requestState = RequestState.Init
}
}

View File

@@ -19,28 +19,35 @@
package com.aurora.store.viewmodel.subcategory
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.CategoryHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class SubCategoryClusterViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
var authData: AuthData = AuthProvider.with(application).getAuthData()
var authData: AuthData = AuthProvider.with(context).getAuthData()
var categoryHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient(application))
.using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle()
@@ -65,7 +72,7 @@ class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewMod
observe()
}
override fun observe() {
fun observe() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
@@ -85,11 +92,9 @@ class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewMod
liveData.postValue(ViewState.Success(streamBundle))
} else {
Log.i("End of Bundle")
requestState = RequestState.Complete
}
} catch (e: Exception) {
e.printStackTrace()
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message))
}
}

View File

@@ -19,25 +19,32 @@
package com.aurora.store.viewmodel.topchart
import android.app.Application
import android.annotation.SuppressLint
import android.content.Context
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.TopChartsHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
class TopChartViewModel(application: Application) : BaseAndroidViewModel(application) {
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class TopChartViewModel @Inject constructor(
@ApplicationContext private val context: Context
) : ViewModel() {
private val authData: AuthData = AuthProvider.with(application).getAuthData()
private val authData: AuthData = AuthProvider.with(context).getAuthData()
private val topChartsHelper: TopChartsHelper =
TopChartsHelper(authData).using(HttpClient.getPreferredClient(application))
TopChartsHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster()
@@ -47,14 +54,11 @@ class TopChartViewModel(application: Application) : BaseAndroidViewModel(applica
try {
streamCluster = topChartsHelper.getCluster(type, chart)
liveData.postValue(streamCluster)
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}
override fun observe() {}
fun nextCluster() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
@@ -71,10 +75,8 @@ class TopChartViewModel(application: Application) : BaseAndroidViewModel(applica
liveData.postValue(streamCluster)
} else {
requestState = RequestState.Complete
}
} catch (e: Exception) {
requestState = RequestState.Pending
} catch (_: Exception) {
}
}
}

View File

@@ -42,30 +42,6 @@
app:itemSpacing="@dimen/margin_normal" />
</RelativeLayout>
<LinearLayout
android:id="@+id/error_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="@dimen/margin_normal"
android:gravity="center|center_vertical"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="96dp"
android:layout_height="96dp"
android:contentDescription="@string/error"
android:src="@drawable/ic_problem" />
<TextView
android:id="@+id/error_message"
style="@style/TextAppearance.Aurora.SubTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rate_limited" />
</LinearLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/filter_fab"
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"