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() data class Success<T>(val data: T) : ViewState()
} }
sealed class RequestState {
object Init : RequestState()
object Pending : RequestState()
object Complete : RequestState()
}
sealed class AuthState { sealed class AuthState {
object Available : AuthState() object Available : AuthState()
object Unavailable : AuthState() object Unavailable : AuthState()

View File

@@ -20,25 +20,23 @@
package com.aurora.store.view.ui.commons package com.aurora.store.view.ui.commons
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import com.aurora.Constants import com.aurora.Constants
import com.aurora.gplayapi.data.models.Category import com.aurora.gplayapi.data.models.Category
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.databinding.FragmentGenericRecyclerBinding import com.aurora.store.databinding.FragmentGenericRecyclerBinding
import com.aurora.store.view.epoxy.views.CategoryViewModel_ import com.aurora.store.view.epoxy.views.CategoryViewModel_
import com.aurora.store.viewmodel.category.AppCategoryViewModel import com.aurora.store.viewmodel.category.CategoryViewModel
import com.aurora.store.viewmodel.category.BaseCategoryViewModel
import com.aurora.store.viewmodel.category.GameCategoryViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class CategoryFragment : BaseFragment() { class CategoryFragment : BaseFragment(R.layout.fragment_generic_recycler) {
private lateinit var B: FragmentGenericRecyclerBinding private var _binding: FragmentGenericRecyclerBinding? = null
private lateinit var VM: BaseCategoryViewModel private val binding get() = _binding!!
private val viewModel: CategoryViewModel by viewModels()
private var pageType = 0 private var pageType = 0
@@ -53,18 +51,9 @@ class CategoryFragment : BaseFragment() {
} }
} }
override fun onCreateView( override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
inflater: LayoutInflater, super.onViewCreated(view, savedInstanceState)
container: ViewGroup?, _binding = FragmentGenericRecyclerBinding.bind(view)
savedInstanceState: Bundle?
): View {
B = FragmentGenericRecyclerBinding.bind(
inflater.inflate(
R.layout.fragment_generic_recycler,
container,
false
)
)
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
@@ -72,31 +61,27 @@ class CategoryFragment : BaseFragment() {
} }
when (pageType) { when (pageType) {
0 -> VM = ViewModelProvider(this)[AppCategoryViewModel::class.java] 0 -> viewModel.getCategoryList(Category.Type.APPLICATION)
1 -> VM = ViewModelProvider(this)[GameCategoryViewModel::class.java] 1 -> viewModel.getCategoryList(Category.Type.GAME)
} }
return B.root viewModel.liveData.observe(viewLifecycleOwner) { categoryList ->
} binding.recycler.withModels {
setFilterDuplicates(true)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { categoryList.forEach {
super.onViewCreated(view, savedInstanceState) add(
VM.liveData.observe(viewLifecycleOwner) { CategoryViewModel_()
updateController(it) .id(it.title)
} .category(it)
} .click { _ -> openCategoryBrowseFragment(it) }
)
private fun updateController(categoryList: List<Category>) { }
B.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 package com.aurora.store.view.ui.commons
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import androidx.fragment.app.viewModels
import androidx.lifecycle.ViewModelProvider
import com.aurora.Constants import com.aurora.Constants
import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle
import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster
import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.databinding.FragmentForYouBinding import com.aurora.store.databinding.FragmentForYouBinding
import com.aurora.store.view.epoxy.controller.EditorChoiceController import com.aurora.store.view.epoxy.controller.EditorChoiceController
import com.aurora.store.viewmodel.editorschoice.AppEditorChoiceViewModel import com.aurora.store.viewmodel.editorschoice.EditorChoiceViewModel
import com.aurora.store.viewmodel.editorschoice.BaseEditorChoiceViewModel
import com.aurora.store.viewmodel.editorschoice.GameEditorChoiceViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class EditorChoiceFragment : BaseFragment(), EditorChoiceController.Callbacks { class EditorChoiceFragment : BaseFragment(R.layout.fragment_for_you),
EditorChoiceController.Callbacks {
private lateinit var B: FragmentForYouBinding private var _binding: FragmentForYouBinding? = null
private lateinit var C: EditorChoiceController private val binding get() = _binding!!
private lateinit var VM: BaseEditorChoiceViewModel
private val viewModel: EditorChoiceViewModel by viewModels()
private var pageType = 0 private var pageType = 0
@@ -55,46 +53,31 @@ class EditorChoiceFragment : BaseFragment(), EditorChoiceController.Callbacks {
} }
} }
override fun onCreateView( override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
inflater: LayoutInflater, super.onViewCreated(view, savedInstanceState)
container: ViewGroup?, _binding = FragmentForYouBinding.bind(view)
savedInstanceState: Bundle?
): View {
B = FragmentForYouBinding.bind(
inflater.inflate(
R.layout.fragment_for_you,
container,
false
)
)
C = EditorChoiceController(this)
val bundle = arguments val bundle = arguments
if (bundle != null) { if (bundle != null) {
pageType = bundle.getInt(Constants.PAGE_TYPE, 0) pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
} }
val editorChoiceController = EditorChoiceController(this)
binding.recycler.setController(editorChoiceController)
when (pageType) { when (pageType) {
0 -> VM = ViewModelProvider(this)[AppEditorChoiceViewModel::class.java] 0 -> viewModel.getEditorChoiceStream(StreamHelper.Category.APPLICATION)
1 -> VM = ViewModelProvider(this)[GameEditorChoiceViewModel::class.java] 1 -> viewModel.getEditorChoiceStream(StreamHelper.Category.GAME)
} }
return B.root viewModel.liveData.observe(viewLifecycleOwner) {
} editorChoiceController.setData(it)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
B.recycler.setController(C)
VM.liveData.observe(viewLifecycleOwner) {
updateController(it)
} }
} }
private fun updateController(editorChoiceBundles: List<EditorChoiceBundle>) { override fun onDestroyView() {
C.setData(editorChoiceBundles) super.onDestroyView()
_binding = null
} }
override fun onClick(editorChoiceCluster: EditorChoiceCluster) { override fun onClick(editorChoiceCluster: EditorChoiceCluster) {

View File

@@ -28,11 +28,7 @@ import android.view.KeyEvent
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.TextView import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.SearchBundle
@@ -124,32 +120,6 @@ class SearchResultsFragment : BaseFragment(R.layout.fragment_search_result),
query = requireArguments().getString("query") query = requireArguments().getString("query")
query?.let { updateQuery(it) } 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() { override fun onDestroyView() {
@@ -163,19 +133,6 @@ class SearchResultsFragment : BaseFragment(R.layout.fragment_search_result),
_binding = null _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?) { private fun updateController(searchBundle: SearchBundle?) {
if (searchBundle == null) { if (searchBundle == null) {
shimmerAnimationVisible = true 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 package com.aurora.store.viewmodel.all
import android.app.Application import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.store.data.RequestState
import com.aurora.store.data.model.Black import com.aurora.store.data.model.Black
import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import java.util.Locale 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 packageManager: PackageManager = context.packageManager
private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(application) private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(context)
var blackList: MutableList<Black> = mutableListOf() var blackList: MutableList<Black> = mutableListOf()
var selected: MutableSet<String> = mutableSetOf() var selected: MutableSet<String> = mutableSetOf()
@@ -45,16 +52,15 @@ class BlacklistViewModel(application: Application) : BaseAndroidViewModel(applic
val liveData: MutableLiveData<List<Black>> = MutableLiveData() val liveData: MutableLiveData<List<Black>> = MutableLiveData()
init { init {
requestState = RequestState.Init
selected = blacklistProvider.getBlackList() selected = blacklistProvider.getBlackList()
observe() observe()
} }
override fun observe() { fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
val packageInfoMap = PackageUtil.getPackageInfoMap(getApplication()) val packageInfoMap = PackageUtil.getPackageInfoMap(context)
packageInfoMap.values packageInfoMap.values
.filter { .filter {
it.packageName != null it.packageName != null
@@ -72,10 +78,8 @@ class BlacklistViewModel(application: Application) : BaseAndroidViewModel(applic
blackList.add(black) blackList.add(black)
} }
liveData.postValue(blackList.sortedBy { it.displayName.lowercase(Locale.getDefault()) }) liveData.postValue(blackList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
requestState = RequestState.Complete
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
requestState = RequestState.Pending
} }
} }
} }

View File

@@ -19,34 +19,49 @@
package com.aurora.store.viewmodel.all 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 androidx.lifecycle.viewModelScope
import com.aurora.extensions.flushAndAdd 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.data.event.BusEvent
import com.aurora.store.util.AppUtil 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import java.util.Locale 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 { init {
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
requestState = RequestState.Init observe()
} }
override fun observe() { fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
requestState = try { try {
appList = AppUtil.getFilteredInstalledApps(getApplication()).toMutableList() appList = AppUtil.getFilteredInstalledApps(context).toMutableList()
liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) }) liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
RequestState.Complete } catch (exception: Exception) {
} catch (e: Exception) { Log.e(TAG, "Failed to get installed apps", exception)
RequestState.Pending
} }
} }
} }

