Profile toolbar & 4 ChatItem types & Other UI improvements
This commit is contained in:
parent
e77887a51c
commit
2f01c86359
@ -42,9 +42,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.3"
|
||||
implementation 'androidx.core:core-ktx:1.5.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.3.0'
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.4"
|
||||
implementation "androidx.preference:preference-ktx:1.1.1"
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
//implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
|
@ -28,8 +28,8 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".ChatActivity"/>
|
||||
<activity android:name=".MainActivity">
|
||||
<activity android:name=".ChatActivity" android:theme="@style/Theme.AIRA.NoActionBar"/>
|
||||
<activity android:name=".MainActivity" android:theme="@style/Theme.AIRA.NoActionBar">
|
||||
<intent-filter android:label="@string/share_label">
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE"/>
|
||||
|
@ -1,16 +1,17 @@
|
||||
package sushi.hardcore.aira
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.ServiceConnection
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@ -32,14 +33,7 @@ class ChatActivity : ServiceBoundActivity() {
|
||||
private var lastLoadedMessageOffset = 0
|
||||
private val filePicker = registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { uris ->
|
||||
if (isServiceInitialized() && uris.size > 0) {
|
||||
airaService.sendFilesFromUris(sessionId, uris)?.let { msgs ->
|
||||
for (msg in msgs) {
|
||||
chatAdapter.newMessage(ChatItem(true, msg))
|
||||
if (airaService.contacts.contains(sessionId)) {
|
||||
lastLoadedMessageOffset += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
airaService.sendFilesFromUris(sessionId, uris)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,13 +41,15 @@ class ChatActivity : ServiceBoundActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityChatBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
sessionId = intent.getIntExtra("sessionId", -1)
|
||||
if (sessionId != -1) {
|
||||
intent.getStringExtra("sessionName")?.let { name ->
|
||||
sessionName = name
|
||||
title = name
|
||||
setSupportActionBar(binding.toolbar.toolbar)
|
||||
binding.toolbar.textAvatar.setLetterFrom(name)
|
||||
binding.toolbar.title.text = name
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
chatAdapter = ChatAdapter(this@ChatActivity, ::onClickSaveFile)
|
||||
binding.recyclerChat.apply {
|
||||
@ -112,7 +108,7 @@ class ChatActivity : ServiceBoundActivity() {
|
||||
}
|
||||
binding.recyclerChat.smoothScrollToPosition(chatAdapter.itemCount)
|
||||
val showBottomPanel = {
|
||||
findViewById<ConstraintLayout>(R.id.bottom_panel).visibility = View.VISIBLE
|
||||
binding.bottomPanel.visibility = View.VISIBLE
|
||||
}
|
||||
airaService.uiCallbacks = object : AIRAService.UiCallbacks {
|
||||
override fun onNewSession(sessionId: Int, ip: String) {
|
||||
@ -125,7 +121,9 @@ class ChatActivity : ServiceBoundActivity() {
|
||||
override fun onSessionDisconnect(sessionId: Int) {
|
||||
if (this@ChatActivity.sessionId == sessionId) {
|
||||
runOnUiThread {
|
||||
findViewById<ConstraintLayout>(R.id.bottom_panel).visibility = View.GONE
|
||||
val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputManager.hideSoftInputFromWindow(binding.editMessage.windowToken, 0)
|
||||
binding.bottomPanel.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,14 @@ import sushi.hardcore.aira.background_service.Protocol
|
||||
|
||||
class ChatItem(val outgoing: Boolean, val data: ByteArray) {
|
||||
companion object {
|
||||
const val MESSAGE = 0
|
||||
const val FILE = 1
|
||||
const val OUTGOING_MESSAGE = 0
|
||||
const val INCOMING_MESSAGE = 1
|
||||
const val OUTGOING_FILE = 2
|
||||
const val INCOMING_FILE = 3
|
||||
}
|
||||
val itemType = if (data[0] == Protocol.MESSAGE) {
|
||||
if (outgoing) OUTGOING_MESSAGE else INCOMING_MESSAGE
|
||||
} else {
|
||||
if (outgoing) OUTGOING_FILE else INCOMING_FILE
|
||||
}
|
||||
val itemType = if (data[0] == Protocol.MESSAGE) { MESSAGE } else { FILE }
|
||||
}
|
@ -79,7 +79,11 @@ class MainActivity : ServiceBoundActivity() {
|
||||
setContentView(binding.root)
|
||||
|
||||
val identityName = intent.getStringExtra("identityName")
|
||||
identityName?.let { title = it }
|
||||
identityName?.let {
|
||||
setSupportActionBar(binding.toolbar.toolbar)
|
||||
binding.toolbar.textAvatar.setLetterFrom(it)
|
||||
binding.toolbar.title.text = it
|
||||
}
|
||||
|
||||
val openedToShareFile = intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE
|
||||
|
||||
|
@ -11,7 +11,6 @@ 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
|
||||
@ -23,7 +22,8 @@ class ChatAdapter(
|
||||
): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
companion object {
|
||||
const val BUBBLE_MARGIN = 70
|
||||
const val CONTAINER_MARGIN = 70
|
||||
const val BUBBLE_HORIZONTAL_PADDING = 40
|
||||
}
|
||||
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
@ -44,88 +44,106 @@ class ChatAdapter(
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
internal open class BubbleViewHolder(private val context: Context, itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
fun handleItemView(position: Int) {
|
||||
itemView.updatePadding(top = if (position == 0) {
|
||||
50
|
||||
} else {
|
||||
itemView.paddingBottom
|
||||
})
|
||||
}
|
||||
fun setBubbleColor(bubble: View, outgoing: Boolean) {
|
||||
internal open class BubbleViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
|
||||
protected fun setPadding(outgoing: Boolean) {
|
||||
if (outgoing) {
|
||||
bubble.background.clearColorFilter()
|
||||
itemView.updatePadding(right = BUBBLE_HORIZONTAL_PADDING)
|
||||
} else {
|
||||
bubble.background.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.incomingBubbleBackground), PorterDuff.Mode.SRC)
|
||||
itemView.updatePadding(left = BUBBLE_HORIZONTAL_PADDING)
|
||||
}
|
||||
}
|
||||
protected fun configureBubble(context: Context, view: View, outgoing: Boolean) {
|
||||
view.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
gravity = if (outgoing) {
|
||||
marginStart = CONTAINER_MARGIN
|
||||
Gravity.END
|
||||
} else {
|
||||
marginEnd = CONTAINER_MARGIN
|
||||
Gravity.START
|
||||
}
|
||||
}
|
||||
if (!outgoing) {
|
||||
view.background.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(context, R.color.incomingBubbleBackground),
|
||||
PorterDuff.Mode.SRC
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageViewHolder(context: Context, itemView: View): BubbleViewHolder(context, itemView) {
|
||||
fun bind(chatItem: ChatItem, position: Int) {
|
||||
internal open class MessageViewHolder(itemView: View): BubbleViewHolder(itemView) {
|
||||
protected fun bindMessage(chatItem: ChatItem): TextView {
|
||||
itemView.findViewById<TextView>(R.id.text_message).apply {
|
||||
text = chatItem.data.sliceArray(1 until chatItem.data.size).decodeToString()
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
if (chatItem.outgoing) {
|
||||
gravity = Gravity.END
|
||||
marginStart = BUBBLE_MARGIN
|
||||
} else {
|
||||
gravity = Gravity.START
|
||||
marginEnd = BUBBLE_MARGIN
|
||||
}
|
||||
}
|
||||
setBubbleColor(this, chatItem.outgoing)
|
||||
return this
|
||||
}
|
||||
handleItemView(position)
|
||||
}
|
||||
}
|
||||
|
||||
internal class FileViewHolder(context: Context, itemView: View): BubbleViewHolder(context, itemView) {
|
||||
fun bind(chatItem: ChatItem, position: Int, onSavingFile: (filename: String, rawUuid: ByteArray) -> Unit) {
|
||||
internal class OutgoingMessageViewHolder(private val context: Context, itemView: View): MessageViewHolder(itemView) {
|
||||
fun bind(chatItem: ChatItem) {
|
||||
configureBubble(context, bindMessage(chatItem), true)
|
||||
setPadding(true)
|
||||
}
|
||||
}
|
||||
|
||||
internal class IncomingMessageViewHolder(private val context: Context, itemView: View): MessageViewHolder(itemView) {
|
||||
fun bind(chatItem: ChatItem) {
|
||||
configureBubble(context, bindMessage(chatItem), false)
|
||||
setPadding(false)
|
||||
}
|
||||
}
|
||||
|
||||
internal open class FileViewHolder(itemView: View, private val onSavingFile: (filename: String, rawUuid: ByteArray) -> Unit): BubbleViewHolder(itemView) {
|
||||
protected fun bindFile(chatItem: ChatItem): LinearLayout {
|
||||
val filename = chatItem.data.sliceArray(17 until chatItem.data.size).decodeToString()
|
||||
itemView.findViewById<TextView>(R.id.text_filename).text = filename
|
||||
itemView.findViewById<ImageButton>(R.id.button_save).setOnClickListener {
|
||||
onSavingFile(filename, chatItem.data.sliceArray(1 until 17))
|
||||
}
|
||||
itemView.findViewById<LinearLayout>(R.id.bubble_content).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT).apply {
|
||||
if (chatItem.outgoing) {
|
||||
gravity = Gravity.END
|
||||
marginStart = BUBBLE_MARGIN
|
||||
} else {
|
||||
gravity = Gravity.START
|
||||
marginEnd = BUBBLE_MARGIN
|
||||
}
|
||||
}
|
||||
setBubbleColor(this, chatItem.outgoing)
|
||||
}
|
||||
handleItemView(position)
|
||||
return itemView.findViewById(R.id.bubble_content)
|
||||
}
|
||||
}
|
||||
|
||||
internal class OutgoingFileViewHolder(private val context: Context, itemView: View, onSavingFile: (filename: String, rawUuid: ByteArray) -> Unit): FileViewHolder(itemView, onSavingFile) {
|
||||
fun bind(chatItem: ChatItem) {
|
||||
configureBubble(context, bindFile(chatItem), true)
|
||||
setPadding(true)
|
||||
}
|
||||
}
|
||||
|
||||
internal class IncomingFileViewHolder(private val context: Context, itemView: View, onSavingFile: (filename: String, rawUuid: ByteArray) -> Unit): FileViewHolder(itemView, onSavingFile) {
|
||||
fun bind(chatItem: ChatItem) {
|
||||
configureBubble(context, bindFile(chatItem), false)
|
||||
setPadding(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return when (viewType) {
|
||||
ChatItem.MESSAGE -> {
|
||||
val view = inflater.inflate(R.layout.adapter_chat_message, parent, false)
|
||||
MessageViewHolder(context, view)
|
||||
return if (viewType == ChatItem.OUTGOING_MESSAGE || viewType == ChatItem.INCOMING_MESSAGE) {
|
||||
val view = inflater.inflate(R.layout.adapter_chat_message, parent, false)
|
||||
if (viewType == ChatItem.OUTGOING_MESSAGE) {
|
||||
OutgoingMessageViewHolder(context, view)
|
||||
} else {
|
||||
IncomingMessageViewHolder(context, view)
|
||||
}
|
||||
ChatItem.FILE -> {
|
||||
val view = inflater.inflate(R.layout.adapter_chat_file, parent, false)
|
||||
FileViewHolder(context, view)
|
||||
} else {
|
||||
val view = inflater.inflate(R.layout.adapter_chat_file, parent, false)
|
||||
if (viewType == ChatItem.OUTGOING_FILE) {
|
||||
OutgoingFileViewHolder(context, view, onSavingFile)
|
||||
} else {
|
||||
IncomingFileViewHolder(context, view, onSavingFile)
|
||||
}
|
||||
else -> throw RuntimeException("Invalid chat item type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
val chatItem = chatItems[position]
|
||||
when (chatItem.itemType) {
|
||||
ChatItem.MESSAGE -> {
|
||||
(holder as MessageViewHolder).bind(chatItem, position)
|
||||
}
|
||||
ChatItem.FILE -> {
|
||||
(holder as FileViewHolder).bind(chatItem, position, onSavingFile)
|
||||
}
|
||||
ChatItem.OUTGOING_MESSAGE -> (holder as OutgoingMessageViewHolder).bind(chatItem)
|
||||
ChatItem.INCOMING_MESSAGE -> (holder as IncomingMessageViewHolder).bind(chatItem)
|
||||
ChatItem.OUTGOING_FILE -> (holder as OutgoingFileViewHolder).bind(chatItem)
|
||||
ChatItem.INCOMING_FILE -> (holder as IncomingFileViewHolder).bind(chatItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +135,7 @@ class AIRAService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun sendFilesFromUris(sessionId: Int, uris: List<Uri>): List<ByteArray>? {
|
||||
fun sendFilesFromUris(sessionId: Int, uris: List<Uri>) {
|
||||
val files = mutableListOf<SendFile>()
|
||||
var useLargeFileTransfer = false
|
||||
for (uri in uris) {
|
||||
@ -146,30 +146,22 @@ class AIRAService : Service() {
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (useLargeFileTransfer) {
|
||||
if (useLargeFileTransfer) {
|
||||
sendLargeFilesTo(sessionId, files)
|
||||
null
|
||||
} else {
|
||||
val msgs = mutableListOf<ByteArray>()
|
||||
for (file in files) {
|
||||
sendSmallFileTo(sessionId, file)?.let { msg ->
|
||||
msgs.add(msg)
|
||||
}
|
||||
sendSmallFileTo(sessionId, file)
|
||||
}
|
||||
msgs
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendSmallFileTo(sessionId: Int, sendFile: SendFile): ByteArray? {
|
||||
private fun sendSmallFileTo(sessionId: Int, sendFile: SendFile) {
|
||||
val buffer = sendFile.inputStream.readBytes()
|
||||
sendFile.inputStream.close()
|
||||
sendTo(sessionId, Protocol.newFile(sendFile.fileName, buffer))
|
||||
AIRADatabase.storeFile(contacts[sessionId]?.uuid, buffer)?.let { rawFileUuid ->
|
||||
val msg = byteArrayOf(Protocol.FILE) + rawFileUuid + sendFile.fileName.toByteArray()
|
||||
saveMsg(sessionId, msg)
|
||||
return msg
|
||||
saveMsg(sessionId, byteArrayOf(Protocol.FILE) + rawFileUuid + sendFile.fileName.toByteArray())
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun sendLargeFilesTo(sessionId: Int, files: MutableList<SendFile>) {
|
||||
|
@ -11,10 +11,10 @@ jni = { version = "0.19", default-features = false }
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
rand-8 = {package = "rand", version = "0.8.3"}
|
||||
rand = "0.8.3"
|
||||
rand-7 = {package = "rand", version = "0.7.3"}
|
||||
lazy_static = "1.4.0"
|
||||
rusqlite = {version = "0.25.1", features = ["bundled"]}
|
||||
rusqlite = { version = "0.25.1", features = ["bundled"] }
|
||||
ed25519-dalek = "1" #for singing
|
||||
x25519-dalek = "1.1" #for shared secret
|
||||
sha2 = "0.9.3"
|
||||
@ -24,7 +24,7 @@ aes-gcm-siv = "0.10.0" #Database
|
||||
hmac = "0.11.0"
|
||||
hex = "0.4.3"
|
||||
strum_macros = "0.20.1" #display enums
|
||||
uuid = {version = "0.8", features = ["v4"]}
|
||||
uuid = { version = "0.8", features = ["v4"] }
|
||||
scrypt = "0.7.0"
|
||||
zeroize = "1.2.0"
|
||||
log = "0.4.14"
|
||||
|
@ -1,14 +1,12 @@
|
||||
use std::convert::TryInto;
|
||||
use std::{convert::TryInto, fmt::Display};
|
||||
use hkdf::Hkdf;
|
||||
use sha2::Sha384;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use hmac::{Hmac, NewMac, Mac};
|
||||
use scrypt::{scrypt, Params};
|
||||
use rand_8::{RngCore, rngs::OsRng};
|
||||
use rand::{RngCore, rngs::OsRng};
|
||||
use aes_gcm::{aead::Aead, NewAead, Nonce};
|
||||
use aes_gcm_siv::Aes256GcmSiv;
|
||||
use zeroize::Zeroize;
|
||||
use strum_macros::Display;
|
||||
use crate::utils::*;
|
||||
|
||||
pub const HASH_OUTPUT_LEN: usize = 48; //SHA384
|
||||
const KEY_LEN: usize = 16;
|
||||
@ -33,6 +31,26 @@ fn hkdf_expand_label(key: &[u8], label: &str, context: Option<&[u8]>, okm: &mut
|
||||
};
|
||||
}
|
||||
|
||||
fn get_labels(handshake: bool, i_am_bob: bool) -> (String, String) {
|
||||
let mut label = if handshake {
|
||||
"handshake"
|
||||
} else {
|
||||
"application"
|
||||
}.to_owned();
|
||||
label += "_i_am_";
|
||||
let local_label = label.clone() + if i_am_bob {
|
||||
"bob"
|
||||
} else {
|
||||
"alice"
|
||||
};
|
||||
let peer_label = label + if i_am_bob {
|
||||
"alice"
|
||||
} else {
|
||||
"bob"
|
||||
};
|
||||
(local_label, peer_label)
|
||||
}
|
||||
|
||||
pub struct HandshakeKeys {
|
||||
pub local_key: [u8; KEY_LEN],
|
||||
pub local_iv: [u8; IV_LEN],
|
||||
@ -47,11 +65,11 @@ impl HandshakeKeys {
|
||||
pub fn derive_keys(shared_secret: [u8; 32], handshake_hash: [u8; HASH_OUTPUT_LEN], i_am_bob: bool) -> HandshakeKeys {
|
||||
let (handshake_secret, _) = Hkdf::<Sha384>::extract(None, &shared_secret);
|
||||
|
||||
let local_label = "handshake".to_owned() + if i_am_bob {"i_am_bob"} else {"i_am_alice"};
|
||||
let (local_label, peer_label) = get_labels(true, i_am_bob);
|
||||
|
||||
let mut local_handshake_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(handshake_secret.as_slice(), &local_label, Some(&handshake_hash), &mut local_handshake_traffic_secret);
|
||||
|
||||
let peer_label = "handshake".to_owned() + if i_am_bob {"i_am_alice"} else {"i_am_bob"};
|
||||
let mut peer_handshake_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(handshake_secret.as_slice(), &peer_label, Some(&handshake_hash), &mut peer_handshake_traffic_secret);
|
||||
|
||||
@ -72,7 +90,7 @@ impl HandshakeKeys {
|
||||
peer_key: peer_handshake_key,
|
||||
peer_iv: peer_handshake_iv,
|
||||
peer_handshake_traffic_secret: peer_handshake_traffic_secret,
|
||||
handshake_secret: to_array_48(handshake_secret.as_slice())
|
||||
handshake_secret: handshake_secret.as_slice().try_into().unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,11 +108,11 @@ impl ApplicationKeys {
|
||||
hkdf_expand_label(&handshake_secret, "derived", None, &mut derived_secret);
|
||||
let (master_secret, _) = Hkdf::<Sha384>::extract(Some(&derived_secret), b"");
|
||||
|
||||
let local_label = "application".to_owned() + if i_am_bob {"i_am_bob"} else {"i_am_alice"};
|
||||
let (local_label, peer_label) = get_labels(false, i_am_bob);
|
||||
|
||||
let mut local_application_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&master_secret, &local_label, Some(&handshake_hash), &mut local_application_traffic_secret);
|
||||
|
||||
let peer_label = "application".to_owned() + if i_am_bob {"i_am_alice"} else {"i_am_bob"};
|
||||
let mut peer_application_traffic_secret = [0; HASH_OUTPUT_LEN];
|
||||
hkdf_expand_label(&master_secret, &peer_label, Some(&handshake_hash), &mut peer_application_traffic_secret);
|
||||
|
||||
@ -161,12 +179,21 @@ pub fn encrypt_data(data: &[u8], master_key: &[u8]) -> Result<Vec<u8>, CryptoErr
|
||||
Ok(cipher_text)
|
||||
}
|
||||
|
||||
#[derive(Display, Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum CryptoError {
|
||||
DecryptionFailed,
|
||||
InvalidLength
|
||||
}
|
||||
|
||||
impl Display for CryptoError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
CryptoError::DecryptionFailed => "Decryption failed",
|
||||
CryptoError::InvalidLength => "Invalid length",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decrypt_data(data: &[u8], master_key: &[u8]) -> Result<Vec<u8>, CryptoError> {
|
||||
if data.len() <= IV_LEN || master_key.len() != MASTER_KEY_LEN {
|
||||
return Err(CryptoError::InvalidLength);
|
||||
|
@ -256,56 +256,57 @@ impl Identity {
|
||||
match db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) {
|
||||
Ok(mut stmt) => {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let row = rows.next().unwrap();
|
||||
if row.is_some() {
|
||||
let total: usize = row.unwrap().get(0).unwrap();
|
||||
if offset >= total {
|
||||
print_error!("Offset larger than total numbers of rows");
|
||||
None
|
||||
} else {
|
||||
if offset+count >= total {
|
||||
count = total-offset;
|
||||
}
|
||||
match db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)) {
|
||||
Ok(mut stmt) => {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut msgs = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||
Ok(outgoing) => {
|
||||
match byte_to_bool(outgoing[0]) {
|
||||
Ok(outgoing) => {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => {
|
||||
msgs.push(
|
||||
(
|
||||
outgoing,
|
||||
data
|
||||
match rows.next() {
|
||||
Ok(row) => if row.is_some() {
|
||||
let total: usize = row.unwrap().get(0).unwrap();
|
||||
if offset >= total {
|
||||
None
|
||||
} else {
|
||||
if offset+count >= total {
|
||||
count = total-offset;
|
||||
}
|
||||
match db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)) {
|
||||
Ok(mut stmt) => {
|
||||
let mut rows = stmt.query([]).unwrap();
|
||||
let mut msgs = Vec::new();
|
||||
while let Some(row) = rows.next().unwrap() {
|
||||
let encrypted_outgoing: Vec<u8> = row.get(0).unwrap();
|
||||
match crypto::decrypt_data(encrypted_outgoing.as_slice(), &self.master_key){
|
||||
Ok(outgoing) => {
|
||||
match byte_to_bool(outgoing[0]) {
|
||||
Ok(outgoing) => {
|
||||
let encrypted_data: Vec<u8> = row.get(1).unwrap();
|
||||
match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) {
|
||||
Ok(data) => {
|
||||
msgs.push(
|
||||
(
|
||||
outgoing,
|
||||
data
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
Err(e) => print_error!(e)
|
||||
},
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
Err(_) => {}
|
||||
|
||||
}
|
||||
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Err(e) => print_error!(e)
|
||||
}
|
||||
Some(msgs)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
Some(msgs)
|
||||
}
|
||||
Err(e) => {
|
||||
print_error!(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
Err(_) => None
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -2,10 +2,6 @@ use std::convert::TryInto;
|
||||
use uuid::Bytes;
|
||||
use crate::print_error;
|
||||
|
||||
pub fn to_array_48(s: &[u8]) -> [u8; 48] {
|
||||
s.try_into().unwrap()
|
||||
}
|
||||
|
||||
pub fn to_uuid_bytes(bytes: &[u8]) -> Option<Bytes> {
|
||||
match bytes.try_into() {
|
||||
Ok(uuid) => Some(uuid),
|
||||
|
@ -6,13 +6,13 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".ChatActivity">
|
||||
|
||||
<include layout="@layout/profile_toolbar" android:id="@+id/toolbar"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_chat"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_panel"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
@ -20,7 +20,9 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:visibility="gone">
|
||||
android:visibility="gone"
|
||||
android:paddingVertical="5dp"
|
||||
android:background="@color/primary">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_attach"
|
||||
|
@ -1,79 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/refresher"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<include layout="@layout/profile_toolbar" android:id="@+id/toolbar"/>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/refresher"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_online_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/online_peers"
|
||||
style="@style/Label"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/online_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_online_sessions"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_offline_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/offline_contacts"
|
||||
style="@style/Label"
|
||||
app:layout_constraintTop_toBottomOf="@id/online_sessions"/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/offline_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_offline_sessions"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottom_panel"/>
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bottom_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_peer_ip"
|
||||
android:layout_width="0dp"
|
||||
<TextView
|
||||
android:id="@+id/text_online_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:imeOptions="actionGo"
|
||||
android:hint="@string/add_peer_ip"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_show_ip"/>
|
||||
android:text="@string/online_peers"
|
||||
style="@style/Label"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_show_ip"
|
||||
android:layout_width="@dimen/image_button_size"
|
||||
android:layout_height="@dimen/image_button_size"
|
||||
android:src="@drawable/ic_info"
|
||||
style="@style/ImageButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
<ListView
|
||||
android:id="@+id/online_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:divider="@color/transparent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_online_sessions"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_offline_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/offline_contacts"
|
||||
style="@style/Label"
|
||||
app:layout_constraintTop_toBottomOf="@id/online_sessions"/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/offline_sessions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:divider="@color/transparent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_offline_sessions"
|
||||
app:layout_constraintBottom_toTopOf="@+id/bottom_panel"/>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/bottom_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/edit_peer_ip"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="5dp"
|
||||
android:background="@color/primary">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_peer_ip"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:imeOptions="actionGo"
|
||||
android:hint="@string/add_peer_ip"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_show_ip"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_show_ip"
|
||||
android:layout_width="@dimen/image_button_size"
|
||||
android:layout_height="@dimen/image_button_size"
|
||||
android:src="@drawable/ic_info"
|
||||
style="@style/ImageButton"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/edit_peer_ip"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -4,7 +4,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="10dp">
|
||||
android:padding="15dp">
|
||||
|
||||
<sushi.hardcore.aira.widgets.TextAvatar
|
||||
android:id="@+id/text_avatar"
|
||||
|
32
app/src/main/res/layout/profile_toolbar.xml
Normal file
32
app/src/main/res/layout/profile_toolbar.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="@color/primary"
|
||||
app:contentInsetStartWithNavigation="0dp"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.LinearLayoutCompat
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<sushi.hardcore.aira.widgets.TextAvatar
|
||||
android:id="@+id/text_avatar"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
app:textSize="8sp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_gravity="center_vertical"/>
|
||||
|
||||
</androidx.appcompat.widget.LinearLayoutCompat>
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
@ -4,7 +4,7 @@
|
||||
<color name="secondary">#19a52c</color>
|
||||
<color name="white">#FFFFFF</color>
|
||||
<color name="backgroundColor">#111111</color>
|
||||
<color name="sessionBackground">#1F1F1F</color>
|
||||
<color name="sessionBackground">#1A1A1A</color>
|
||||
<color name="bubbleBackground">@color/sessionBackground</color>
|
||||
<color name="incomingBubbleBackground">@color/secondary</color>
|
||||
<color name="textLink">#3845A3</color>
|
||||
|
@ -1,18 +1,11 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.AIRA" parent="Theme.AppCompat">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryVariant">@color/primary</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/secondary</item>
|
||||
<item name="colorSecondaryVariant">@color/secondary</item>
|
||||
|
||||
<item name="colorAccent">@color/secondary</item>
|
||||
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
|
||||
<item name="android:windowBackground">@color/backgroundColor</item>
|
||||
</style>
|
||||
</resources>
|
@ -1,12 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.4.32"
|
||||
ext.kotlin_version = "1.5.0"
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.0'
|
||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
@ -17,7 +17,7 @@ buildscript {
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user