mirror of
https://github.com/Michatec/michas-droid.git
synced 2026-05-31 02:12:42 +02:00
Rework action buttons in product activity
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package nya.kitsunyan.foxydroid.screen
|
package nya.kitsunyan.foxydroid.screen
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -13,6 +14,7 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.Toolbar
|
import android.widget.Toolbar
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -49,21 +51,26 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class Nullable<T>(val value: T?)
|
private class Nullable<T>(val value: T?)
|
||||||
|
|
||||||
private enum class Action(val id: Int, val adapterAction: ProductAdapter.Action, val iconResId: Int) {
|
private enum class Action(val id: Int, val adapterAction: ProductAdapter.Action, val iconResId: Int) {
|
||||||
LAUNCH(1, ProductAdapter.Action.LAUNCH, R.drawable.ic_launch),
|
INSTALL(1, ProductAdapter.Action.INSTALL, R.drawable.ic_archive),
|
||||||
DETAILS(2, ProductAdapter.Action.DETAILS, R.drawable.ic_tune),
|
UPDATE(2, ProductAdapter.Action.UPDATE, R.drawable.ic_archive),
|
||||||
UNINSTALL(3, ProductAdapter.Action.UNINSTALL, R.drawable.ic_delete)
|
LAUNCH(3, ProductAdapter.Action.LAUNCH, R.drawable.ic_launch),
|
||||||
|
DETAILS(4, ProductAdapter.Action.DETAILS, R.drawable.ic_tune),
|
||||||
|
UNINSTALL(5, ProductAdapter.Action.UNINSTALL, R.drawable.ic_delete)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class Installed(val installedItem: InstalledItem, val isSystem: Boolean,
|
||||||
|
val launcherActivities: List<Pair<String, String>>)
|
||||||
|
|
||||||
val packageName: String
|
val packageName: String
|
||||||
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
get() = requireArguments().getString(EXTRA_PACKAGE_NAME)!!
|
||||||
|
|
||||||
private var layoutManagerState: LinearLayoutManager.SavedState? = null
|
private var layoutManagerState: LinearLayoutManager.SavedState? = null
|
||||||
|
|
||||||
|
private var actions = Pair(emptySet<Action>(), null as Action?)
|
||||||
private var products = emptyList<Pair<Product, Repository>>()
|
private var products = emptyList<Pair<Product, Repository>>()
|
||||||
private var installedItem: InstalledItem? = null
|
private var installed: Installed? = null
|
||||||
private var installedMainActivity: String? = null
|
|
||||||
private var installedIsSystem = false
|
|
||||||
private var downloading = false
|
private var downloading = false
|
||||||
|
|
||||||
private var toolbar: Toolbar? = null
|
private var toolbar: Toolbar? = null
|
||||||
@@ -119,6 +126,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
return if (adapter.requiresGrid(position)) 1 else layoutManager.spanCount
|
return if (adapter.requiresGrid(position)) 1 else layoutManager.spanCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
addOnScrollListener(scrollListener)
|
||||||
addItemDecoration(adapter.gridItemDecoration)
|
addItemDecoration(adapter.gridItemDecoration)
|
||||||
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
addItemDecoration(DividerItemDecoration(context, adapter::configureDivider))
|
||||||
savedInstanceState?.getParcelable<ProductAdapter.SavedState>(STATE_ADAPTER)?.let(adapter::restoreState)
|
savedInstanceState?.getParcelable<ProductAdapter.SavedState>(STATE_ADAPTER)?.let(adapter::restoreState)
|
||||||
@@ -144,7 +152,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
val firstChanged = first
|
val firstChanged = first
|
||||||
first = false
|
first = false
|
||||||
val productChanged = this.products != products
|
val productChanged = this.products != products
|
||||||
val installedItemChanged = this.installedItem != installedItem.value
|
val installedItemChanged = this.installed?.installedItem != installedItem.value
|
||||||
if (firstChanged || productChanged || installedItemChanged) {
|
if (firstChanged || productChanged || installedItemChanged) {
|
||||||
layoutManagerState?.let { recyclerView?.layoutManager!!.onRestoreInstanceState(it) }
|
layoutManagerState?.let { recyclerView?.layoutManager!!.onRestoreInstanceState(it) }
|
||||||
layoutManagerState = null
|
layoutManagerState = null
|
||||||
@@ -152,7 +160,34 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
this.products = products
|
this.products = products
|
||||||
}
|
}
|
||||||
if (firstChanged || installedItemChanged) {
|
if (firstChanged || installedItemChanged) {
|
||||||
this.installedItem = installedItem.value
|
installed = installedItem.value?.let {
|
||||||
|
val isSystem = try {
|
||||||
|
((requireContext().packageManager.getApplicationInfo(packageName, 0).flags)
|
||||||
|
and ApplicationInfo.FLAG_SYSTEM) != 0
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
val launcherActivities = if (packageName == requireContext().packageName) {
|
||||||
|
// Don't allow to launch self
|
||||||
|
emptyList()
|
||||||
|
} else {
|
||||||
|
val packageManager = requireContext().packageManager
|
||||||
|
packageManager
|
||||||
|
.queryIntentActivities(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 0)
|
||||||
|
.asSequence().mapNotNull { it.activityInfo }.filter { it.packageName == packageName }
|
||||||
|
.mapNotNull { activityInfo ->
|
||||||
|
val label = try {
|
||||||
|
activityInfo.loadLabel(packageManager).toString()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
label?.let { Pair(activityInfo.name, it) }
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
Installed(it, isSystem, launcherActivities)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val recyclerView = recyclerView!!
|
val recyclerView = recyclerView!!
|
||||||
val adapter = recyclerView.adapter as ProductAdapter
|
val adapter = recyclerView.adapter as ProductAdapter
|
||||||
@@ -161,15 +196,6 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
if (installedItemChanged) {
|
if (installedItemChanged) {
|
||||||
adapter.installedItem = installedItem.value
|
adapter.installedItem = installedItem.value
|
||||||
installedMainActivity = requireContext().packageManager
|
|
||||||
.queryIntentActivities(Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER), 0)
|
|
||||||
.find { it.activityInfo?.packageName == packageName }?.activityInfo?.name
|
|
||||||
installedIsSystem = try {
|
|
||||||
((requireContext().packageManager.getApplicationInfo(packageName, 0).flags)
|
|
||||||
and ApplicationInfo.FLAG_SYSTEM) != 0
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
updateButtons()
|
updateButtons()
|
||||||
}
|
}
|
||||||
@@ -206,44 +232,66 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
|
|
||||||
private fun updateButtons(preference: ProductPreference) {
|
private fun updateButtons(preference: ProductPreference) {
|
||||||
val product = Product.findSuggested(products) { it.first }?.first
|
val product = Product.findSuggested(products) { it.first }?.first
|
||||||
val installedItem = installedItem
|
val installed = installed
|
||||||
val compatible = product != null && product.selectedRelease.let { it != null && it.incompatibilities.isEmpty() }
|
val compatible = product != null && product.selectedRelease.let { it != null && it.incompatibilities.isEmpty() }
|
||||||
val canInstall = product != null && installedItem == null && compatible
|
val canInstall = product != null && installed == null && compatible
|
||||||
val canUpdate = product != null && compatible && product.canUpdate(installedItem) &&
|
val canUpdate = product != null && compatible && product.canUpdate(installed?.installedItem) &&
|
||||||
!preference.shouldIgnoreUpdate(product.versionCode)
|
!preference.shouldIgnoreUpdate(product.versionCode)
|
||||||
val canUninstall = product != null && installedItem != null && !installedIsSystem
|
val canUninstall = product != null && installed != null && !installed.isSystem
|
||||||
val canLaunch = product != null && installedItem != null && installedMainActivity != null
|
val canLaunch = product != null && installed != null && installed.launcherActivities.isNotEmpty()
|
||||||
|
|
||||||
val actions = mutableSetOf<Action>()
|
val actions = mutableSetOf<Action>()
|
||||||
|
if (canInstall) {
|
||||||
|
actions += Action.INSTALL
|
||||||
|
}
|
||||||
|
if (canUpdate) {
|
||||||
|
actions += Action.UPDATE
|
||||||
|
}
|
||||||
if (canLaunch) {
|
if (canLaunch) {
|
||||||
actions += Action.LAUNCH
|
actions += Action.LAUNCH
|
||||||
}
|
}
|
||||||
if (installedItem != null) {
|
if (installed != null) {
|
||||||
actions += Action.DETAILS
|
actions += Action.DETAILS
|
||||||
}
|
}
|
||||||
if (canUninstall) {
|
if (canUninstall) {
|
||||||
actions += Action.UNINSTALL
|
actions += Action.UNINSTALL
|
||||||
}
|
}
|
||||||
|
val primaryAction = when {
|
||||||
val recyclerView = recyclerView
|
canUpdate -> Action.UPDATE
|
||||||
if (recyclerView != null) {
|
canLaunch -> Action.LAUNCH
|
||||||
val adapterAction = when {
|
canInstall -> Action.INSTALL
|
||||||
downloading -> ProductAdapter.Action.CANCEL
|
installed != null -> Action.DETAILS
|
||||||
canUpdate -> ProductAdapter.Action.UPDATE
|
else -> null
|
||||||
canLaunch -> ProductAdapter.Action.LAUNCH
|
|
||||||
canUninstall -> ProductAdapter.Action.UNINSTALL
|
|
||||||
canInstall -> ProductAdapter.Action.INSTALL
|
|
||||||
installedItem != null -> ProductAdapter.Action.DETAILS
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
(recyclerView.adapter as ProductAdapter).setAction(adapterAction)
|
|
||||||
Action.values().find { it.adapterAction == adapterAction }?.let { actions -= it }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val adapterAction = if (downloading) ProductAdapter.Action.CANCEL else primaryAction?.adapterAction
|
||||||
|
(recyclerView?.adapter as? ProductAdapter)?.setAction(adapterAction)
|
||||||
|
|
||||||
|
val toolbar = toolbar
|
||||||
|
if (toolbar != null) {
|
||||||
|
for (action in sequenceOf(Action.INSTALL, Action.UPDATE, Action.UNINSTALL)) {
|
||||||
|
toolbar.menu.findItem(action.id).isEnabled = !downloading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.actions = Pair(actions, primaryAction)
|
||||||
|
updateToolbarButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateToolbarButtons() {
|
||||||
|
val (actions, primaryAction) = actions
|
||||||
|
val showPrimaryAction = recyclerView
|
||||||
|
?.let { (it.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() != 0 } == true
|
||||||
|
val displayActions = actions.toMutableSet()
|
||||||
|
if (!showPrimaryAction && primaryAction != null) {
|
||||||
|
displayActions -= primaryAction
|
||||||
|
}
|
||||||
|
if (displayActions.size >= 4 && resources.configuration.screenWidthDp < 400) {
|
||||||
|
displayActions -= Action.DETAILS
|
||||||
|
}
|
||||||
val toolbar = toolbar
|
val toolbar = toolbar
|
||||||
if (toolbar != null) {
|
if (toolbar != null) {
|
||||||
toolbar.menu.findItem(Action.UNINSTALL.id).isEnabled = !downloading
|
|
||||||
for (action in Action.values()) {
|
for (action in Action.values()) {
|
||||||
toolbar.menu.findItem(action.id).isVisible = action in actions
|
toolbar.menu.findItem(action.id).isVisible = action in displayActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -267,6 +315,19 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val scrollListener = object: RecyclerView.OnScrollListener() {
|
||||||
|
private var lastPosition = -1
|
||||||
|
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
val position = (recyclerView.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
|
||||||
|
val lastPosition = lastPosition
|
||||||
|
this.lastPosition = position
|
||||||
|
if ((lastPosition == 0) != (position == 0)) {
|
||||||
|
updateToolbarButtons()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActionClick(action: ProductAdapter.Action) {
|
override fun onActionClick(action: ProductAdapter.Action) {
|
||||||
when (action) {
|
when (action) {
|
||||||
ProductAdapter.Action.INSTALL,
|
ProductAdapter.Action.INSTALL,
|
||||||
@@ -280,16 +341,11 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
ProductAdapter.Action.LAUNCH -> {
|
ProductAdapter.Action.LAUNCH -> {
|
||||||
val installedMainActivity = installedMainActivity
|
val launcherActivities = installed?.launcherActivities.orEmpty()
|
||||||
if (installedMainActivity != null) {
|
if (launcherActivities.size >= 2) {
|
||||||
try {
|
LaunchDialog(launcherActivities).show(childFragmentManager, LaunchDialog::class.java.name)
|
||||||
startActivity(Intent(Intent.ACTION_MAIN)
|
} else {
|
||||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
launcherActivities.firstOrNull()?.let { startLauncherActivity(it.first) }
|
||||||
.setComponent(ComponentName(packageName, installedMainActivity))
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
@@ -313,6 +369,17 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}::class
|
}::class
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun startLauncherActivity(name: String) {
|
||||||
|
try {
|
||||||
|
startActivity(Intent(Intent.ACTION_MAIN)
|
||||||
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||||
|
.setComponent(ComponentName(packageName, name))
|
||||||
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPreferenceChanged(preference: ProductPreference) {
|
override fun onPreferenceChanged(preference: ProductPreference) {
|
||||||
updateButtons(preference)
|
updateButtons(preference)
|
||||||
}
|
}
|
||||||
@@ -334,7 +401,7 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onReleaseClick(release: Release) {
|
override fun onReleaseClick(release: Release) {
|
||||||
val installedItem = installedItem
|
val installedItem = installed?.installedItem
|
||||||
when {
|
when {
|
||||||
release.incompatibilities.isNotEmpty() -> {
|
release.incompatibilities.isNotEmpty() -> {
|
||||||
MessageDialog(MessageDialog.Message.ReleaseIncompatible(release.incompatibilities,
|
MessageDialog(MessageDialog.Message.ReleaseIncompatible(release.incompatibilities,
|
||||||
@@ -370,4 +437,29 @@ class ProductFragment(): ScreenFragment(), ProductAdapter.Callbacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class LaunchDialog(): DialogFragment() {
|
||||||
|
companion object {
|
||||||
|
private const val EXTRA_NAMES = "names"
|
||||||
|
private const val EXTRA_LABELS = "labels"
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(launcherActivities: List<Pair<String, String>>): this() {
|
||||||
|
arguments = Bundle().apply {
|
||||||
|
putStringArrayList(EXTRA_NAMES, ArrayList(launcherActivities.map { it.first }))
|
||||||
|
putStringArrayList(EXTRA_LABELS, ArrayList(launcherActivities.map { it.second }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): AlertDialog {
|
||||||
|
val names = requireArguments().getStringArrayList(EXTRA_NAMES)!!
|
||||||
|
val labels = requireArguments().getStringArrayList(EXTRA_LABELS)!!
|
||||||
|
return AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.launch)
|
||||||
|
.setItems(labels.toTypedArray()) { _, position -> (parentFragment as ProductFragment)
|
||||||
|
.startLauncherActivity(names[position]) }
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffffffff"
|
||||||
|
android:pathData="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88 .21-1.16 .55L3.46 5.23C3.17 5.57 3
|
||||||
|
6.02 3 6.5V19c0 1.1 .9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM
|
||||||
|
5.12 5l.81-1h12l.94 1H5.12z" />
|
||||||
|
|
||||||
|
</vector>
|
||||||
Reference in New Issue
Block a user