Delete multiple app in the same time
Show application names in main view
This commit is contained in:
parent
ac30bf8aa8
commit
504b58ecec
|
@ -13,3 +13,4 @@
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
.idea
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue