diff --git a/apps/android/app/src/androidTest/java/chat/simplex/app/ExampleInstrumentedTest.kt b/apps/android/app/src/androidTest/java/chat/simplex/app/ExampleInstrumentedTest.kt
index 72f99dd818..f49be92003 100644
--- a/apps/android/app/src/androidTest/java/chat/simplex/app/ExampleInstrumentedTest.kt
+++ b/apps/android/app/src/androidTest/java/chat/simplex/app/ExampleInstrumentedTest.kt
@@ -1,13 +1,11 @@
package chat.simplex.app
-import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
-
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.Assert.*
-
/**
* Instrumented test, which will execute on an Android device.
*
diff --git a/apps/android/app/src/main/AndroidManifest.xml b/apps/android/app/src/main/AndroidManifest.xml
index 7a0ce24bb8..4df768a83c 100644
--- a/apps/android/app/src/main/AndroidManifest.xml
+++ b/apps/android/app/src/main/AndroidManifest.xml
@@ -23,9 +23,15 @@
android:theme="@style/Theme.SimpleX">
-
+
+
+
+
+
+
+
diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
index 76468bd013..64d2a26e9f 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
@@ -1,46 +1,67 @@
package chat.simplex.app
import android.app.Application
+import android.content.Intent
import android.os.Bundle
+import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
+import androidx.compose.foundation.layout.Box
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.runtime.Composable
-import chat.simplex.app.ui.theme.SimpleXTheme
import androidx.lifecycle.AndroidViewModel
-import androidx.navigation.NavType
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import androidx.navigation.compose.rememberNavController
-import androidx.navigation.navArgument
+import androidx.navigation.*
+import androidx.navigation.compose.*
import chat.simplex.app.model.ChatModel
+import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.*
import chat.simplex.app.views.chat.ChatView
-import chat.simplex.app.views.chatlist.*
-import chat.simplex.app.views.newchat.AddContactView
-import chat.simplex.app.views.newchat.ConnectContactView
+import chat.simplex.app.views.chatlist.ChatListView
+import chat.simplex.app.views.helpers.withApi
+import chat.simplex.app.views.newchat.*
import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import kotlinx.coroutines.DelicateCoroutinesApi
+@DelicateCoroutinesApi
@ExperimentalPermissionsApi
@ExperimentalMaterialApi
class MainActivity: ComponentActivity() {
- private val viewModel by viewModels()
+ private val vm by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ connectIfOpenedViaUri(intent, vm.chatModel)
setContent {
SimpleXTheme {
- Navigation(viewModel.chatModel)
+ Navigation(vm.chatModel)
}
}
}
}
+@DelicateCoroutinesApi
class SimplexViewModel(application: Application) : AndroidViewModel(application) {
val chatModel = getApplication().chatModel
}
+@DelicateCoroutinesApi
+@ExperimentalPermissionsApi
+@ExperimentalMaterialApi
+@Composable
+fun MainPage(chatModel: ChatModel, nav: NavController) {
+ Box {
+ if (chatModel.currentUser.value == null) WelcomeView(chatModel) {
+ nav.navigate(Pages.ChatList.route)
+ } else {
+ ChatListView(chatModel, nav)
+ }
+ val am = chatModel.alertManager
+ if (am.presentAlert.value) am.alertView.value?.invoke()
+ }
+}
+
+@DelicateCoroutinesApi
@ExperimentalPermissionsApi
@ExperimentalMaterialApi
@Composable
@@ -51,7 +72,7 @@ fun Navigation(chatModel: ChatModel) {
composable(route=Pages.Home.route){
MainPage(chatModel, nav)
}
- composable(route = Pages.Welcome.route){
+ composable(route = Pages.Welcome.route) {
WelcomeView(chatModel) {
nav.navigate(Pages.Home.route) {
popUpTo(Pages.Home.route) { inclusive = true }
@@ -71,7 +92,7 @@ fun Navigation(chatModel: ChatModel) {
ConnectContactView(chatModel, nav)
}
composable(route = Pages.Terminal.route) {
- TerminalView(chatModel, navController = nav)
+ TerminalView(chatModel, nav)
}
composable(
Pages.TerminalItemDetails.route + "/{identifier}",
@@ -94,3 +115,28 @@ sealed class Pages(val route: String) {
object AddContact: Pages("add_contact")
object Connect: Pages("connect")
}
+
+@DelicateCoroutinesApi
+fun connectIfOpenedViaUri(intent: Intent?, chatModel: ChatModel) {
+ val uri = intent?.data
+ if (intent?.action == "android.intent.action.VIEW" && uri != null) {
+ Log.d("SIMPLEX", "connectIfOpenedViaUri: opened via link")
+ if (chatModel.currentUser.value == null) {
+ chatModel.appOpenUrl.value = uri
+ } else {
+ withUriAction(chatModel, uri) { action ->
+ chatModel.alertManager.showAlertMsg(
+ title = "Connect via $action link?",
+ text = "Your profile will be sent to the contact that you received this link from.",
+ confirmText = "Connect",
+ onConfirm = {
+ withApi {
+ Log.d("SIMPLEX", "connectIfOpenedViaUri: connecting")
+ connectViaUri(chatModel, action, uri)
+ }
+ }
+ )
+ }
+ }
+ }
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainPage.kt b/apps/android/app/src/main/java/chat/simplex/app/MainPage.kt
deleted file mode 100644
index f42208b2a0..0000000000
--- a/apps/android/app/src/main/java/chat/simplex/app/MainPage.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package chat.simplex.app
-
-import androidx.compose.material.ExperimentalMaterialApi
-import androidx.compose.runtime.Composable
-import androidx.navigation.NavController
-import chat.simplex.app.model.ChatModel
-import chat.simplex.app.views.TerminalView
-import chat.simplex.app.views.WelcomeView
-import chat.simplex.app.views.chatlist.ChatListView
-import com.google.accompanist.permissions.ExperimentalPermissionsApi
-
-@ExperimentalPermissionsApi
-@ExperimentalMaterialApi
-@Composable
-fun MainPage(chatModel: ChatModel, nav: NavController) {
- if (chatModel.currentUser.value == null) WelcomeView(chatModel) {
- nav.navigate(Pages.ChatList.route)
- }
- else ChatListView(chatModel, nav)
-}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
index 502c2fb9fe..2602960e8b 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
@@ -3,16 +3,15 @@ package chat.simplex.app
import android.app.Application
import android.net.LocalServerSocket
import android.util.Log
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.snapshots.SnapshotStateList
-import chat.simplex.app.model.*
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import androidx.compose.material.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import chat.simplex.app.model.ChatController
+import chat.simplex.app.model.ChatModel
+import chat.simplex.app.views.helpers.withApi
+import kotlinx.coroutines.DelicateCoroutinesApi
import java.io.BufferedReader
import java.io.InputStreamReader
-import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.Semaphore
import kotlin.concurrent.thread
@@ -28,18 +27,52 @@ external fun chatInit(path: String): ChatCtrl
external fun chatSendCmd(ctrl: ChatCtrl, msg: String) : String
external fun chatRecvMsg(ctrl: ChatCtrl) : String
+@DelicateCoroutinesApi
class SimplexApp: Application() {
private lateinit var controller: ChatController
lateinit var chatModel: ChatModel
override fun onCreate() {
super.onCreate()
- controller = ChatController(chatInit(applicationContext.filesDir.toString()))
+ val ctrl = chatInit(applicationContext.filesDir.toString())
+ controller = ChatController(ctrl, AlertManager())
chatModel = controller.chatModel
- GlobalScope.launch {
- withContext(Dispatchers.Main) {
- var user = controller.apiGetActiveUser()
- if (user != null) controller.startChat(user)
+ withApi {
+ val user = controller.apiGetActiveUser()
+ if (user != null) controller.startChat(user)
+ }
+ }
+
+ class AlertManager {
+ var alertView = mutableStateOf<(@Composable () -> Unit)?>(null)
+ var presentAlert = mutableStateOf(false)
+
+ fun showAlert(alert: @Composable () -> Unit) {
+ Log.d("SIMPLEX", "AlertManager.showAlert")
+ alertView.value = alert
+ presentAlert.value = true
+ }
+
+ fun hideAlert() {
+ presentAlert.value = false
+ alertView.value = null
+ }
+
+ fun showAlertMsg(title: String, text: String? = null,
+ confirmText: String = "Ok", onConfirm: (() -> Unit)? = null) {
+ val alertText: (@Composable () -> Unit)? = if (text == null) null else { -> Text(text) }
+ showAlert {
+ AlertDialog(
+ onDismissRequest = this::hideAlert,
+ title = { Text(title) },
+ text = alertText,
+ confirmButton = {
+ Button(onClick = {
+ onConfirm?.invoke()
+ hideAlert()
+ }) { Text(confirmText) }
+ }
+ )
}
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
index 8c64d423f0..ff3ed6fd7c 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
@@ -1,12 +1,16 @@
package chat.simplex.app.model
import android.annotation.SuppressLint
-import androidx.compose.runtime.*
+import android.net.Uri
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import chat.simplex.app.SimplexApp
import kotlinx.datetime.*
-import kotlinx.serialization.*
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
import java.time.format.DateTimeFormatter
-class ChatModel(val controller: ChatController) {
+class ChatModel(val controller: ChatController, val alertManager: SimplexApp.AlertManager) {
var currentUser = mutableStateOf(null)
var chats = mutableStateListOf()
var chatId = mutableStateOf(null)
@@ -14,6 +18,8 @@ class ChatModel(val controller: ChatController) {
var connReqInvitation: String? = null
var terminalItems = mutableStateListOf()
+ // set when app is opened via contact or invitation URI
+ var appOpenUrl = mutableStateOf(null)
fun hasChat(id: String): Boolean = chats.firstOrNull() { it.id == id } != null
fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id }
@@ -155,17 +161,6 @@ class ChatModel(val controller: ChatController) {
// chats.removeAll(where: { $0.id == id })
// }
// }
-
- companion object {
- val sampleData: ChatModel get() {
- val m = ChatModel(ChatController.Mock())
- m.terminalItems = mutableStateListOf(
- TerminalItem.Cmd(0, CC.ShowActiveUser()),
- TerminalItem.Resp(1, CR.ActiveUser(User.sampleData))
- )
- return m
- }
- }
}
enum class ChatType(val type: String) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
index 831267325c..94361b56fd 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
@@ -2,21 +2,19 @@ package chat.simplex.app.model
import android.util.Log
import androidx.compose.runtime.mutableStateOf
-import chat.simplex.app.chatRecvMsg
-import chat.simplex.app.chatSendCmd
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import chat.simplex.app.*
+import kotlinx.coroutines.*
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
-import java.lang.Exception
import java.util.*
import kotlin.concurrent.thread
typealias ChatCtrl = Long
-open class ChatController(val ctrl: ChatCtrl) {
- var chatModel = ChatModel(this)
+@DelicateCoroutinesApi
+open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.AlertManager) {
+ var chatModel = ChatModel(this, alertManager)
suspend fun startChat(u: User) {
chatModel.currentUser = mutableStateOf(u)
@@ -110,9 +108,28 @@ open class ChatController(val ctrl: ChatCtrl) {
suspend fun apiConnect(connReq: String): Boolean {
val r = sendCmd(CC.Connect(connReq))
- if (r is CR.SentConfirmation || r is CR.SentInvitation) return true
- Log.d("SIMPLEX", "apiConnect bad response: ${r.responseType} ${r.details}")
- return false
+ when {
+ r is CR.SentConfirmation || r is CR.SentInvitation -> return true
+ r is CR.ContactAlreadyExists -> {
+ alertManager.showAlertMsg("Contact already exists",
+ "You are already connected to ${r.contact.displayName} via this link"
+ )
+ return false
+ }
+ r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorChat
+ && r.chatError.errorType is ChatErrorType.InvalidConnReq -> {
+ alertManager.showAlertMsg("Invalid connection link",
+ "Please check that you used the correct link or ask your contact to send you another one."
+ )
+ return false
+ }
+ else -> {
+ val errMsg = "${r.responseType}: ${r.details}"
+ Log.e("SIMPLEX", "apiConnect bad response: $errMsg")
+ alertManager.showAlertMsg("Connection error", errMsg)
+ return false
+ }
+ }
}
suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean {
@@ -147,7 +164,7 @@ open class ChatController(val ctrl: ChatCtrl) {
suspend fun apiGetUserAddress(): String? {
val r = sendCmd(CC.ShowMyAddress())
if (r is CR.UserContactLink) return r.connReqContact
- if (r is CR.ChatCmdError && r.chatError is ChatError.ErrorStore
+ if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore
&& r.chatError.storeError is StoreError.UserContactLinkNotFound) {
return null
}
@@ -238,8 +255,6 @@ open class ChatController(val ctrl: ChatCtrl) {
// }
}
}
-
- class Mock: ChatController(0) {}
}
// ChatCommand
@@ -348,17 +363,17 @@ val json = Json {
class APIResponse(val resp: CR, val corr: String? = null) {
companion object {
fun decodeStr(str: String): APIResponse {
- try {
- return json.decodeFromString(str)
+ return try {
+ json.decodeFromString(str)
} catch(e: Exception) {
try {
val data = json.parseToJsonElement(str).jsonObject
- return APIResponse(
+ APIResponse(
resp = CR.Response(data["resp"]!!.jsonObject["type"]?.toString() ?: "invalid", json.encodeToString(data)),
corr = data["corr"]?.toString()
)
} catch(e: Exception) {
- return APIResponse(CR.Invalid(str))
+ APIResponse(CR.Invalid(str))
}
}
}
@@ -413,6 +428,12 @@ sealed class CR {
override val details get() = noDetails()
}
+ @Serializable @SerialName("contactAlreadyExists")
+ class ContactAlreadyExists(val contact: Contact): CR() {
+ override val responseType get() = "contactAlreadyExists"
+ override val details get() = contact.toString()
+ }
+
@Serializable @SerialName("contactDeleted")
class ContactDeleted(val contact: Contact): CR() {
override val responseType get() = "contactDeleted"
@@ -589,8 +610,17 @@ abstract class TerminalItem {
@Serializable
sealed class ChatError {
+ @Serializable @SerialName("error")
+ class ChatErrorChat(val errorType: ChatErrorType): ChatError()
+
@Serializable @SerialName("errorStore")
- class ErrorStore(val storeError: StoreError): ChatError()
+ class ChatErrorStore(val storeError: StoreError): ChatError()
+}
+
+@Serializable
+sealed class ChatErrorType {
+ @Serializable @SerialName("invalidConnReq")
+ class InvalidConnReq: ChatErrorType()
}
@Serializable
diff --git a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt
index ef4d3784f1..26669f83a0 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/ui/theme/Theme.kt
@@ -1,15 +1,16 @@
package chat.simplex.app.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.darkColors
-import androidx.compose.material.lightColors
+import androidx.compose.material.*
import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.Color
private val DarkColorPalette = darkColors(
primary = SimplexBlue,
primaryVariant = SimplexGreen,
secondary = DarkGray,
+ background = Color.Black,
+ surface = Color.Black
/*
background: Color = Color(0xFF121212),
surface: Color = Color(0xFF121212),
@@ -26,6 +27,8 @@ private val LightColorPalette = lightColors(
primary = SimplexBlue,
primaryVariant = SimplexGreen,
secondary = LightGray,
+ background = Color.White,
+ surface = Color.White
/* Other default colors to override
surface = Color.White,
onPrimary = Color.White,
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
index d506754675..480339e0b8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
@@ -7,15 +7,13 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.Text
import androidx.compose.material.Button
+import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
-import androidx.navigation.*
+import androidx.navigation.NavController
import chat.simplex.app.model.*
-import chat.simplex.app.model.ChatModel
-import chat.simplex.app.model.TerminalItem
import chat.simplex.app.views.chat.SendMsgView
import chat.simplex.app.views.helpers.withApi
import kotlinx.coroutines.DelicateCoroutinesApi
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
index 3ac36a12b3..2c09ec0cb8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/WelcomeView.kt
@@ -1,18 +1,13 @@
package chat.simplex.app.views
+import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.Image
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.ui.res.painterResource
+import androidx.compose.material.*
import androidx.compose.runtime.*
-import androidx.compose.runtime.Composable
-import chat.simplex.app.R
-import androidx.compose.material.Text
-import androidx.compose.material.TextField
-import androidx.compose.material.Button
-import androidx.compose.ui.unit.dp
import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import chat.simplex.app.R
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import chat.simplex.app.views.helpers.withApi
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt
index 35a537bd31..9b7ab104f8 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt
@@ -7,11 +7,10 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.*
+import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.material.Icon
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt
index 73fa80ec01..aa2551efa6 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/CIMetaView.kt
@@ -7,7 +7,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import chat.simplex.app.model.CIDirection
import chat.simplex.app.model.ChatItem
-import kotlinx.datetime.*
+import kotlinx.datetime.Clock
@Composable
fun CIMetaView(chatItem: ChatItem) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
index 9ad9f16ad4..60ec4ee67e 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
@@ -1,14 +1,13 @@
package chat.simplex.app.views.chat.item
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import chat.simplex.app.model.*
+import chat.simplex.app.model.CIDirection
+import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
import kotlinx.datetime.Clock
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt
index 6974c34b14..fdb2d5d80e 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt
@@ -9,9 +9,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import chat.simplex.app.model.*
+import chat.simplex.app.model.CIDirection
+import chat.simplex.app.model.ChatItem
import chat.simplex.app.ui.theme.SimpleXTheme
-import kotlinx.datetime.*
+import kotlinx.datetime.Clock
// TODO move to theme
val SentColorLight = Color(0x1E45B8FF)
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
index cd6466c468..10d36a6663 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
@@ -1,32 +1,30 @@
package chat.simplex.app.views.chatlist
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.PersonAdd
+import androidx.compose.material.icons.outlined.Settings
import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.simplex.app.Pages
-import chat.simplex.app.model.*
-import kotlinx.coroutines.launch
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.font.FontWeight
+import chat.simplex.app.model.Chat
+import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.helpers.withApi
import chat.simplex.app.views.newchat.NewChatSheet
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import kotlinx.coroutines.*
-
@ExperimentalMaterialApi
class ScaffoldController(val state: BottomSheetScaffoldState, val scope: CoroutineScope) {
fun expand() = scope.launch { state.bottomSheetState.expand() }
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt
index 5b870cba81..08fbb02511 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt
@@ -1,33 +1,28 @@
package chat.simplex.app.views.chatlist
import android.icu.text.SimpleDateFormat
-import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
+import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import chat.simplex.app.model.*
-import androidx.compose.material.Icon
+import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
-import androidx.compose.ui.text.font.*
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import chat.simplex.app.model.Chat
+import chat.simplex.app.model.ChatInfo
import chat.simplex.app.ui.theme.HighOrLowlight
import chat.simplex.app.ui.theme.SimpleXTheme
import kotlinx.datetime.Instant
import kotlinx.datetime.toJavaInstant
import java.time.LocalDateTime
-import java.time.Instant as JavaInstant
import java.util.*
+import java.time.Instant as JavaInstant
fun getDisplayTime(t: Instant) : String {
val timeFormatter = SimpleDateFormat("HH:mm", Locale.getDefault())
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt
index c323f4317b..90199cd953 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/CloseSheetBar.kt
@@ -4,17 +4,14 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Surface
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
-import androidx.compose.material.icons.outlined.Settings
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.SimpleXTheme
-import chat.simplex.app.views.newchat.AddContactLayout
@Composable
fun CloseSheetBar(close: () -> Unit) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt
index 056dfe626e..bd2f037c47 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/SimpleButton.kt
@@ -2,13 +2,9 @@ package chat.simplex.app.ui.theme
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.material.Icon
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
+import androidx.compose.material.*
import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Share
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -16,7 +12,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import chat.simplex.app.views.helpers.CloseSheetBar
@Composable
fun SimpleButton(text: String, icon: ImageVector, click: () -> Unit) {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt
index be219e4c06..2a7274a1be 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/ConnectContactView.kt
@@ -1,5 +1,6 @@
package chat.simplex.app.views.newchat
+import android.net.Uri
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
@@ -24,17 +25,51 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) {
ConnectContactLayout(
qrCodeScanner = {
QRCodeScanner { connReqUri ->
- withApi {
- val res = chatModel.controller.apiConnect(connReqUri)
- // check if it is valid
- nav.popBackStack()
+ try {
+ val uri = Uri.parse(connReqUri)
+ withUriAction(chatModel, uri) { action ->
+ connectViaUri(chatModel, action, uri)
+ }
+ } catch(e: RuntimeException) {
+ chatModel.alertManager.showAlertMsg(
+ title = "Invalid QR code",
+ text = "This QR code is not a link!"
+ )
}
+ nav.popBackStack()
}
},
close = { nav.popBackStack() }
)
}
+@DelicateCoroutinesApi
+fun withUriAction(chatModel: ChatModel, uri: Uri,
+ run: suspend (String) -> Unit) {
+ val action = uri.path?.drop(1)
+ if (action == "contact" || action == "invitation") {
+ withApi { run(action) }
+ } else {
+ chatModel.alertManager.showAlertMsg(
+ title = "Invalid link!",
+ text = "This link is not a valid connection link!"
+ )
+ }
+}
+
+suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) {
+ val r = chatModel.controller.apiConnect(uri.toString())
+ if (r) {
+ val whenConnected =
+ if (action == "contact") "your connection request is accepted"
+ else "your contact's device is online"
+ chatModel.alertManager.showAlertMsg(
+ title = "Connection request sent!",
+ text = "You will be connected when $whenConnected, please wait or check later!"
+ )
+ }
+}
+
@Composable
fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Unit) {
Column(
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt
index 4a2f47f6f5..b4f8094224 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt
@@ -1,19 +1,14 @@
package chat.simplex.app.views.newchat
import android.Manifest
-import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
@@ -21,8 +16,9 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import chat.simplex.app.Pages
-import chat.simplex.app.model.*
-import chat.simplex.app.ui.theme.*
+import chat.simplex.app.model.ChatModel
+import chat.simplex.app.ui.theme.DarkGray
+import chat.simplex.app.ui.theme.SimpleXTheme
import chat.simplex.app.views.chatlist.ScaffoldController
import chat.simplex.app.views.helpers.withApi
import com.google.accompanist.permissions.ExperimentalPermissionsApi
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt
index c3d1cd0814..6dee26d107 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCode.kt
@@ -3,7 +3,7 @@ package chat.simplex.app.views.newchat
import android.graphics.Bitmap
import android.graphics.Color
import androidx.compose.foundation.Image
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.tooling.preview.Preview
import chat.simplex.app.ui.theme.SimpleXTheme
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt
index 873d4e6ae6..8f2b940258 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/QRCodeScanner.kt
@@ -11,13 +11,11 @@ import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
import com.google.mlkit.vision.barcode.BarcodeScannerOptions
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.*
// Bar code scanner adapted from https://github.com/MakeItEasyDev/Jetpack-Compose-BarCode-Scanner
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
index 35c54c5f69..527494be95 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
@@ -1,12 +1,9 @@
package chat.simplex.app.views.usersettings
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.simplex.app.model.Profile
diff --git a/apps/android/app/src/test/java/chat/simplex/app/ExampleUnitTest.kt b/apps/android/app/src/test/java/chat/simplex/app/ExampleUnitTest.kt
index eb08839d27..c168b93f49 100644
--- a/apps/android/app/src/test/java/chat/simplex/app/ExampleUnitTest.kt
+++ b/apps/android/app/src/test/java/chat/simplex/app/ExampleUnitTest.kt
@@ -1,9 +1,8 @@
package chat.simplex.app
+import org.junit.Assert.assertEquals
import org.junit.Test
-import org.junit.Assert.*
-
/**
* Example local unit test, which will execute on the development machine (host).
*