View File

@@ -19,34 +19,37 @@
package com.aurora.store.viewmodel.all 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.ClusterHelper import com.aurora.gplayapi.helpers.ClusterHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 = private val clusterHelper: ClusterHelper =
ClusterHelper(authData).using(HttpClient.getPreferredClient(application)) ClusterHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData() val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster() var streamCluster: StreamCluster = StreamCluster()
init { fun observe() {
requestState = RequestState.Init
}
override fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
@@ -62,12 +65,9 @@ class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(appl
updateCluster(newCluster) updateCluster(newCluster)
liveData.postValue(streamCluster) liveData.postValue(streamCluster)
} }
else -> { else -> {}
requestState = RequestState.Complete
}
} }
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }

View File

@@ -19,15 +19,18 @@
package com.aurora.store.viewmodel.all 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.PurchaseHelper import com.aurora.gplayapi.helpers.PurchaseHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
@@ -37,24 +40,24 @@ data class PaginatedAppList(
var hasMore: Boolean 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() private var appList: MutableList<App> = mutableListOf()
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData() val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
init { fun observe() {
requestState = RequestState.Init
}
override fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
requestState = try { try {
val nextAppList = purchaseHelper.getPurchaseHistory(appList.size) val nextAppList = purchaseHelper.getPurchaseHistory(appList.size)
.filter { it.displayName.isNotEmpty() } .filter { it.displayName.isNotEmpty() }
@@ -72,9 +75,7 @@ class PurchasedViewModel(application: Application) : BaseAndroidViewModel(applic
) )
) )
} }
RequestState.Complete } catch (_: Exception) {
} catch (e: Exception) {
RequestState.Pending
} }
} }
} }

