Batch contact delete
This commit is contained in:
parent
e7e3db60b4
commit
458be75114
@ -1,6 +1,9 @@
|
|||||||
package sushi.hardcore.aira
|
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.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
@ -12,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import sushi.hardcore.aira.adapters.ChatAdapter
|
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.FileUtils
|
||||||
import sushi.hardcore.aira.utils.StringUtils
|
import sushi.hardcore.aira.utils.StringUtils
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class ChatActivity : AppCompatActivity() {
|
class ChatActivity : AppCompatActivity() {
|
||||||
private external fun generateFingerprint(publicKey: ByteArray): String
|
private external fun generateFingerprint(publicKey: ByteArray): String
|
||||||
@ -185,6 +188,7 @@ class ChatActivity : AppCompatActivity() {
|
|||||||
airaService.isAppInBackground = false
|
airaService.isAppInBackground = false
|
||||||
if (airaService.isOnline(sessionId)) {
|
if (airaService.isOnline(sessionId)) {
|
||||||
onConnected()
|
onConnected()
|
||||||
|
binding.recyclerChat.updatePadding(bottom = 0)
|
||||||
}
|
}
|
||||||
airaService.setSeen(sessionId, true)
|
airaService.setSeen(sessionId, true)
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private lateinit var airaService: AIRAService
|
private lateinit var airaService: AIRAService
|
||||||
private lateinit var onlineSessionAdapter: SessionAdapter
|
private lateinit var onlineSessionAdapter: SessionAdapter
|
||||||
private var offlineSessionAdapter: SessionAdapter? = null
|
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, _ ->
|
private val onSessionsItemClickSendFile = AdapterView.OnItemClickListener { adapter, _, position, _ ->
|
||||||
askShareFileTo(adapter.getItemAtPosition(position) as Session)
|
askShareFileTo(adapter.getItemAtPosition(position) as Session)
|
||||||
}
|
}
|
||||||
@ -97,8 +94,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onItemClickListener = if (openedToShareFile) {
|
onItemClickListener = if (openedToShareFile) {
|
||||||
onSessionsItemClickSendFile
|
onSessionsItemClickSendFile
|
||||||
} else {
|
} 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)
|
setOnScrollListener(onSessionsScrollListener)
|
||||||
}
|
}
|
||||||
if (openedToShareFile) {
|
if (openedToShareFile) {
|
||||||
@ -111,7 +118,17 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onItemClickListener = if (openedToShareFile) {
|
onItemClickListener = if (openedToShareFile) {
|
||||||
onSessionsItemClickSendFile
|
onSessionsItemClickSendFile
|
||||||
} else {
|
} 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)
|
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)
|
menuInflater.inflate(R.menu.main_activity, menu)
|
||||||
|
menu.findItem(R.id.remove_contact).isVisible = isSelecting()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,6 +212,30 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
true
|
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)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,10 +253,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
if (AIRAService.isServiceRunning) {
|
if (AIRAService.isServiceRunning) {
|
||||||
airaService.isAppInBackground = false
|
airaService.isAppInBackground = false
|
||||||
airaService.uiCallbacks = uiCallbacks //restoring callbacks
|
airaService.uiCallbacks = uiCallbacks //restoring callbacks
|
||||||
onlineSessionAdapter.reset()
|
refreshSessions()
|
||||||
offlineSessionAdapter?.reset()
|
|
||||||
loadContacts()
|
|
||||||
loadSessions()
|
|
||||||
title = airaService.identityName
|
title = airaService.identityName
|
||||||
} else {
|
} else {
|
||||||
finish()
|
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() {
|
private fun loadContacts() {
|
||||||
if (offlineSessionAdapter != null) {
|
if (offlineSessionAdapter != null) {
|
||||||
for ((sessionId, contact) in airaService.contacts) {
|
for ((sessionId, contact) in airaService.contacts) {
|
||||||
|
@ -11,6 +11,8 @@ import android.widget.ImageButton
|
|||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.marginEnd
|
||||||
|
import androidx.core.view.updatePadding
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import sushi.hardcore.aira.ChatItem
|
import sushi.hardcore.aira.ChatItem
|
||||||
import sushi.hardcore.aira.R
|
import sushi.hardcore.aira.R
|
||||||
@ -44,9 +46,11 @@ class ChatAdapter(
|
|||||||
|
|
||||||
internal open class BubbleViewHolder(private val context: Context, itemView: View): RecyclerView.ViewHolder(itemView) {
|
internal open class BubbleViewHolder(private val context: Context, itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||||
fun handleItemView(position: Int) {
|
fun handleItemView(position: Int) {
|
||||||
if (position == 0) {
|
itemView.updatePadding(top = if (position == 0) {
|
||||||
itemView.setPadding(itemView.paddingLeft, 50, itemView.paddingRight, itemView.paddingBottom)
|
50
|
||||||
}
|
} else {
|
||||||
|
itemView.paddingBottom
|
||||||
|
})
|
||||||
}
|
}
|
||||||
fun setBubbleColor(bubble: View, outgoing: Boolean) {
|
fun setBubbleColor(bubble: View, outgoing: Boolean) {
|
||||||
if (outgoing) {
|
if (outgoing) {
|
||||||
|
@ -8,12 +8,14 @@ import android.view.ViewGroup
|
|||||||
import android.widget.BaseAdapter
|
import android.widget.BaseAdapter
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import sushi.hardcore.aira.R
|
import sushi.hardcore.aira.R
|
||||||
import sushi.hardcore.aira.widgets.TextAvatar
|
import sushi.hardcore.aira.widgets.TextAvatar
|
||||||
|
|
||||||
class SessionAdapter(context: Context): BaseAdapter() {
|
class SessionAdapter(val context: Context): BaseAdapter() {
|
||||||
private val sessions = mutableListOf<Session>()
|
private val sessions = mutableListOf<Session>()
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
|
val selectedItems = mutableListOf<Int>()
|
||||||
|
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return sessions.size
|
return sessions.size
|
||||||
@ -55,6 +57,11 @@ class SessionAdapter(context: Context): BaseAdapter() {
|
|||||||
} else {
|
} else {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
}
|
}
|
||||||
|
view.setBackgroundColor(ContextCompat.getColor(context, if (selectedItems.contains(position)) {
|
||||||
|
R.color.itemSelected
|
||||||
|
} else {
|
||||||
|
R.color.sessionBackground
|
||||||
|
}))
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,4 +106,22 @@ class SessionAdapter(context: Context): BaseAdapter() {
|
|||||||
sessions.clear()
|
sessions.clear()
|
||||||
notifyDataSetChanged()
|
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.NotificationManagerCompat
|
||||||
import androidx.core.app.RemoteInput
|
import androidx.core.app.RemoteInput
|
||||||
import sushi.hardcore.aira.*
|
import sushi.hardcore.aira.*
|
||||||
|
import sushi.hardcore.aira.utils.StringUtils
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.*
|
import java.net.*
|
||||||
@ -556,7 +557,7 @@ class AIRAService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Protocol.TELL_NAME -> {
|
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)
|
uiCallbacks?.onNameTold(sessionId, name)
|
||||||
val contact = contacts[sessionId]
|
val contact = contacts[sessionId]
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
|
@ -21,4 +21,8 @@ object StringUtils {
|
|||||||
rawIp.substring(0, i)
|
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:id="@+id/recycler_chat"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginHorizontal="20dp"
|
android:paddingHorizontal="20dp"
|
||||||
|
android:paddingBottom="20dp"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toTopOf="@id/bottom_panel"/>
|
app:layout_constraintBottom_toTopOf="@id/bottom_panel"/>
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@
|
|||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="10dp"
|
android:padding="10dp">
|
||||||
android:background="@drawable/background_session">
|
|
||||||
|
|
||||||
<sushi.hardcore.aira.widgets.TextAvatar
|
<sushi.hardcore.aira.widgets.TextAvatar
|
||||||
android:id="@+id/text_avatar"
|
android:id="@+id/text_avatar"
|
||||||
|
@ -5,11 +5,20 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/settings"
|
android:id="@+id/settings"
|
||||||
app:showAsAction="ifRoom"
|
app:showAsAction="ifRoom"
|
||||||
android:icon="@drawable/ic_settings"/>
|
android:icon="@drawable/ic_settings"
|
||||||
|
android:title="@string/settings" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/close"
|
android:id="@+id/close"
|
||||||
app:showAsAction="ifRoom"
|
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>
|
</menu>
|
@ -9,4 +9,5 @@
|
|||||||
<color name="incomingBubbleBackground">@color/secondary</color>
|
<color name="incomingBubbleBackground">@color/secondary</color>
|
||||||
<color name="textLink">#3845A3</color>
|
<color name="textLink">#3845A3</color>
|
||||||
<color name="messageTextColor">#ffffff</color>
|
<color name="messageTextColor">#ffffff</color>
|
||||||
|
<color name="itemSelected">#66666666</color>
|
||||||
</resources>
|
</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="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="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_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="enable_password">Encrypt with a password</string>
|
||||||
<string name="msg_notification_channel_name">New Messages</string>
|
<string name="msg_notification_channel_name">New Messages</string>
|
||||||
<string name="mark_read">Mark read</string>
|
<string name="mark_read">Mark read</string>
|
||||||
@ -72,4 +73,6 @@
|
|||||||
<string name="details">Details</string>
|
<string name="details">Details</string>
|
||||||
<string name="your_addresses">Your IP addresses:</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="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>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user