mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 20:08:57 +00:00
android: add chat info page, delete contacts, show network connection status for contacts, improve error handling
This commit is contained in:
@@ -16,6 +16,7 @@ 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.ChatInfoView
|
||||
import chat.simplex.app.views.chat.ChatView
|
||||
import chat.simplex.app.views.chatlist.ChatListView
|
||||
import chat.simplex.app.views.helpers.withApi
|
||||
@@ -50,14 +51,10 @@ class SimplexViewModel(application: Application) : AndroidViewModel(application)
|
||||
@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()
|
||||
if (chatModel.currentUser.value == null) WelcomeView(chatModel) {
|
||||
nav.navigate(Pages.ChatList.route)
|
||||
} else {
|
||||
ChatListView(chatModel, nav)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,40 +65,47 @@ fun MainPage(chatModel: ChatModel, nav: NavController) {
|
||||
fun Navigation(chatModel: ChatModel) {
|
||||
val nav = rememberNavController()
|
||||
|
||||
NavHost(navController = nav, startDestination=Pages.Home.route){
|
||||
composable(route=Pages.Home.route){
|
||||
MainPage(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Welcome.route) {
|
||||
WelcomeView(chatModel) {
|
||||
nav.navigate(Pages.Home.route) {
|
||||
popUpTo(Pages.Home.route) { inclusive = true }
|
||||
Box {
|
||||
NavHost(navController = nav, startDestination = Pages.Home.route) {
|
||||
composable(route = Pages.Home.route) {
|
||||
MainPage(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Welcome.route) {
|
||||
WelcomeView(chatModel) {
|
||||
nav.navigate(Pages.Home.route) {
|
||||
popUpTo(Pages.Home.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
composable(route = Pages.ChatList.route) {
|
||||
ChatListView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Chat.route) {
|
||||
ChatView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.AddContact.route) {
|
||||
AddContactView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Connect.route) {
|
||||
ConnectContactView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.ChatInfo.route) {
|
||||
ChatInfoView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Terminal.route) {
|
||||
TerminalView(chatModel, nav)
|
||||
}
|
||||
composable(
|
||||
Pages.TerminalItemDetails.route + "/{identifier}",
|
||||
arguments = listOf(
|
||||
navArgument("identifier") {
|
||||
type = NavType.LongType
|
||||
}
|
||||
)
|
||||
) { entry -> DetailView(entry.arguments!!.getLong("identifier"), chatModel.terminalItems, nav) }
|
||||
}
|
||||
composable(route = Pages.ChatList.route) {
|
||||
ChatListView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Chat.route) {
|
||||
ChatView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.AddContact.route) {
|
||||
AddContactView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Connect.route) {
|
||||
ConnectContactView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Terminal.route) {
|
||||
TerminalView(chatModel, nav)
|
||||
}
|
||||
composable(
|
||||
Pages.TerminalItemDetails.route + "/{identifier}",
|
||||
arguments = listOf(
|
||||
navArgument("identifier"){
|
||||
type = NavType.LongType
|
||||
}
|
||||
)
|
||||
) { entry -> DetailView( entry.arguments!!.getLong("identifier"), chatModel.terminalItems, nav) }
|
||||
val am = chatModel.alertManager
|
||||
if (am.presentAlert.value) am.alertView.value?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +118,7 @@ sealed class Pages(val route: String) {
|
||||
object Chat: Pages("chat")
|
||||
object AddContact: Pages("add_contact")
|
||||
object Connect: Pages("connect")
|
||||
object ChatInfo: Pages("chat_info")
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
|
||||
@@ -5,9 +5,12 @@ import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import chat.simplex.app.SimplexApp
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.datetime.TimeZone
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.Boolean
|
||||
import kotlin.Int
|
||||
import kotlin.Long
|
||||
import kotlin.String
|
||||
|
||||
class ChatModel(val controller: ChatController, val alertManager: SimplexApp.AlertManager) {
|
||||
var currentUser = mutableStateOf<User?>(null)
|
||||
@@ -39,12 +42,14 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
}
|
||||
}
|
||||
|
||||
// func updateNetworkStatus(_ contact: Contact, _ status: Chat.NetworkStatus) {
|
||||
// if let ix = getChatIndex(contact.id) {
|
||||
// chats[ix].serverInfo.networkStatus = status
|
||||
// }
|
||||
// }
|
||||
//
|
||||
fun updateNetworkStatus(contact: Contact, status: Chat.NetworkStatus) {
|
||||
val i = getChatIndex(contact.id)
|
||||
if (i >= 0) {
|
||||
val chat = chats[i]
|
||||
chats[i] = chat.copy(serverInfo = chat.serverInfo.copy(networkStatus = status))
|
||||
}
|
||||
}
|
||||
|
||||
// func replaceChat(_ id: String, _ chat: Chat) {
|
||||
// if let i = getChatIndex(id) {
|
||||
// chats[i] = chat
|
||||
@@ -59,7 +64,7 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
val i = getChatIndex(cInfo.id)
|
||||
if (i >= 0) {
|
||||
val chat = chats[i]
|
||||
val updatedChat = chat.copy(
|
||||
chats[i] = chat.copy(
|
||||
chatItems = arrayListOf(cItem),
|
||||
chatStats =
|
||||
if (cItem.meta.itemStatus is CIStatus.RcvNew)
|
||||
@@ -67,7 +72,6 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
else
|
||||
chat.chatStats
|
||||
)
|
||||
chats[i] = updatedChat
|
||||
if (i > 0) {
|
||||
popChat_(i)
|
||||
}
|
||||
@@ -154,18 +158,23 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
val chat = chats.removeAt(i)
|
||||
chats.add(index = 0, chat)
|
||||
}
|
||||
//
|
||||
// func removeChat(_ id: String) {
|
||||
// withAnimation {
|
||||
// chats.removeAll(where: { $0.id == id })
|
||||
// }
|
||||
// }
|
||||
|
||||
fun removeChat(id: String) {
|
||||
chats.removeAll { it.id == id }
|
||||
}
|
||||
}
|
||||
|
||||
enum class ChatType(val type: String) {
|
||||
Direct("@"),
|
||||
Group("#"),
|
||||
ContactRequest("<@")
|
||||
ContactRequest("<@");
|
||||
|
||||
val chatTypeName: String get () =
|
||||
when (this) {
|
||||
Direct -> "contact"
|
||||
Group -> "group"
|
||||
ContactRequest -> "contact request"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -221,20 +230,22 @@ data class Chat (
|
||||
data class ChatStats(val unreadCount: Int = 0, val minUnreadItemId: Long = 0)
|
||||
|
||||
@Serializable
|
||||
class ServerInfo(val networkStatus: NetworkStatus)
|
||||
data class ServerInfo(val networkStatus: NetworkStatus)
|
||||
|
||||
@Serializable
|
||||
sealed class NetworkStatus {
|
||||
abstract val statusString: String
|
||||
abstract val statusExplanation: String
|
||||
abstract val imageName: String
|
||||
val statusString: String get() = if (this is Connected) "Server connected" else "Connecting server…"
|
||||
val statusExplanation: String get() =
|
||||
when {
|
||||
this is Connected -> "You are connected to the server you use to receve messages from this contact."
|
||||
this is Error -> "Trying to connect to the server you use to receve messages from this contact (error: $error)."
|
||||
else -> "Trying to connect to the server you use to receve messages from this contact."
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class Unknown: NetworkStatus() {
|
||||
override val statusString get() = "Server connected"
|
||||
override val statusExplanation get() = "You are connected to the server you use to receve messages from this contact."
|
||||
override val imageName get() = "circle.dotted" // ?
|
||||
}
|
||||
@Serializable @SerialName("unknown") class Unknown: NetworkStatus()
|
||||
@Serializable @SerialName("connected") class Connected: NetworkStatus()
|
||||
@Serializable @SerialName("disconnected") class Disconnected: NetworkStatus()
|
||||
@Serializable @SerialName("error") class Error(val error: String): NetworkStatus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +260,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val ready get() = contact.ready
|
||||
override val createdAt get() = contact.createdAt
|
||||
override val displayName get() = contact.displayName
|
||||
override val fullName get() = contact.displayName
|
||||
override val fullName get() = contact.fullName
|
||||
|
||||
companion object {
|
||||
val sampleData = Direct(Contact.sampleData)
|
||||
@@ -265,7 +276,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val ready get() = groupInfo.ready
|
||||
override val createdAt get() = groupInfo.createdAt
|
||||
override val displayName get() = groupInfo.displayName
|
||||
override val fullName get() = groupInfo.displayName
|
||||
override val fullName get() = groupInfo.fullName
|
||||
|
||||
companion object {
|
||||
val sampleData = Group(GroupInfo.sampleData)
|
||||
@@ -281,7 +292,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val ready get() = contactRequest.ready
|
||||
override val createdAt get() = contactRequest.createdAt
|
||||
override val displayName get() = contactRequest.displayName
|
||||
override val fullName get() = contactRequest.displayName
|
||||
override val fullName get() = contactRequest.fullName
|
||||
|
||||
companion object {
|
||||
val sampleData = ContactRequest(UserContactRequest.sampleData)
|
||||
@@ -565,8 +576,3 @@ sealed class MsgContent {
|
||||
class RcvFileTransfer {
|
||||
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class AgentErrorType {
|
||||
|
||||
}
|
||||
|
||||
@@ -124,9 +124,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
return false
|
||||
}
|
||||
else -> {
|
||||
val errMsg = "${r.responseType}: ${r.details}"
|
||||
Log.e("SIMPLEX", "apiConnect bad response: $errMsg")
|
||||
alertManager.showAlertMsg("Connection error", errMsg)
|
||||
apiErrorAlert("apiConnect", "Connection error", r)
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -134,8 +132,20 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
|
||||
suspend fun apiDeleteChat(type: ChatType, id: Long): Boolean {
|
||||
val r = sendCmd(CC.ApiDeleteChat(type, id))
|
||||
if (r is CR.ContactDeleted) return true // TODO groups
|
||||
Log.d("SIMPLEX", "apiDeleteChat bad response: ${r.responseType} ${r.details}")
|
||||
when {
|
||||
r is CR.ContactDeleted -> return true // TODO groups
|
||||
r is CR.ChatCmdError -> {
|
||||
val e = r.chatError
|
||||
if (e is ChatError.ChatErrorChat && e.errorType is ChatErrorType.ContactGroups) {
|
||||
alertManager.showAlertMsg(
|
||||
"Can't delete contact!",
|
||||
"Contact ${e.errorType.contact.displayName} cannot be deleted, it is a member of the group(s) ${e.errorType.groupNames}"
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
apiErrorAlert("apiDeleteChat", "Error deleting ${type.chatTypeName}", r)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -193,22 +203,53 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
return false
|
||||
}
|
||||
|
||||
fun apiErrorAlert(method: String, title: String, r: CR) {
|
||||
val errMsg = "${r.responseType}: ${r.details}"
|
||||
Log.e("SIMPLEX", "$method bad response: $errMsg")
|
||||
alertManager.showAlertMsg(title, errMsg)
|
||||
}
|
||||
|
||||
fun processReceivedMsg(r: CR) {
|
||||
chatModel.terminalItems.add(TerminalItem.resp(r))
|
||||
when {
|
||||
r is CR.ContactConnected -> chatModel.updateContact(r.contact)
|
||||
// r is CR.UpdateNetworkStatus -> return
|
||||
// r is CR.ReceivedContactRequest -> return
|
||||
// r is CR.ContactUpdated -> return
|
||||
// r is CR.ContactSubscribed -> return
|
||||
// r is CR.ContactSubError -> return
|
||||
// r is CR.UpdateContact -> return
|
||||
r is CR.ContactUpdated -> {
|
||||
val cInfo = ChatInfo.Direct(r.toContact)
|
||||
if (chatModel.hasChat(r.toContact.id)) {
|
||||
chatModel.updateChatInfo(cInfo)
|
||||
}
|
||||
}
|
||||
|
||||
r is CR.ContactSubscribed -> {
|
||||
chatModel.updateContact(r.contact)
|
||||
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected())
|
||||
}
|
||||
r is CR.ContactDisconnected -> {
|
||||
chatModel.updateContact(r.contact)
|
||||
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Disconnected())
|
||||
}
|
||||
r is CR.ContactSubError -> {
|
||||
chatModel.updateContact(r.contact)
|
||||
val e = r.chatError
|
||||
val err: String =
|
||||
if (e is ChatError.ChatErrorAgent) {
|
||||
val a = e.agentError
|
||||
when {
|
||||
a is AgentErrorType.BROKER && a.brokerErr is BrokerErrorType.NETWORK -> "network"
|
||||
a is AgentErrorType.SMP && a.smpErr is SMPErrorType.AUTH -> "contact deleted"
|
||||
else -> e.string
|
||||
}
|
||||
}
|
||||
else e.string
|
||||
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Error(err))
|
||||
}
|
||||
r is CR.NewChatItem -> {
|
||||
val cInfo = r.chatItem.chatInfo
|
||||
val cItem = r.chatItem.chatItem
|
||||
chatModel.addChatItem(cInfo, cItem)
|
||||
}
|
||||
// NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
}
|
||||
|
||||
// switch res {
|
||||
// chatModel.updateNetworkStatus(contact, .connected)
|
||||
@@ -219,31 +260,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
// chatItems: []
|
||||
// ))
|
||||
// NtfManager.shared.notifyContactRequest(contactRequest)
|
||||
// case let .contactUpdated(toContact):
|
||||
// let cInfo = ChatInfo.direct(contact: toContact)
|
||||
// if chatModel.hasChat(toContact.id) {
|
||||
// chatModel.updateChatInfo(cInfo)
|
||||
// }
|
||||
// case let .contactSubscribed(contact):
|
||||
// chatModel.updateContact(contact)
|
||||
// chatModel.updateNetworkStatus(contact, .connected)
|
||||
// case let .contactDisconnected(contact):
|
||||
// chatModel.updateContact(contact)
|
||||
// chatModel.updateNetworkStatus(contact, .disconnected)
|
||||
// case let .contactSubError(contact, chatError):
|
||||
// chatModel.updateContact(contact)
|
||||
//// var err: String
|
||||
//// switch chatError {
|
||||
//// case .errorAgent(agentError: .BROKER(brokerErr: .NETWORK)): err = "network"
|
||||
//// case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
||||
//// default: err = String(describing: chatError)
|
||||
//// }
|
||||
//// chatModel.updateNetworkStatus(contact, .error(err))
|
||||
// case let .newChatItem(aChatItem):
|
||||
// let cInfo = aChatItem.chatInfo
|
||||
// let cItem = aChatItem.chatItem
|
||||
// chatModel.addChatItem(cInfo, cItem)
|
||||
// NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
//
|
||||
// case let .chatItemUpdated(aChatItem):
|
||||
// let cInfo = aChatItem.chatInfo
|
||||
// let cItem = aChatItem.chatItem
|
||||
@@ -557,13 +574,13 @@ sealed class CR {
|
||||
@Serializable @SerialName("chatCmdError")
|
||||
class ChatCmdError(val chatError: ChatError): CR() {
|
||||
override val responseType get() = "chatCmdError"
|
||||
override val details get() = chatError.toString()
|
||||
override val details get() = chatError.string
|
||||
}
|
||||
|
||||
@Serializable @SerialName("chatError")
|
||||
class ChatRespError(val chatError: ChatError): CR() {
|
||||
override val responseType get() = "chatError"
|
||||
override val details get() = chatError.toString()
|
||||
override val details get() = chatError.string
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -610,21 +627,186 @@ abstract class TerminalItem {
|
||||
|
||||
@Serializable
|
||||
sealed class ChatError {
|
||||
@Serializable @SerialName("error")
|
||||
class ChatErrorChat(val errorType: ChatErrorType): ChatError()
|
||||
|
||||
@Serializable @SerialName("errorStore")
|
||||
class ChatErrorStore(val storeError: StoreError): ChatError()
|
||||
val string: String get() = when {
|
||||
this is ChatErrorChat -> "chat ${errorType.string}"
|
||||
this is ChatErrorAgent -> "agent ${agentError.string}"
|
||||
this is ChatErrorStore -> "store ${storeError.string}"
|
||||
else -> "ChatError"
|
||||
}
|
||||
@Serializable @SerialName("error") class ChatErrorChat(val errorType: ChatErrorType): ChatError()
|
||||
@Serializable @SerialName("errorAgent") class ChatErrorAgent(val agentError: AgentErrorType): ChatError()
|
||||
@Serializable @SerialName("errorStore") class ChatErrorStore(val storeError: StoreError): ChatError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ChatErrorType {
|
||||
@Serializable @SerialName("invalidConnReq")
|
||||
class InvalidConnReq: ChatErrorType()
|
||||
val string: String get() = when {
|
||||
this is InvalidConnReq -> "invalidConnReq"
|
||||
this is ContactGroups -> "groupNames $groupNames"
|
||||
else -> "ChatErrorType"
|
||||
}
|
||||
@Serializable @SerialName("invalidConnReq") class InvalidConnReq: ChatErrorType()
|
||||
@Serializable @SerialName("contactGroups") class ContactGroups(val contact: Contact, val groupNames: List<String>): ChatErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class StoreError {
|
||||
@Serializable @SerialName("userContactLinkNotFound")
|
||||
class UserContactLinkNotFound: StoreError()
|
||||
}
|
||||
val string: String get() = when {
|
||||
this is UserContactLinkNotFound -> "userContactLinkNotFound"
|
||||
else -> "StoreError"
|
||||
}
|
||||
@Serializable @SerialName("userContactLinkNotFound") class UserContactLinkNotFound: StoreError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class AgentErrorType {
|
||||
val string: String get() = when {
|
||||
this is CMD -> "CMD ${cmdErr.string}"
|
||||
this is CONN -> "CONN ${connErr.string}"
|
||||
this is SMP -> "SMP ${smpErr.string}"
|
||||
this is BROKER -> "BROKER ${brokerErr.string}"
|
||||
this is AGENT -> "AGENT ${agentErr.string}"
|
||||
this is INTERNAL -> "INTERNAL $internalErr"
|
||||
else -> "AgentErrorType"
|
||||
}
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("SMP") class SMP(val smpErr: SMPErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("BROKER") class BROKER(val brokerErr: BrokerErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
||||
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class CommandErrorType {
|
||||
val string: String get() = when {
|
||||
this is PROHIBITED -> "PROHIBITED"
|
||||
this is SYNTAX -> "SYNTAX"
|
||||
this is NO_CONN -> "NO_CONN"
|
||||
this is SIZE -> "SIZE"
|
||||
this is LARGE -> "LARGE"
|
||||
else -> "CommandErrorType"
|
||||
}
|
||||
@Serializable @SerialName("PROHIBITED") class PROHIBITED: CommandErrorType()
|
||||
@Serializable @SerialName("SYNTAX") class SYNTAX: CommandErrorType()
|
||||
@Serializable @SerialName("NO_CONN") class NO_CONN: CommandErrorType()
|
||||
@Serializable @SerialName("SIZE") class SIZE: CommandErrorType()
|
||||
@Serializable @SerialName("LARGE") class LARGE: CommandErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class ConnectionErrorType {
|
||||
val string: String get() = when {
|
||||
this is NOT_FOUND -> "NOT_FOUND"
|
||||
this is DUPLICATE -> "DUPLICATE"
|
||||
this is SIMPLEX -> "SIMPLEX"
|
||||
this is NOT_ACCEPTED -> "NOT_ACCEPTED"
|
||||
this is NOT_AVAILABLE -> "NOT_AVAILABLE"
|
||||
else -> "ConnectionErrorType"
|
||||
}
|
||||
@Serializable @SerialName("NOT_FOUND") class NOT_FOUND: ConnectionErrorType()
|
||||
@Serializable @SerialName("DUPLICATE") class DUPLICATE: ConnectionErrorType()
|
||||
@Serializable @SerialName("SIMPLEX") class SIMPLEX: ConnectionErrorType()
|
||||
@Serializable @SerialName("NOT_ACCEPTED") class NOT_ACCEPTED: ConnectionErrorType()
|
||||
@Serializable @SerialName("NOT_AVAILABLE") class NOT_AVAILABLE: ConnectionErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class BrokerErrorType {
|
||||
val string: String get() = when {
|
||||
this is RESPONSE -> "RESPONSE ${smpErr.string}"
|
||||
this is UNEXPECTED -> "UNEXPECTED"
|
||||
this is NETWORK -> "NETWORK"
|
||||
this is TRANSPORT -> "TRANSPORT ${transportErr.string}"
|
||||
this is TIMEOUT -> "TIMEOUT"
|
||||
else -> "BrokerErrorType"
|
||||
}
|
||||
@Serializable @SerialName("RESPONSE") class RESPONSE(val smpErr: SMPErrorType): BrokerErrorType()
|
||||
@Serializable @SerialName("UNEXPECTED") class UNEXPECTED: BrokerErrorType()
|
||||
@Serializable @SerialName("NETWORK") class NETWORK: BrokerErrorType()
|
||||
@Serializable @SerialName("TRANSPORT") class TRANSPORT(val transportErr: SMPTransportError): BrokerErrorType()
|
||||
@Serializable @SerialName("TIMEOUT") class TIMEOUT: BrokerErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPErrorType {
|
||||
val string: String get() = when {
|
||||
this is BLOCK -> "BLOCK"
|
||||
this is SESSION -> "SESSION"
|
||||
this is CMD -> "CMD ${cmdErr.string}"
|
||||
this is AUTH -> "AUTH"
|
||||
this is QUOTA -> "QUOTA"
|
||||
this is NO_MSG -> "NO_MSG"
|
||||
this is LARGE_MSG -> "LARGE_MSG"
|
||||
this is INTERNAL -> "INTERNAL"
|
||||
else -> "SMPErrorType"
|
||||
}
|
||||
@Serializable @SerialName("BLOCK") class BLOCK: SMPErrorType()
|
||||
@Serializable @SerialName("SESSION") class SESSION: SMPErrorType()
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: SMPCommandError): SMPErrorType()
|
||||
@Serializable @SerialName("AUTH") class AUTH: SMPErrorType()
|
||||
@Serializable @SerialName("QUOTA") class QUOTA: SMPErrorType()
|
||||
@Serializable @SerialName("NO_MSG") class NO_MSG: SMPErrorType()
|
||||
@Serializable @SerialName("LARGE_MSG") class LARGE_MSG: SMPErrorType()
|
||||
@Serializable @SerialName("INTERNAL") class INTERNAL: SMPErrorType()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPCommandError {
|
||||
val string: String get() = when {
|
||||
this is UNKNOWN -> "UNKNOWN"
|
||||
this is SYNTAX -> "SYNTAX"
|
||||
this is NO_AUTH -> "NO_AUTH"
|
||||
this is HAS_AUTH -> "HAS_AUTH"
|
||||
this is NO_QUEUE -> "NO_QUEUE"
|
||||
else -> "SMPCommandError"
|
||||
}
|
||||
@Serializable @SerialName("UNKNOWN") class UNKNOWN: SMPCommandError()
|
||||
@Serializable @SerialName("SYNTAX") class SYNTAX: SMPCommandError()
|
||||
@Serializable @SerialName("NO_AUTH") class NO_AUTH: SMPCommandError()
|
||||
@Serializable @SerialName("HAS_AUTH") class HAS_AUTH: SMPCommandError()
|
||||
@Serializable @SerialName("NO_QUEUE") class NO_QUEUE: SMPCommandError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPTransportError {
|
||||
val string: String get() = when {
|
||||
this is BadBlock -> "badBlock"
|
||||
this is LargeMsg -> "largeMsg"
|
||||
this is BadSession -> "badSession"
|
||||
this is Handshake -> "handshake ${handshakeErr.string}"
|
||||
else -> "SMPTransportError"
|
||||
}
|
||||
@Serializable @SerialName("badBlock") class BadBlock: SMPTransportError()
|
||||
@Serializable @SerialName("largeMsg") class LargeMsg: SMPTransportError()
|
||||
@Serializable @SerialName("badSession") class BadSession: SMPTransportError()
|
||||
@Serializable @SerialName("handshake") class Handshake(val handshakeErr: SMPHandshakeError): SMPTransportError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPHandshakeError {
|
||||
val string: String get() = when {
|
||||
this is PARSE -> "PARSE"
|
||||
this is VERSION -> "VERSION"
|
||||
this is IDENTITY -> "IDENTITY"
|
||||
else -> "SMPHandshakeError"
|
||||
}
|
||||
@Serializable @SerialName("PARSE") class PARSE: SMPHandshakeError()
|
||||
@Serializable @SerialName("VERSION") class VERSION: SMPHandshakeError()
|
||||
@Serializable @SerialName("IDENTITY") class IDENTITY: SMPHandshakeError()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class SMPAgentError {
|
||||
val string: String get() = when {
|
||||
this is A_MESSAGE -> "A_MESSAGE"
|
||||
this is A_PROHIBITED -> "A_PROHIBITED"
|
||||
this is A_VERSION -> "A_VERSION"
|
||||
this is A_ENCRYPTION -> "A_ENCRYPTION"
|
||||
else -> "SMPAgentError"
|
||||
}
|
||||
@Serializable @SerialName("A_MESSAGE") class A_MESSAGE: SMPAgentError()
|
||||
@Serializable @SerialName("A_PROHIBITED") class A_PROHIBITED: SMPAgentError()
|
||||
@Serializable @SerialName("A_VERSION") class A_VERSION: SMPAgentError()
|
||||
@Serializable @SerialName("A_ENCRYPTION") class A_ENCRYPTION: SMPAgentError()
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ fun DetailView(identifier: Long, terminalItems: List<TerminalItem>, navControlle
|
||||
Text("Back")
|
||||
}
|
||||
SelectionContainer {
|
||||
Text((terminalItems.filter { it.id == identifier }).first().details)
|
||||
Text((terminalItems.firstOrNull { it.id == identifier })?.details ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
package chat.simplex.app.views.chat
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.Circle
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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.views.helpers.*
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
@Composable
|
||||
fun ChatInfoView(chatModel: ChatModel, nav: NavController) {
|
||||
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
|
||||
if (chat != null) {
|
||||
ChatInfoLayout(chat,
|
||||
close = { nav.popBackStack() },
|
||||
deleteContact = {
|
||||
chatModel.alertManager.showAlertMsg(
|
||||
title = "Delete contact?",
|
||||
text = "Contact and all messages will be deleted - this cannot be undone!",
|
||||
confirmText = "Delete",
|
||||
onConfirm = {
|
||||
val cInfo = chat.chatInfo
|
||||
withApi {
|
||||
val r = chatModel.controller.apiDeleteChat(cInfo.chatType, cInfo.apiId)
|
||||
if (r) {
|
||||
chatModel.removeChat(cInfo.id)
|
||||
nav.navigate(Pages.ChatList.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatInfoLayout(chat: Chat, close: () -> Unit, deleteContact: () -> Unit) {
|
||||
Column(Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
CloseSheetBar(close)
|
||||
Spacer(Modifier.size(48.dp))
|
||||
ChatInfoImage(chat, size = 192.dp)
|
||||
val cInfo = chat.chatInfo
|
||||
Text(
|
||||
cInfo.displayName, style = MaterialTheme.typography.h1,
|
||||
modifier = Modifier.padding(top = 32.dp).padding(bottom = 8.dp)
|
||||
)
|
||||
Text(
|
||||
cInfo.fullName, style = MaterialTheme.typography.h2,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
if (cInfo is ChatInfo.Direct) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Row(Modifier.padding(horizontal = 32.dp)) {
|
||||
ServerImage(chat)
|
||||
Text(
|
||||
chat.serverInfo.networkStatus.statusString,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
chat.serverInfo.networkStatus.statusExplanation,
|
||||
style = MaterialTheme.typography.body2,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 16.dp).padding(horizontal = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1F))
|
||||
|
||||
Box(Modifier.padding(24.dp)) {
|
||||
SimpleButton(
|
||||
"Delete contact", icon = Icons.Outlined.Delete,
|
||||
color = Color.Red,
|
||||
click = deleteContact
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ServerImage(chat: Chat) {
|
||||
val status = chat.serverInfo.networkStatus
|
||||
when {
|
||||
status is Chat.NetworkStatus.Connected ->
|
||||
Icon(Icons.Filled.Circle, "Connected", tint = MaterialTheme.colors.primaryVariant)
|
||||
status is Chat.NetworkStatus.Disconnected ->
|
||||
Icon(Icons.Filled.Pending, "Disconnected", tint = HighOrLowlight)
|
||||
status is Chat.NetworkStatus.Error ->
|
||||
Icon(Icons.Filled.Error, "Error", tint = HighOrLowlight)
|
||||
else ->
|
||||
Icon(Icons.Outlined.Circle, "Pending", tint = HighOrLowlight)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewChatInfoLayout() {
|
||||
SimpleXTheme {
|
||||
ChatInfoLayout(
|
||||
chat = Chat(
|
||||
chatInfo = ChatInfo.Direct.sampleData,
|
||||
chatItems = arrayListOf(),
|
||||
serverInfo = Chat.ServerInfo(Chat.NetworkStatus.Error("agent BROKER TIMEOUT"))
|
||||
),
|
||||
close = {}, deleteContact = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,15 @@ 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.ui.text.font.FontWeight
|
||||
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.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.item.ChatItemView
|
||||
import chat.simplex.app.views.helpers.ChatInfoImage
|
||||
import chat.simplex.app.views.helpers.withApi
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.datetime.Clock
|
||||
@@ -25,20 +28,26 @@ import kotlinx.datetime.Clock
|
||||
@Composable
|
||||
fun ChatView(chatModel: ChatModel, nav: NavController) {
|
||||
if (chatModel.chatId.value != null && chatModel.chats.count() > 0) {
|
||||
val chat: Chat = chatModel.chats.first { chat -> chat.chatInfo.id == chatModel.chatId.value }
|
||||
ChatLayout(chat, chatModel.chatItems, back = { nav.popBackStack() }, sendMessage = { msg ->
|
||||
withApi {
|
||||
// show "in progress"
|
||||
val cInfo = chat.chatInfo
|
||||
val newItem = chatModel.controller.apiSendMessage(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
mc = MsgContent.MCText(msg)
|
||||
)
|
||||
// hide "in progress"
|
||||
if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem)
|
||||
}
|
||||
})
|
||||
val chat: Chat? = chatModel.chats.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value }
|
||||
if (chat != null) {
|
||||
ChatLayout(chat, chatModel.chatItems,
|
||||
back = { nav.popBackStack() },
|
||||
info = { nav.navigate(Pages.ChatInfo.route) },
|
||||
sendMessage = { msg ->
|
||||
withApi {
|
||||
// show "in progress"
|
||||
val cInfo = chat.chatInfo
|
||||
val newItem = chatModel.controller.apiSendMessage(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
mc = MsgContent.MCText(msg)
|
||||
)
|
||||
// hide "in progress"
|
||||
if (newItem != null) chatModel.addChatItem(cInfo, newItem.chatItem)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,10 +55,11 @@ fun ChatView(chatModel: ChatModel, nav: NavController) {
|
||||
fun ChatLayout(
|
||||
chat: Chat, chatItems: List<ChatItem>,
|
||||
back: () -> Unit,
|
||||
info: () -> Unit,
|
||||
sendMessage: (String) -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = { ChatInfoToolbar(chat, back) },
|
||||
topBar = { ChatInfoToolbar(chat, back, info) },
|
||||
bottomBar = { SendMsgView(sendMessage) }
|
||||
) { contentPadding ->
|
||||
Box(
|
||||
@@ -64,9 +74,10 @@ fun ChatLayout(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatInfoToolbar(chat: Chat, back: () -> Unit) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fun ChatInfoToolbar(chat: Chat, back: () -> Unit, info: () -> Unit) {
|
||||
Box(Modifier
|
||||
.fillMaxWidth()
|
||||
.height(60.dp),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
Icon(
|
||||
@@ -77,14 +88,23 @@ fun ChatInfoToolbar(chat: Chat, back: () -> Unit) {
|
||||
.clickable(onClick = back)
|
||||
.padding(start = 16.dp)
|
||||
)
|
||||
Column(
|
||||
Modifier
|
||||
.padding(horizontal = 40.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
Row(Modifier
|
||||
.padding(horizontal = 40.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = info),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(chat.chatInfo.displayName)
|
||||
Text(chat.chatInfo.fullName)
|
||||
val cInfo = chat.chatInfo
|
||||
ChatInfoImage(chat, size = 40.dp)
|
||||
Column(Modifier.padding(start = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(cInfo.displayName, fontWeight = FontWeight.Bold)
|
||||
if (cInfo.fullName != "" && cInfo.fullName != cInfo.displayName) {
|
||||
Text(cInfo.fullName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,6 +147,7 @@ fun PreviewChatViewLayout() {
|
||||
),
|
||||
chatItems = chatItems,
|
||||
back = {},
|
||||
info = {},
|
||||
sendMessage = {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,18 +3,18 @@ package chat.simplex.app.views.chat.item
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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 chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@Composable
|
||||
fun CIMetaView(chatItem: ChatItem) {
|
||||
Text(
|
||||
chatItem.timestampText,
|
||||
color = Color.Gray,
|
||||
style = MaterialTheme.typography.caption
|
||||
color = HighOrLowlight,
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -64,13 +64,13 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) {
|
||||
.background(MaterialTheme.colors.background)
|
||||
) {
|
||||
ChatListToolbar(newChatCtrl)
|
||||
ChatList(chatModel, nav)
|
||||
Button(
|
||||
onClick = { nav.navigate(Pages.Terminal.route) },
|
||||
modifier = Modifier.padding(14.dp)
|
||||
) {
|
||||
Text("Terminal")
|
||||
}
|
||||
ChatList(chatModel, nav)
|
||||
}
|
||||
if (newChatCtrl.state.bottomSheetState.isExpanded) {
|
||||
Surface(Modifier
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package chat.simplex.app.views.chatlist
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
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.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -16,6 +13,7 @@ import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.helpers.ChatInfoImage
|
||||
|
||||
@Composable
|
||||
fun ChatPreviewView(chat: Chat, goToChat: () -> Unit) {
|
||||
@@ -27,30 +25,19 @@ fun ChatPreviewView(chat: Chat, goToChat: () -> Unit) {
|
||||
.height(80.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp),
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxHeight()) {
|
||||
Icon(
|
||||
Icons.Filled.Person,
|
||||
contentDescription = "Avatar Placeholder",
|
||||
tint = MaterialTheme.colors.background,
|
||||
modifier = Modifier
|
||||
.size(55.dp)
|
||||
.clip(CircleShape)
|
||||
.border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
|
||||
.background(MaterialTheme.colors.secondary)
|
||||
)
|
||||
ChatInfoImage(chat, size = 60.dp)
|
||||
}
|
||||
Spacer(modifier = Modifier.width(6.dp))
|
||||
Column(modifier = Modifier.padding(all = 8.dp)) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
Text(chat.chatInfo.chatViewName, fontWeight = FontWeight.Bold)
|
||||
(
|
||||
if (chat.chatItems.count() > 0) {
|
||||
Text(getTimestampText(chat.chatItems.last().meta.itemTs), color = HighOrLowlight)
|
||||
}
|
||||
else Text(getTimestampText(chat.chatInfo.createdAt), color = HighOrLowlight)
|
||||
)
|
||||
val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt)
|
||||
Text(ts, color = HighOrLowlight, style = MaterialTheme.typography.body2)
|
||||
}
|
||||
if (chat.chatItems.count() > 0) {
|
||||
Text(
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package chat.simplex.app.views.helpers
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.SupervisedUserCircle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.model.Chat
|
||||
import chat.simplex.app.model.ChatInfo
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
|
||||
@Composable
|
||||
fun ChatInfoImage(chat: Chat, size: Dp) {
|
||||
val icon =
|
||||
if (chat.chatInfo is ChatInfo.Group) Icons.Filled.SupervisedUserCircle
|
||||
else Icons.Filled.AccountCircle
|
||||
Box(Modifier.size(size)) {
|
||||
Icon(icon,
|
||||
contentDescription = "Avatar Placeholder",
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
// .clip(CircleShape)
|
||||
// .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
|
||||
// .background(MaterialTheme.colors.secondary)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewChatInfoImage() {
|
||||
SimpleXTheme {
|
||||
ChatInfoImage(
|
||||
chat = Chat(chatInfo = ChatInfo.Direct.sampleData, chatItems = arrayListOf()),
|
||||
size = 55.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -9,24 +9,23 @@ import androidx.compose.material.icons.outlined.Share
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SimpleButton(text: String, icon: ImageVector, click: () -> Unit) {
|
||||
fun SimpleButton(text: String, icon: ImageVector,
|
||||
color: Color = MaterialTheme.colors.primary,
|
||||
click: () -> Unit) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.clickable { click() }
|
||||
) {
|
||||
Icon(icon, text,
|
||||
tint = MaterialTheme.colors.primary,
|
||||
Icon(icon, text, tint = color,
|
||||
modifier = Modifier.padding(horizontal = 10.dp)
|
||||
)
|
||||
Text(text,
|
||||
style = MaterialTheme.typography.caption,
|
||||
color = MaterialTheme.colors.primary
|
||||
)
|
||||
Text(text, style = MaterialTheme.typography.caption, color = color)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -685,10 +685,10 @@ enum SMPCommandError: Decodable {
|
||||
}
|
||||
|
||||
enum SMPTransportError: Decodable {
|
||||
case TEBadBlock
|
||||
case TELargeMsg
|
||||
case TEBadSession
|
||||
case TEHandshake(handshakeErr: SMPHandshakeError)
|
||||
case badBlock
|
||||
case largeMsg
|
||||
case badSession
|
||||
case handshake(handshakeErr: SMPHandshakeError)
|
||||
}
|
||||
|
||||
enum SMPHandshakeError: Decodable {
|
||||
|
||||
Reference in New Issue
Block a user