Improve handling of sessions with no name

This commit is contained in:
Matéo Duparc 2021-05-27 21:00:57 +02:00
parent c18bb1cb60
commit aa4ae9ee92
Signed by: hardcoresushi
GPG Key ID: 007F84120107191E
7 changed files with 180 additions and 174 deletions

View File

@ -28,7 +28,7 @@ class ChatActivity : ServiceBoundActivity() {
private lateinit var binding: ActivityChatBinding private lateinit var binding: ActivityChatBinding
private var sessionId = -1 private var sessionId = -1
private lateinit var sessionName: String private var sessionName: String? = null
private var avatar: ByteArray? = null private var avatar: ByteArray? = null
private lateinit var chatAdapter: ChatAdapter private lateinit var chatAdapter: ChatAdapter
private var lastLoadedMessageOffset = 0 private var lastLoadedMessageOffset = 0
@ -47,155 +47,159 @@ class ChatActivity : ServiceBoundActivity() {
sessionId = intent.getIntExtra("sessionId", -1) sessionId = intent.getIntExtra("sessionId", -1)
if (sessionId != -1) { if (sessionId != -1) {
intent.getStringExtra("sessionName")?.let { name -> chatAdapter = ChatAdapter(this@ChatActivity, ::onClickSaveFile)
sessionName = name binding.recyclerChat.apply {
binding.toolbar.avatar.setTextAvatar(name) adapter = chatAdapter
binding.toolbar.title.text = name layoutManager = LinearLayoutManager(this@ChatActivity, LinearLayoutManager.VERTICAL, false).apply {
chatAdapter = ChatAdapter(this@ChatActivity, ::onClickSaveFile) stackFromEnd = true
binding.recyclerChat.apply { }
adapter = chatAdapter addOnScrollListener(object : RecyclerView.OnScrollListener() {
layoutManager = LinearLayoutManager(this@ChatActivity, LinearLayoutManager.VERTICAL, false).apply { fun loadMsgsIfNeeded(recyclerView: RecyclerView) {
stackFromEnd = true if (!recyclerView.canScrollVertically(-1) && isServiceInitialized()) {
} airaService.contacts[sessionId]?.let { contact ->
addOnScrollListener(object : RecyclerView.OnScrollListener() { loadMsgs(contact.uuid)
fun loadMsgsIfNeeded(recyclerView: RecyclerView) {
if (!recyclerView.canScrollVertically(-1) && isServiceInitialized()) {
airaService.contacts[sessionId]?.let { contact ->
loadMsgs(contact.uuid)
}
} }
} }
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { }
loadMsgsIfNeeded(recyclerView) override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
} loadMsgsIfNeeded(recyclerView)
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { }
loadMsgsIfNeeded(recyclerView) override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
} loadMsgsIfNeeded(recyclerView)
}) }
})
}
binding.toolbar.toolbar.setOnClickListener {
showSessionInfo()
}
binding.buttonSend.setOnClickListener {
val msg = binding.editMessage.text.toString()
airaService.sendTo(sessionId, Protocol.newMessage(msg))
binding.editMessage.text.clear()
chatAdapter.newMessage(ChatItem(true, Protocol.newMessage(msg)))
if (airaService.contacts.contains(sessionId)) {
lastLoadedMessageOffset += 1
} }
binding.toolbar.toolbar.setOnClickListener { binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
showSessionInfo() }
} binding.buttonAttach.setOnClickListener {
binding.buttonSend.setOnClickListener { filePicker.launch("*/*")
val msg = binding.editMessage.text.toString() }
airaService.sendTo(sessionId, Protocol.newMessage(msg)) serviceConnection = object : ServiceConnection {
binding.editMessage.text.clear() override fun onServiceConnected(componentName: ComponentName?, service: IBinder) {
chatAdapter.newMessage(ChatItem(true, Protocol.newMessage(msg))) val binder = service as AIRAService.AIRABinder
if (airaService.contacts.contains(sessionId)) { airaService = binder.getService()
lastLoadedMessageOffset += 1
chatAdapter.clear()
val contact = airaService.contacts[sessionId]
val avatar = if (contact == null) {
sessionName = airaService.savedNames[sessionId]
airaService.savedAvatars[sessionId]
} else {
sessionName = contact.name
contact.avatar
}
binding.toolbar.title.text = sessionName ?: airaService.sessions[sessionId]!!.ip
if (avatar == null) {
binding.toolbar.avatar.setTextAvatar(sessionName)
} else {
AIRADatabase.loadAvatar(avatar)?.let { image ->
this@ChatActivity.avatar = image
binding.toolbar.avatar.setImageAvatar(image)
}
}
if (contact != null) {
displayIconTrustLevel(true, contact.verified)
loadMsgs(contact.uuid)
}
airaService.savedMsgs[sessionId]?.let {
for (chatItem in it.asReversed()) {
chatAdapter.newLoadedMessage(chatItem)
}
}
airaService.receiveFileTransfers[sessionId]?.let {
if (it.shouldAsk) {
it.ask(this@ChatActivity)
}
} }
binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount) binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
} val showBottomPanel = {
binding.buttonAttach.setOnClickListener { binding.bottomPanel.visibility = View.VISIBLE
filePicker.launch("*/*")
}
serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder) {
val binder = service as AIRAService.AIRABinder
airaService = binder.getService()
chatAdapter.clear()
val contact = airaService.contacts[sessionId]
if (contact == null) {
airaService.savedAvatars[sessionId]
} else {
contact.avatar
}?.let {
AIRADatabase.loadAvatar(it)?.let { image ->
avatar = image
binding.toolbar.avatar.setImageAvatar(image)
}
}
if (contact != null) {
displayIconTrustLevel(true, contact.verified)
loadMsgs(contact.uuid)
}
airaService.savedMsgs[sessionId]?.let {
for (chatItem in it.asReversed()) {
chatAdapter.newLoadedMessage(chatItem)
}
}
airaService.receiveFileTransfers[sessionId]?.let {
if (it.shouldAsk) {
it.ask(this@ChatActivity, sessionName)
}
}
binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
val showBottomPanel = {
binding.bottomPanel.visibility = View.VISIBLE
}
airaService.uiCallbacks = object : AIRAService.UiCallbacks {
override fun onNewSession(sessionId: Int, ip: String) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
showBottomPanel()
}
}
}
override fun onSessionDisconnect(sessionId: Int) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(binding.editMessage.windowToken, 0)
binding.bottomPanel.visibility = View.GONE
invalidateOptionsMenu()
}
}
}
override fun onNameTold(sessionId: Int, name: String) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
sessionName = name
binding.toolbar.title.text = name
}
}
}
override fun onAvatarChanged(sessionId: Int, avatar: ByteArray?) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
this@ChatActivity.avatar = avatar
if (avatar == null) {
binding.toolbar.avatar.setTextAvatar(sessionName)
} else {
binding.toolbar.avatar.setImageAvatar(avatar)
}
}
}
}
override fun onNewMessage(sessionId: Int, data: ByteArray): Boolean {
return if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
chatAdapter.newMessage(ChatItem(false, data))
binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
}
if (airaService.contacts.contains(sessionId)) {
lastLoadedMessageOffset += 1
}
!airaService.isAppInBackground
} else {
false
}
}
override fun onAskLargeFiles(sessionId: Int, name: String, filesReceiver: FilesReceiver): Boolean {
return if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
filesReceiver.ask(this@ChatActivity, name)
}
true
} else {
false
}
}
}
airaService.isAppInBackground = false
if (airaService.isOnline(sessionId)) {
showBottomPanel()
binding.recyclerChat.updatePadding(bottom = 0)
}
airaService.setSeen(sessionId, true)
} }
override fun onServiceDisconnected(name: ComponentName?) {} airaService.uiCallbacks = object : AIRAService.UiCallbacks {
override fun onNewSession(sessionId: Int, ip: String) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
showBottomPanel()
}
}
}
override fun onSessionDisconnect(sessionId: Int) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputManager.hideSoftInputFromWindow(binding.editMessage.windowToken, 0)
binding.bottomPanel.visibility = View.GONE
invalidateOptionsMenu()
}
}
}
override fun onNameTold(sessionId: Int, name: String) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
sessionName = name
binding.toolbar.title.text = name
if (avatar == null) {
binding.toolbar.avatar.setTextAvatar(name)
}
}
}
}
override fun onAvatarChanged(sessionId: Int, avatar: ByteArray?) {
if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
this@ChatActivity.avatar = avatar
if (avatar == null) {
binding.toolbar.avatar.setTextAvatar(sessionName)
} else {
binding.toolbar.avatar.setImageAvatar(avatar)
}
}
}
}
override fun onNewMessage(sessionId: Int, data: ByteArray): Boolean {
return if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
chatAdapter.newMessage(ChatItem(false, data))
binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
}
if (airaService.contacts.contains(sessionId)) {
lastLoadedMessageOffset += 1
}
!airaService.isAppInBackground
} else {
false
}
}
override fun onAskLargeFiles(sessionId: Int, filesReceiver: FilesReceiver): Boolean {
return if (this@ChatActivity.sessionId == sessionId) {
runOnUiThread {
filesReceiver.ask(this@ChatActivity)
}
true
} else {
false
}
}
}
airaService.isAppInBackground = false
if (airaService.isOnline(sessionId)) {
showBottomPanel()
binding.recyclerChat.updatePadding(bottom = 0)
}
airaService.setSeen(sessionId, true)
} }
override fun onServiceDisconnected(name: ComponentName?) {}
} }
} }
} }
@ -251,9 +255,13 @@ class ChatActivity : ServiceBoundActivity() {
true true
} }
R.id.set_as_contact -> { R.id.set_as_contact -> {
if (airaService.setAsContact(sessionId, sessionName)) { if (sessionName == null) {
invalidateOptionsMenu() Toast.makeText(this, R.string.no_name_error, Toast.LENGTH_SHORT).show()
displayIconTrustLevel(true, false) } else {
if (airaService.setAsContact(sessionId, sessionName!!)) {
invalidateOptionsMenu()
displayIconTrustLevel(true, false)
}
} }
true true
} }

