Merge branch 'dev' into 'master'
Improve AppUpdate pregressbar UI See merge request AuroraOSS/AuroraStore!346
This commit is contained in:
@@ -25,7 +25,7 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.hilt.work.HiltWorkerFactory
|
||||
import androidx.work.Configuration
|
||||
import com.aurora.extensions.isPAndAbove
|
||||
import com.aurora.store.data.event.FlowEvent
|
||||
import com.aurora.store.data.event.EventFlow
|
||||
import com.aurora.store.data.receiver.PackageManagerReceiver
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.util.DownloadWorkerUtil
|
||||
@@ -58,7 +58,7 @@ class AuroraApp : Application(), Configuration.Provider {
|
||||
private set
|
||||
|
||||
val enqueuedInstalls: MutableSet<String> = mutableSetOf()
|
||||
val flowEvent = FlowEvent()
|
||||
val events = EventFlow()
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
|
||||
@@ -21,48 +21,27 @@ package com.aurora.store.data.event
|
||||
|
||||
abstract class Event
|
||||
|
||||
sealed class BusEvent: Event() {
|
||||
data class InstallEvent(
|
||||
var packageName: String,
|
||||
var extra: String? = ""
|
||||
) : BusEvent()
|
||||
sealed class BusEvent : Event() {
|
||||
lateinit var extra: String
|
||||
lateinit var error: String
|
||||
|
||||
data class UninstallEvent(
|
||||
var packageName: String,
|
||||
var extra: String? = ""
|
||||
) : BusEvent()
|
||||
|
||||
data class Blacklisted(
|
||||
var packageName: String,
|
||||
var error: String? = ""
|
||||
) : BusEvent()
|
||||
|
||||
data class GoogleAAS(
|
||||
var success: Boolean,
|
||||
var email: String = String(),
|
||||
var aasToken: String = String()
|
||||
) : BusEvent()
|
||||
|
||||
data class ManualDownload(
|
||||
var packageName: String,
|
||||
var versionCode: Int
|
||||
) : BusEvent()
|
||||
data class Blacklisted(val packageName: String) : BusEvent()
|
||||
data class ManualDownload(val packageName: String, val versionCode: Int) : BusEvent()
|
||||
}
|
||||
|
||||
sealed class InstallerEvent: Event() {
|
||||
data class Success(
|
||||
var packageName: String? = "",
|
||||
var extra: String? = ""
|
||||
) : InstallerEvent()
|
||||
|
||||
data class Cancelled(
|
||||
var packageName: String? = "",
|
||||
var extra: String? = ""
|
||||
) : InstallerEvent()
|
||||
|
||||
data class Failed(
|
||||
var packageName: String? = "",
|
||||
var error: String? = "",
|
||||
var extra: String? = ""
|
||||
) : InstallerEvent()
|
||||
sealed class AuthEvent : Event() {
|
||||
data class GoogleLogin(val success: Boolean, val email: String, val token: String) : AuthEvent()
|
||||
}
|
||||
|
||||
sealed class InstallerEvent : Event() {
|
||||
lateinit var extra: String
|
||||
lateinit var error: String
|
||||
|
||||
var progress: Int = -1
|
||||
|
||||
data class Installed(val packageName: String) : InstallerEvent()
|
||||
data class Uninstalled(val packageName: String) : InstallerEvent()
|
||||
data class Installing(val packageName: String) : InstallerEvent()
|
||||
data class Cancelled(val packageName: String) : InstallerEvent()
|
||||
data class Failed(val packageName: String) : InstallerEvent()
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import kotlinx.coroutines.flow.asSharedFlow
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class FlowEvent {
|
||||
class EventFlow {
|
||||
|
||||
private val TAG = FlowEvent::class.java.simpleName
|
||||
private val TAG = EventFlow::class.java.simpleName
|
||||
|
||||
private val _busEvent = MutableSharedFlow<BusEvent>(extraBufferCapacity = 1)
|
||||
val busEvent = _busEvent.asSharedFlow()
|
||||
@@ -16,10 +16,14 @@ class FlowEvent {
|
||||
private val _installerEvent = MutableSharedFlow<InstallerEvent>(extraBufferCapacity = 1)
|
||||
val installerEvent = _installerEvent.asSharedFlow()
|
||||
|
||||
fun emitEvent(event: Event) {
|
||||
private val _authEvent = MutableSharedFlow<AuthEvent>(extraBufferCapacity = 1)
|
||||
val authEvent = _authEvent.asSharedFlow()
|
||||
|
||||
fun send(event: Event) {
|
||||
when (event) {
|
||||
is InstallerEvent -> _installerEvent.tryEmit(event)
|
||||
is BusEvent -> _busEvent.tryEmit(event)
|
||||
is AuthEvent -> _authEvent.tryEmit(event)
|
||||
else -> Log.e(TAG, "Got an unhandled event")
|
||||
}
|
||||
}
|
||||
@@ -66,16 +66,19 @@ abstract class InstallerBase(protected var context: Context) : IInstaller {
|
||||
open fun postError(packageName: String, error: String?, extra: String?) {
|
||||
Log.e("Service Error :$error")
|
||||
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
error,
|
||||
extra
|
||||
)
|
||||
val event = InstallerEvent.Failed(packageName).apply {
|
||||
this.error = error ?: ""
|
||||
this.extra = extra ?: ""
|
||||
}
|
||||
|
||||
AuroraApp.flowEvent.emitEvent(event)
|
||||
AuroraApp.events.send(event)
|
||||
}
|
||||
|
||||
open fun getFiles(packageName: String, versionCode: Int, sharedLibPackageName: String = ""): List<File> {
|
||||
open fun getFiles(
|
||||
packageName: String,
|
||||
versionCode: Int,
|
||||
sharedLibPackageName: String = ""
|
||||
): List<File> {
|
||||
val downloadDir = if (sharedLibPackageName.isNotBlank()) {
|
||||
PathUtil.getLibDownloadDir(context, packageName, versionCode, sharedLibPackageName)
|
||||
} else {
|
||||
|
||||
@@ -104,12 +104,11 @@ class RootInstaller @Inject constructor(
|
||||
if (packageName == download?.packageName) onInstallationSuccess()
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
val event = InstallerEvent.Failed(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_failure),
|
||||
parseError(shellResult)
|
||||
)
|
||||
AuroraApp.flowEvent.emitEvent(event)
|
||||
val event = InstallerEvent.Failed(packageName).apply {
|
||||
this.extra = context.getString(R.string.installer_status_failure)
|
||||
this.error = parseError(shellResult)
|
||||
}
|
||||
AuroraApp.events.send(event)
|
||||
}
|
||||
} else {
|
||||
removeFromInstallQueue(packageName)
|
||||
|
||||
@@ -35,7 +35,6 @@ import com.aurora.services.IPrivilegedService
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.BuildConfig
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.InstallerInfo
|
||||
import com.aurora.store.data.room.download.Download
|
||||
@@ -91,6 +90,7 @@ class ServiceInstaller @Inject constructor(
|
||||
fileList.map { it.absolutePath }
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
postError(
|
||||
download.packageName,
|
||||
@@ -148,7 +148,11 @@ class ServiceInstaller @Inject constructor(
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||
postError(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
} else {
|
||||
@@ -166,7 +170,11 @@ class ServiceInstaller @Inject constructor(
|
||||
)
|
||||
} catch (e: RemoteException) {
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, e.localizedMessage, e.stackTraceToString())
|
||||
postError(
|
||||
packageName,
|
||||
e.localizedMessage,
|
||||
e.stackTraceToString()
|
||||
)
|
||||
readyWithAction.set(true)
|
||||
}
|
||||
}
|
||||
@@ -210,13 +218,13 @@ class ServiceInstaller @Inject constructor(
|
||||
try {
|
||||
when (returnCode) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
AuroraApp.flowEvent.emitEvent(
|
||||
BusEvent.UninstallEvent(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_success)
|
||||
)
|
||||
AuroraApp.events.send(
|
||||
InstallerEvent.Uninstalled(packageName).apply {
|
||||
this.extra = context.getString(R.string.action_uninstall_success)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val error = AppInstaller.getErrorString(
|
||||
context,
|
||||
@@ -240,15 +248,15 @@ class ServiceInstaller @Inject constructor(
|
||||
try {
|
||||
when (returnCode) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
AuroraApp.flowEvent.emitEvent(
|
||||
InstallerEvent.Success(
|
||||
packageName,
|
||||
context.getString(R.string.installer_status_success)
|
||||
)
|
||||
AuroraApp.events.send(
|
||||
InstallerEvent.Installed(packageName).apply {
|
||||
this.extra = context.getString(R.string.installer_status_success)
|
||||
}
|
||||
)
|
||||
// Installation is not yet finished if this is a shared library
|
||||
if (packageName == download?.packageName) onInstallationSuccess()
|
||||
}
|
||||
|
||||
else -> {
|
||||
val error = AppInstaller.getErrorString(
|
||||
context,
|
||||
|
||||
@@ -36,7 +36,9 @@ import com.aurora.extensions.isSAndAbove
|
||||
import com.aurora.extensions.isTAndAbove
|
||||
import com.aurora.extensions.isUAndAbove
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller.Companion.ACTION_INSTALL_STATUS
|
||||
import com.aurora.store.data.installer.AppInstaller.Companion.EXTRA_DISPLAY_NAME
|
||||
import com.aurora.store.data.installer.AppInstaller.Companion.EXTRA_PACKAGE_NAME
|
||||
@@ -48,6 +50,7 @@ import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.store.util.PackageUtil.isSharedLibraryInstalled
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -69,27 +72,38 @@ class SessionInstaller @Inject constructor(
|
||||
|
||||
override fun onActiveChanged(sessionId: Int, active: Boolean) {}
|
||||
|
||||
override fun onProgressChanged(sessionId: Int, progress: Float) {}
|
||||
override fun onProgressChanged(sessionId: Int, progress: Float) {
|
||||
val packageName = enqueuedSessions
|
||||
.find { set -> set.any { it.sessionId == sessionId } }
|
||||
?.first()
|
||||
?.packageName
|
||||
|
||||
if (packageName != null && progress > 0.0) {
|
||||
AuroraApp.events.send(
|
||||
InstallerEvent.Installing(packageName).apply {
|
||||
this.progress = (progress * 100).toInt()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinished(sessionId: Int, success: Boolean) {
|
||||
enqueuedSessions.find { set -> set.any { it.sessionId == sessionId } }?.let { sessionSet ->
|
||||
sessionSet.remove(sessionSet.first { it.sessionId == sessionId })
|
||||
enqueuedSessions.find { set -> set.any { it.sessionId == sessionId } }
|
||||
?.let { sessionSet ->
|
||||
sessionSet.remove(sessionSet.first { it.sessionId == sessionId })
|
||||
|
||||
// If this was a shared lib, proceed installing other libs or actual package
|
||||
if (sessionSet.isNotEmpty() && success) {
|
||||
commitInstall(sessionSet.first()); return
|
||||
} else {
|
||||
enqueuedSessions.remove(sessionSet)
|
||||
// If this was a shared lib, proceed installing other libs or actual package
|
||||
if (sessionSet.isNotEmpty() && success) {
|
||||
commitInstall(sessionSet.first()); return
|
||||
} else {
|
||||
enqueuedSessions.remove(sessionSet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (enqueuedSessions.isNotEmpty()) {
|
||||
enqueuedSessions.firstOrNull()?.let { sessionSet ->
|
||||
commitInstall(sessionSet.first())
|
||||
}
|
||||
} else {
|
||||
// Nothing else in queue, unregister callback
|
||||
packageInstaller.unregisterSessionCallback(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +127,8 @@ class SessionInstaller @Inject constructor(
|
||||
override fun install(download: Download) {
|
||||
super.install(download)
|
||||
|
||||
val sessionSet = enqueuedSessions.find { set -> set.any { it.packageName == download.packageName } }
|
||||
val sessionSet =
|
||||
enqueuedSessions.find { set -> set.any { it.packageName == download.packageName } }
|
||||
if (sessionSet != null) {
|
||||
Log.i("${download.packageName} already queued")
|
||||
commitInstall(sessionSet.first())
|
||||
@@ -124,7 +139,11 @@ class SessionInstaller @Inject constructor(
|
||||
download.sharedLibs.forEach {
|
||||
// Shared library packages cannot be updated
|
||||
if (!isSharedLibraryInstalled(context, it.packageName, it.versionCode)) {
|
||||
stageInstall(download.packageName, download.versionCode, it.packageName)?.let { sessionID ->
|
||||
stageInstall(
|
||||
download.packageName,
|
||||
download.versionCode,
|
||||
it.packageName
|
||||
)?.let { sessionID ->
|
||||
sessionInfoSet.add(SessionInfo(sessionID, it.packageName, it.versionCode))
|
||||
}
|
||||
}
|
||||
@@ -147,18 +166,50 @@ class SessionInstaller @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun stageInstall(packageName: String, versionCode: Int, sharedLibPkgName: String = ""): Int? {
|
||||
val packageInstaller = context.packageManager.packageInstaller
|
||||
private fun stageInstall(
|
||||
packageName: String,
|
||||
versionCode: Int,
|
||||
sharedLibPkgName: String = ""
|
||||
): Int? {
|
||||
val resolvedPackageName = sharedLibPkgName.ifBlank { packageName }
|
||||
|
||||
val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(sharedLibPkgName.ifBlank { packageName })
|
||||
setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO)
|
||||
if (isOAndAbove()) {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
val sessionParams = buildSessionParams(resolvedPackageName)
|
||||
val sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
|
||||
return try {
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
getFiles(packageName, versionCode, sharedLibPkgName).forEach { file ->
|
||||
file.inputStream().use { input ->
|
||||
session.openWrite(
|
||||
"${resolvedPackageName}_${file.name}",
|
||||
0,
|
||||
file.length()
|
||||
).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
sessionId
|
||||
} catch (exception: IOException) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, exception.localizedMessage, exception.stackTraceToString())
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSessionParams(packageName: String): SessionParams {
|
||||
return SessionParams(SessionParams.MODE_FULL_INSTALL).apply {
|
||||
setAppPackageName(packageName)
|
||||
setInstallLocation(PackageInfo.INSTALL_LOCATION_AUTO)
|
||||
if (isNAndAbove()) {
|
||||
setOriginatingUid(Process.myUid())
|
||||
}
|
||||
if (isOAndAbove()) {
|
||||
setInstallReason(PackageManager.INSTALL_REASON_USER)
|
||||
}
|
||||
if (isSAndAbove()) {
|
||||
setRequireUserAction(SessionParams.USER_ACTION_NOT_REQUIRED)
|
||||
}
|
||||
@@ -170,26 +221,6 @@ class SessionInstaller @Inject constructor(
|
||||
setRequestUpdateOwnership(true)
|
||||
}
|
||||
}
|
||||
val sessionId = packageInstaller.createSession(sessionParams)
|
||||
val session = packageInstaller.openSession(sessionId)
|
||||
|
||||
return try {
|
||||
Log.i("Writing splits to session for $packageName")
|
||||
getFiles(packageName, versionCode, sharedLibPkgName).forEach {
|
||||
it.inputStream().use { input ->
|
||||
session.openWrite("${sharedLibPkgName.ifBlank { packageName }}_${it.name}", 0, it.length()).use { output ->
|
||||
input.copyTo(output)
|
||||
session.fsync(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
sessionId
|
||||
} catch (exception: Exception) {
|
||||
session.abandon()
|
||||
removeFromInstallQueue(packageName)
|
||||
postError(packageName, exception.localizedMessage, exception.stackTraceToString())
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun commitInstall(sessionInfo: SessionInfo) {
|
||||
|
||||
@@ -29,5 +29,6 @@ enum class State {
|
||||
QUEUED,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
CANCELED
|
||||
CANCELED,
|
||||
INSTALLING,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.aurora.store.data.model
|
||||
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
|
||||
data class PaginatedAppList(
|
||||
val appList: MutableList<App> = mutableListOf(),
|
||||
var hasMore: Boolean
|
||||
)
|
||||
@@ -80,7 +80,12 @@ class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyUser(context: Context, packageName: String, displayName: String, status: Int) {
|
||||
private fun notifyUser(
|
||||
context: Context,
|
||||
packageName: String,
|
||||
displayName: String,
|
||||
status: Int
|
||||
) {
|
||||
val notificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notification = NotificationUtil.getInstallerStatusNotification(
|
||||
@@ -109,23 +114,24 @@ class InstallerStatusReceiver : BroadcastReceiver() {
|
||||
private fun postStatus(status: Int, packageName: String?, extra: String?, context: Context) {
|
||||
val event = when (status) {
|
||||
PackageInstaller.STATUS_SUCCESS -> {
|
||||
InstallerEvent.Success(
|
||||
packageName, context.getString(R.string.installer_status_success)
|
||||
)
|
||||
InstallerEvent.Installed(packageName!!).apply {
|
||||
this.extra = context.getString(R.string.installer_status_success)
|
||||
}
|
||||
}
|
||||
|
||||
PackageInstaller.STATUS_FAILURE_ABORTED -> {
|
||||
InstallerEvent.Cancelled(
|
||||
packageName, AppInstaller.getErrorString(context, status)
|
||||
)
|
||||
InstallerEvent.Cancelled(packageName!!).apply {
|
||||
this.extra = AppInstaller.getErrorString(context, status)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
InstallerEvent.Failed(
|
||||
packageName, AppInstaller.getErrorString(context, status), extra
|
||||
)
|
||||
InstallerEvent.Failed(packageName!!).apply {
|
||||
this.error = AppInstaller.getErrorString(context, status)
|
||||
this.extra = extra ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
AuroraApp.flowEvent.emitEvent(event)
|
||||
AuroraApp.events.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.data.event.BusEvent.InstallEvent
|
||||
import com.aurora.store.data.event.BusEvent.UninstallEvent
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
@@ -41,11 +40,11 @@ open class PackageManagerReceiver : BroadcastReceiver() {
|
||||
|
||||
when (intent.action) {
|
||||
Intent.ACTION_PACKAGE_ADDED -> {
|
||||
AuroraApp.flowEvent.emitEvent(InstallEvent(packageName, ""))
|
||||
AuroraApp.events.send(InstallerEvent.Installed(packageName))
|
||||
}
|
||||
|
||||
Intent.ACTION_PACKAGE_REMOVED -> {
|
||||
AuroraApp.flowEvent.emitEvent(UninstallEvent(packageName, ""))
|
||||
AuroraApp.events.send(InstallerEvent.Uninstalled(packageName))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.model.State
|
||||
import com.aurora.store.databinding.ViewActionButtonBinding
|
||||
@@ -101,19 +100,21 @@ class ActionButton : RelativeLayout {
|
||||
binding.btn.text = getString(text)
|
||||
}
|
||||
|
||||
fun setButtonState(enabled: Boolean = true) {
|
||||
binding.btn.isEnabled = enabled
|
||||
}
|
||||
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.IDLE -> 0
|
||||
State.PROGRESS -> 1
|
||||
State.COMPLETE -> 2
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (binding.viewFlipper.displayedChild != displayChild) {
|
||||
runOnUiThread {
|
||||
binding.viewFlipper.displayedChild = displayChild
|
||||
if (displayChild == 2) updateState(State.IDLE)
|
||||
}
|
||||
binding.viewFlipper.displayedChild = displayChild
|
||||
|
||||
if (displayChild == 2) updateState(State.IDLE)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class CarouselModelGroup(
|
||||
val models = ArrayList<EpoxyModel<*>>()
|
||||
val clusterViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
val idPrefix = streamCluster.id
|
||||
val idPrefix = streamCluster.clusterTitle.hashCode()
|
||||
|
||||
models.add(
|
||||
HeaderViewModel_()
|
||||
|
||||
@@ -19,13 +19,17 @@
|
||||
|
||||
package com.aurora.store.view.epoxy.views.app
|
||||
|
||||
import android.animation.ObjectAnimator
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateDecelerateInterpolator
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.isVisible
|
||||
import coil.load
|
||||
import coil.transform.CircleCropTransformation
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
@@ -51,6 +55,8 @@ class AppUpdateView @JvmOverloads constructor(
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : BaseView<ViewAppUpdateBinding>(context, attrs, defStyleAttr) {
|
||||
private var iconDrawable: Drawable? = null
|
||||
private val cornersTransformation = RoundedCornersTransformation(8.px.toFloat())
|
||||
|
||||
@ModelProp
|
||||
fun app(app: App) {
|
||||
@@ -59,7 +65,10 @@ class AppUpdateView @JvmOverloads constructor(
|
||||
binding.txtLine1.text = displayName
|
||||
binding.imgIcon.load(iconArtwork.url) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(8.px.toFloat()))
|
||||
transformations(cornersTransformation)
|
||||
listener { _, result ->
|
||||
result.drawable.let { iconDrawable = it }
|
||||
}
|
||||
}
|
||||
|
||||
binding.txtLine2.text = developerName
|
||||
@@ -90,29 +99,22 @@ class AppUpdateView @JvmOverloads constructor(
|
||||
@ModelProp
|
||||
fun download(download: Download?) {
|
||||
if (download != null) {
|
||||
/*Inflate Download details*/
|
||||
binding.btnAction.updateState(download.downloadStatus)
|
||||
binding.progressDownload.isIndeterminate = download.progress < 1
|
||||
when (download.downloadStatus) {
|
||||
DownloadStatus.QUEUED -> {
|
||||
binding.progressDownload.progress = 0
|
||||
binding.progressDownload.show()
|
||||
binding.progressDownload.isIndeterminate = true
|
||||
animateImageView(scaleFactor = 0.75f)
|
||||
}
|
||||
|
||||
DownloadStatus.DOWNLOADING -> {
|
||||
if (download.progress > 0) {
|
||||
if (download.progress == 100) {
|
||||
binding.progressDownload.invisible()
|
||||
} else {
|
||||
binding.progressDownload.progress = download.progress
|
||||
binding.progressDownload.show()
|
||||
}
|
||||
}
|
||||
binding.progressDownload.isIndeterminate = false
|
||||
binding.progressDownload.progress = download.progress
|
||||
animateImageView(scaleFactor = 0.75f)
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.progressDownload.progress = 0
|
||||
binding.progressDownload.invisible()
|
||||
binding.progressDownload.isIndeterminate = true
|
||||
animateImageView(scaleFactor = 1f)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +143,48 @@ class AppUpdateView @JvmOverloads constructor(
|
||||
@OnViewRecycled
|
||||
fun clear() {
|
||||
binding.headerIndicator.removeCallbacks { }
|
||||
binding.progressDownload.progress = 0
|
||||
binding.progressDownload.invisible()
|
||||
iconDrawable = null
|
||||
}
|
||||
|
||||
private fun animateImageView(scaleFactor: Float = 1f) {
|
||||
val isDownloadVisible = binding.progressDownload.isShown
|
||||
|
||||
// Avoids flickering when the download is in progress
|
||||
if (isDownloadVisible && scaleFactor != 1f)
|
||||
return
|
||||
|
||||
if (!isDownloadVisible && scaleFactor == 1f)
|
||||
return
|
||||
|
||||
if (scaleFactor == 1f) {
|
||||
binding.progressDownload.invisible()
|
||||
} else {
|
||||
binding.progressDownload.show()
|
||||
}
|
||||
|
||||
val scale = listOf(
|
||||
ObjectAnimator.ofFloat(binding.imgIcon, "scaleX", scaleFactor),
|
||||
ObjectAnimator.ofFloat(binding.imgIcon, "scaleY", scaleFactor)
|
||||
)
|
||||
|
||||
scale.forEach { animation ->
|
||||
animation.apply {
|
||||
interpolator = AccelerateDecelerateInterpolator()
|
||||
duration = 250
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
iconDrawable?.let {
|
||||
binding.imgIcon.load(it) {
|
||||
transformations(
|
||||
if (scaleFactor == 1f)
|
||||
cornersTransformation
|
||||
else
|
||||
CircleCropTransformation()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.AuthEvent
|
||||
import com.aurora.store.databinding.FragmentGoogleBinding
|
||||
import com.aurora.store.util.AC2DMUtil
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
@@ -110,15 +110,15 @@ class GoogleFragment : BaseFragment<FragmentGoogleBinding>() {
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.flowEvent.busEvent.collect { onEventReceived(it) }
|
||||
AuroraApp.events.authEvent.collect { onEventReceived(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEventReceived(event: BusEvent) {
|
||||
private fun onEventReceived(event: AuthEvent) {
|
||||
when (event) {
|
||||
is BusEvent.GoogleAAS -> {
|
||||
is AuthEvent.GoogleLogin -> {
|
||||
if (event.success) {
|
||||
viewModel.buildGoogleAuthData(event.email, event.aasToken)
|
||||
viewModel.buildGoogleAuthData(event.email, event.token)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
|
||||
@@ -65,8 +65,6 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithPagerBinding>() {
|
||||
) { tab: TabLayout.Tab, position: Int ->
|
||||
when (position) {
|
||||
0 -> tab.text = getString(R.string.title_installed)
|
||||
1 -> tab.text = getString(R.string.title_library)
|
||||
2 -> tab.text = getString(R.string.title_purchase_history)
|
||||
else -> {}
|
||||
}
|
||||
}.attach()
|
||||
@@ -81,14 +79,12 @@ class AppsGamesFragment : BaseFragment<FragmentGenericWithPagerBinding>() {
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
0 -> InstalledAppsFragment.newInstance()
|
||||
1 -> LibraryAppsFragment.newInstance()
|
||||
2 -> PurchasedAppsFragment.newInstance()
|
||||
else -> Fragment()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (isAnonymous) 1 else 3
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ import androidx.lifecycle.lifecycleScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.databinding.FragmentAppsBinding
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
@@ -49,10 +51,10 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEvent(event: BusEvent) {
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent,
|
||||
is BusEvent.UninstallEvent,
|
||||
is InstallerEvent.Installed,
|
||||
is InstallerEvent.Uninstalled,
|
||||
is BusEvent.Blacklisted -> {
|
||||
viewModel.fetchApps()
|
||||
}
|
||||
@@ -73,7 +75,7 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.flowEvent.busEvent.collect { onEvent(it) }
|
||||
AuroraApp.events.busEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,106 +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.view.ui.all
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentAppsBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.all.LibraryAppsViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LibraryAppsFragment : BaseFragment<FragmentAppsBinding>() {
|
||||
private val viewModel: LibraryAppsViewModel by viewModels()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(): LibraryAppsFragment {
|
||||
return LibraryAppsFragment().apply {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.observe()
|
||||
}
|
||||
}
|
||||
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(it)
|
||||
}
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(streamCluster: StreamCluster?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (streamCluster == null) {
|
||||
for (i in 1..10) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("header")
|
||||
.title(
|
||||
if (streamCluster.clusterTitle.isEmpty())
|
||||
getString(R.string.title_apps_library)
|
||||
else
|
||||
streamCluster.clusterTitle
|
||||
)
|
||||
)
|
||||
streamCluster.clusterAppList.forEach { app ->
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
)
|
||||
}
|
||||
|
||||
if (streamCluster.hasNext()) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +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.view.ui.all
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentAppsBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.all.PaginatedAppList
|
||||
import com.aurora.store.viewmodel.all.PurchasedViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PurchasedAppsFragment : BaseFragment<FragmentAppsBinding>() {
|
||||
private val viewModel: PurchasedViewModel by viewModels()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(): PurchasedAppsFragment {
|
||||
return PurchasedAppsFragment()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener(8) {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.observe()
|
||||
}
|
||||
}
|
||||
|
||||
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(it)
|
||||
}
|
||||
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(paginatedAppList: PaginatedAppList?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (paginatedAppList == null) {
|
||||
for (i in 1..10) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
paginatedAppList.appList.forEach { app ->
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
.longClick { _ ->
|
||||
openAppMenuSheet(app)
|
||||
false
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (paginatedAppList.hasMore) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,6 @@ import com.aurora.store.databinding.FragmentAppsGamesBinding
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.view.ui.commons.CategoryFragment
|
||||
import com.aurora.store.view.ui.commons.EditorChoiceFragment
|
||||
import com.aurora.store.view.ui.commons.ForYouFragment
|
||||
import com.aurora.store.view.ui.commons.TopChartContainerFragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -92,10 +91,6 @@ class AppsContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
||||
|
||||
add(getString(R.string.tab_top_charts))
|
||||
add(getString(R.string.tab_categories))
|
||||
|
||||
if (!authProvider.isAnonymous) {
|
||||
add(getString(R.string.tab_editor_choice))
|
||||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(
|
||||
@@ -130,10 +125,6 @@ class AppsContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
||||
}
|
||||
add(TopChartContainerFragment.newInstance(0))
|
||||
add(CategoryFragment.newInstance(0))
|
||||
|
||||
if (isGoogleAccount) {
|
||||
add(EditorChoiceFragment.newInstance(0))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
|
||||
@@ -28,6 +28,7 @@ import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Category
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.google.gson.Gson
|
||||
import java.lang.reflect.ParameterizedType
|
||||
@@ -93,19 +94,12 @@ abstract class BaseFragment<ViewBindingType : ViewBinding> : Fragment() {
|
||||
title
|
||||
)
|
||||
)
|
||||
} else {
|
||||
findNavController().navigate(
|
||||
MobileNavigationDirections.actionGlobalStreamBrowseFragment(
|
||||
browseUrl,
|
||||
title
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun openEditorStreamBrowseFragment(browseUrl: String, title: String = "") {
|
||||
fun openStreamBrowseFragment(streamCluster: StreamCluster) {
|
||||
findNavController().navigate(
|
||||
MobileNavigationDirections.actionGlobalEditorStreamBrowseFragment(title, browseUrl)
|
||||
MobileNavigationDirections.actionGlobalStreamBrowseFragment(streamCluster)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,20 +28,21 @@ import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.gplayapi.utils.CategoryUtil
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.model.ViewState.Loading.getDataAs
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.controller.CategoryCarouselController
|
||||
import com.aurora.store.view.epoxy.controller.GenericCarouselController
|
||||
import com.aurora.store.viewmodel.subcategory.SubCategoryClusterViewModel
|
||||
import com.aurora.store.viewmodel.subcategory.CategoryStreamViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CategoryBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>(),
|
||||
GenericCarouselController.Callbacks {
|
||||
private val args: CategoryBrowseFragmentArgs by navArgs()
|
||||
private val viewModel: SubCategoryClusterViewModel by activityViewModels()
|
||||
private val viewModel: CategoryStreamViewModel by activityViewModels()
|
||||
|
||||
private lateinit var category: StreamContract.Category
|
||||
private var streamBundle: StreamBundle? = StreamBundle()
|
||||
@@ -49,8 +50,7 @@ class CategoryBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>()
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val rawCategory = args.browseUrl.split("/").last()
|
||||
category = StreamContract.Category.NONE.apply { value = rawCategory }
|
||||
category = CategoryUtil.getCategoryFromUrl(args.browseUrl)
|
||||
|
||||
val genericCarouselController = CategoryCarouselController(this)
|
||||
|
||||
@@ -68,6 +68,7 @@ class CategoryBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>()
|
||||
}
|
||||
})
|
||||
|
||||
viewModel.getStreamBundle(category)
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
when (it) {
|
||||
is ViewState.Loading -> {
|
||||
@@ -84,12 +85,10 @@ class CategoryBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.observe(category)
|
||||
}
|
||||
|
||||
override fun onHeaderClicked(streamCluster: StreamCluster) {
|
||||
|
||||
openStreamBrowseFragment(streamCluster)
|
||||
}
|
||||
|
||||
override fun onClusterScrolled(streamCluster: StreamCluster) {
|
||||
|
||||
@@ -1,77 +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.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.aurora.Constants
|
||||
import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.store.databinding.FragmentForYouBinding
|
||||
import com.aurora.store.view.epoxy.controller.EditorChoiceController
|
||||
import com.aurora.store.viewmodel.editorschoice.EditorChoiceViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class EditorChoiceFragment : BaseFragment<FragmentForYouBinding>(),
|
||||
EditorChoiceController.Callbacks {
|
||||
private val viewModel: EditorChoiceViewModel by viewModels()
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(pageType: Int): EditorChoiceFragment {
|
||||
return EditorChoiceFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putInt(Constants.PAGE_TYPE, pageType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
var pageType = 0
|
||||
val bundle = arguments
|
||||
if (bundle != null) {
|
||||
pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
|
||||
}
|
||||
|
||||
val editorChoiceController = EditorChoiceController(this)
|
||||
binding.recycler.setController(editorChoiceController)
|
||||
|
||||
when (pageType) {
|
||||
0 -> viewModel.getEditorChoiceStream(StreamContract.Category.APPLICATION)
|
||||
1 -> viewModel.getEditorChoiceStream(StreamContract.Category.GAME)
|
||||
}
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
editorChoiceController.setData(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClick(editorChoiceCluster: EditorChoiceCluster) {
|
||||
openEditorStreamBrowseFragment(
|
||||
editorChoiceCluster.clusterBrowseUrl,
|
||||
editorChoiceCluster.clusterTitle
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,139 +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.view.ui.commons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.epoxy.groups.CarouselHorizontalModel_
|
||||
import com.aurora.store.view.epoxy.views.EditorHeadViewModel_
|
||||
import com.aurora.store.view.epoxy.views.HorizontalDividerViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotView
|
||||
import com.aurora.store.view.epoxy.views.details.MiniScreenshotViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.viewmodel.editorschoice.EditorBrowseViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class EditorStreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
private val args: EditorStreamBrowseFragmentArgs by navArgs()
|
||||
private val viewModel: EditorBrowseViewModel by viewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.apply {
|
||||
txtTitle.text = args.title
|
||||
imgActionPrimary.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(it)
|
||||
}
|
||||
|
||||
viewModel.getEditorStreamBundle(args.browseUrl)
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(appList: MutableList<App>?) {
|
||||
binding.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (appList == null) {
|
||||
for (i in 1..6) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
appList.forEach { app ->
|
||||
val screenshotsViewModels = mutableListOf<EpoxyModel<*>>()
|
||||
|
||||
for ((position, artwork) in app.screenshots.withIndex()) {
|
||||
screenshotsViewModels.add(
|
||||
MiniScreenshotViewModel_()
|
||||
.id(artwork.url)
|
||||
.position(position)
|
||||
.artwork(artwork)
|
||||
.callback(object : MiniScreenshotView.ScreenshotCallback {
|
||||
override fun onClick(position: Int) {
|
||||
openScreenshotFragment(app, position)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id("app_${app.id}")
|
||||
.app(app)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
)
|
||||
|
||||
app.editorReason?.let { editorReason ->
|
||||
add(
|
||||
EditorHeadViewModel_()
|
||||
.id("bulletin_${app.id}")
|
||||
.title(
|
||||
editorReason.bulletins
|
||||
.joinToString(transform = { "\n• $it" })
|
||||
.substringAfter(delimiter = "\n")
|
||||
)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
)
|
||||
}
|
||||
|
||||
if (screenshotsViewModels.isNotEmpty()) {
|
||||
add(
|
||||
CarouselHorizontalModel_()
|
||||
.id("screenshots_${app.id}")
|
||||
.models(screenshotsViewModels)
|
||||
)
|
||||
}
|
||||
|
||||
app.editorReason?.let { editorReason ->
|
||||
if (editorReason.description.isNotEmpty()) {
|
||||
add(
|
||||
EditorHeadViewModel_()
|
||||
.id("description_${app.id}")
|
||||
.title(editorReason.description)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
add(
|
||||
HorizontalDividerViewModel_()
|
||||
.id("divider_${app.id}")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,27 +67,18 @@ class ForYouFragment : BaseFragment<FragmentForYouBinding>(),
|
||||
pageType = bundle.getInt(Constants.PAGE_TYPE, 0)
|
||||
}
|
||||
|
||||
when (pageType) {
|
||||
0 -> {
|
||||
category = Category.APPLICATION
|
||||
viewModel.getStreamBundle(category, Type.HOME)
|
||||
}
|
||||
|
||||
1 -> {
|
||||
category = Category.GAME
|
||||
viewModel.getStreamBundle(category, Type.HOME)
|
||||
}
|
||||
}
|
||||
category = if (pageType == 0) Category.APPLICATION else Category.GAME
|
||||
|
||||
binding.recycler.setController(genericCarouselController)
|
||||
binding.recycler.addOnScrollListener(
|
||||
object : EndlessRecyclerOnScrollListener() {
|
||||
object : EndlessRecyclerOnScrollListener(visibleThreshold = 4) {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.observe(category, Type.HOME)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
viewModel.getStreamBundle(category, Type.HOME)
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
when (it) {
|
||||
is ViewState.Loading -> {
|
||||
@@ -107,7 +98,7 @@ class ForYouFragment : BaseFragment<FragmentForYouBinding>(),
|
||||
}
|
||||
|
||||
override fun onHeaderClicked(streamCluster: StreamCluster) {
|
||||
|
||||
openStreamBrowseFragment(streamCluster)
|
||||
}
|
||||
|
||||
override fun onClusterScrolled(streamCluster: StreamCluster) {
|
||||
|
||||
@@ -270,11 +270,6 @@ class MoreDialogFragment : DialogFragment() {
|
||||
icon = R.drawable.ic_apps,
|
||||
destinationID = R.id.appsGamesFragment
|
||||
),
|
||||
Option(
|
||||
title = R.string.title_apps_sale,
|
||||
icon = R.drawable.ic_sale,
|
||||
destinationID = R.id.appSalesFragment
|
||||
),
|
||||
Option(
|
||||
title = R.string.title_blacklist_manager,
|
||||
icon = R.drawable.ic_blacklist,
|
||||
|
||||
@@ -38,38 +38,32 @@ class StreamBrowseFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
private val args: StreamBrowseFragmentArgs by navArgs()
|
||||
private val viewModel: StreamBrowseViewModel by viewModels()
|
||||
|
||||
private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener
|
||||
private lateinit var cluster: StreamCluster
|
||||
private lateinit var streamCluster: StreamCluster
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
streamCluster = args.cluster
|
||||
|
||||
// Toolbar
|
||||
binding.layoutToolbarAction.apply {
|
||||
txtTitle.text = args.title
|
||||
txtTitle.text = streamCluster.clusterTitle
|
||||
imgActionPrimary.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
if (!::cluster.isInitialized) {
|
||||
endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.nextCluster()
|
||||
}
|
||||
}
|
||||
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
binding.recycler.addOnScrollListener(object :
|
||||
EndlessRecyclerOnScrollListener(visibleThreshold = 4) {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.nextCluster()
|
||||
}
|
||||
})
|
||||
|
||||
cluster = it
|
||||
|
||||
updateController(cluster)
|
||||
binding.layoutToolbarAction.txtTitle.text = it.clusterTitle
|
||||
viewModel.initCluster(streamCluster)
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(streamCluster)
|
||||
}
|
||||
|
||||
viewModel.getStreamBundle(args.browseUrl)
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(streamCluster: StreamCluster?) {
|
||||
|
||||
@@ -55,8 +55,8 @@ import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.Review
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.AppStreamStash
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.Event
|
||||
@@ -135,7 +135,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent -> {
|
||||
is InstallerEvent.Installed -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
binding.layoutDetailsToolbar.toolbar.menu.apply {
|
||||
@@ -147,7 +147,7 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
}
|
||||
|
||||
is BusEvent.UninstallEvent -> {
|
||||
is InstallerEvent.Uninstalled -> {
|
||||
if (app.packageName == event.packageName) {
|
||||
attachActions()
|
||||
binding.layoutDetailsToolbar.toolbar.menu.apply {
|
||||
@@ -166,14 +166,23 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
is InstallerEvent.Failed -> {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToInstallErrorDialogSheet(
|
||||
app,
|
||||
event.packageName ?: "",
|
||||
event.error ?: "",
|
||||
event.extra ?: ""
|
||||
if (app.packageName == event.packageName) {
|
||||
findNavController().navigate(
|
||||
AppDetailsFragmentDirections.actionAppDetailsFragmentToInstallErrorDialogSheet(
|
||||
app,
|
||||
event.packageName ?: "",
|
||||
event.error ?: "",
|
||||
event.extra ?: ""
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is InstallerEvent.Installing -> {
|
||||
if (event.packageName == app.packageName) {
|
||||
attachActions()
|
||||
updateActionState(State.INSTALLING)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
@@ -356,10 +365,10 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.flowEvent.busEvent.collect { onEvent(it) }
|
||||
AuroraApp.events.busEvent.collect { onEvent(it) }
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.flowEvent.installerEvent.collect { onEvent(it) }
|
||||
AuroraApp.events.installerEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +516,15 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
}
|
||||
|
||||
private fun updateActionState(state: State) {
|
||||
binding.layoutDetailsInstall.btnDownload.updateState(state)
|
||||
runOnUiThread {
|
||||
binding.layoutDetailsInstall.btnDownload.apply {
|
||||
updateState(state)
|
||||
if (state == State.INSTALLING) {
|
||||
setButtonState(false)
|
||||
setText(R.string.action_installing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun openApp() {
|
||||
@@ -589,9 +606,8 @@ class AppDetailsFragment : BaseFragment<FragmentDetailsBinding>() {
|
||||
app.isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
|
||||
runOnUiThread {
|
||||
app.isInstalled = PackageUtil.isInstalled(requireContext(), app.packageName)
|
||||
|
||||
binding.layoutDetailsInstall.btnDownload.let { btn ->
|
||||
btn.setButtonState(true)
|
||||
if (app.isInstalled) {
|
||||
isUpdatable = PackageUtil.isUpdatable(
|
||||
requireContext(),
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.aurora.store.databinding.FragmentAppsGamesBinding
|
||||
import com.aurora.store.util.Preferences
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.view.ui.commons.CategoryFragment
|
||||
import com.aurora.store.view.ui.commons.EditorChoiceFragment
|
||||
import com.aurora.store.view.ui.commons.ForYouFragment
|
||||
import com.aurora.store.view.ui.commons.TopChartContainerFragment
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
@@ -91,10 +90,6 @@ class GamesContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
||||
|
||||
add(getString(R.string.tab_top_charts))
|
||||
add(getString(R.string.tab_categories))
|
||||
|
||||
if (!authProvider.isAnonymous) {
|
||||
add(getString(R.string.tab_editor_choice))
|
||||
}
|
||||
}
|
||||
|
||||
TabLayoutMediator(
|
||||
@@ -129,10 +124,6 @@ class GamesContainerFragment : BaseFragment<FragmentAppsGamesBinding>() {
|
||||
|
||||
add(TopChartContainerFragment.newInstance(1))
|
||||
add(CategoryFragment.newInstance(1))
|
||||
|
||||
if (isGoogleAccount) {
|
||||
add(EditorChoiceFragment.newInstance(1))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
|
||||
@@ -1,113 +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.view.ui.sale
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.databinding.FragmentGenericWithToolbarBinding
|
||||
import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener
|
||||
import com.aurora.store.view.epoxy.views.AppProgressViewModel_
|
||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
|
||||
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
|
||||
import com.aurora.store.view.ui.commons.BaseFragment
|
||||
import com.aurora.store.viewmodel.all.PaginatedAppList
|
||||
import com.aurora.store.viewmodel.sale.AppSalesViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class AppSalesFragment : BaseFragment<FragmentGenericWithToolbarBinding>() {
|
||||
|
||||
private val viewModel: AppSalesViewModel by viewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.layoutToolbarAction.txtTitle.text = getString(R.string.title_apps_sale)
|
||||
binding.layoutToolbarAction.imgActionPrimary.setOnClickListener {
|
||||
findNavController().navigateUp()
|
||||
}
|
||||
|
||||
val endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() {
|
||||
override fun onLoadMore(currentPage: Int) {
|
||||
viewModel.observe()
|
||||
}
|
||||
}
|
||||
binding.recycler.addOnScrollListener(endlessRecyclerOnScrollListener)
|
||||
updateController(null)
|
||||
|
||||
viewModel.liveData.observe(viewLifecycleOwner) {
|
||||
updateController(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateController(paginatedAppList: PaginatedAppList?) {
|
||||
binding.recycler
|
||||
.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (paginatedAppList == null) {
|
||||
for (i in 1..6) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
.id(i)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
add(
|
||||
HeaderViewModel_()
|
||||
.id("header")
|
||||
.title(getString(R.string.title_apps_sale_provider))
|
||||
)
|
||||
|
||||
paginatedAppList.appList
|
||||
.filter { it.packageName.isNotEmpty() }
|
||||
.forEach {
|
||||
add(
|
||||
AppListViewModel_()
|
||||
.id(it.packageName.hashCode())
|
||||
.app(it)
|
||||
.click { _ -> openDetailsFragment(it.packageName, it) }
|
||||
)
|
||||
setFilterDuplicates(true)
|
||||
}
|
||||
|
||||
if (paginatedAppList.hasMore) {
|
||||
add(
|
||||
AppProgressViewModel_()
|
||||
.id("progress")
|
||||
)
|
||||
}
|
||||
|
||||
if (!paginatedAppList.hasMore && paginatedAppList.appList.isEmpty()) {
|
||||
add(
|
||||
NoAppViewModel_()
|
||||
.id("no_app_sale")
|
||||
.icon(R.drawable.ic_apps)
|
||||
.message(getString(R.string.details_no_apps_on_sale))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,8 +94,8 @@ class AppMenuSheet : BaseDialogSheet<SheetAppMenuBinding>() {
|
||||
}
|
||||
|
||||
dismissAllowingStateLoss()
|
||||
AuroraApp.flowEvent.emitEvent(
|
||||
BusEvent.Blacklisted(args.app.packageName, "")
|
||||
AuroraApp.events.send(
|
||||
BusEvent.Blacklisted(args.app.packageName)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.MobileNavigationDirections
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.Event
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.room.download.Download
|
||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||
import com.aurora.store.util.PackageUtil
|
||||
@@ -143,13 +144,13 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
AuroraApp.flowEvent.busEvent.collect { onEvent(it) }
|
||||
AuroraApp.events.busEvent.collect { onEvent(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEvent(event: BusEvent) {
|
||||
private fun onEvent(event: Event) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent, is BusEvent.UninstallEvent -> {
|
||||
is InstallerEvent.Installed, is InstallerEvent.Uninstalled -> {
|
||||
viewModel.fetchUpdates()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,81 +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.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.ClusterHelper
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class LibraryAppsViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val clusterHelper: ClusterHelper = ClusterHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
|
||||
var streamCluster: StreamCluster = StreamCluster()
|
||||
|
||||
fun observe() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
when {
|
||||
streamCluster.clusterAppList.isEmpty() -> {
|
||||
val newCluster =
|
||||
clusterHelper.getCluster(ClusterHelper.Type.MY_APPS_LIBRARY)
|
||||
updateCluster(newCluster)
|
||||
liveData.postValue(streamCluster)
|
||||
}
|
||||
streamCluster.hasNext() -> {
|
||||
val newCluster = clusterHelper.next(streamCluster.clusterNextPageUrl)
|
||||
updateCluster(newCluster)
|
||||
liveData.postValue(streamCluster)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCluster(newCluster: StreamCluster) {
|
||||
streamCluster.apply {
|
||||
clusterAppList.addAll(newCluster.clusterAppList)
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
data class PaginatedAppList(
|
||||
var appList: MutableList<App> = mutableListOf(),
|
||||
var hasMore: Boolean
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class PurchasedViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val purchaseHelper = PurchaseHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
private var appList: MutableList<App> = mutableListOf()
|
||||
|
||||
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
|
||||
|
||||
fun observe() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
val nextAppList = purchaseHelper.getPurchaseHistory(appList.size)
|
||||
.filter { it.displayName.isNotEmpty() }
|
||||
|
||||
if (nextAppList.isEmpty()) {
|
||||
liveData.postValue(PaginatedAppList(
|
||||
appList,
|
||||
false
|
||||
))
|
||||
} else {
|
||||
appList.addAll(nextAppList)
|
||||
liveData.postValue(
|
||||
PaginatedAppList(
|
||||
appList,
|
||||
true
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import com.aurora.gplayapi.data.providers.DeviceInfoProvider
|
||||
import com.aurora.gplayapi.helpers.AuthHelper
|
||||
import com.aurora.store.AuroraApp
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.store.data.event.AuthEvent
|
||||
import com.aurora.store.data.model.AccountType
|
||||
import com.aurora.store.data.model.AuthState
|
||||
import com.aurora.store.data.model.InsecureAuth
|
||||
@@ -201,18 +201,18 @@ class AuthViewModel @Inject constructor(
|
||||
if (aasToken != null) {
|
||||
Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, email)
|
||||
Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, aasToken)
|
||||
AuroraApp.flowEvent.emitEvent(BusEvent.GoogleAAS(true, email, aasToken))
|
||||
AuroraApp.events.send(AuthEvent.GoogleLogin(true, email, aasToken))
|
||||
} else {
|
||||
Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, "")
|
||||
Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, "")
|
||||
AuroraApp.flowEvent.emitEvent(BusEvent.GoogleAAS(false))
|
||||
AuroraApp.events.send(AuthEvent.GoogleLogin(false, "", ""))
|
||||
}
|
||||
} else {
|
||||
AuroraApp.flowEvent.emitEvent(BusEvent.GoogleAAS(false))
|
||||
AuroraApp.events.send(AuthEvent.GoogleLogin(false, "", ""))
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to build AuthData", exception)
|
||||
AuroraApp.flowEvent.emitEvent(BusEvent.GoogleAAS(false))
|
||||
AuroraApp.events.send(AuthEvent.GoogleLogin(false, "", ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,6 +238,7 @@ class AuthViewModel @Inject constructor(
|
||||
)
|
||||
buildGoogleAuthData(email, aasToken)
|
||||
}
|
||||
|
||||
AccountType.ANONYMOUS -> {
|
||||
buildAnonymousAuthData()
|
||||
}
|
||||
@@ -248,9 +249,11 @@ class AuthViewModel @Inject constructor(
|
||||
is UnknownHostException -> {
|
||||
context.getString(R.string.title_no_network)
|
||||
}
|
||||
|
||||
is ConnectException -> {
|
||||
context.getString(R.string.server_unreachable)
|
||||
}
|
||||
|
||||
else -> {
|
||||
context.getString(R.string.bad_request)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.StreamHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.Log
|
||||
@@ -43,31 +44,16 @@ class StreamBrowseViewModel @Inject constructor(
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val streamHelper: StreamHelper = StreamHelper(authProvider.authData)
|
||||
private val streamHelper: StreamContract = WebStreamHelper()
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<StreamCluster> = MutableLiveData()
|
||||
var streamCluster: StreamCluster = StreamCluster()
|
||||
|
||||
fun getStreamBundle(browseUrl: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
liveData.postValue(getInitialCluster(browseUrl))
|
||||
}
|
||||
}
|
||||
private lateinit var streamCluster: StreamCluster
|
||||
|
||||
private fun getInitialCluster(browseUrl: String): StreamCluster {
|
||||
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
|
||||
|
||||
if (browseResponse.contentsUrl.isNotEmpty())
|
||||
streamCluster = streamHelper.getNextStreamCluster(browseResponse.contentsUrl)
|
||||
else if (browseResponse.hasBrowseTab())
|
||||
streamCluster = streamHelper.getNextStreamCluster(browseResponse.browseTab.listUrl)
|
||||
|
||||
streamCluster.apply {
|
||||
clusterTitle = browseResponse.title
|
||||
}
|
||||
|
||||
return streamCluster
|
||||
fun initCluster(cluster: StreamCluster) {
|
||||
streamCluster = cluster
|
||||
liveData.postValue(streamCluster)
|
||||
}
|
||||
|
||||
fun nextCluster() {
|
||||
@@ -75,18 +61,18 @@ class StreamBrowseViewModel @Inject constructor(
|
||||
supervisorScope {
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster = streamHelper.getNextStreamCluster(
|
||||
val nextCluster = streamHelper.nextStreamCluster(
|
||||
streamCluster.clusterNextPageUrl
|
||||
)
|
||||
|
||||
streamCluster.apply {
|
||||
clusterAppList.addAll(newCluster.clusterAppList)
|
||||
clusterNextPageUrl = newCluster.clusterNextPageUrl
|
||||
clusterAppList.addAll(nextCluster.clusterAppList)
|
||||
clusterNextPageUrl = nextCluster.clusterNextPageUrl
|
||||
}
|
||||
|
||||
liveData.postValue(streamCluster)
|
||||
} else {
|
||||
Log.i("End of Bundle")
|
||||
Log.i("End of Cluster")
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.Category
|
||||
import com.aurora.gplayapi.helpers.CategoryHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.CategoryContract
|
||||
import com.aurora.gplayapi.helpers.web.WebCategoryHelper
|
||||
import com.aurora.store.CategoryStash
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
@@ -50,9 +49,6 @@ class CategoryViewModel @Inject constructor(
|
||||
private val categoryHelper: CategoryHelper = CategoryHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
private val webCategoryHelper: CategoryContract = WebCategoryHelper()
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
private var stash: CategoryStash = mutableMapOf(
|
||||
Category.Type.APPLICATION to emptyList(),
|
||||
Category.Type.GAME to emptyList()
|
||||
@@ -61,11 +57,7 @@ class CategoryViewModel @Inject constructor(
|
||||
val liveData = MutableLiveData<ViewState>()
|
||||
|
||||
private fun contract(): CategoryContract {
|
||||
return if (authProvider.isAnonymous) {
|
||||
webCategoryHelper
|
||||
} else {
|
||||
categoryHelper
|
||||
}
|
||||
return categoryHelper
|
||||
}
|
||||
|
||||
fun getCategoryList(type: Category.Type) {
|
||||
@@ -74,10 +66,11 @@ class CategoryViewModel @Inject constructor(
|
||||
|
||||
if (categories.isNotEmpty()) {
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
return@launch
|
||||
}
|
||||
|
||||
try {
|
||||
stash[type] = contract().getAllCategoriesList(type)
|
||||
stash[type] = contract().getAllCategories(type)
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed fetching list of categories", exception)
|
||||
|
||||
@@ -1,74 +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.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class EditorBrowseViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val streamHelper: StreamHelper = StreamHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<MutableList<App>> = MutableLiveData()
|
||||
val appList: MutableList<App> = mutableListOf()
|
||||
|
||||
fun getEditorStreamBundle(browseUrl: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl)
|
||||
val listResponse =
|
||||
streamHelper.getNextStreamResponse(browseResponse.browseTab.listUrl)
|
||||
|
||||
listResponse.itemList.forEach {
|
||||
it?.let {
|
||||
it.subItemList.forEach {
|
||||
appList.addAll(streamHelper.getAppsFromItem(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
liveData.postValue(appList)
|
||||
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +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.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.editor.EditorChoiceBundle
|
||||
import com.aurora.gplayapi.helpers.StreamHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class EditorChoiceViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val TAG = EditorChoiceViewModel::class.java.simpleName
|
||||
|
||||
private val streamHelper: StreamHelper = StreamHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<List<EditorChoiceBundle>> = MutableLiveData()
|
||||
|
||||
fun getEditorChoiceStream(category: StreamContract.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,13 +26,11 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.StreamHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
||||
import com.aurora.store.HomeStash
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.Log
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
@@ -44,14 +42,10 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class StreamViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
private var streamHelper: StreamContract = StreamHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
private var webStreamHelper: StreamContract = WebStreamHelper()
|
||||
private var webStreamHelper = WebStreamHelper()
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
@@ -59,11 +53,7 @@ class StreamViewModel @Inject constructor(
|
||||
private var stash: HomeStash = mutableMapOf()
|
||||
|
||||
fun contract(): StreamContract {
|
||||
return if (authProvider.isAnonymous) {
|
||||
webStreamHelper
|
||||
} else {
|
||||
streamHelper
|
||||
}
|
||||
return webStreamHelper
|
||||
}
|
||||
|
||||
fun getStreamBundle(category: StreamContract.Category, type: StreamContract.Type) {
|
||||
@@ -75,7 +65,7 @@ class StreamViewModel @Inject constructor(
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
val bundle = targetBundle(category)
|
||||
if (bundle.streamClusters.isNotEmpty()) {
|
||||
if (bundle.hasCluster()) {
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
}
|
||||
|
||||
@@ -83,13 +73,13 @@ class StreamViewModel @Inject constructor(
|
||||
if (!bundle.hasCluster() || bundle.hasNext()) {
|
||||
|
||||
//Fetch new stream bundle
|
||||
val newBundle = if (bundle.streamClusters.isEmpty()) {
|
||||
contract().fetch(type, category)
|
||||
} else {
|
||||
val newBundle = if (bundle.hasCluster()) {
|
||||
contract().nextStreamBundle(
|
||||
category,
|
||||
bundle.streamNextPageUrl
|
||||
)
|
||||
} else {
|
||||
contract().fetch(type, category)
|
||||
}
|
||||
|
||||
//Update old bundle
|
||||
@@ -142,7 +132,7 @@ class StreamViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun targetBundle(category: StreamContract.Category): StreamBundle {
|
||||
val streamBundle = stash.getOrPut(category){
|
||||
val streamBundle = stash.getOrPut(category) {
|
||||
StreamBundle()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,95 +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.sale
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.helpers.AppSalesHelper
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.viewmodel.all.PaginatedAppList
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class AppSalesViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val appSalesHelper: AppSalesHelper = AppSalesHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
private var page: Int = 0
|
||||
private val appList: MutableList<App> = mutableListOf()
|
||||
|
||||
val liveData: MutableLiveData<PaginatedAppList> = MutableLiveData()
|
||||
|
||||
init {
|
||||
observe()
|
||||
}
|
||||
|
||||
fun observe() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
try {
|
||||
val nextAppList = getSearchResults()
|
||||
|
||||
if (nextAppList.isEmpty()) {
|
||||
liveData.postValue(
|
||||
PaginatedAppList(
|
||||
appList,
|
||||
hasMore = false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
appList.addAll(nextAppList)
|
||||
liveData.postValue(
|
||||
PaginatedAppList(
|
||||
appList,
|
||||
hasMore = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSearchResults(): List<App> {
|
||||
return try {
|
||||
appSalesHelper.getAppsOnSale(page = page++, offer = 100)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class SheetsViewModel @Inject constructor(
|
||||
val purchaseHelper = PurchaseHelper(authProvider.authData)
|
||||
val files = purchaseHelper.purchase(app.packageName, customVersion, app.offerType)
|
||||
if (files.isNotEmpty()) {
|
||||
AuroraApp.flowEvent.emitEvent(
|
||||
AuroraApp.events.send(
|
||||
BusEvent.ManualDownload(app.packageName, customVersion)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -26,8 +26,9 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.StreamBundle
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.contracts.CategoryStreamContract
|
||||
import com.aurora.gplayapi.helpers.contracts.StreamContract
|
||||
import com.aurora.gplayapi.helpers.web.WebStreamHelper
|
||||
import com.aurora.gplayapi.helpers.web.WebCategoryStreamHelper
|
||||
import com.aurora.store.data.model.ViewState
|
||||
import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.util.Log
|
||||
@@ -40,25 +41,24 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class SubCategoryClusterViewModel @Inject constructor(
|
||||
class CategoryStreamViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
) : ViewModel() {
|
||||
|
||||
var contract: StreamContract = WebStreamHelper()
|
||||
private var webCategoryStreamHelper = WebCategoryStreamHelper()
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
|
||||
private var stash: MutableMap<String, StreamBundle> = mutableMapOf()
|
||||
|
||||
private fun getCategoryStreamBundle(
|
||||
category: StreamContract.Category,
|
||||
nextPageUrl: String
|
||||
): StreamBundle {
|
||||
return if (targetBundle(category).streamClusters.isEmpty())
|
||||
contract.fetch(StreamContract.Type.HOME, category)
|
||||
else
|
||||
contract.nextStreamBundle(category, nextPageUrl)
|
||||
fun contract(): CategoryStreamContract {
|
||||
return webCategoryStreamHelper
|
||||
}
|
||||
|
||||
fun getStreamBundle(category: StreamContract.Category) {
|
||||
liveData.postValue(ViewState.Loading)
|
||||
observe(category)
|
||||
}
|
||||
|
||||
fun observe(category: StreamContract.Category) {
|
||||
@@ -66,18 +66,22 @@ class SubCategoryClusterViewModel @Inject constructor(
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
supervisorScope {
|
||||
val bundle = targetBundle(category)
|
||||
|
||||
if (bundle.streamClusters.isNotEmpty()) {
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
}
|
||||
|
||||
try {
|
||||
if (!bundle.hasCluster() || bundle.hasNext()) {
|
||||
|
||||
//Fetch new stream bundle
|
||||
val newBundle = getCategoryStreamBundle(
|
||||
category,
|
||||
bundle.streamNextPageUrl
|
||||
)
|
||||
val newBundle = if (bundle.streamClusters.isEmpty()) {
|
||||
contract().fetch(category.value)
|
||||
} else {
|
||||
contract().nextStreamBundle(
|
||||
category,
|
||||
bundle.streamNextPageUrl
|
||||
)
|
||||
}
|
||||
|
||||
//Update old bundle
|
||||
bundle.apply {
|
||||
@@ -103,7 +107,7 @@ class SubCategoryClusterViewModel @Inject constructor(
|
||||
try {
|
||||
if (streamCluster.hasNext()) {
|
||||
val newCluster =
|
||||
contract.nextStreamCluster(streamCluster.clusterNextPageUrl)
|
||||
contract().nextStreamCluster(streamCluster.clusterNextPageUrl)
|
||||
updateCluster(category, streamCluster.id, newCluster)
|
||||
liveData.postValue(ViewState.Success(stash))
|
||||
} else {
|
||||
@@ -25,13 +25,11 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.StreamCluster
|
||||
import com.aurora.gplayapi.helpers.TopChartsHelper
|
||||
import com.aurora.gplayapi.helpers.contracts.TopChartsContract
|
||||
import com.aurora.gplayapi.helpers.web.WebTopChartsHelper
|
||||
import com.aurora.store.TopChartStash
|
||||
import com.aurora.store.data.model.ViewState
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
@@ -41,13 +39,8 @@ import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||
class TopChartViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val authProvider: AuthProvider
|
||||
) : ViewModel() {
|
||||
|
||||
private val topChartsHelper: TopChartsHelper = TopChartsHelper(authProvider.authData)
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
class TopChartViewModel @Inject constructor(@ApplicationContext private val context: Context) :
|
||||
ViewModel() {
|
||||
|
||||
private val webTopChartsHelper: TopChartsContract = WebTopChartsHelper()
|
||||
.using(HttpClient.getPreferredClient(context))
|
||||
@@ -57,11 +50,7 @@ class TopChartViewModel @Inject constructor(
|
||||
val liveData: MutableLiveData<ViewState> = MutableLiveData()
|
||||
|
||||
private fun contract(): TopChartsContract {
|
||||
return if (authProvider.isAnonymous) {
|
||||
webTopChartsHelper
|
||||
} else {
|
||||
topChartsHelper
|
||||
}
|
||||
return webTopChartsHelper
|
||||
}
|
||||
|
||||
fun getStreamCluster(type: TopChartsContract.Type, chart: TopChartsContract.Chart) {
|
||||
@@ -85,7 +74,7 @@ class TopChartViewModel @Inject constructor(
|
||||
try {
|
||||
val target = targetCluster(type, chart)
|
||||
if (target.hasNext()) {
|
||||
val newCluster = topChartsHelper.getNextStreamCluster(
|
||||
val newCluster = contract().getNextStreamCluster(
|
||||
target.clusterNextPageUrl
|
||||
)
|
||||
|
||||
|
||||
@@ -52,15 +52,12 @@
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progress_download"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="@dimen/icon_size_medium"
|
||||
android:layout_height="@dimen/icon_size_medium"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorScrimAlt"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="@color/colorScrimAlt"
|
||||
app:trackColor="@color/colorScrim"
|
||||
app:trackThickness="@dimen/icon_size_default"
|
||||
app:indicatorSize="@dimen/icon_size_medium"
|
||||
app:trackThickness="3dp"
|
||||
tools:progress="40" />
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
<action
|
||||
android:id="@+id/action_global_categoryBrowseFragment"
|
||||
app:destination="@id/categoryBrowseFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_editorStreamBrowseFragment"
|
||||
app:destination="@id/editorStreamBrowseFragment" />
|
||||
<action
|
||||
android:id="@+id/action_global_expandedStreamBrowseFragment"
|
||||
app:destination="@id/expandedStreamBrowseFragment" />
|
||||
@@ -78,11 +75,6 @@
|
||||
android:name="com.aurora.store.view.ui.all.AppsGamesFragment"
|
||||
android:label="@string/title_apps_games"
|
||||
tools:layout="@layout/fragment_generic_with_pager" />
|
||||
<fragment
|
||||
android:id="@+id/appSalesFragment"
|
||||
android:name="com.aurora.store.view.ui.sale.AppSalesFragment"
|
||||
android:label="@string/title_apps_sale"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar" />
|
||||
<fragment
|
||||
android:id="@+id/spoofFragment"
|
||||
android:name="com.aurora.store.view.ui.spoof.SpoofFragment"
|
||||
@@ -220,16 +212,6 @@
|
||||
android:id="@+id/action_categoryBrowseFragment_to_appPeekDialogSheet"
|
||||
app:destination="@id/appPeekDialogSheet" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/editorStreamBrowseFragment"
|
||||
android:name="com.aurora.store.view.ui.commons.EditorStreamBrowseFragment"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar" >
|
||||
<argument android:name="title"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="browseUrl"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/expandedStreamBrowseFragment"
|
||||
android:name="com.aurora.store.view.ui.commons.ExpandedStreamBrowseFragment"
|
||||
@@ -273,11 +255,8 @@
|
||||
android:name="com.aurora.store.view.ui.commons.StreamBrowseFragment"
|
||||
tools:layout="@layout/fragment_generic_with_toolbar" >
|
||||
<argument
|
||||
android:name="browseUrl"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="title"
|
||||
app:argType="string" />
|
||||
android:name="cluster"
|
||||
app:argType="com.aurora.gplayapi.data.models.StreamCluster" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/devAppsFragment"
|
||||
|
||||
@@ -93,6 +93,7 @@
|
||||
<string name="action_search">"Search"</string>
|
||||
<string name="action_share">"Share"</string>
|
||||
<string name="action_uninstall">"Uninstall"</string>
|
||||
<string name="action_uninstall_success">"Successfully uninstalled"</string>
|
||||
<string name="action_home_screen">Add to Home screen</string>
|
||||
<string name="action_uninstall_confirmation">"Do you want to uninstall this app ?"</string>
|
||||
<string name="action_info">"App Info"</string>
|
||||
|
||||
@@ -7,7 +7,7 @@ composeBom = "2024.06.00"
|
||||
coreVersion = "1.13.1"
|
||||
epoxyVersion = "5.1.4"
|
||||
espressoVersion = "3.6.1"
|
||||
gplayapiVersion = "3.3.0"
|
||||
gplayapiVersion = "3.3.1"
|
||||
gsonVersion = "2.10.1"
|
||||
hiddenapibypassVersion = "4.3"
|
||||
hiltVersion = "2.51.1"
|
||||
|
||||
Reference in New Issue
Block a user