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). *