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