Improve app updates page [1/2]
This commit is contained in:
@@ -23,3 +23,11 @@ enum class AccountType {
|
||||
ANONYMOUS,
|
||||
GOOGLE
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
QUEUED,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
CANCELED
|
||||
}
|
||||
|
||||
49
app/src/main/java/com/aurora/store/data/model/UpdateFile.kt
Normal file
49
app/src/main/java/com/aurora/store/data/model/UpdateFile.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.data.model
|
||||
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.State
|
||||
import com.tonyodev.fetch2.FetchGroup
|
||||
|
||||
data class UpdateFile(val app: App) {
|
||||
|
||||
var group: FetchGroup? = null
|
||||
var state: State = State.IDLE
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return app.id
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return when (other) {
|
||||
is UpdateFile -> other.app.id == app.id
|
||||
&& other.state == state
|
||||
&& isGroupSame(group, other.group)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun isGroupSame(oldGroup: FetchGroup?, newGroup: FetchGroup?): Boolean {
|
||||
if (oldGroup != null && newGroup == null)
|
||||
return true
|
||||
return oldGroup?.groupDownloadProgress != newGroup?.groupDownloadProgress
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.isLAndAbove
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.databinding.ViewActionButtonBinding
|
||||
import nl.komponents.kovenant.task
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -81,6 +82,20 @@ class ActionButton : RelativeLayout {
|
||||
R.drawable.ic_check
|
||||
)
|
||||
|
||||
val stateBackground =
|
||||
when (typedArray.getString(R.styleable.ActionButton_btnActionBackground)) {
|
||||
"0" -> R.drawable.bg_state_outline
|
||||
"1" -> R.drawable.bg_state_outline_rounded
|
||||
"2" -> R.drawable.bg_state_flat
|
||||
"3" -> R.drawable.bg_state_flat_rounded
|
||||
"4" -> null
|
||||
else -> null
|
||||
}
|
||||
|
||||
stateBackground?.let {
|
||||
B.root.background = ContextCompat.getDrawable(context, it)
|
||||
}
|
||||
|
||||
val stateColor = ContextCompat.getColor(context, btnTxtColor)
|
||||
|
||||
B.btn.text = btnTxt
|
||||
@@ -108,6 +123,7 @@ class ActionButton : RelativeLayout {
|
||||
State.IDLE -> 0
|
||||
State.PROGRESS -> 1
|
||||
State.COMPLETE -> 2
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (B.viewFlipper.displayedChild != displayChild) {
|
||||
@@ -127,13 +143,7 @@ class ActionButton : RelativeLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fun addOnClickListener(onClickListener: OnClickListener) {
|
||||
fun addOnClickListener(onClickListener: OnClickListener?) {
|
||||
B.btn.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
enum class State {
|
||||
IDLE,
|
||||
PROGRESS,
|
||||
COMPLETE,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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.custom.layouts.button
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.databinding.ViewUpdateButtonBinding
|
||||
|
||||
class UpdateButton : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewUpdateButtonBinding
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
init(context, null)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleAttr
|
||||
) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet?,
|
||||
defStyleAttr: Int,
|
||||
defStyleRes: Int
|
||||
) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
val view = inflate(context, R.layout.view_update_button, this)
|
||||
B = ViewUpdateButtonBinding.bind(view)
|
||||
}
|
||||
|
||||
fun setText(text: String) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btnPositive.text = text
|
||||
}
|
||||
|
||||
fun setText(text: Int) {
|
||||
B.viewFlipper.displayedChild = 0
|
||||
B.btnPositive.text = getString(text)
|
||||
}
|
||||
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.IDLE, State.COMPLETE, State.CANCELED -> 0
|
||||
State.QUEUED -> 1
|
||||
State.PROGRESS -> 2
|
||||
}
|
||||
|
||||
if (B.viewFlipper.displayedChild != displayChild) {
|
||||
runOnUiThread {
|
||||
B.viewFlipper.displayedChild = displayChild
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addPositiveOnClickListener(onClickListener: OnClickListener?) {
|
||||
B.btnPositive.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
fun addNegativeOnClickListener(onClickListener: OnClickListener?) {
|
||||
B.btnNegative.setOnClickListener(onClickListener)
|
||||
}
|
||||
}
|
||||
@@ -21,17 +21,19 @@ package com.aurora.store.view.epoxy.views.app
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.RelativeLayout
|
||||
import androidx.core.text.HtmlCompat
|
||||
import com.airbnb.epoxy.CallbackProp
|
||||
import com.airbnb.epoxy.ModelProp
|
||||
import com.airbnb.epoxy.ModelView
|
||||
import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.extensions.clear
|
||||
import com.aurora.extensions.hide
|
||||
import com.aurora.extensions.load
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.extensions.px
|
||||
import com.aurora.extensions.show
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.model.UpdateFile
|
||||
import com.aurora.store.databinding.ViewAppUpdateBinding
|
||||
import com.aurora.store.util.CommonUtil
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
@@ -45,7 +47,6 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withC
|
||||
class AppUpdateView : RelativeLayout {
|
||||
|
||||
private lateinit var B: ViewAppUpdateBinding
|
||||
private var expanded: Boolean = false
|
||||
|
||||
constructor(context: Context?) : super(context) {
|
||||
init(context, null)
|
||||
@@ -69,43 +70,67 @@ class AppUpdateView : RelativeLayout {
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun app(app: App) {
|
||||
B.txtLine1.text = app.displayName
|
||||
B.imgIcon.load(app.iconArtwork.url, withCrossFade()) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transform(RoundedCorners(25))
|
||||
}
|
||||
fun updateFile(updateFile: UpdateFile?) {
|
||||
if (updateFile != null) {
|
||||
|
||||
B.txtLine2.text = app.developerName
|
||||
B.txtLine3.text = ("${CommonUtil.addSiPrefix(app.size)} • ${app.updatedOn}")
|
||||
B.txtLine4.text = ("v${app.versionName}.${app.versionCode}")
|
||||
B.txtChangelog.text = if (app.changes.isNotEmpty())
|
||||
HtmlCompat.fromHtml(
|
||||
app.changes,
|
||||
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||
)
|
||||
else
|
||||
context.getString(R.string.details_changelog_unavailable)
|
||||
/*Inflate App details*/
|
||||
with(updateFile.app) {
|
||||
B.txtLine1.text = displayName
|
||||
B.imgIcon.load(iconArtwork.url, withCrossFade()) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transform(RoundedCorners(8.px.toInt()))
|
||||
}
|
||||
|
||||
B.headerIndicator.setOnClickListener {
|
||||
B.expansionLayout.let {
|
||||
if (it.isExpanded) {
|
||||
it.collapse(true)
|
||||
} else {
|
||||
it.expand(true)
|
||||
B.txtLine2.text = developerName
|
||||
B.txtLine3.text = ("${CommonUtil.addSiPrefix(size)} • $updatedOn")
|
||||
B.txtLine4.text = ("v${versionName}.${versionCode}")
|
||||
B.txtChangelog.text = if (changes.isNotEmpty())
|
||||
HtmlCompat.fromHtml(
|
||||
changes,
|
||||
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||
)
|
||||
else
|
||||
context.getString(R.string.details_changelog_unavailable)
|
||||
|
||||
B.headerIndicator.setOnClickListener {
|
||||
B.expansionLayout.let {
|
||||
if (it.isExpanded) {
|
||||
it.collapse(true)
|
||||
} else {
|
||||
it.expand(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*Inflate Download details*/
|
||||
updateFile.group?.let {
|
||||
if (updateFile.state == State.PROGRESS) {
|
||||
val progress = it.groupDownloadProgress
|
||||
if (progress > 0) {
|
||||
if (progress == 100) {
|
||||
B.txtProgressPercent.hide()
|
||||
} else {
|
||||
B.txtProgressPercent.show()
|
||||
B.txtProgressPercent.text = ("${progress}%")
|
||||
}
|
||||
}
|
||||
} else if (updateFile.state == State.IDLE || updateFile.state == State.CANCELED) {
|
||||
B.btnAction.updateState(State.IDLE)
|
||||
B.txtProgressPercent.text = ("0%")
|
||||
B.txtProgressPercent.hide()
|
||||
} else if (updateFile.state == State.QUEUED) {
|
||||
B.btnAction.updateState(State.QUEUED)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun markChecked(isChecked: Boolean) {
|
||||
B.checkbox.isChecked = isChecked
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) {
|
||||
B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener)
|
||||
fun state(state: State?) {
|
||||
state?.let {
|
||||
B.btnAction.updateState(it)
|
||||
}
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
@@ -113,6 +138,16 @@ class AppUpdateView : RelativeLayout {
|
||||
B.layoutContent.setOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun positiveAction(onClickListener: OnClickListener?) {
|
||||
B.btnAction.addPositiveOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun negativeAction(onClickListener: OnClickListener?) {
|
||||
B.btnAction.addNegativeOnClickListener(onClickListener)
|
||||
}
|
||||
|
||||
@CallbackProp
|
||||
fun longClick(onClickListener: OnLongClickListener?) {
|
||||
B.layoutContent.setOnLongClickListener(onClickListener)
|
||||
@@ -120,7 +155,6 @@ class AppUpdateView : RelativeLayout {
|
||||
|
||||
@OnViewRecycled
|
||||
fun clear() {
|
||||
B.imgIcon.clear()
|
||||
B.headerIndicator.removeCallbacks { }
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,6 @@ import java.lang.reflect.Modifier
|
||||
|
||||
open class BaseFragment : Fragment() {
|
||||
|
||||
protected lateinit var app: App
|
||||
|
||||
var gson: Gson = GsonBuilder().excludeFieldsWithModifiers(
|
||||
Modifier.TRANSIENT
|
||||
).create()
|
||||
|
||||
@@ -38,6 +38,7 @@ import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.MainActivity
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
@@ -47,7 +48,6 @@ import com.aurora.store.data.network.HttpClient
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.databinding.ActivityDetailsBinding
|
||||
import com.aurora.store.util.*
|
||||
import com.aurora.store.view.custom.layouts.button.ActionButton
|
||||
import com.aurora.store.view.ui.downloads.DownloadActivity
|
||||
import com.aurora.store.view.ui.sheets.InstallErrorDialogSheet
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
||||
@@ -125,7 +125,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
event.extra
|
||||
).show(supportFragmentManager, "SED")
|
||||
attachActions()
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
updateActionState(State.IDLE)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
@@ -234,7 +234,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
checkAndSetupInstall()
|
||||
}
|
||||
|
||||
private fun updateActionState(state: ActionButton.State) {
|
||||
private fun updateActionState(state: State) {
|
||||
B.layoutDetailsInstall.btnDownload.updateState(state)
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
@Synchronized
|
||||
private fun install(files: List<Download>) {
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
updateActionState(State.IDLE)
|
||||
|
||||
task {
|
||||
AppInstaller(this)
|
||||
@@ -398,7 +398,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
}
|
||||
|
||||
private fun purchase() {
|
||||
updateActionState(ActionButton.State.PROGRESS)
|
||||
updateActionState(State.PROGRESS)
|
||||
|
||||
task {
|
||||
val authData = AuthProvider
|
||||
@@ -424,10 +424,10 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
enqueue(files)
|
||||
} else {
|
||||
Log.e("Failed to download : ${app.displayName}")
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
updateActionState(State.IDLE)
|
||||
}
|
||||
} failUi {
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
updateActionState(State.IDLE)
|
||||
var reason = "Unknown"
|
||||
|
||||
when (it) {
|
||||
@@ -477,7 +477,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
Log.i("Downloading Apks : %s", app.displayName)
|
||||
}
|
||||
} else {
|
||||
updateActionState(ActionButton.State.IDLE)
|
||||
updateActionState(State.IDLE)
|
||||
expandBottomSheet(getString(R.string.purchase_session_expired))
|
||||
}
|
||||
}
|
||||
@@ -521,7 +521,7 @@ class AppDetailsActivity : BaseDetailsActivity() {
|
||||
|
||||
with(B.layoutDetailsInstall) {
|
||||
txtPurchaseError.text = message
|
||||
btnDownload.updateState(ActionButton.State.IDLE)
|
||||
btnDownload.updateState(State.IDLE)
|
||||
if (app.isFree)
|
||||
btnDownload.setText(R.string.action_install)
|
||||
else
|
||||
|
||||
@@ -25,20 +25,21 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.aurora.Constants
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.gplayapi.data.models.AuthData
|
||||
import com.aurora.gplayapi.helpers.PurchaseHelper
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.downloader.DownloadManager
|
||||
import com.aurora.store.data.downloader.RequestBuilder
|
||||
import com.aurora.store.data.installer.AppInstaller
|
||||
import com.aurora.store.data.model.UpdateFile
|
||||
import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||
import com.aurora.store.util.Log
|
||||
import com.aurora.extensions.flushAndAdd
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.store.view.epoxy.views.app.AppUpdateViewModel_
|
||||
import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_
|
||||
import com.aurora.store.view.epoxy.views.app.AppUpdateViewModel_
|
||||
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
|
||||
@@ -63,7 +64,7 @@ class UpdatesFragment : BaseFragment() {
|
||||
private lateinit var fetch: Fetch
|
||||
private lateinit var fetchListener: AbstractFetchGroupListener
|
||||
|
||||
private val appList: MutableList<App> = mutableListOf()
|
||||
private var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -85,14 +86,36 @@ class UpdatesFragment : BaseFragment() {
|
||||
|
||||
fetch = DownloadManager.with(requireContext()).fetch
|
||||
fetchListener = object : AbstractFetchGroupListener() {
|
||||
|
||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
VM.updateDownload(groupId, fetchGroup)
|
||||
}
|
||||
|
||||
override fun onProgress(
|
||||
groupId: Int,
|
||||
download: Download,
|
||||
etaInMilliSeconds: Long,
|
||||
downloadedBytesPerSecond: Long,
|
||||
fetchGroup: FetchGroup
|
||||
) {
|
||||
VM.updateDownload(groupId, fetchGroup)
|
||||
}
|
||||
|
||||
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
super.onCompleted(groupId, download, fetchGroup)
|
||||
if (fetchGroup.groupDownloadProgress == 100) {
|
||||
VM.updateDownload(groupId, fetchGroup)
|
||||
install(download.tag!!, fetchGroup.downloads)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
VM.updateDownload(groupId, fetchGroup,true)
|
||||
}
|
||||
|
||||
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
VM.updateDownload(groupId, fetchGroup,true)
|
||||
}
|
||||
}
|
||||
|
||||
return B.root
|
||||
}
|
||||
@@ -113,9 +136,10 @@ class UpdatesFragment : BaseFragment() {
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
VM.liveData.observe(viewLifecycleOwner, {
|
||||
appList.flushAndAdd(it)
|
||||
updateController(it)
|
||||
|
||||
VM.liveUpdateData.observe(viewLifecycleOwner, {
|
||||
updateFileMap = it
|
||||
updateController(updateFileMap)
|
||||
B.swipeRefreshLayout.isRefreshing = false
|
||||
})
|
||||
|
||||
@@ -126,10 +150,10 @@ class UpdatesFragment : BaseFragment() {
|
||||
updateController(null)
|
||||
}
|
||||
|
||||
private fun updateController(appList: List<App>?) {
|
||||
private fun updateController(updateFileMap: MutableMap<Int, UpdateFile>?) {
|
||||
B.recycler.withModels {
|
||||
setFilterDuplicates(true)
|
||||
if (appList == null) {
|
||||
if (updateFileMap == null) {
|
||||
for (i in 1..6) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
@@ -137,7 +161,7 @@ class UpdatesFragment : BaseFragment() {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (appList.isEmpty()) {
|
||||
if (updateFileMap.isEmpty()) {
|
||||
add(
|
||||
NoAppViewModel_()
|
||||
.id("no_update")
|
||||
@@ -146,44 +170,26 @@ class UpdatesFragment : BaseFragment() {
|
||||
)
|
||||
} else {
|
||||
add(
|
||||
if (VM.selectedUpdates.isEmpty()) {
|
||||
UpdateHeaderViewModel_()
|
||||
.id("header_all")
|
||||
.title("${appList.size} updates available")
|
||||
.action(getString(R.string.action_update_all))
|
||||
.click { v ->
|
||||
updateAction(false)
|
||||
}
|
||||
} else {
|
||||
UpdateHeaderViewModel_()
|
||||
.id("header_selected")
|
||||
.title("${VM.selectedUpdates.size} updates selected")
|
||||
.action(getString(R.string.action_update))
|
||||
.click { v ->
|
||||
updateAction(true)
|
||||
}
|
||||
}
|
||||
UpdateHeaderViewModel_()
|
||||
.id("header_all")
|
||||
.title("${updateFileMap.size} updates available")
|
||||
.action(getString(R.string.action_update_all))
|
||||
.click { _ -> updateAll() }
|
||||
)
|
||||
|
||||
appList.forEach { app ->
|
||||
updateFileMap.values.forEach { updateFile ->
|
||||
add(
|
||||
AppUpdateViewModel_()
|
||||
.id(app.id)
|
||||
.app(app)
|
||||
.click { _ -> openDetailsActivity(app) }
|
||||
.id(updateFile.hashCode())
|
||||
.updateFile(updateFile)
|
||||
.click { _ -> openDetailsActivity(updateFile.app) }
|
||||
.longClick { _ ->
|
||||
openAppMenuSheet(app)
|
||||
openAppMenuSheet(updateFile.app)
|
||||
false
|
||||
}
|
||||
.markChecked(VM.selectedUpdates.contains(app.packageName))
|
||||
.checked { _, isChecked ->
|
||||
if (isChecked)
|
||||
VM.selectedUpdates.add(app.packageName)
|
||||
else
|
||||
VM.selectedUpdates.remove(app.packageName)
|
||||
|
||||
requestModelBuild()
|
||||
}
|
||||
.positiveAction { _ -> updateSingle(updateFile.app) }
|
||||
.negativeAction { _ -> cancelSingle(updateFile.app) }
|
||||
.state(updateFile.state)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -191,65 +197,38 @@ class UpdatesFragment : BaseFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAction(selectedOnly: Boolean) {
|
||||
if (VM.isUpdating) {
|
||||
requireContext().toast("Update in progress, let previous batch finish first")
|
||||
} else {
|
||||
if (selectedOnly) {
|
||||
updateSelected()
|
||||
private fun updateSingle(app: App) {
|
||||
|
||||
VM.updateState(app.id, State.QUEUED)
|
||||
|
||||
task {
|
||||
val files = purchaseHelper.purchase(
|
||||
app.packageName,
|
||||
app.versionCode,
|
||||
app.offerType
|
||||
)
|
||||
|
||||
files.map { RequestBuilder.buildRequest(requireContext(), app, it) }
|
||||
} successUi {
|
||||
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
|
||||
if (requests.isNotEmpty()) {
|
||||
fetch.enqueue(requests) {
|
||||
Log.i("Updating ${app.displayName}")
|
||||
}
|
||||
} else {
|
||||
updateAll()
|
||||
requireContext().toast("Failed to update ${app.displayName}")
|
||||
}
|
||||
} failUi {
|
||||
Log.e("Failed to update ${app.displayName}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelSingle(app: App) {
|
||||
fetch.cancelGroup(app.id)
|
||||
}
|
||||
|
||||
private fun updateAll() {
|
||||
appList.forEach { app ->
|
||||
task {
|
||||
val files = purchaseHelper.purchase(app.packageName, app.versionCode, app.offerType)
|
||||
files.map { RequestBuilder.buildRequest(requireContext(), app, it) }
|
||||
} successUi {
|
||||
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
|
||||
if (requests.isNotEmpty()) {
|
||||
VM.isUpdating = true
|
||||
fetch.enqueue(requests) {
|
||||
Log.i("Updating ${app.displayName}")
|
||||
}
|
||||
} else {
|
||||
requireContext().toast("Failed to update ${app.displayName}")
|
||||
}
|
||||
} failUi {
|
||||
Log.e("Failed to update ${app.displayName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSelected() {
|
||||
val selectedPackages = VM.selectedUpdates
|
||||
appList.forEach { app ->
|
||||
if (selectedPackages.contains(app.packageName)) {
|
||||
task {
|
||||
val files = purchaseHelper.purchase(
|
||||
app.packageName,
|
||||
app.versionCode,
|
||||
app.offerType
|
||||
)
|
||||
files.map { RequestBuilder.buildRequest(requireContext(), app, it) }
|
||||
} successUi {
|
||||
val requests = it.filter { request -> request.url.isNotEmpty() }.toList()
|
||||
if (requests.isNotEmpty()) {
|
||||
VM.isUpdating = true
|
||||
fetch.enqueue(requests) {
|
||||
Log.i("Updating ${app.displayName}")
|
||||
}
|
||||
} else {
|
||||
requireContext().toast("Failed to update ${app.displayName}")
|
||||
}
|
||||
} failUi {
|
||||
Log.e("Failed to update ${app.displayName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
updateFileMap.values.forEach { updateSingle(it.app) }
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
|
||||
@@ -20,11 +20,15 @@
|
||||
package com.aurora.store.viewmodel.all
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.RequestState
|
||||
import com.aurora.store.data.event.BusEvent
|
||||
import com.aurora.extensions.flushAndAdd
|
||||
import com.aurora.store.data.event.InstallerEvent
|
||||
import com.aurora.store.data.model.UpdateFile
|
||||
import com.tonyodev.fetch2.FetchGroup
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
@@ -33,8 +37,8 @@ import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class UpdatesViewModel(application: Application) : BaseAppsViewModel(application) {
|
||||
|
||||
var selectedUpdates: MutableSet<String> = mutableSetOf()
|
||||
var isUpdating: Boolean = false
|
||||
var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
|
||||
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
|
||||
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
@@ -63,14 +67,17 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
||||
false
|
||||
}
|
||||
}.also { apps ->
|
||||
appList.flushAndAdd(apps)
|
||||
liveData.postValue(appList.sortedBy { it.displayName })
|
||||
apps.forEach {
|
||||
updateFileMap[it.id] = UpdateFile(it)
|
||||
}
|
||||
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
requestState = RequestState.Complete
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||
fun onEvent(event: BusEvent) {
|
||||
fun onBusEvent(event: BusEvent) {
|
||||
when (event) {
|
||||
is BusEvent.InstallEvent -> {
|
||||
updateListAndPost(event.packageName)
|
||||
@@ -87,22 +94,44 @@ class UpdatesViewModel(application: Application) : BaseAppsViewModel(application
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||
fun onInstallerEvent(event: InstallerEvent) {
|
||||
when (event) {
|
||||
is InstallerEvent.Success -> {
|
||||
|
||||
}
|
||||
is InstallerEvent.Failed -> {
|
||||
val packageName = event.packageName
|
||||
packageName?.let {
|
||||
updateDownload(packageName.hashCode(), null, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateState(id: Int, state: State) {
|
||||
updateFileMap[id]?.state = state
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
}
|
||||
|
||||
fun updateDownload(id: Int, group: FetchGroup?, isCancelled: Boolean = false) {
|
||||
if (isCancelled) {
|
||||
updateFileMap[id]?.state = State.IDLE
|
||||
updateFileMap[id]?.group = null
|
||||
} else {
|
||||
updateFileMap[id]?.state = State.PROGRESS
|
||||
updateFileMap[id]?.group = group
|
||||
}
|
||||
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
}
|
||||
|
||||
private fun updateListAndPost(packageName: String) {
|
||||
//Remove from selected updates
|
||||
selectedUpdates.remove(packageName)
|
||||
|
||||
if (selectedUpdates.isEmpty())
|
||||
isUpdating = false
|
||||
|
||||
//Remove from current update list
|
||||
val updatedList = appList.filter {
|
||||
it.packageName != packageName
|
||||
}.toList()
|
||||
|
||||
appList.flushAndAdd(updatedList)
|
||||
//Remove from map
|
||||
updateFileMap.remove(packageName.hashCode())
|
||||
|
||||
//Post new update list
|
||||
liveData.postValue(appList.sortedBy { it.displayName })
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
||||
6
app/src/main/res/drawable/bg_state_flat.xml
Normal file
6
app/src/main/res/drawable/bg_state_flat.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="8dp" />
|
||||
<solid android:color="?colorAccent" />
|
||||
</shape>
|
||||
6
app/src/main/res/drawable/bg_state_flat_rounded.xml
Normal file
6
app/src/main/res/drawable/bg_state_flat_rounded.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="28dp" />
|
||||
<solid android:color="?colorAccent" />
|
||||
</shape>
|
||||
8
app/src/main/res/drawable/bg_state_outline.xml
Normal file
8
app/src/main/res/drawable/bg_state_outline.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="8dp"/>
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/colorPrimaryAlt" />
|
||||
</shape>
|
||||
8
app/src/main/res/drawable/bg_state_outline_rounded.xml
Normal file
8
app/src/main/res/drawable/bg_state_outline_rounded.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="28dp"/>
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/colorPrimaryAlt" />
|
||||
</shape>
|
||||
@@ -41,19 +41,40 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/head_flipper"
|
||||
android:layout_width="@dimen/icon_size_medium"
|
||||
android:layout_height="@dimen/icon_size_medium" />
|
||||
android:layout_height="@dimen/icon_size_medium">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/img_icon"
|
||||
android:layout_width="@dimen/icon_size_medium"
|
||||
android:layout_height="@dimen/icon_size_medium" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_progress_percent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
android:background="@drawable/bg_rounded"
|
||||
android:gravity="center"
|
||||
android:paddingStart="@dimen/padding_normal"
|
||||
android:paddingEnd="@dimen/padding_normal"
|
||||
android:text="0%"
|
||||
android:textAlignment="center"
|
||||
android:textSize="12sp"
|
||||
app:fontFamily="@font/gilroy_extra_bold" />
|
||||
</RelativeLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/txt_line1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:layout_marginEnd="@dimen/margin_normal"
|
||||
android:layout_toStartOf="@+id/layout_action"
|
||||
android:layout_toEndOf="@id/img_icon"
|
||||
android:layout_toStartOf="@+id/headerIndicator"
|
||||
android:layout_toEndOf="@id/head_flipper"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
||||
@@ -91,41 +112,32 @@
|
||||
android:singleLine="true"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line3" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_action"
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/headerIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@id/layout_action"
|
||||
android:layout_toStartOf="@id/layout_action"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/margin_small"
|
||||
app:srcCompat="@drawable/ic_arrow_right"
|
||||
app:tint="?colorControlNormal" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_action"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="@dimen/height_button"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:weightSum="2">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/headerIndicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:adjustViewBounds="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="@dimen/margin_small"
|
||||
app:srcCompat="@drawable/ic_arrow_right"
|
||||
app:tint="?colorControlNormal" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="@dimen/icon_size_small"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:layout_marginEnd="@dimen/margin_small"
|
||||
android:background="@drawable/bg_placeholder"
|
||||
android:backgroundTint="?android:colorControlNormal" />
|
||||
|
||||
<com.google.android.material.checkbox.MaterialCheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1" />
|
||||
<com.aurora.store.view.custom.layouts.button.UpdateButton
|
||||
android:id="@+id/btn_action"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</com.github.florent37.expansionpanel.ExpansionHeader>
|
||||
|
||||
78
app/src/main/res/layout/view_update_button.xml
Normal file
78
app/src/main/res/layout/view_update_button.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ 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/>.
|
||||
~
|
||||
-->
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ViewFlipper
|
||||
android:id="@+id/view_flipper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inAnimation="@anim/fade_in"
|
||||
android:outAnimation="@anim/fade_out"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnPositive"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_update"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnQueued"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:enabled="false"
|
||||
android:text="@string/download_queued"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnNegative"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/action_cancel"
|
||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||
android:textColor="@color/colorRed" />
|
||||
</RelativeLayout>
|
||||
</ViewFlipper>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user