View File

@ -71,9 +71,9 @@ class MainActivity : ServiceBoundActivity() {
return false return false
} }
override fun onAskLargeFiles(sessionId: Int, name: String, filesReceiver: FilesReceiver): Boolean { override fun onAskLargeFiles(sessionId: Int, filesReceiver: FilesReceiver): Boolean {
runOnUiThread { runOnUiThread {
filesReceiver.ask(this@MainActivity, name) filesReceiver.ask(this@MainActivity)
} }
return true return true
} }
@ -322,7 +322,6 @@ class MainActivity : ServiceBoundActivity() {
private fun launchChatActivity(session: Session) { private fun launchChatActivity(session: Session) {
startActivity(Intent(this, ChatActivity::class.java).apply { startActivity(Intent(this, ChatActivity::class.java).apply {
putExtra("sessionId", session.sessionId) putExtra("sessionId", session.sessionId)
putExtra("sessionName", airaService.getNameOf(session.sessionId))
}) })
} }

View File

@ -31,18 +31,16 @@ class SessionAdapter(private val activity: AppCompatActivity): BaseAdapter() {
val view: View = convertView ?: inflater.inflate(R.layout.adapter_session, parent, false) val view: View = convertView ?: inflater.inflate(R.layout.adapter_session, parent, false)
val currentSession = getItem(position) val currentSession = getItem(position)
view.findViewById<TextView>(R.id.text_name).apply { view.findViewById<TextView>(R.id.text_name).apply {
val avatarName = if (currentSession.name == null) { setTextColor(if (currentSession.name == null) {
text = currentSession.ip text = currentSession.ip
setTextColor(Color.RED) Color.RED
"?"
} else { } else {
text = currentSession.name text = currentSession.name
setTextColor(Color.WHITE) Color.WHITE
currentSession.name!! })
}
val avatar = view.findViewById<Avatar>(R.id.avatar) val avatar = view.findViewById<Avatar>(R.id.avatar)
if (currentSession.avatar == null) { if (currentSession.avatar == null) {
avatar.setTextAvatar(avatarName) avatar.setTextAvatar(currentSession.name)
} else { } else {
avatar.setImageAvatar(currentSession.avatar!!) avatar.setImageAvatar(currentSession.avatar!!)
} }

View File

@ -108,7 +108,7 @@ class AIRAService : Service() {
fun onNameTold(sessionId: Int, name: String) fun onNameTold(sessionId: Int, name: String)
fun onAvatarChanged(sessionId: Int, avatar: ByteArray?) fun onAvatarChanged(sessionId: Int, avatar: ByteArray?)
fun onNewMessage(sessionId: Int, data: ByteArray): Boolean fun onNewMessage(sessionId: Int, data: ByteArray): Boolean
fun onAskLargeFiles(sessionId: Int, name: String, filesReceiver: FilesReceiver): Boolean fun onAskLargeFiles(sessionId: Int, filesReceiver: FilesReceiver): Boolean
} }
fun connectTo(ip: String) { fun connectTo(ip: String) {
@ -188,7 +188,7 @@ class AIRAService : Service() {
return sessions.contains(sessionId) return sessions.contains(sessionId)
} }
fun getNameOf(sessionId: Int): String { private fun getNameOf(sessionId: Int): String {
return contacts[sessionId]?.name ?: savedNames[sessionId] ?: sessions[sessionId]!!.ip return contacts[sessionId]?.name ?: savedNames[sessionId] ?: sessions[sessionId]!!.ip
} }
@ -343,11 +343,10 @@ class AIRAService : Service() {
} }
private fun sendNotification(sessionId: Int, msgContent: ByteArray) { private fun sendNotification(sessionId: Int, msgContent: ByteArray) {
val name = getNameOf(sessionId)
val notificationBuilder = NotificationCompat.Builder(this, MESSAGES_NOTIFICATION_CHANNEL_ID) val notificationBuilder = NotificationCompat.Builder(this, MESSAGES_NOTIFICATION_CHANNEL_ID)
.setCategory(NotificationCompat.CATEGORY_MESSAGE) .setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(name) .setContentTitle(getNameOf(sessionId))
.setContentText( .setContentText(
if (msgContent[0] == Protocol.MESSAGE) { if (msgContent[0] == Protocol.MESSAGE) {
msgContent.decodeToString(1) msgContent.decodeToString(1)
@ -358,7 +357,6 @@ class AIRAService : Service() {
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(this, 0, Intent(this, ChatActivity::class.java).apply { PendingIntent.getActivity(this, 0, Intent(this, ChatActivity::class.java).apply {
putExtra("sessionId", sessionId) putExtra("sessionId", sessionId)
putExtra("sessionName", name)
}, 0) }, 0)
) )
.setAutoCancel(true) .setAutoCancel(true)
@ -706,7 +704,7 @@ class AIRAService : Service() {
Protocol.ASK_LARGE_FILES -> { Protocol.ASK_LARGE_FILES -> {
if (!receiveFileTransfers.containsKey(sessionId) && !sendFileTransfers.containsKey(sessionId)) { if (!receiveFileTransfers.containsKey(sessionId) && !sendFileTransfers.containsKey(sessionId)) {
Protocol.parseAskFiles(buffer)?.let { files -> Protocol.parseAskFiles(buffer)?.let { files ->
val name = getNameOf(sessionId) val sessionName = getNameOf(sessionId)
val filesReceiver = FilesReceiver( val filesReceiver = FilesReceiver(
files, files,
{ filesReceiver -> { filesReceiver ->
@ -723,12 +721,12 @@ class AIRAService : Service() {
}, },
this, this,
notificationManager, notificationManager,
name sessionName
) )
receiveFileTransfers[sessionId] = filesReceiver receiveFileTransfers[sessionId] = filesReceiver
var shouldSendNotification = true var shouldSendNotification = true
if (!isAppInBackground) { if (!isAppInBackground) {
if (uiCallbacks?.onAskLargeFiles(sessionId, name, filesReceiver) == true) { if (uiCallbacks?.onAskLargeFiles(sessionId, filesReceiver) == true) {
shouldSendNotification = false shouldSendNotification = false
} }
} }
@ -737,12 +735,11 @@ class AIRAService : Service() {
.setCategory(NotificationCompat.CATEGORY_EVENT) .setCategory(NotificationCompat.CATEGORY_EVENT)
.setSmallIcon(R.drawable.ic_launcher) .setSmallIcon(R.drawable.ic_launcher)
.setContentTitle(getString(R.string.download_file_request)) .setContentTitle(getString(R.string.download_file_request))
.setContentText(getString(R.string.want_to_send_files, name)) .setContentText(getString(R.string.want_to_send_files, sessionName))
.setOngoing(true) //not cancelable .setOngoing(true) //not cancelable
.setContentIntent( .setContentIntent(
PendingIntent.getActivity(this, 0, Intent(this, ChatActivity::class.java).apply { PendingIntent.getActivity(this, 0, Intent(this, ChatActivity::class.java).apply {
putExtra("sessionId", sessionId) putExtra("sessionId", sessionId)
putExtra("sessionName", name)
}, 0) }, 0)
) )
.setDefaults(Notification.DEFAULT_ALL) .setDefaults(Notification.DEFAULT_ALL)

View File

@ -15,14 +15,14 @@ class FilesReceiver(
private val onAborted: (FilesReceiver) -> Unit, private val onAborted: (FilesReceiver) -> Unit,
context: Context, context: Context,
notificationManager: NotificationManagerCompat, notificationManager: NotificationManagerCompat,
sessionName: String private val sessionName: String
): FilesTransfer(context, notificationManager, sessionName) { ): FilesTransfer(context, notificationManager, sessionName) {
var shouldAsk = true var shouldAsk = true
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun ask(activity: AppCompatActivity, senderName: String) { fun ask(activity: AppCompatActivity) {
val dialogBinding = DialogAskFileBinding.inflate(activity.layoutInflater) val dialogBinding = DialogAskFileBinding.inflate(activity.layoutInflater)
dialogBinding.textTitle.text = activity.getString(R.string.want_to_send_files, senderName)+':' dialogBinding.textTitle.text = activity.getString(R.string.want_to_send_files, sessionName)+':'
val filesInfo = StringBuilder() val filesInfo = StringBuilder()
for (file in files) { for (file in files) {
filesInfo.appendLine(file.fileName+" ("+FileUtils.formatSize(file.fileSize)+')') filesInfo.appendLine(file.fileName+" ("+FileUtils.formatSize(file.fileSize)+')')

View File

@ -33,12 +33,15 @@ class Avatar @JvmOverloads constructor(
} }
} }
fun setTextAvatar(name: String) { fun setTextAvatar(name: String?) {
if (name.isNotEmpty()) { val char = if (name == null || name.isEmpty()) {
binding.textLetter.text = name[0].toString() '?'
binding.imageAvatar.visibility = View.GONE } else {
binding.textAvatar.visibility = View.VISIBLE name[0]
} }
binding.textLetter.text = char.toString()
binding.imageAvatar.visibility = View.GONE
binding.textAvatar.visibility = View.VISIBLE
} }
fun setImageAvatar(avatar: ByteArray) { fun setImageAvatar(avatar: ByteArray) {

View File

@ -93,6 +93,7 @@
<string name="remove">Remove</string> <string name="remove">Remove</string>
<string name="choose_avatar">Choose avatar</string> <string name="choose_avatar">Choose avatar</string>
<string name="message_hint">Send a message…</string> <string name="message_hint">Send a message…</string>
<string name="no_name_error">This session has no name !</string>
<!--accessibility strings--> <!--accessibility strings-->
<string name="send_file">Send file</string> <string name="send_file">Send file</string>