Improve app updates page [1/2]

This commit is contained in:
Rahul Kumar Patel
2021-03-08 00:17:45 +05:30
parent 23ed1bd1a1
commit 527989a393
15 changed files with 526 additions and 202 deletions

View File

@@ -23,3 +23,11 @@ enum class AccountType {
ANONYMOUS,
GOOGLE
}
enum class State {
IDLE,
QUEUED,
PROGRESS,
COMPLETE,
CANCELED
}

View 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
}
}

View File

@@ -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,
}
}

View File

@@ -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)
}
}

View File

@@ -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 { }
}
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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() {

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>