From 2f01c863597e551af46efc0f8705179ff7c935ff Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Fri, 21 May 2021 14:55:37 +0200 Subject: [PATCH] Profile toolbar & 4 ChatItem types & Other UI improvements --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 4 +- .../java/sushi/hardcore/aira/ChatActivity.kt | 24 ++-- .../main/java/sushi/hardcore/aira/ChatItem.kt | 12 +- .../java/sushi/hardcore/aira/MainActivity.kt | 6 +- .../hardcore/aira/adapters/ChatAdapter.kt | 128 ++++++++++------- .../aira/background_service/AIRAService.kt | 18 +-- app/src/main/native/Cargo.toml | 6 +- app/src/main/native/src/crypto.rs | 49 +++++-- app/src/main/native/src/identity.rs | 81 +++++------ app/src/main/native/src/utils.rs | 4 - app/src/main/res/layout/activity_chat.xml | 10 +- app/src/main/res/layout/activity_main.xml | 134 ++++++++++-------- app/src/main/res/layout/adapter_session.xml | 2 +- app/src/main/res/layout/profile_toolbar.xml | 32 +++++ app/src/main/res/values/colors.xml | 2 +- app/src/main/res/values/themes.xml | 7 - build.gradle | 8 +- 18 files changed, 308 insertions(+), 225 deletions(-) create mode 100644 app/src/main/res/layout/profile_toolbar.xml 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() } }