diff --git a/app/src/main/java/com/aurora/store/Enumerations.kt b/app/src/main/java/com/aurora/store/Enumerations.kt index b2e72c267..c5f632cbd 100644 --- a/app/src/main/java/com/aurora/store/Enumerations.kt +++ b/app/src/main/java/com/aurora/store/Enumerations.kt @@ -23,3 +23,11 @@ enum class AccountType { ANONYMOUS, GOOGLE } + +enum class State { + IDLE, + QUEUED, + PROGRESS, + COMPLETE, + CANCELED +} diff --git a/app/src/main/java/com/aurora/store/data/model/UpdateFile.kt b/app/src/main/java/com/aurora/store/data/model/UpdateFile.kt new file mode 100644 index 000000000..28dd5817c --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/UpdateFile.kt @@ -0,0 +1,49 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +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 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/button/ActionButton.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/button/ActionButton.kt index 3584cb0b4..a7c8d476d 100644 --- a/app/src/main/java/com/aurora/store/view/custom/layouts/button/ActionButton.kt +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/button/ActionButton.kt @@ -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, - } } \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt new file mode 100644 index 000000000..7be935a33 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/button/UpdateButton.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * 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 . + * + */ + +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) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt index 2539e3898..704087964 100644 --- a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt @@ -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 { } } } \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt index f8c1fef91..7308f8a3d 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt @@ -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() diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt index 78e6c1d8e..5f80324bd 100644 --- a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt @@ -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) { - 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 diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt index e973f4043..520455b7b 100644 --- a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -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 = mutableListOf() + private var updateFileMap: MutableMap = 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?) { + private fun updateController(updateFileMap: MutableMap?) { 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 diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt index 633768ed2..14c22caea 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt @@ -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 = mutableSetOf() - var isUpdating: Boolean = false + var updateFileMap: MutableMap = mutableMapOf() + var liveUpdateData: MutableLiveData> = 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() { diff --git a/app/src/main/res/drawable/bg_state_flat.xml b/app/src/main/res/drawable/bg_state_flat.xml new file mode 100644 index 000000000..46b2992fa --- /dev/null +++ b/app/src/main/res/drawable/bg_state_flat.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_state_flat_rounded.xml b/app/src/main/res/drawable/bg_state_flat_rounded.xml new file mode 100644 index 000000000..c0defdc60 --- /dev/null +++ b/app/src/main/res/drawable/bg_state_flat_rounded.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_state_outline.xml b/app/src/main/res/drawable/bg_state_outline.xml new file mode 100644 index 000000000..2d9051f45 --- /dev/null +++ b/app/src/main/res/drawable/bg_state_outline.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_state_outline_rounded.xml b/app/src/main/res/drawable/bg_state_outline_rounded.xml new file mode 100644 index 000000000..7492d8c48 --- /dev/null +++ b/app/src/main/res/drawable/bg_state_outline_rounded.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_update.xml b/app/src/main/res/layout/view_app_update.xml index b2429203b..81b708a25 100644 --- a/app/src/main/res/layout/view_app_update.xml +++ b/app/src/main/res/layout/view_app_update.xml @@ -41,19 +41,40 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - + android:layout_height="@dimen/icon_size_medium"> + + + + + @@ -91,41 +112,32 @@ android:singleLine="true" android:textAppearance="@style/TextAppearance.Aurora.Line3" /> - + + + android:orientation="horizontal"> - - - - - + diff --git a/app/src/main/res/layout/view_update_button.xml b/app/src/main/res/layout/view_update_button.xml new file mode 100644 index 000000000..5b97f7c22 --- /dev/null +++ b/app/src/main/res/layout/view_update_button.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +