diff --git a/app/build.gradle b/app/build.gradle
index c99f58f..17b5c4e 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dc3194f..d89d63d 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -28,8 +28,8 @@
-
-
+
+
diff --git a/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt b/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt
index 70f9a85..14b4556 100644
--- a/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt
+++ b/app/src/main/java/sushi/hardcore/aira/ChatActivity.kt
@@ -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(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(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
}
}
}
diff --git a/app/src/main/java/sushi/hardcore/aira/ChatItem.kt b/app/src/main/java/sushi/hardcore/aira/ChatItem.kt
index 438e7ae..7847b73 100644
--- a/app/src/main/java/sushi/hardcore/aira/ChatItem.kt
+++ b/app/src/main/java/sushi/hardcore/aira/ChatItem.kt
@@ -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 }
}
\ No newline at end of file
diff --git a/app/src/main/java/sushi/hardcore/aira/MainActivity.kt b/app/src/main/java/sushi/hardcore/aira/MainActivity.kt
index 6e55a17..6cca1c8 100644
--- a/app/src/main/java/sushi/hardcore/aira/MainActivity.kt
+++ b/app/src/main/java/sushi/hardcore/aira/MainActivity.kt
@@ -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
diff --git a/app/src/main/java/sushi/hardcore/aira/adapters/ChatAdapter.kt b/app/src/main/java/sushi/hardcore/aira/adapters/ChatAdapter.kt
index cc6bb1f..9dc555a 100644
--- a/app/src/main/java/sushi/hardcore/aira/adapters/ChatAdapter.kt
+++ b/app/src/main/java/sushi/hardcore/aira/adapters/ChatAdapter.kt
@@ -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() {
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(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(R.id.text_filename).text = filename
itemView.findViewById(R.id.button_save).setOnClickListener {
onSavingFile(filename, chatItem.data.sliceArray(1 until 17))
}
- itemView.findViewById(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)
}
}
diff --git a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt
index 6ac325c..95ea6e1 100644
--- a/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt
+++ b/app/src/main/java/sushi/hardcore/aira/background_service/AIRAService.kt
@@ -135,7 +135,7 @@ class AIRAService : Service() {
}
}
- fun sendFilesFromUris(sessionId: Int, uris: List): List? {
+ fun sendFilesFromUris(sessionId: Int, uris: List) {
val files = mutableListOf()
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()
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) {
diff --git a/app/src/main/native/Cargo.toml b/app/src/main/native/Cargo.toml
index 4e7a1d0..ed0e178 100644
--- a/app/src/main/native/Cargo.toml
+++ b/app/src/main/native/Cargo.toml
@@ -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"
diff --git a/app/src/main/native/src/crypto.rs b/app/src/main/native/src/crypto.rs
index 29f1142..c9ec11c 100644
--- a/app/src/main/native/src/crypto.rs
+++ b/app/src/main/native/src/crypto.rs
@@ -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::::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::::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, 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, CryptoError> {
if data.len() <= IV_LEN || master_key.len() != MASTER_KEY_LEN {
return Err(CryptoError::InvalidLength);
diff --git a/app/src/main/native/src/identity.rs b/app/src/main/native/src/identity.rs
index 3e74e16..7fde962 100644
--- a/app/src/main/native/src/identity.rs
+++ b/app/src/main/native/src/identity.rs
@@ -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 = 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 = 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 = 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 = 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) => {
diff --git a/app/src/main/native/src/utils.rs b/app/src/main/native/src/utils.rs
index 31faddf..c4074fa 100644
--- a/app/src/main/native/src/utils.rs
+++ b/app/src/main/native/src/utils.rs
@@ -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 {
match bytes.try_into() {
Ok(uuid) => Some(uuid),
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
index 44ed997..94cb05f 100644
--- a/app/src/main/res/layout/activity_chat.xml
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -6,13 +6,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ChatActivity">
+
+
+ android:visibility="gone"
+ android:paddingVertical="5dp"
+ android:background="@color/primary">
-
-
+
+
-
-
-
-
-
-
-
-
+ android:layout_height="0dp"
+ app:layout_constraintTop_toBottomOf="@id/toolbar"
+ app:layout_constraintBottom_toBottomOf="parent">
+ android:layout_height="match_parent">
-
+ android:text="@string/online_peers"
+ style="@style/Label"
+ app:layout_constraintTop_toTopOf="parent"/>
-
+
+
+
+
+
+
+ android:paddingHorizontal="10dp"
+ android:paddingVertical="5dp"
+ android:background="@color/primary">
+
+
+
+
+
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/adapter_session.xml b/app/src/main/res/layout/adapter_session.xml
index 9b61fc2..40e6d06 100644
--- a/app/src/main/res/layout/adapter_session.xml
+++ b/app/src/main/res/layout/adapter_session.xml
@@ -4,7 +4,7 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="10dp">
+ android:padding="15dp">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 561a1ad..482336b 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -4,7 +4,7 @@
#19a52c
#FFFFFF
#111111
- #1F1F1F
+ #1A1A1A
@color/sessionBackground
@color/secondary
#3845A3
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index bba878e..7a18964 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,18 +1,11 @@
-
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 7a2fb21..e016c38 100644
--- a/build.gradle
+++ b/build.gradle
@@ -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()
}
}