From 504b58ececd0c86160962b29514342eed5799230 Mon Sep 17 00:00:00 2001 From: sim Date: Wed, 31 May 2023 22:14:08 +0200 Subject: [PATCH] Delete multiple app in the same time Show application names in main view --- .gitignore | 1 + app/src/main/AndroidManifest.xml | 6 + .../nextpush/activities/AppListAdapter.kt | 93 ++++++++++++++ .../nextpush/activities/MainActivity.kt | 118 +++++++++++++----- app/src/main/res/drawable/ic_delete_24.xml | 5 + app/src/main/res/layout/item_app.xml | 21 ++++ app/src/main/res/menu/menu_delete.xml | 8 ++ app/src/main/res/menu/menu_main.xml | 7 ++ app/src/main/res/values-night/themes.xml | 3 +- app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/themes.xml | 3 +- 12 files changed, 239 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/org/unifiedpush/distributor/nextpush/activities/AppListAdapter.kt create mode 100644 app/src/main/res/drawable/ic_delete_24.xml create mode 100644 app/src/main/res/layout/item_app.xml create mode 100644 app/src/main/res/menu/menu_delete.xml diff --git a/.gitignore b/.gitignore index aa724b7..5bd175f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .externalNativeBuild .cxx local.properties +.idea diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 249d97a..4f16a4d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,6 +8,12 @@ + + + + + + ) : ArrayAdapter(context, resource, apps) { + private var selectedItemsIds = SparseBooleanArray() + private val inflater = LayoutInflater.from(context) + + private class ViewHolder { + var name: TextView? = null + var packageId: TextView? = null + } + + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var viewHolder: ViewHolder? = null + val convertView = convertView?.apply { + viewHolder = tag as ViewHolder + } ?: run { + val rConvertView = inflater.inflate(resource, parent, false) + viewHolder = ViewHolder().apply { + this.name = rConvertView.findViewById(R.id.item_app_name) as TextView + this.packageId = rConvertView.findViewById(R.id.item_app_id) as TextView + } + rConvertView.apply { + tag = viewHolder + } + } + getItem(position)?.let { + try { + val ai = if (Build.VERSION.SDK_INT >= 33) { + context.packageManager.getApplicationInfo( + it.packageId, + PackageManager.ApplicationInfoFlags.of( + PackageManager.GET_META_DATA.toLong() + ) + ) + } else { + context.packageManager.getApplicationInfo(it.packageId, 0) + } + viewHolder?.name?.text = context.packageManager.getApplicationLabel(ai) + viewHolder?.packageId?.text = it.packageId + } catch (e: PackageManager.NameNotFoundException) { + Log.e(TAG, "Could not resolve app name", e) + viewHolder?.name?.text = it.packageId + viewHolder?.packageId?.isGone = true + } + } + if (selectedItemsIds.get(position)) { + convertView?.setBackgroundColor( + MaterialColors.getColor(convertView, R.attr.colorOnTertiary) + ) + } else { + convertView?.setBackgroundResource(0) + } + return convertView + } + + fun toggleSelection(position: Int) { + selectView(position, !selectedItemsIds.get(position)) + } + + fun removeSelection() { + selectedItemsIds = SparseBooleanArray() + notifyDataSetChanged() + } + + private fun selectView(position: Int, value: Boolean) { + selectedItemsIds.put(position, value) + notifyDataSetChanged() + } + + fun getSelectedIds(): SparseBooleanArray { + return selectedItemsIds + } +} diff --git a/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt b/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt index bd19825..f725aa3 100644 --- a/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt +++ b/app/src/main/java/org/unifiedpush/distributor/nextpush/activities/MainActivity.kt @@ -6,15 +6,17 @@ import android.os.Bundle import android.os.PowerManager import android.provider.Settings import android.util.Log +import android.view.ActionMode import android.view.Menu import android.view.MenuItem -import android.view.View import android.widget.* // ktlint-disable no-wildcard-imports -import androidx.appcompat.app.AlertDialog +import android.widget.AbsListView.MultiChoiceModeListener import androidx.appcompat.app.AppCompatActivity +import androidx.core.util.size import androidx.core.view.isGone import androidx.core.view.setPadding import com.google.android.material.card.MaterialCardView +import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.unifiedpush.distributor.nextpush.R import org.unifiedpush.distributor.nextpush.account.Account import org.unifiedpush.distributor.nextpush.account.Account.getAccount @@ -34,6 +36,9 @@ import java.lang.String.format class MainActivity : AppCompatActivity() { private lateinit var listView: ListView + + // if the unregister dialog is shown, we prevent the list to be reset + private var preventListReset = false private var lastClickTime = 0L private var clickCount = 0 @@ -62,7 +67,11 @@ class MainActivity : AppCompatActivity() { override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (hasFocus) { - setListView() + if (preventListReset) { + preventListReset = false + } else { + setListView() + } } } @@ -128,44 +137,91 @@ class MainActivity : AppCompatActivity() { private fun setListView() { listView = findViewById(R.id.applications_list) - val tokenList = emptyList().toMutableList() - val appList = emptyList().toMutableList() + val appList = emptyList().toMutableList() getDb(this).let { db -> db.listTokens().forEach { - tokenList.add(it) - appList.add(db.getPackageName(it) ?: it) + appList.add( + App(token = it, packageId = db.getPackageName(it) ?: it) + ) } } - listView.adapter = ArrayAdapter( + val editListAdapter = AppListAdapter( this, - android.R.layout.simple_list_item_1, + R.layout.item_app, appList ) - listView.setOnItemLongClickListener( - fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean { - val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder( - this - ) - alert.setTitle("Unregistering") - alert.setMessage("Are you sure to unregister ${appList[position]} ?") - alert.setPositiveButton("YES") { dialog, _ -> - val connectorToken = tokenList[position] - deleteApp(this, connectorToken) { - Log.d(TAG, "Unregistration is finished") - this@MainActivity.runOnUiThread { - setListView() - } - } - dialog.dismiss() - } - alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() } - alert.show() + listView.adapter = editListAdapter + listView.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL + + listView.setMultiChoiceModeListener(object : MultiChoiceModeListener { + override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean { + return false + } + + override fun onDestroyActionMode(mode: ActionMode?) { + editListAdapter.removeSelection() + } + + override fun onItemCheckedStateChanged( + mode: ActionMode, + position: Int, + id: Long, + checked: Boolean + ) { + val checkedCount = listView.checkedItemCount + mode.title = "$checkedCount selected" + editListAdapter.toggleSelection(position) + } + + override fun onCreateActionMode(mode: ActionMode, menu: Menu?): Boolean { + mode.menuInflater.inflate(R.menu.menu_delete, menu) return true } - ) + + override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { + Log.d(TAG, "Action clicked") + return when (item.itemId) { + R.id.action_delete -> { + Log.d(TAG, "deleting") + val selected = editListAdapter.getSelectedIds() + val alert = MaterialAlertDialogBuilder(this@MainActivity) + alert.setTitle(getString(R.string.dialog_unregistering_title)) + alert.setMessage(getString(R.string.dialog_unregistering_content).format(selected.size)) + alert.setPositiveButton(getString(R.string.dialog_yes)) { dialog, _ -> + var i = selected.size - 1 + while (i >= 0) { + if (selected.valueAt(i)) { + editListAdapter.getItem(selected.keyAt(i))?.let { + deleteApp(this@MainActivity, it.token) { + Log.d(TAG, "${it.packageId} unregistered") + editListAdapter.remove(it) + this@MainActivity.runOnUiThread { + setListView() + } + } + } + i-- + } + } + preventListReset = false + dialog.dismiss() + mode.finish() + } + alert.setNegativeButton(getString(R.string.dialog_no)) { dialog, _ -> dialog.dismiss() } + alert.setOnCancelListener { + Log.d(TAG, "Cancelled") + } + preventListReset = true + alert.show() + true + } + else -> false + } + } + }) } private fun setDebugInformationListener() { @@ -182,7 +238,7 @@ class MainActivity : AppCompatActivity() { text = getDebugInfo() setPadding(dpAsPixels.toInt()) } - AlertDialog.Builder(this) + MaterialAlertDialogBuilder(this) .setTitle("Debug information") .setView(showText) .setCancelable(false) @@ -191,7 +247,7 @@ class MainActivity : AppCompatActivity() { } .show() - clickCount = 0 // Réinitialisez le compteur après l'affichage de la popup + clickCount = 0 // Reset count after showing the dialog } } else { clickCount = 1 diff --git a/app/src/main/res/drawable/ic_delete_24.xml b/app/src/main/res/drawable/ic_delete_24.xml new file mode 100644 index 0000000..de011dd --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/item_app.xml b/app/src/main/res/layout/item_app.xml new file mode 100644 index 0000000..88eff6f --- /dev/null +++ b/app/src/main/res/layout/item_app.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_delete.xml b/app/src/main/res/menu/menu_delete.xml new file mode 100644 index 0000000..4b6f1ff --- /dev/null +++ b/app/src/main/res/menu/menu_delete.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml index d3f4b91..1950fed 100644 --- a/app/src/main/res/menu/menu_main.xml +++ b/app/src/main/res/menu/menu_main.xml @@ -2,6 +2,13 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:context="org.unifiedpush.distributor.nextpush.activities.MainActivity"> + + @color/teal_200 @color/teal_200 @color/black + @color/dark_gray - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 76c8d4b..0e80839 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,5 +7,7 @@ #FF018786 #FF000000 #FFFFFFFF + #FFEFEFEF + #FF4F4F4F #0F9AE6 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 107dc74..eb0ecfc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,4 +39,9 @@ Show password Disable optimisation To ensure the app functions properly, it is important to disable battery optimization. This will prevent the app from being put to sleep and causing delays in notifications. + Delete + YES + NO + Unregistering + Are you sure to unregister %d app(s)? diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 368a7c0..04f4d6d 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -9,8 +9,9 @@ @color/teal_200 @color/teal_700 @color/black + @color/light_gray - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant