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:
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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).
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user