Batch contact delete
This commit is contained in:
parent
e7e3db60b4
commit
458be75114
@ -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)
|
||||
}
|
||||
|
@ -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,8 +94,18 @@ 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)
|
||||
}
|
||||
if (openedToShareFile) {
|
||||
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -21,4 +21,8 @@ object StringUtils {
|
||||
rawIp.substring(0, i)
|
||||
}
|
||||
}
|
||||
|
||||
fun sanitizeName(name: String): String {
|
||||
return name.replace('\n', ' ')
|
||||
}
|
||||
}
|
@ -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>
|
@ -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"/>
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user