Delete multiple app in the same time

Show application names in main view
This commit is contained in:
sim 2023-05-31 22:14:08 +02:00
parent ac30bf8aa8
commit 504b58ecec
12 changed files with 239 additions and 33 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@
.externalNativeBuild .externalNativeBuild
.cxx .cxx
local.properties local.properties
.idea

View File

@ -8,6 +8,12 @@
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<queries>
<intent>
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
</intent>
</queries>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@ -0,0 +1,93 @@
package org.unifiedpush.distributor.nextpush.activities
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.core.view.isGone
import com.google.android.material.color.MaterialColors
import org.unifiedpush.distributor.nextpush.R
import org.unifiedpush.distributor.nextpush.utils.TAG
data class App(
val token: String,
val packageId: String
)
class AppListAdapter(context: Context, private val resource: Int, apps: List<App>) : ArrayAdapter<App>(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
}
}

View File

@ -6,15 +6,17 @@ import android.os.Bundle
import android.os.PowerManager import android.os.PowerManager
import android.provider.Settings import android.provider.Settings
import android.util.Log import android.util.Log
import android.view.ActionMode
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import android.widget.* // ktlint-disable no-wildcard-imports 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.appcompat.app.AppCompatActivity
import androidx.core.util.size
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.setPadding import androidx.core.view.setPadding
import com.google.android.material.card.MaterialCardView 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.R
import org.unifiedpush.distributor.nextpush.account.Account import org.unifiedpush.distributor.nextpush.account.Account
import org.unifiedpush.distributor.nextpush.account.Account.getAccount import org.unifiedpush.distributor.nextpush.account.Account.getAccount
@ -34,6 +36,9 @@ import java.lang.String.format
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var listView: ListView 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 lastClickTime = 0L
private var clickCount = 0 private var clickCount = 0
@ -62,7 +67,11 @@ class MainActivity : AppCompatActivity() {
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
if (hasFocus) { if (hasFocus) {
setListView() if (preventListReset) {
preventListReset = false
} else {
setListView()
}
} }
} }
@ -128,44 +137,91 @@ class MainActivity : AppCompatActivity() {
private fun setListView() { private fun setListView() {
listView = findViewById(R.id.applications_list) listView = findViewById(R.id.applications_list)
val tokenList = emptyList<String>().toMutableList() val appList = emptyList<App>().toMutableList()
val appList = emptyList<String>().toMutableList()
getDb(this).let { db -> getDb(this).let { db ->
db.listTokens().forEach { db.listTokens().forEach {
tokenList.add(it) appList.add(
appList.add(db.getPackageName(it) ?: it) App(token = it, packageId = db.getPackageName(it) ?: it)
)
} }
} }
listView.adapter = ArrayAdapter( val editListAdapter = AppListAdapter(
this, this,
android.R.layout.simple_list_item_1, R.layout.item_app,
appList appList
) )
listView.setOnItemLongClickListener( listView.adapter = editListAdapter
fun(_: AdapterView<*>, _: View, position: Int, _: Long): Boolean { listView.choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL
val alert: android.app.AlertDialog.Builder = android.app.AlertDialog.Builder(
this listView.setMultiChoiceModeListener(object : MultiChoiceModeListener {
) override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
alert.setTitle("Unregistering") return false
alert.setMessage("Are you sure to unregister ${appList[position]} ?") }
alert.setPositiveButton("YES") { dialog, _ ->
val connectorToken = tokenList[position] override fun onDestroyActionMode(mode: ActionMode?) {
deleteApp(this, connectorToken) { editListAdapter.removeSelection()
Log.d(TAG, "Unregistration is finished") }
this@MainActivity.runOnUiThread {
setListView() override fun onItemCheckedStateChanged(
} mode: ActionMode,
} position: Int,
dialog.dismiss() id: Long,
} checked: Boolean
alert.setNegativeButton("NO") { dialog, _ -> dialog.dismiss() } ) {
alert.show() 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 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() { private fun setDebugInformationListener() {
@ -182,7 +238,7 @@ class MainActivity : AppCompatActivity() {
text = getDebugInfo() text = getDebugInfo()
setPadding(dpAsPixels.toInt()) setPadding(dpAsPixels.toInt())
} }
AlertDialog.Builder(this) MaterialAlertDialogBuilder(this)
.setTitle("Debug information") .setTitle("Debug information")
.setView(showText) .setView(showText)
.setCancelable(false) .setCancelable(false)
@ -191,7 +247,7 @@ class MainActivity : AppCompatActivity() {
} }
.show() .show()
clickCount = 0 // Réinitialisez le compteur après l'affichage de la popup clickCount = 0 // Reset count after showing the dialog
} }
} else { } else {
clickCount = 1 clickCount = 1

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:minHeight="?android:attr/listPreferredItemHeightSmall" >
<TextView
android:id="@+id/item_app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItemSmall"
android:gravity="center_vertical"/>
<TextView
android:id="@+id/item_app_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:gravity="center_vertical" />
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_24"
android:title="@string/action_delete"
android:iconTint="@color/white" />
</menu>

View File

@ -2,6 +2,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:context="org.unifiedpush.distributor.nextpush.activities.MainActivity"> tools:context="org.unifiedpush.distributor.nextpush.activities.MainActivity">
<item
android:id="@+id/action_delete"
android:icon="@drawable/ic_delete_24"
android:title="@string/action_delete"
android:visible="false"
app:showAsAction="ifRoom" />
<item <item
android:id="@+id/action_restart" android:id="@+id/action_restart"
android:orderInCategory="100" android:orderInCategory="100"

View File

@ -9,8 +9,9 @@
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item> <item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<item name="colorOnTertiary">@color/dark_gray</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>
</resources> </resources>

View File

@ -7,5 +7,7 @@
<color name="teal_700">#FF018786</color> <color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="light_gray">#FFEFEFEF</color>
<color name="dark_gray">#FF4F4F4F</color>
<color name="nextcloud">#0F9AE6</color> <color name="nextcloud">#0F9AE6</color>
</resources> </resources>

View File

@ -39,4 +39,9 @@
<string name="login_show_password_img_description">Show password</string> <string name="login_show_password_img_description">Show password</string>
<string name="button_disable_optimisation">Disable optimisation</string> <string name="button_disable_optimisation">Disable optimisation</string>
<string name="card_disable_optimisation_description">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.</string> <string name="card_disable_optimisation_description">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.</string>
<string name="action_delete">Delete</string>
<string name="dialog_yes">YES</string>
<string name="dialog_no">NO</string>
<string name="dialog_unregistering_title">Unregistering</string>
<string name="dialog_unregistering_content">Are you sure to unregister %d app(s)?</string>
</resources> </resources>

View File

@ -9,8 +9,9 @@
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item> <item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<item name="colorOnTertiary">@color/light_gray</item>
<!-- Status bar color. --> <!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
</style> </style>