auth: refresh anonymous session on activity resume
Long-backgrounded sessions (especially on aggressive vendors like Huawei) could leave the Play token invalid and OkHttp's connection pool full of dead sockets, producing a perpetual loading spinner with no error. - ComposeActivity#onStart probes the saved token via isSavedAuthDataValid; on failure evicts the OkHttp pool and refreshes anonymous auth. - AuthProvider gains authReady StateFlow, awaitReady(), and a mutex-guarded refreshAnonymousAuth() so concurrent callers don't double-fetch. - Play-backed ViewModels (AppDetails, DevProfile, MoreApps, Reviews, Search, ExpandedStreamBrowse, Category) await refresh completion before issuing helper calls, closing the race between resume-time refresh and screen LaunchedEffect fetches. - AppDetailsScreen rendered Error as Loading when app was null; reorder so Error wins and wire a Retry button through Placeholder. - Stream/Review/Search/DevProfile/CategoryBrowse error placeholders gain Retry actions wired to PagingItems.retry() or the VM's fetch. - CategoryViewModel was logging-only on failure; now emits ViewState.Error with a matching Retry path in CategoriesPage.
This commit is contained in:
@@ -15,6 +15,7 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.aurora.extensions.getPackageName
|
import com.aurora.extensions.getPackageName
|
||||||
import com.aurora.store.compose.composition.LocalNetworkStatus
|
import com.aurora.store.compose.composition.LocalNetworkStatus
|
||||||
import com.aurora.store.compose.composition.LocalUI
|
import com.aurora.store.compose.composition.LocalUI
|
||||||
@@ -23,18 +24,27 @@ import com.aurora.store.compose.navigation.NavDisplay
|
|||||||
import com.aurora.store.compose.navigation.Screen
|
import com.aurora.store.compose.navigation.Screen
|
||||||
import com.aurora.store.compose.theme.AuroraTheme
|
import com.aurora.store.compose.theme.AuroraTheme
|
||||||
import com.aurora.store.data.model.NetworkStatus
|
import com.aurora.store.data.model.NetworkStatus
|
||||||
|
import com.aurora.store.data.providers.AccountProvider
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import com.aurora.store.data.providers.NetworkProvider
|
import com.aurora.store.data.providers.NetworkProvider
|
||||||
import com.aurora.store.data.receiver.MigrationReceiver
|
import com.aurora.store.data.receiver.MigrationReceiver
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ComposeActivity : ComponentActivity() {
|
class ComposeActivity : ComponentActivity() {
|
||||||
|
|
||||||
@Inject lateinit var networkProvider: NetworkProvider
|
@Inject lateinit var networkProvider: NetworkProvider
|
||||||
|
|
||||||
|
@Inject lateinit var authProvider: AuthProvider
|
||||||
|
|
||||||
|
@Inject lateinit var okHttpClient: OkHttpClient
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
MigrationReceiver.runMigrationsIfRequired(this)
|
MigrationReceiver.runMigrationsIfRequired(this)
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
@@ -91,4 +101,18 @@ class ComposeActivity : ComponentActivity() {
|
|||||||
!Preferences.getBoolean(this, Preferences.PREFERENCE_INTRO) -> Screen.Onboarding
|
!Preferences.getBoolean(this, Preferences.PREFERENCE_INTRO) -> Screen.Onboarding
|
||||||
else -> Screen.Splash
|
else -> Screen.Splash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
if (!AccountProvider.isLoggedIn(this)) return
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
// Ask Play directly if the saved token still works. If it does,
|
||||||
|
// sockets are fine too and there's nothing to do. If it doesn't,
|
||||||
|
// evict the pool (the same backgrounded state that invalidates
|
||||||
|
// tokens also leaves dead sockets) and refresh anonymous auth.
|
||||||
|
if (authProvider.isSavedAuthDataValid()) return@launch
|
||||||
|
okHttpClient.connectionPool.evictAll()
|
||||||
|
if (authProvider.isAnonymous) authProvider.refreshAnonymousAuth()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ import androidx.compose.runtime.LaunchedEffect
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
import androidx.compose.ui.tooling.preview.PreviewWrapper
|
||||||
import com.aurora.gplayapi.data.models.Category
|
import com.aurora.gplayapi.data.models.Category
|
||||||
import com.aurora.store.CategoryStash
|
import com.aurora.store.CategoryStash
|
||||||
|
import com.aurora.store.R
|
||||||
import com.aurora.store.compose.composable.CategoryItem
|
import com.aurora.store.compose.composable.CategoryItem
|
||||||
|
import com.aurora.store.compose.composable.Placeholder
|
||||||
import com.aurora.store.compose.composable.ShimmerCategoryRow
|
import com.aurora.store.compose.composable.ShimmerCategoryRow
|
||||||
import com.aurora.store.compose.preview.ThemePreviewProvider
|
import com.aurora.store.compose.preview.ThemePreviewProvider
|
||||||
import com.aurora.store.data.model.ViewState
|
import com.aurora.store.data.model.ViewState
|
||||||
@@ -35,6 +39,17 @@ internal fun CategoriesContent(
|
|||||||
viewModel.getCategoryList(categoryType)
|
viewModel.getCategoryList(categoryType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (state is ViewState.Error) {
|
||||||
|
Placeholder(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { viewModel.getCategoryList(categoryType) }
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val categories = (state as? ViewState.Success<*>)?.data as? CategoryStash
|
val categories = (state as? ViewState.Success<*>)?.data as? CategoryStash
|
||||||
val list = categories?.get(categoryType)
|
val list = categories?.get(categoryType)
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ fun CategoryBrowseScreen(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { viewModel.fetchNextPage() }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val bundle = (uiState as? ViewState.Success<*>)?.data as? StreamBundle
|
val bundle = (uiState as? ViewState.Success<*>)?.data as? StreamBundle
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ fun ExpandedStreamBrowseScreen(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { apps.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ private fun ScreenContent(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { apps.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,14 +148,15 @@ fun AppDetailsScreen(
|
|||||||
label = "AppDetailsState"
|
label = "AppDetailsState"
|
||||||
) { currentState ->
|
) { currentState ->
|
||||||
when {
|
when {
|
||||||
currentState is AppState.Loading || app == null ->
|
|
||||||
ScreenContentLoading()
|
|
||||||
|
|
||||||
currentState is AppState.Error ->
|
currentState is AppState.Error ->
|
||||||
ScreenContentError(
|
ScreenContentError(
|
||||||
message = currentState.message
|
message = currentState.message,
|
||||||
|
onRetry = { viewModel.fetchAppDetails(packageName) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
currentState is AppState.Loading || app == null ->
|
||||||
|
ScreenContentLoading()
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val loadedApp = app!!
|
val loadedApp = app!!
|
||||||
ScreenContentApp(
|
ScreenContentApp(
|
||||||
@@ -199,8 +200,8 @@ fun AppDetailsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun stateKey(state: AppState, app: App?): String = when {
|
private fun stateKey(state: AppState, app: App?): String = when {
|
||||||
state is AppState.Loading || app == null -> "loading"
|
|
||||||
state is AppState.Error -> "error"
|
state is AppState.Error -> "error"
|
||||||
|
state is AppState.Loading || app == null -> "loading"
|
||||||
else -> "content"
|
else -> "content"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,14 +221,16 @@ private fun ScreenContentLoading() {
|
|||||||
* Composable to display errors related to fetching app details
|
* Composable to display errors related to fetching app details
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun ScreenContentError(message: String? = null) {
|
private fun ScreenContentError(message: String? = null, onRetry: (() -> Unit)? = null) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { TopAppBar() }
|
topBar = { TopAppBar() }
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_apps_outage),
|
painter = painterResource(R.drawable.ic_apps_outage),
|
||||||
message = message ?: stringResource(R.string.toast_app_unavailable)
|
message = message ?: stringResource(R.string.toast_app_unavailable),
|
||||||
|
actionLabel = onRetry?.let { stringResource(R.string.action_retry) },
|
||||||
|
onAction = onRetry
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,9 @@ private fun ScreenContent(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { reviews.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,9 @@ private fun ScreenContent(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { apps.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,7 +253,9 @@ private fun ScreenContent(
|
|||||||
Placeholder(
|
Placeholder(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
painter = painterResource(R.drawable.ic_disclaimer),
|
painter = painterResource(R.drawable.ic_disclaimer),
|
||||||
message = stringResource(R.string.error)
|
message = stringResource(R.string.error),
|
||||||
|
actionLabel = stringResource(R.string.action_retry),
|
||||||
|
onAction = { results.retry() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
@@ -67,11 +73,45 @@ class AuthProvider @Inject constructor(
|
|||||||
val isAnonymous: Boolean
|
val isAnonymous: Boolean
|
||||||
get() = AccountProvider.getAccountType(context) == AccountType.ANONYMOUS
|
get() = AccountProvider.getAccountType(context) == AccountType.ANONYMOUS
|
||||||
|
|
||||||
|
private val _authReady = MutableStateFlow(true)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits false while an auth refresh is in flight so callers can defer
|
||||||
|
* network work until fresh credentials are persisted.
|
||||||
|
*/
|
||||||
|
val authReady: StateFlow<Boolean> = _authReady.asStateFlow()
|
||||||
|
|
||||||
|
private val refreshMutex = Mutex()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether saved AuthData is valid or not
|
* Checks whether saved AuthData is valid or not
|
||||||
*/
|
*/
|
||||||
fun isSavedAuthDataValid(): Boolean = AuthHelper.isValid(authData!!)
|
fun isSavedAuthDataValid(): Boolean = AuthHelper.isValid(authData!!)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suspends until no auth refresh is in flight. ViewModels should call this
|
||||||
|
* before issuing Play requests so a foreground refresh doesn't race the fetch.
|
||||||
|
*/
|
||||||
|
suspend fun awaitReady() {
|
||||||
|
_authReady.first { it }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds anonymous [AuthData] from a dispenser and persists it. Serialized
|
||||||
|
* so concurrent callers don't double-fetch. On failure, the previously saved
|
||||||
|
* auth is left in place so requests can still attempt with stale credentials.
|
||||||
|
*/
|
||||||
|
suspend fun refreshAnonymousAuth(): Result<AuthData> = refreshMutex.withLock {
|
||||||
|
try {
|
||||||
|
_authReady.value = false
|
||||||
|
val result = buildAnonymousAuthData()
|
||||||
|
result.onSuccess { saveAuthData(it) }
|
||||||
|
result
|
||||||
|
} finally {
|
||||||
|
_authReady.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds [AuthData] for login using personal Google account
|
* Builds [AuthData] for login using personal Google account
|
||||||
* @param email E-mail ID
|
* @param email E-mail ID
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.gplayapi.helpers.ExpandedBrowseHelper
|
import com.aurora.gplayapi.helpers.ExpandedBrowseHelper
|
||||||
import com.aurora.store.data.PageResult
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
@@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
@HiltViewModel(assistedFactory = ExpandedStreamBrowseViewModel.Factory::class)
|
@HiltViewModel(assistedFactory = ExpandedStreamBrowseViewModel.Factory::class)
|
||||||
class ExpandedStreamBrowseViewModel @AssistedInject constructor(
|
class ExpandedStreamBrowseViewModel @AssistedInject constructor(
|
||||||
@Assisted val browseUrl: String,
|
@Assisted val browseUrl: String,
|
||||||
|
private val authProvider: AuthProvider,
|
||||||
private val streamHelper: ExpandedBrowseHelper
|
private val streamHelper: ExpandedBrowseHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ class ExpandedStreamBrowseViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
val items = try {
|
val items = try {
|
||||||
|
authProvider.awaitReady()
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> {
|
1 -> {
|
||||||
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
|
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import com.aurora.gplayapi.helpers.CategoryHelper
|
|||||||
import com.aurora.gplayapi.helpers.contracts.CategoryContract
|
import com.aurora.gplayapi.helpers.contracts.CategoryContract
|
||||||
import com.aurora.store.CategoryStash
|
import com.aurora.store.CategoryStash
|
||||||
import com.aurora.store.data.model.ViewState
|
import com.aurora.store.data.model.ViewState
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -36,6 +37,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class CategoryViewModel @Inject constructor(
|
class CategoryViewModel @Inject constructor(
|
||||||
|
private val authProvider: AuthProvider,
|
||||||
private val categoryHelper: CategoryHelper
|
private val categoryHelper: CategoryHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -57,11 +59,15 @@ class CategoryViewModel @Inject constructor(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
liveData.postValue(ViewState.Loading)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
authProvider.awaitReady()
|
||||||
stash[type] = contract().getAllCategories(type)
|
stash[type] = contract().getAllCategories(type)
|
||||||
liveData.postValue(ViewState.Success(stash))
|
liveData.postValue(ViewState.Success(stash))
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e(TAG, "Failed fetching list of categories", exception)
|
Log.e(TAG, "Failed fetching list of categories", exception)
|
||||||
|
liveData.postValue(ViewState.Error(exception.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,6 +168,7 @@ class AppDetailsViewModel @Inject constructor(
|
|||||||
fun fetchAppDetails(packageName: String) {
|
fun fetchAppDetails(packageName: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
authProvider.awaitReady()
|
||||||
_app.value = appDetailsHelper.getAppByPackageName(packageName).copy(
|
_app.value = appDetailsHelper.getAppByPackageName(packageName).copy(
|
||||||
isInstalled = PackageUtil.isInstalled(context, packageName)
|
isInstalled = PackageUtil.isInstalled(context, packageName)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper
|
|||||||
import com.aurora.gplayapi.helpers.StreamHelper
|
import com.aurora.gplayapi.helpers.StreamHelper
|
||||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||||
import com.aurora.store.data.model.ViewState
|
import com.aurora.store.data.model.ViewState
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -39,6 +40,7 @@ import kotlinx.coroutines.supervisorScope
|
|||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class DevProfileViewModel @Inject constructor(
|
class DevProfileViewModel @Inject constructor(
|
||||||
|
private val authProvider: AuthProvider,
|
||||||
private val appDetailsHelper: AppDetailsHelper,
|
private val appDetailsHelper: AppDetailsHelper,
|
||||||
private val streamHelper: StreamHelper
|
private val streamHelper: StreamHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
@@ -54,6 +56,7 @@ class DevProfileViewModel @Inject constructor(
|
|||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
supervisorScope {
|
||||||
try {
|
try {
|
||||||
|
authProvider.awaitReady()
|
||||||
devStream = appDetailsHelper.getDeveloperStream(devId)
|
devStream = appDetailsHelper.getDeveloperStream(devId)
|
||||||
streamBundle = devStream.streamBundle
|
streamBundle = devStream.streamBundle
|
||||||
liveData.postValue(ViewState.Success(devStream))
|
liveData.postValue(ViewState.Success(devStream))
|
||||||
@@ -69,6 +72,7 @@ class DevProfileViewModel @Inject constructor(
|
|||||||
supervisorScope {
|
supervisorScope {
|
||||||
try {
|
try {
|
||||||
if (streamCluster.hasNext()) {
|
if (streamCluster.hasNext()) {
|
||||||
|
authProvider.awaitReady()
|
||||||
val newCluster = streamHelper.getNextStreamCluster(
|
val newCluster = streamHelper.getNextStreamCluster(
|
||||||
streamCluster.clusterNextPageUrl
|
streamCluster.clusterNextPageUrl
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.aurora.extensions.TAG
|
import com.aurora.extensions.TAG
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
@@ -24,6 +25,7 @@ import kotlinx.coroutines.launch
|
|||||||
@HiltViewModel(assistedFactory = MoreViewModel.Factory::class)
|
@HiltViewModel(assistedFactory = MoreViewModel.Factory::class)
|
||||||
class MoreViewModel @AssistedInject constructor(
|
class MoreViewModel @AssistedInject constructor(
|
||||||
@Assisted private val dependencies: List<String>,
|
@Assisted private val dependencies: List<String>,
|
||||||
|
private val authProvider: AuthProvider,
|
||||||
private val appDetailsHelper: AppDetailsHelper
|
private val appDetailsHelper: AppDetailsHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ class MoreViewModel @AssistedInject constructor(
|
|||||||
private fun fetchDependencies() {
|
private fun fetchDependencies() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
|
authProvider.awaitReady()
|
||||||
_dependentApps.value = appDetailsHelper.getAppByPackageName(dependencies)
|
_dependentApps.value = appDetailsHelper.getAppByPackageName(dependencies)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e(TAG, "Failed to fetch dependencies", exception)
|
Log.e(TAG, "Failed to fetch dependencies", exception)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import com.aurora.gplayapi.data.models.Review
|
|||||||
import com.aurora.gplayapi.helpers.ReviewsHelper
|
import com.aurora.gplayapi.helpers.ReviewsHelper
|
||||||
import com.aurora.store.data.PageResult
|
import com.aurora.store.data.PageResult
|
||||||
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
import com.aurora.store.data.paging.GenericPagingSource.Companion.manualPager
|
||||||
|
import com.aurora.store.data.providers.AuthProvider
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedFactory
|
import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
@@ -29,6 +30,7 @@ import kotlinx.coroutines.flow.onEach
|
|||||||
@HiltViewModel(assistedFactory = ReviewViewModel.Factory::class)
|
@HiltViewModel(assistedFactory = ReviewViewModel.Factory::class)
|
||||||
class ReviewViewModel @AssistedInject constructor(
|
class ReviewViewModel @AssistedInject constructor(
|
||||||
@Assisted private val packageName: String,
|
@Assisted private val packageName: String,
|
||||||
|
private val authProvider: AuthProvider,
|
||||||
private val reviewsHelper: ReviewsHelper
|
private val reviewsHelper: ReviewsHelper
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ class ReviewViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
val items = try {
|
val items = try {
|
||||||
|
authProvider.awaitReady()
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> reviewsHelper.getReviews(packageName, filter).also {
|
1 -> reviewsHelper.getReviews(packageName, filter).also {
|
||||||
reviewsNextPageUrl = it.nextPageUrl
|
reviewsNextPageUrl = it.nextPageUrl
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
|
|
||||||
manualPager { page ->
|
manualPager { page ->
|
||||||
val items = try {
|
val items = try {
|
||||||
|
authProvider.awaitReady()
|
||||||
when (page) {
|
when (page) {
|
||||||
1 -> contract.searchResults(query)
|
1 -> contract.searchResults(query)
|
||||||
.also { nextBundleUrl = it.streamNextPageUrl }
|
.also { nextBundleUrl = it.streamNextPageUrl }
|
||||||
@@ -121,6 +122,7 @@ class SearchViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun fetchSuggestions(query: String) {
|
fun fetchSuggestions(query: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
authProvider.awaitReady()
|
||||||
_suggestions.value = contract.searchSuggestions(query)
|
_suggestions.value = contract.searchSuggestions(query)
|
||||||
.filter { it.title.isNotBlank() }
|
.filter { it.title.isNotBlank() }
|
||||||
.take(3)
|
.take(3)
|
||||||
|
|||||||
Reference in New Issue
Block a user