Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ package nl.tudelft.trustchain.common.contacts
import nl.tudelft.ipv8.keyvault.PublicKey
import nl.tudelft.ipv8.keyvault.defaultCryptoProvider
import nl.tudelft.ipv8.messaging.Deserializable
import nl.tudelft.ipv8.messaging.Serializable
import nl.tudelft.ipv8.util.hexToBytes
import nl.tudelft.ipv8.util.toHex
import org.json.JSONObject
import java.io.Serializable

data class Contact(
val name: String,
Expand All @@ -26,23 +26,25 @@ data class Contact(
return result
}

fun serialize(): ByteArray {
override fun serialize(): ByteArray {
val json = JSONObject()
json.put("name", name)
json.put("public_key", publicKey.keyToBin().toHex())

json.put(ARG_NAME, name)
json.put(ARG_PUBLIC_KEY, publicKey.keyToBin().toHex())
return json.toString().toByteArray()
}

companion object : Deserializable<Contact> {
val ARG_PUBLIC_KEY = "public_key"
val ARG_NAME = "name"

override fun deserialize(buffer: ByteArray, offset: Int): Pair<Contact, Int> {
val offsetBuffer = buffer.copyOfRange(0, buffer.size)
val offsetBuffer = buffer.copyOfRange(offset, buffer.size)
val json = JSONObject(offsetBuffer.decodeToString())
val name = json.getString("name")
val publicKeyString = json.getString("public_key")
val publicKey = defaultCryptoProvider.keyFromPublicBin(publicKeyString.hexToBytes())

return Pair(Contact(name, publicKey), 0)
val jname = json.getString(ARG_NAME)
val publicKeyBin = json.getString(ARG_PUBLIC_KEY).hexToBytes()
val jpublicKey = defaultCryptoProvider.keyFromPublicBin(publicKeyBin)
val contact = Contact(jname, jpublicKey)
return Pair(contact, 0)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nl.tudelft.trustchain.peerchat

import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import nl.tudelft.trustchain.common.ui.BaseFragment
import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity

abstract class PeerChatFragment (@LayoutRes contentLayoutId: Int = 0) : BaseFragment(contentLayoutId){

protected fun getPeerChatCommunity(): PeerChatCommunity {
return getIpv8().getOverlay()
?: throw java.lang.IllegalStateException("PeerChatCommunity is not configured")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ class AddContactFragment : BaseFragment(R.layout.fragment_add_contact) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val originalName: String? = requireArguments().getString(ARG_NAME)
val publicKeyBin = requireArguments().getString(ARG_PUBLIC_KEY)!!
binding.txtPublicKey.text = publicKeyBin
binding.edtName.setText(originalName ?: "")
lifecycleScope.launch {
val bitmap = withContext(Dispatchers.Default) {
QRCodeUtils(requireContext()).createQR(publicKeyBin)
Expand Down Expand Up @@ -55,6 +57,7 @@ class AddContactFragment : BaseFragment(R.layout.fragment_add_contact) {
}

companion object {
const val ARG_NAME = "name"
const val ARG_PUBLIC_KEY = "public_key"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,51 @@ import java.text.SimpleDateFormat

class ContactItemRenderer(
private val onItemClick: (Contact) -> Unit,
private val onItemLongClick: (Contact) -> Unit
private val onItemLongClick: (Contact) -> Unit,
private val hideDetails: Boolean
) : ItemLayoutRenderer<ContactItem, View>(
ContactItem::class.java
) {
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
private var selectedItem: ContactItem? = null
private var selectedContainer: View? = null

override fun bindView(item: ContactItem, view: View) = with(view) {
txtName.text = item.contact.name
// txtName.isVisible = item.contact.name.isNotEmpty()
txtPeerId.text = item.contact.mid
val lastMessageDate = item.lastMessage?.timestamp
txtDate.isVisible = lastMessageDate != null
txtDate.text = if (lastMessageDate != null) dateFormat.format(lastMessageDate) else null
txtMessage.isVisible = item.lastMessage != null
txtMessage.text = item.lastMessage?.message
val textStyle = if (item.lastMessage?.read == false) Typeface.BOLD else Typeface.NORMAL
txtMessage.setTypeface(null, textStyle)
avatar.setUser(item.contact.mid, item.contact.name)

if (hideDetails) {
txtDate.isVisible = false
txtMessage.isVisible = false
imgWifi.isVisible = false
imgBluetooth.isVisible = false
container.setBackgroundResource(R.drawable.bg_selectable_container)
container.isSelected = selectedItem?.areItemsTheSame(item) ?: false
} else {
val lastMessageDate = item.lastMessage?.timestamp
txtDate.isVisible = lastMessageDate != null
txtDate.text = if (lastMessageDate != null) dateFormat.format(lastMessageDate) else null
txtMessage.isVisible = item.lastMessage != null
txtMessage.text = item.lastMessage?.message
val textStyle = if (item.lastMessage?.read == false) Typeface.BOLD else Typeface.NORMAL
txtMessage.setTypeface(null, textStyle)
imgWifi.isVisible = item.isOnline
imgBluetooth.isVisible = item.isBluetooth
}

setOnClickListener {
if (selectedContainer != null && selectedItem != null &&
!selectedItem!!.areItemsTheSame(item)) {
selectedContainer!!.isSelected = false
}

selectedItem = item
selectedContainer = container
container.isSelected = true
onItemClick(item.contact)
}
imgWifi.isVisible = item.isOnline
imgBluetooth.isVisible = item.isBluetooth
avatar.setUser(item.contact.mid, item.contact.name)
setOnLongClickListener {
onItemLongClick(item.contact)
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,21 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.mattskala.itemadapter.Item
import com.mattskala.itemadapter.ItemAdapter
import kotlinx.android.synthetic.main.fragment_contacts.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.isActive
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import nl.tudelft.ipv8.Peer
import nl.tudelft.ipv8.util.toHex
import nl.tudelft.trustchain.common.contacts.Contact
import nl.tudelft.trustchain.common.ui.BaseFragment
import nl.tudelft.trustchain.common.util.viewBinding
import nl.tudelft.trustchain.peerchat.PeerChatFragment
import nl.tudelft.trustchain.peerchat.R
import nl.tudelft.trustchain.peerchat.community.PeerChatCommunity
import nl.tudelft.trustchain.peerchat.databinding.FragmentContactsBinding
import nl.tudelft.trustchain.peerchat.db.PeerChatStore
import nl.tudelft.trustchain.peerchat.entity.ChatMessage
import nl.tudelft.trustchain.peerchat.ui.conversation.ConversationFragment

@OptIn(ExperimentalCoroutinesApi::class)
class ContactsFragment : BaseFragment(R.layout.fragment_contacts) {
class ContactsFragment : PeerChatFragment(R.layout.fragment_contacts) {
private val binding by viewBinding(FragmentContactsBinding::bind)

private val adapter = ItemAdapter()
Expand All @@ -59,11 +55,6 @@ class ContactsFragment : BaseFragment(R.layout.fragment_contacts) {
}.asLiveData()
}

private fun getPeerChatCommunity(): PeerChatCommunity {
return getIpv8().getOverlay()
?: throw java.lang.IllegalStateException("PeerChatCommunity is not configured")
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -77,7 +68,8 @@ class ContactsFragment : BaseFragment(R.layout.fragment_contacts) {
},
{
showOptions(it)
}
},
false
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ package nl.tudelft.trustchain.peerchat.ui.conversation

import android.content.res.ColorStateList
import android.graphics.Color
import android.os.Bundle
import android.text.format.DateUtils
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.findFragment
import androidx.navigation.fragment.findNavController
import com.bumptech.glide.Glide
import com.mattskala.itemadapter.ItemLayoutRenderer
import kotlinx.android.synthetic.main.item_message.view.*
import nl.tudelft.ipv8.util.toHex
import nl.tudelft.trustchain.common.contacts.Contact
import nl.tudelft.trustchain.common.eurotoken.TransactionRepository
import nl.tudelft.trustchain.common.util.getColorByHash
import nl.tudelft.trustchain.peerchat.R
import nl.tudelft.trustchain.peerchat.ui.addcontact.AddContactFragment
import java.math.BigInteger
import java.text.SimpleDateFormat

Expand Down Expand Up @@ -53,6 +59,7 @@ class ChatMessageItemRenderer : ItemLayoutRenderer<ChatMessageItem, View>(
constraintSet.clone(constraintLayout)
constraintSet.removeFromHorizontalChain(content.id)
constraintSet.removeFromHorizontalChain(bottomContainer.id)
contactLayout.isVisible = false

val attachment = item.chatMessage.attachment
if (attachment != null && attachment.type == MessageAttachment.TYPE_IMAGE) {
Expand All @@ -67,6 +74,27 @@ class ChatMessageItemRenderer : ItemLayoutRenderer<ChatMessageItem, View>(
image.isVisible = true
txtMessage.isVisible = false
txtTransaction.isVisible = false
} else if (attachment != null && attachment.type == MessageAttachment.TYPE_CONTACT) {
contactLayout.isVisible = true
val contact = Contact.deserialize(attachment.content, 0).first
contactName.text =contact.name
progress.isVisible = false
txtMessage.isVisible = false
txtTransaction.isVisible = false
image.isVisible = false
if (!item.chatMessage.outgoing) {
contactLayout.setOnClickListener {
val args = Bundle()
val publicKeyBin = contact.publicKey.keyToBin().toHex()
args.putString(AddContactFragment.ARG_NAME, contact.name)
args.putString(AddContactFragment.ARG_PUBLIC_KEY, publicKeyBin)
view.findFragment<ConversationFragment>().
findNavController().navigate(
R.id.action_conversationFragment_to_addContactFragment,
args
)
}
}
} else if (item.chatMessage.transactionHash != null) {
progress.isVisible =
item.transaction == null // transaction not yet received via trustchain
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ class ConversationFragment : BaseFragment(R.layout.fragment_conversation) {
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE)
}

btnSendContact.setOnClickListener {
val args = Bundle()
fab.collapse()
args.putString(TransferFragment.ARG_PUBLIC_KEY, publicKeyBin)
args.putString(TransferFragment.ARG_NAME, name)
findNavController().navigate(
R.id.action_conversationFragment_to_sendContactsFragment,
args
)
}
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package nl.tudelft.trustchain.peerchat.ui.conversation

import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.Toast
import androidx.core.view.isVisible
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.asLiveData
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.mattskala.itemadapter.Item
import com.mattskala.itemadapter.ItemAdapter
import kotlinx.android.synthetic.main.fragment_send_contact.*
import kotlinx.coroutines.flow.map
import nl.tudelft.trustchain.common.contacts.Contact
import nl.tudelft.trustchain.peerchat.PeerChatFragment
import nl.tudelft.trustchain.peerchat.R
import nl.tudelft.trustchain.peerchat.db.PeerChatStore
import nl.tudelft.ipv8.keyvault.defaultCryptoProvider
import nl.tudelft.ipv8.util.hexToBytes
import nl.tudelft.trustchain.peerchat.ui.contacts.ContactItem
import nl.tudelft.trustchain.peerchat.ui.contacts.ContactItemRenderer

class SendContactFragment : PeerChatFragment(R.layout.fragment_send_contact) {

private val store by lazy {
PeerChatStore.getInstance(requireContext())
}

private val publicKeyBin by lazy {
requireArguments().getString(TransferFragment.ARG_PUBLIC_KEY)!!
}

private val publicKey by lazy {
defaultCryptoProvider.keyFromPublicBin(publicKeyBin.hexToBytes())
}

private val adapter = ItemAdapter()

private val items: LiveData<List<Item>> by lazy {
store.getContactsWithLastMessages().map { contacts ->
contacts.filter { it.first.publicKey != getIpv8().myPeer.publicKey &&
it.first.publicKey != publicKey}
.sortedBy { it.first.name }
.map { contactWithMessage ->
val (contact, message) = contactWithMessage
ContactItem(
contact,
message,
false,
false
)
}
}.asLiveData()
}

private var selectedContact: Contact? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

adapter.registerRenderer(
ContactItemRenderer(
{
selectedContact = it
}, {}, true
)
)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.addItemDecoration(
DividerItemDecoration(
context,
LinearLayout.VERTICAL
)
)

btnSendContact.setOnClickListener {
if (selectedContact != null) {
getPeerChatCommunity().sendContact(selectedContact!!, publicKey)
findNavController().popBackStack(R.id.conversationFragment, false)
} else {
Toast.makeText(requireContext(), "No contact selected", Toast.LENGTH_SHORT).show()
}
}

items.observe(
viewLifecycleOwner,
Observer {
adapter.updateItems(it)
imgEmpty.isVisible = it.isEmpty()
}
)

}
}
Loading