View File

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

View File

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

View File

@@ -19,34 +19,37 @@
package com.aurora.store.viewmodel.browse 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.ExpandedBrowseHelper import com.aurora.gplayapi.helpers.ExpandedBrowseHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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) private val streamHelper: ExpandedBrowseHelper = ExpandedBrowseHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData() val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster() var streamCluster: StreamCluster = StreamCluster()
override fun observe() {
requestState = RequestState.Init
}
fun getInitialCluster(expandedStreamUrl: String) { fun getInitialCluster(expandedStreamUrl: String) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
@@ -57,10 +60,8 @@ class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewM
streamHelper.getExpandedBrowseClusters(browseResponse.browseTab.listUrl) streamHelper.getExpandedBrowseClusters(browseResponse.browseTab.listUrl)
liveData.postValue(streamCluster) liveData.postValue(streamCluster)
} else { } else {
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }
@@ -84,10 +85,8 @@ class ExpandedStreamBrowseViewModel(application: Application) : BaseAndroidViewM
if (!streamCluster.hasNext()) { if (!streamCluster.hasNext()) {
Log.i("End of Bundle") Log.i("End of Bundle")
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (e: Exception) {
requestState = RequestState.Pending
} }
} }
} }

View File

@@ -19,26 +19,33 @@
package com.aurora.store.viewmodel.browse 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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) private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData() val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster() var streamCluster: StreamCluster = StreamCluster()
@@ -49,12 +56,7 @@ class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(app
} }
} }
private fun getInitialCluster( private fun getInitialCluster(browseUrl: String): StreamCluster {
browseUrl: String
): StreamCluster {
requestState = RequestState.Init
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl) val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
if (browseResponse.contentsUrl.isNotEmpty()) if (browseResponse.contentsUrl.isNotEmpty())
@@ -86,16 +88,10 @@ class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(app
liveData.postValue(streamCluster) liveData.postValue(streamCluster)
} else { } else {
Log.i("End of Bundle") Log.i("End of Bundle")
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }
} }
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 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.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 { private val TAG = CategoryViewModel::class.java.simpleName
type = Category.Type.APPLICATION
requestState = RequestState.Init private val authData: AuthData = AuthProvider
observe() .with(context)
} .getAuthData()
}
private val streamHelper: CategoryHelper = CategoryHelper(authData)
class GameCategoryViewModel(application: Application) : BaseCategoryViewModel(application) { .using(HttpClient.getPreferredClient(context))
init { val liveData: MutableLiveData<List<Category>> = MutableLiveData()
type = Category.Type.GAME
requestState = RequestState.Init fun getCategoryList(type: Category.Type) {
observe() viewModelScope.launch(Dispatchers.IO) {
try {
liveData.postValue(streamHelper.getAllCategoriesList(type))
} catch (exception: Exception) {
Log.e(TAG, "Failed fetching list of categories", exception)
}
}
} }
} }

