android: use deep links to connect (#339)

* simple case

* version almost working with true links

* show alerts in imperative way, like they were meant to

* connecting via links works

* add error handling to connections

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
IanRDavies
2022-02-20 15:47:24 +00:00
committed by GitHub
parent d48d4ed8f9
commit 7fc5b833aa
24 changed files with 258 additions and 165 deletions

View File

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

View File

@@ -23,9 +23,15 @@
android:theme="@style/Theme.SimpleX">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="simplex" />
</intent-filter>
</activity>
</application>

View File

@@ -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<SimplexViewModel>()
private val vm by viewModels<SimplexViewModel>()
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<SimplexApp>().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)
}
}
)
}
}
}
}

View File

@@ -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)
}

View File

@@ -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<Boolean>(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) }
}
)
}
}
}

View File

@@ -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<User?>(null)
var chats = mutableStateListOf<Chat>()
var chatId = mutableStateOf<String?>(null)
@@ -14,6 +18,8 @@ class ChatModel(val controller: ChatController) {
var connReqInvitation: String? = null
var terminalItems = mutableStateListOf<TerminalItem>()
// set when app is opened via contact or invitation URI
var appOpenUrl = mutableStateOf<Uri?>(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) {

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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

View File

@@ -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)

View File

@@ -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() }

View File

@@ -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())

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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