Batch contact delete

This commit is contained in:
Matéo Duparc 2021-05-05 20:54:25 +02:00
parent e7e3db60b4
commit 458be75114
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
12 changed files with 146 additions and 30 deletions

View File

@ -1,6 +1,9 @@
package sushi.hardcore.aira
import android.content.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.provider.OpenableColumns
@ -12,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.aira.adapters.ChatAdapter
@ -24,7 +28,6 @@ import sushi.hardcore.aira.databinding.DialogInfoBinding
import sushi.hardcore.aira.utils.FileUtils
import sushi.hardcore.aira.utils.StringUtils
import java.io.FileNotFoundException
import java.util.*
class ChatActivity : AppCompatActivity() {
private external fun generateFingerprint(publicKey: ByteArray): String
@ -185,6 +188,7 @@ class ChatActivity : AppCompatActivity() {
airaService.isAppInBackground = false
if (airaService.isOnline(sessionId)) {
onConnected()
binding.recyclerChat.updatePadding(bottom = 0)
}
airaService.setSeen(sessionId, true)
}

View File

@ -32,9 +32,6 @@ class MainActivity : AppCompatActivity() {
private lateinit var airaService: AIRAService
private lateinit var onlineSessionAdapter: SessionAdapter
private var offlineSessionAdapter: SessionAdapter? = null
private val onSessionsItemClick = AdapterView.OnItemClickListener { adapter, _, position, _ ->
launchChatActivity(adapter.getItemAtPosition(position) as Session)
}
private val onSessionsItemClickSendFile = AdapterView.OnItemClickListener { adapter, _, position, _ ->
askShareFileTo(adapter.getItemAtPosition(position) as Session)
}
@ -97,7 +94,17 @@ class MainActivity : AppCompatActivity() {
onItemClickListener = if (openedToShareFile) {
onSessionsItemClickSendFile
} else {
onSessionsItemClick
AdapterView.OnItemClickListener { _, _, position, _ ->
if (isSelecting()) {
changeSelection(onlineSessionAdapter, position)
} else {
launchChatActivity(onlineSessionAdapter.getItem(position))
}
}
}
onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, position, _ ->
changeSelection(onlineSessionAdapter, position)
true
}
setOnScrollListener(onSessionsScrollListener)
}
@ -111,7 +118,17 @@ class MainActivity : AppCompatActivity() {
onItemClickListener = if (openedToShareFile) {
onSessionsItemClickSendFile
} else {
onSessionsItemClick
AdapterView.OnItemClickListener { _, _, position, _ ->
if (isSelecting()) {
changeSelection(offlineSessionAdapter!!, position)
} else {
launchChatActivity(offlineSessionAdapter!!.getItem(position))
}
}
}
onItemLongClickListener = AdapterView.OnItemLongClickListener { _, _, position, _ ->
changeSelection(offlineSessionAdapter!!, position)
true
}
setOnScrollListener(onSessionsScrollListener)
}
@ -166,8 +183,9 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_activity, menu)
menu.findItem(R.id.remove_contact).isVisible = isSelecting()
return true
}
@ -194,6 +212,30 @@ class MainActivity : AppCompatActivity() {
}
true
}
R.id.remove_contact -> {
AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.ask_remove_contacts)
.setPositiveButton(R.string.delete) { _, _ ->
Thread {
for (sessionId in onlineSessionAdapter.getSelectedSessionIds()) {
airaService.removeContact(sessionId)
}
offlineSessionAdapter?.let {
for (sessionId in it.getSelectedSessionIds()) {
airaService.removeContact(sessionId)
}
}
runOnUiThread {
unSelectAll()
refreshSessions()
}
}.start()
}
.setNegativeButton(R.string.cancel, null)
.show()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@ -211,10 +253,7 @@ class MainActivity : AppCompatActivity() {
if (AIRAService.isServiceRunning) {
airaService.isAppInBackground = false
airaService.uiCallbacks = uiCallbacks //restoring callbacks
onlineSessionAdapter.reset()
offlineSessionAdapter?.reset()
loadContacts()
loadSessions()
refreshSessions()
title = airaService.identityName
} else {
finish()
@ -222,6 +261,40 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onBackPressed() {
if (isSelecting()) {
unSelectAll()
} else {
super.onBackPressed()
}
}
private fun refreshSessions() {
onlineSessionAdapter.reset()
offlineSessionAdapter?.reset()
loadContacts()
loadSessions()
}
private fun unSelectAll() {
onlineSessionAdapter.unSelectAll()
offlineSessionAdapter?.unSelectAll()
invalidateOptionsMenu()
}
private fun changeSelection(adapter: SessionAdapter, position: Int) {
val wasSelecting = adapter.selectedItems.isNotEmpty()
adapter.onSelectionChanged(position)
val isSelecting = adapter.selectedItems.isNotEmpty()
if (wasSelecting != isSelecting) {
invalidateOptionsMenu()
}
}
private fun isSelecting(): Boolean {
return onlineSessionAdapter.selectedItems.isNotEmpty() || offlineSessionAdapter?.selectedItems?.isNotEmpty() == true
}
private fun loadContacts() {
if (offlineSessionAdapter != null) {
for ((sessionId, contact) in airaService.contacts) {

View File

@ -11,6 +11,8 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.marginEnd
import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView
import sushi.hardcore.aira.ChatItem
import sushi.hardcore.aira.R
@ -44,9 +46,11 @@ class ChatAdapter(
internal open class BubbleViewHolder(private val context: Context, itemView: View): RecyclerView.ViewHolder(itemView) {
fun handleItemView(position: Int) {
if (position == 0) {
itemView.setPadding(itemView.paddingLeft, 50, itemView.paddingRight, itemView.paddingBottom)
}
itemView.updatePadding(top = if (position == 0) {
50
} else {
itemView.paddingBottom
})
}
fun setBubbleColor(bubble: View, outgoing: Boolean) {
if (outgoing) {

View File

@ -8,12 +8,14 @@ import android.view.ViewGroup
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat
import sushi.hardcore.aira.R
import sushi.hardcore.aira.widgets.TextAvatar
class SessionAdapter(context: Context): BaseAdapter() {
class SessionAdapter(val context: Context): BaseAdapter() {
private val sessions = mutableListOf<Session>()
private val inflater: LayoutInflater = LayoutInflater.from(context)
val selectedItems = mutableListOf<Int>()
override fun getCount(): Int {
return sessions.size
@ -55,6 +57,11 @@ class SessionAdapter(context: Context): BaseAdapter() {
} else {
View.VISIBLE
}
view.setBackgroundColor(ContextCompat.getColor(context, if (selectedItems.contains(position)) {
R.color.itemSelected
} else {
R.color.sessionBackground
}))
return view
}
@ -99,4 +106,22 @@ class SessionAdapter(context: Context): BaseAdapter() {
sessions.clear()
notifyDataSetChanged()
}
fun onSelectionChanged(position: Int) {
if (!selectedItems.contains(position)) {
selectedItems.add(position)
} else {
selectedItems.remove(position)
}
notifyDataSetChanged()
}
fun unSelectAll() {
selectedItems.clear()
notifyDataSetChanged()
}
fun getSelectedSessionIds(): List<Int> {
return selectedItems.map { position -> sessions[position].sessionId }
}
}

View File

@ -14,6 +14,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.app.RemoteInput
import sushi.hardcore.aira.*
import sushi.hardcore.aira.utils.StringUtils
import java.io.IOException
import java.io.InputStream
import java.net.*
@ -556,7 +557,7 @@ class AIRAService : Service() {
}
}
Protocol.TELL_NAME -> {
val name = buffer.sliceArray(1 until buffer.size).decodeToString()
val name = StringUtils.sanitizeName(buffer.sliceArray(1 until buffer.size).decodeToString())
uiCallbacks?.onNameTold(sessionId, name)
val contact = contacts[sessionId]
if (contact == null) {

View File

@ -21,4 +21,8 @@ object StringUtils {
rawIp.substring(0, i)
}
}
fun sanitizeName(name: String): String {
return name.replace('\n', ' ')
}
}

View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/sessionBackground"/>
</shape>
</item>
</selector>

View File

@ -10,7 +10,8 @@
android:id="@+id/recycler_chat"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="20dp"
android:paddingHorizontal="20dp"
android:paddingBottom="20dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/bottom_panel"/>

View File

@ -4,8 +4,7 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:background="@drawable/background_session">
android:padding="10dp">
<sushi.hardcore.aira.widgets.TextAvatar
android:id="@+id/text_avatar"

View File

@ -5,11 +5,20 @@
<item
android:id="@+id/settings"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_settings"/>
android:icon="@drawable/ic_settings"
android:title="@string/settings" />
<item
android:id="@+id/close"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_close"/>
android:icon="@drawable/ic_close"
android:title="@string/log_out"/>
<item
android:id="@+id/remove_contact"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_person_remove"
android:title="@string/remove_contact"
android:visible="false"/>
</menu>

View File

@ -9,4 +9,5 @@
<color name="incomingBubbleBackground">@color/secondary</color>
<color name="textLink">#3845A3</color>
<color name="messageTextColor">#ffffff</color>
<color name="itemSelected">#66666666</color>
</resources>

View File

@ -42,6 +42,7 @@
<string name="ask_delete_conversation">Deleting a conversation only affects you. Your contact will still have a copy of this conversation if she/he doesn\'t delete it too. Do you really want to delete all this conversation (messages and files) ?</string>
<string name="delete">Delete</string>
<string name="ask_remove_contact">Deleting contact will remove her/his identity key and your conversation (messages and files). You won\'t be able to recognize her/him anymore. This action only affects you. Do you really want to remove this contact ?</string>
<string name="ask_remove_contacts">Deleting contacts will remove their identity keys and your conversations (messages and files). You won\'t be able to recognize them anymore. This action only affects you. Do you really want to remove these contacts ?</string>
<string name="enable_password">Encrypt with a password</string>
<string name="msg_notification_channel_name">New Messages</string>
<string name="mark_read">Mark read</string>
@ -72,4 +73,6 @@
<string name="details">Details</string>
<string name="your_addresses">Your IP addresses:</string>
<string name="file_transfer_already_in_progress">Another file transfer is already in progress</string>
<string name="settings">Settings</string>
<string name="log_out">Log out</string>
</resources>