View File

@@ -19,28 +19,35 @@
package com.aurora.store.viewmodel.details 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 authData: AuthData = AuthProvider.with(context).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application)) private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
private var streamHelper = StreamHelper(authData) private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
@@ -49,21 +56,13 @@ class DetailsClusterViewModel(application: Application) : BaseAndroidViewModel(a
lateinit var type: StreamHelper.Type lateinit var type: StreamHelper.Type
lateinit var category: StreamHelper.Category lateinit var category: StreamHelper.Category
override fun observe() { fun getStreamBundle(streamUrl: String) {
}
fun getStreamBundle(
streamUrl: String
) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
streamBundle = appDetailsHelper.getDetailsStream(streamUrl) streamBundle = appDetailsHelper.getDetailsStream(streamUrl)
liveData.postValue(ViewState.Success(streamBundle)) liveData.postValue(ViewState.Success(streamBundle))
requestState = RequestState.Complete
} catch (e: Exception) { } catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message)) liveData.postValue(ViewState.Error(e.message))
} }
} }

View File

@@ -19,8 +19,10 @@
package com.aurora.store.viewmodel.details 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle 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.data.models.details.DevStream
import com.aurora.gplayapi.helpers.AppDetailsHelper import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 authData: AuthData = AuthProvider.with(context).getAuthData()
private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(application)) private var appDetailsHelper = AppDetailsHelper(authData).using(HttpClient.getPreferredClient(context))
private var streamHelper = StreamHelper(authData) private var streamHelper = StreamHelper(authData)
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
@@ -51,22 +58,14 @@ class DevProfileViewModel(application: Application) : BaseAndroidViewModel(appli
lateinit var type: StreamHelper.Type lateinit var type: StreamHelper.Type
lateinit var category: StreamHelper.Category lateinit var category: StreamHelper.Category
override fun observe() { fun getStreamBundle(devId: String) {
}
fun getStreamBundle(
devId: String
) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
devStream = appDetailsHelper.getDeveloperStream(devId) devStream = appDetailsHelper.getDeveloperStream(devId)
streamBundle = devStream.streamBundle streamBundle = devStream.streamBundle
liveData.postValue(ViewState.Success(devStream)) liveData.postValue(ViewState.Success(devStream))
requestState = RequestState.Complete
} catch (e: Exception) { } catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message)) 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 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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) private val streamHelper: StreamHelper = StreamHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<MutableList<App>> = MutableLiveData() val liveData: MutableLiveData<MutableList<App>> = MutableLiveData()
val appList: MutableList<App> = mutableListOf() val appList: MutableList<App> = mutableListOf()
override fun observe() { fun getEditorStreamBundle(browseUrl: String) {
requestState = RequestState.Init
}
fun getEditorStreamBundle(
browseUrl: String
) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
requestState = RequestState.Init
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl) val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
val listResponse = val listResponse =
streamHelper.getNextStreamResponse(browseResponse.browseTab.listUrl) streamHelper.getNextStreamResponse(browseResponse.browseTab.listUrl)
@@ -68,10 +66,8 @@ class EditorBrowseViewModel(application: Application) : BaseAndroidViewModel(app
} }
liveData.postValue(appList) liveData.postValue(appList)
requestState = RequestState.Complete
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }

View File

@@ -19,20 +19,51 @@
package com.aurora.store.viewmodel.editorschoice 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.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) { @HiltViewModel
init { @SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
category = StreamHelper.Category.APPLICATION class EditorChoiceViewModel @Inject constructor(
observe() @ApplicationContext private val context: Context
} ) : ViewModel() {
}
private val TAG = EditorChoiceViewModel::class.java.simpleName
class GameEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) { private val authData: AuthData = AuthProvider
init { .with(context)
category = StreamHelper.Category.GAME .getAuthData()
observe()
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 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.StreamHelper import com.aurora.gplayapi.helpers.StreamHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 = var streamHelper: StreamHelper =
StreamHelper(authData).using(HttpClient.getPreferredClient(application)) StreamHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle() var streamBundle: StreamBundle = StreamBundle()
@@ -56,7 +63,7 @@ class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(appl
observe() observe()
} }
override fun observe() { fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
@@ -79,10 +86,8 @@ class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(appl
liveData.postValue(ViewState.Success(streamBundle)) liveData.postValue(ViewState.Success(streamBundle))
} else { } else {
Log.i("End of Bundle") Log.i("End of Bundle")
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (e: Exception) {
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message)) liveData.postValue(ViewState.Error(e.message))
} }
} }

View File

@@ -19,29 +19,36 @@
package com.aurora.store.viewmodel.review 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.Review import com.aurora.gplayapi.data.models.Review
import com.aurora.gplayapi.data.models.ReviewCluster import com.aurora.gplayapi.data.models.ReviewCluster
import com.aurora.gplayapi.helpers.ReviewsHelper import com.aurora.gplayapi.helpers.ReviewsHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 var authData: AuthData = AuthProvider
.with(application) .with(context)
.getAuthData() .getAuthData()
var reviewsHelper: ReviewsHelper = ReviewsHelper(authData) var reviewsHelper: ReviewsHelper = ReviewsHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ReviewCluster> = MutableLiveData() val liveData: MutableLiveData<ReviewCluster> = MutableLiveData()
@@ -53,8 +60,7 @@ class ReviewViewModel(application: Application) : BaseAndroidViewModel(applicati
try { try {
reviewsCluster = reviewsHelper.getReviews(packageName, filter) reviewsCluster = reviewsHelper.getReviews(packageName, filter)
liveData.postValue(reviewsCluster) liveData.postValue(reviewsCluster)
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }
@@ -72,14 +78,9 @@ class ReviewViewModel(application: Application) : BaseAndroidViewModel(applicati
} }
liveData.postValue(reviewsCluster) liveData.postValue(reviewsCluster)
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }
} }
override fun observe() {
}
} }

View File

@@ -19,26 +19,33 @@
package com.aurora.store.viewmodel.sale 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppSalesHelper import com.aurora.gplayapi.helpers.AppSalesHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.viewmodel.BaseAndroidViewModel
import com.aurora.store.viewmodel.all.PaginatedAppList 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 = private val appSalesHelper: AppSalesHelper =
AppSalesHelper(authData).using(HttpClient.getPreferredClient(application)) AppSalesHelper(authData).using(HttpClient.getPreferredClient(context))
private var page: Int = 0 private var page: Int = 0
private val appList: MutableList<App> = mutableListOf() private val appList: MutableList<App> = mutableListOf()
@@ -46,14 +53,13 @@ class AppSalesViewModel(application: Application) : BaseAndroidViewModel(applica
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData() val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
init { init {
requestState = RequestState.Init
observe() observe()
} }
override fun observe() { fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
requestState = try { try {
val nextAppList = getSearchResults() val nextAppList = getSearchResults()
if (nextAppList.isEmpty()) { if (nextAppList.isEmpty()) {
@@ -73,16 +79,13 @@ class AppSalesViewModel(application: Application) : BaseAndroidViewModel(applica
) )
} }
RequestState.Complete } catch (_: Exception) {
} catch (e: Exception) {
RequestState.Pending
} }
} }
} }
} }
private fun getSearchResults( private fun getSearchResults(): List<App> {
): List<App> {
return try { return try {
appSalesHelper.getAppsOnSale(page = page++, offer = 100) appSalesHelper.getAppsOnSale(page = page++, offer = 100)
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -19,33 +19,40 @@
package com.aurora.store.viewmodel.search package com.aurora.store.viewmodel.search
import android.app.Application import android.annotation.SuppressLint
import android.content.Context
import android.util.Log import android.util.Log
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.extensions.flushAndAdd import com.aurora.extensions.flushAndAdd
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.SearchBundle import com.aurora.gplayapi.data.models.SearchBundle
import com.aurora.gplayapi.helpers.SearchHelper import com.aurora.gplayapi.helpers.SearchHelper
import com.aurora.gplayapi.helpers.WebSearchHelper import com.aurora.gplayapi.helpers.WebSearchHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 TAG = SearchResultViewModel::class.java.simpleName
private val authData: AuthData = AuthProvider private val authData: AuthData = AuthProvider
.with(application) .with(context)
.getAuthData() .getAuthData()
private val webSearchHelper: WebSearchHelper = WebSearchHelper(authData) private val webSearchHelper: WebSearchHelper = WebSearchHelper(authData)
private val searchHelper: SearchHelper = SearchHelper(authData) private val searchHelper: SearchHelper = SearchHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<SearchBundle> = MutableLiveData() val liveData: MutableLiveData<SearchBundle> = MutableLiveData()
@@ -87,7 +94,7 @@ class SearchResultViewModel(application: Application) : BaseAndroidViewModel(app
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
if (nextSubBundleSet.isNotEmpty() && responseCode.value != 429) { if (nextSubBundleSet.isNotEmpty()) {
val newSearchBundle = helper().next(nextSubBundleSet) val newSearchBundle = helper().next(nextSubBundleSet)
if (newSearchBundle.appList.isNotEmpty()) { if (newSearchBundle.appList.isNotEmpty()) {
searchBundle.apply { searchBundle.apply {
@@ -99,13 +106,9 @@ class SearchResultViewModel(application: Application) : BaseAndroidViewModel(app
} }
} }
} catch (e: Exception) { } 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 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamBundle import com.aurora.gplayapi.data.models.StreamBundle
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.CategoryHelper import com.aurora.gplayapi.helpers.CategoryHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.ViewState import com.aurora.store.data.ViewState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.Log 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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) var categoryHelper: CategoryHelper = CategoryHelper(authData)
.using(HttpClient.getPreferredClient(application)) .using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<ViewState> = MutableLiveData() val liveData: MutableLiveData<ViewState> = MutableLiveData()
var streamBundle: StreamBundle = StreamBundle() var streamBundle: StreamBundle = StreamBundle()
@@ -65,7 +72,7 @@ class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewMod
observe() observe()
} }
override fun observe() { fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
@@ -85,11 +92,9 @@ class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewMod
liveData.postValue(ViewState.Success(streamBundle)) liveData.postValue(ViewState.Success(streamBundle))
} else { } else {
Log.i("End of Bundle") Log.i("End of Bundle")
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
requestState = RequestState.Pending
liveData.postValue(ViewState.Error(e.message)) liveData.postValue(ViewState.Error(e.message))
} }
} }

View File

@@ -19,25 +19,32 @@
package com.aurora.store.viewmodel.topchart 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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.AuthData import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.data.models.StreamCluster import com.aurora.gplayapi.data.models.StreamCluster
import com.aurora.gplayapi.helpers.TopChartsHelper import com.aurora.gplayapi.helpers.TopChartsHelper
import com.aurora.store.data.RequestState
import com.aurora.store.data.network.HttpClient import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider 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.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope 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 = private val topChartsHelper: TopChartsHelper =
TopChartsHelper(authData).using(HttpClient.getPreferredClient(application)) TopChartsHelper(authData).using(HttpClient.getPreferredClient(context))
val liveData: MutableLiveData<StreamCluster> = MutableLiveData() val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
var streamCluster: StreamCluster = StreamCluster() var streamCluster: StreamCluster = StreamCluster()
@@ -47,14 +54,11 @@ class TopChartViewModel(application: Application) : BaseAndroidViewModel(applica
try { try {
streamCluster = topChartsHelper.getCluster(type, chart) streamCluster = topChartsHelper.getCluster(type, chart)
liveData.postValue(streamCluster) liveData.postValue(streamCluster)
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }
override fun observe() {}
fun nextCluster() { fun nextCluster() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
@@ -71,10 +75,8 @@ class TopChartViewModel(application: Application) : BaseAndroidViewModel(applica
liveData.postValue(streamCluster) liveData.postValue(streamCluster)
} else { } else {
requestState = RequestState.Complete
} }
} catch (e: Exception) { } catch (_: Exception) {
requestState = RequestState.Pending
} }
} }
} }

View File

@@ -42,30 +42,6 @@
app:itemSpacing="@dimen/margin_normal" /> app:itemSpacing="@dimen/margin_normal" />
</RelativeLayout> </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 <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/filter_fab" android:id="@+id/filter_fab"
style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon" style="@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon"