diff --git a/apps/android/.gitignore b/apps/android/.gitignore
index aa724b7707..4d1f29a2d1 100644
--- a/apps/android/.gitignore
+++ b/apps/android/.gitignore
@@ -7,6 +7,8 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
+/.idea/deploymentTargetDropDown.xml
+/.idea/misc.xml
.DS_Store
/build
/captures
diff --git a/apps/android/.idea/misc.xml b/apps/android/.idea/misc.xml
deleted file mode 100644
index f0d8957869..0000000000
--- a/apps/android/.idea/misc.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
index 62a4113a1e..8adeceecf6 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/SimplexApp.kt
@@ -37,9 +37,10 @@ class SimplexApp: Application() {
var user = controller.apiGetActiveUser()
controller.setCurrentUser(user)
if (user == null) {
-// user = controller.apiCreateActiveUser(Profile("android", "Android test"))
+ user = controller.apiCreateActiveUser(Profile("android", "Android test"))
}
Log.d("SIMPLEX (user)", user.toString())
+ chatModel.currentUser = user
try {
controller.apiStartChat()
Log.d("SIMPLEX", "started chat")
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
index 5c82f51225..f8a7082595 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/ChatModel.kt
@@ -1,8 +1,7 @@
package chat.simplex.app.model
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.mutableStateOf
-import kotlinx.serialization.Serializable
+import androidx.compose.runtime.*
+import kotlinx.serialization.*
class ChatModel(val controller: ChatController) {
var currentUser = mutableStateOf(null)
@@ -32,8 +31,8 @@ enum class ChatType(val type: String) {
@Serializable
class User (
- val userId: Int,
- val userContactId: Int,
+ val userId: Long,
+ val userContactId: Long,
val localDisplayName: String,
val profile: Profile,
val activeUser: Boolean
@@ -54,21 +53,111 @@ class User (
typealias ChatId = String
+interface NamedChat {
+ val displayName: String
+ val fullName: String
+ val chatViewName: String
+ get() = displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName")
+}
+
+interface SomeChat {
+ val localDisplayName: String
+ val id: ChatId
+ val apiId: Long
+ val ready: Boolean
+}
+
+@Serializable
+class Chat (
+ val chatInfo: ChatInfo,
+ val chatItems: List,
+ val chatStats: ChatStats,
+ val serverInfo: ServerInfo = ServerInfo(NetworkStatus.Unknown())
+) {
+
+ @Serializable
+ class ChatStats {
+
+ }
+
+ @Serializable
+ class ServerInfo(val networkStatus: NetworkStatus)
+
+ @Serializable
+ sealed class NetworkStatus {
+ abstract val statusString: String
+ abstract val statusExplanation: String
+ abstract val imageName: String
+
+ @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
+sealed class ChatInfo: SomeChat, NamedChat {
+ @Serializable @SerialName("direct")
+ class Direct(val contact: Contact): ChatInfo() {
+ override val localDisplayName get() = contact.localDisplayName
+ override val id get() = contact.id
+ override val apiId get() = contact.apiId
+ override val ready get() = contact.ready
+ override val displayName get() = contact.displayName
+ override val fullName get() = contact.displayName
+
+ companion object {
+ val sampleData = Direct(Contact.sampleData)
+ }
+ }
+
+ @Serializable @SerialName("group")
+ class Group(val groupInfo: GroupInfo): ChatInfo() {
+ override val localDisplayName get() = groupInfo.localDisplayName
+ override val id get() = groupInfo.id
+ override val apiId get() = groupInfo.apiId
+ override val ready get() = groupInfo.ready
+ override val displayName get() = groupInfo.displayName
+ override val fullName get() = groupInfo.displayName
+
+ companion object {
+ val sampleData = Group(GroupInfo.sampleData)
+ }
+ }
+
+ @Serializable @SerialName("contactRequest")
+ class ContactRequest(val contactRequest: UserContactRequest): ChatInfo() {
+ override val localDisplayName get() = contactRequest.localDisplayName
+ override val id get() = contactRequest.id
+ override val apiId get() = contactRequest.apiId
+ override val ready get() = contactRequest.ready
+ override val displayName get() = contactRequest.displayName
+ override val fullName get() = contactRequest.displayName
+
+ companion object {
+ val sampleData = ContactRequest(UserContactRequest.sampleData)
+ }
+ }
+}
+
@Serializable
class Contact(
- val contactId: Int,
- val localDisplayName: String,
+ val contactId: Long,
+ override val localDisplayName: String,
val profile: Profile,
val activeConn: Connection,
- val viaGroup: Int? = null,
+ val viaGroup: Long? = null,
// no serializer for type Date?
// val createdAt: Date
-): NamedChat {
- val id: ChatId get() = "@$contactId"
- val apiId: Int get() = contactId
- val ready: Boolean get() = activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready"
- override val displayName: String get() = profile.displayName
- override val fullName: String get() = profile.fullName
+): SomeChat, NamedChat {
+ override val id get() = "@$contactId"
+ override val apiId get() = contactId
+ override val ready get() = activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready"
+ override val displayName get() = profile.displayName
+ override val fullName get() = profile.fullName
companion object {
val sampleData = Contact(
@@ -101,9 +190,228 @@ class Profile(
}
}
-interface NamedChat {
- abstract val displayName: String
- abstract val fullName: String
- val chatViewName: String
- get() = displayName + (if (fullName == "" || fullName == displayName) "" else " / $fullName")
+@Serializable
+class GroupInfo (
+ val groupId: Long,
+ override val localDisplayName: String,
+ val groupProfile: GroupProfile,
+// var createdAt: Date
+): SomeChat, NamedChat {
+ override val id get() = "#$groupId"
+ override val apiId get() = groupId
+ override val ready get() = true
+ override val displayName get() = groupProfile.displayName
+ override val fullName get() = groupProfile.fullName
+
+ companion object {
+ val sampleData = GroupInfo(
+ groupId = 1,
+ localDisplayName = "team",
+ groupProfile = GroupProfile.sampleData,
+// createdAt: Date()
+ )
+ }
+}
+
+@Serializable
+class GroupProfile (
+ override val displayName: String,
+ override val fullName: String
+): NamedChat {
+ companion object {
+ val sampleData = GroupProfile(
+ displayName = "team",
+ fullName = "My Team"
+ )
+ }
+}
+
+@Serializable
+class GroupMember (
+ val groupMemberId: Long,
+ val memberId: String,
+// var memberRole: GroupMemberRole
+// var memberCategory: GroupMemberCategory
+// var memberStatus: GroupMemberStatus
+// var invitedBy: InvitedBy
+ val localDisplayName: String,
+ val memberProfile: Profile,
+ val memberContactId: Long?
+// var activeConn: Connection?
+) {
+ companion object {
+ val sampleData = GroupMember(
+ groupMemberId = 1,
+ memberId = "abcd",
+ localDisplayName = "alice",
+ memberProfile = Profile.sampleData,
+ memberContactId = 1
+ )
+ }
+}
+
+@Serializable
+class UserContactRequest (
+ val contactRequestId: Long,
+ override val localDisplayName: String,
+ val profile: Profile
+// val createdAt: Date
+): SomeChat, NamedChat {
+ override val id get() = "<@$contactRequestId"
+ override val apiId get() = contactRequestId
+ override val ready get() = true
+ override val displayName get() = profile.displayName
+ override val fullName get() = profile.fullName
+
+ companion object {
+ val sampleData = UserContactRequest(
+ contactRequestId = 1,
+ localDisplayName = "alice",
+ profile = Profile.sampleData,
+// createdAt: Date()
+ )
+ }
+}
+
+@Serializable
+class AChatItem (
+ val chatInfo: ChatInfo,
+ val chatItem: ChatItem
+)
+
+@Serializable
+class ChatItem (
+ val chatDir: CIDirection,
+ val meta: CIMeta,
+ val content: CIContent
+) {
+ val id: Long get() = meta.itemId
+ // val timestampText: String get() = meta.timestampText
+ val isRcvNew: Boolean get() = meta.itemStatus is CIStatus.RcvNew
+
+ companion object {
+ fun getSampleData(id: Long, dir: CIDirection, ts: Date, text: String,status: CIStatus = CIStatus.SndNew()) =
+ ChatItem(
+ chatDir = dir,
+ meta = CIMeta.getSample(id, ts, text, status),
+ content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text))
+ )
+ }
+}
+
+@Serializable
+sealed class CIDirection {
+ abstract val sent: Boolean
+
+ @Serializable @SerialName("directSnd")
+ class DirectSnd: CIDirection() {
+ override val sent get() = true
+ }
+
+ @Serializable @SerialName("directRcv")
+ class DirectRcv: CIDirection() {
+ override val sent get() = false
+ }
+
+ @Serializable @SerialName("groupSnd")
+ class GroupSnd: CIDirection() {
+ override val sent get() = true
+ }
+
+ @Serializable @SerialName("groupRcv")
+ class GroupRcv(val groupMember: GroupMember): CIDirection() {
+ override val sent get() = false
+ }
+}
+
+@Serializable
+class CIMeta (
+ val itemId: Long,
+// val itemTs: Date,
+ val itemText: String,
+ val itemStatus: CIStatus,
+// val createdAt: Date
+) {
+// val timestampText: String get() = getTimestampText(itemTs)
+
+ companion object {
+ fun getSample(id: Long, ts: Date, text: String, status: CIStatus = CIStatus.SndNew()): CIMeta =
+ CIMeta(
+ itemId = id,
+// itemTs = ts,
+ itemText = text,
+ itemStatus = status,
+// createdAt = ts
+ )
+ }
+}
+
+// TODO
+fun getTimestampText(d: Date): String = ""
+
+@Serializable
+sealed class CIStatus {
+ @Serializable @SerialName("sndNew")
+ class SndNew: CIStatus()
+
+ @Serializable @SerialName("sndSent")
+ class SndSent: CIStatus()
+
+ @Serializable @SerialName("sndErrorAuth")
+ class SndErrorAuth: CIStatus()
+
+ @Serializable @SerialName("sndError")
+ class SndError(val agentError: AgentErrorType): CIStatus()
+
+ @Serializable @SerialName("rcvNew")
+ class RcvNew: CIStatus()
+
+ @Serializable @SerialName("rcvRead")
+ class RcvRead: CIStatus()
+}
+
+@Serializable
+sealed class CIContent {
+ abstract val text: String
+
+ @Serializable @SerialName("sndMsgContent")
+ class SndMsgContent(val msgContent: MsgContent): CIContent() {
+ override val text get() = msgContent.text
+ }
+
+ @Serializable @SerialName("rcvMsgContent")
+ class RcvMsgContent(val msgContent: MsgContent): CIContent() {
+ override val text get() = msgContent.text
+ }
+
+ @Serializable @SerialName("sndFileInvitation")
+ class SndFileInvitation(val fileId: Long, val filePath: String): CIContent() {
+ override val text get() = "sending files is not supported yet"
+ }
+
+ @Serializable @SerialName("rcvFileInvitation")
+ class RcvFileInvitation(val rcvFileTransfer: RcvFileTransfer): CIContent() {
+ override val text get() = "receiving files is not supported yet"
+ }
+}
+
+@Serializable
+sealed class MsgContent {
+ abstract val text: String
+ abstract val cmdString: String
+
+ @Serializable @SerialName("text")
+ class MCText(override val text: String): MsgContent() {
+ override val cmdString get() = "text $text"
+ }
+}
+
+@Serializable
+class RcvFileTransfer {
+
+}
+
+@Serializable
+class AgentErrorType {
+
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
index 974e6d1776..bca01a174d 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt
@@ -46,9 +46,10 @@ open class ChatController(val ctrl: ChatCtrl) {
val c = cmd.cmdString
chatModel?.terminalItems?.add(TerminalItem.Cmd(System.currentTimeMillis(), cmd))
val json = chatSendCmd(ctrl, c)
- Log.d("SIMPLEX", "sendCmd: $c")
- Log.d("SIMPLEX", "sendCmd response $json")
+ Log.d("SIMPLEX", "sendCmd: ${cmd.cmdType}")
+ Log.d("SIMPLEX", "sendCmd response json $json")
val r = APIResponse.decodeStr(json)
+ Log.d("SIMPLEX", "sendCmd response type ${r.resp.responseType}")
chatModel?.terminalItems?.add(TerminalItem.Resp(System.currentTimeMillis(), r.resp))
r.resp
}
@@ -74,6 +75,12 @@ open class ChatController(val ctrl: ChatCtrl) {
throw Error("failed starting chat: ${r.toString()}")
}
+ suspend fun apiGetChats() {
+ val r = sendCmd(CC.ApiGetChats())
+ if (r is CR.ApiChats ) return
+ throw Error("failed getting the list of chats: ${r.toString()}")
+ }
+
class Mock: ChatController(0) {}
}
@@ -89,26 +96,36 @@ abstract class CC {
class ShowActiveUser: CC() {
override val cmdString get() = "/u"
- override val cmdType get() = "ShowActiveUser"
+ override val cmdType get() = "showActiveUser"
}
class CreateActiveUser(val profile: Profile): CC() {
override val cmdString get() = "/u ${profile.displayName} ${profile.fullName}"
- override val cmdType get() = "CreateActiveUser"
+ override val cmdType get() = "createActiveUser"
}
class StartChat: CC() {
override val cmdString get() = "/_start"
- override val cmdType get() = "StartChat"
+ override val cmdType get() = "startChat"
}
class ApiGetChats: CC() {
override val cmdString get() = "/_get chats"
- override val cmdType get() = "ApiGetChats"
+ override val cmdType get() = "apiGetChats"
+ }
+
+ class ApiGetChat(val type: ChatType, val id: Long): CC() {
+ override val cmdString get() = "/_get chat ${CC.chatRef(type, id)} count=100"
+ override val cmdType get() = "apiGetChat"
+ }
+
+ class ApiSendMessage(val type: ChatType, val id: Long, val mc: MsgContent): CC() {
+ override val cmdString get() = "/_send ${CC.chatRef(type, id)} ${mc.cmdString}"
+ override val cmdType get() = "apiGetChat"
}
companion object {
- fun chatRef(type: ChatType, id: String) = "${type}${id}"
+ fun chatRef(type: ChatType, id: Long) = "${type}${id}"
}
}
@@ -144,29 +161,49 @@ sealed class CR {
abstract val responseType: String
abstract val details: String
- @Serializable
- @SerialName("activeUser")
+ @Serializable @SerialName("activeUser")
class ActiveUser(val user: User): CR() {
override val responseType get() = "activeUser"
override val details get() = user.toString()
}
- @Serializable
- @SerialName("chatStarted")
+ @Serializable @SerialName("chatStarted")
class ChatStarted: CR() {
override val responseType get() = "chatStarted"
override val details get() = CR.noDetails(this)
}
- @Serializable
- @SerialName("contactSubscribed")
+ @Serializable @SerialName("apiChats")
+ class ApiChats(val chats: List): CR() {
+ override val responseType get() = "apiChats"
+ override val details get() = chats.toString()
+ }
+
+ @Serializable @SerialName("apiChat")
+ class ApiChat(val chat: Chat): CR() {
+ override val responseType get() = "apiChats"
+ override val details get() = chat.toString()
+ }
+
+ @Serializable @SerialName("contactSubscribed")
class ContactSubscribed(val contact: Contact): CR() {
override val responseType get() = "contactSubscribed"
override val details get() = contact.toString()
}
- @Serializable
- @SerialName("cmdAccepted")
+ @Serializable @SerialName("newChatItem")
+ class NewChatItem(val chatItem: AChatItem): CR() {
+ override val responseType get() = "newChatItem"
+ override val details get() = chatItem.toString()
+ }
+
+ @Serializable @SerialName("chatItemUpdated")
+ class ChatItemUpdated(val chatItem: AChatItem): CR() {
+ override val responseType get() = "chatItemUpdated"
+ override val details get() = chatItem.toString()
+ }
+
+ @Serializable @SerialName("cmdAccepted")
class CmdAccepted(val corr: String): CR() {
override val responseType get() = "cmdAccepted"
override val details get() = "corrId: $corr"
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
index 4fde8eb87c..bc7705f5e6 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt
@@ -5,6 +5,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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
@@ -60,9 +61,11 @@ fun DetailView(identifier: Long, terminalItems: List, navControlle
Column(
modifier=Modifier.verticalScroll(rememberScrollState())
) {
- Text((terminalItems.filter {it.id == identifier}).first().details)
Button(onClick = { navController.popBackStack() }) {
Text("Back")
}
+ SelectionContainer {
+ Text((terminalItems.filter { it.id == identifier }).first().details)
+ }
}
}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt
new file mode 100644
index 0000000000..4efa2c50d0
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatView.kt
@@ -0,0 +1,10 @@
+package chat.simplex.app.views.chat
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import chat.simplex.app.model.Chat
+
+@Composable
+fun ChatView(chat: Chat) {
+ Text("ChatView")
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
new file mode 100644
index 0000000000..10fc3c9f9c
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/ChatItemView.kt
@@ -0,0 +1,10 @@
+package chat.simplex.app.views.chat.item
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import chat.simplex.app.model.ChatItem
+
+@Composable
+fun ChatItemView(chatItem: ChatItem) {
+ Text("ChatItemView")
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt
new file mode 100644
index 0000000000..22bbdbb005
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/TextItemView.kt
@@ -0,0 +1,10 @@
+package chat.simplex.app.views.chat.item
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import chat.simplex.app.model.ChatItem
+
+@Composable
+fun TextItemView(chatItem: ChatItem) {
+ Text("TextItemView")
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
new file mode 100644
index 0000000000..634af6248d
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt
@@ -0,0 +1,10 @@
+package chat.simplex.app.views.chatlist
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import chat.simplex.app.model.ChatModel
+
+@Composable
+fun ChatListView(chatModel: ChatModel) {
+ Text("ChatListView")
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt
new file mode 100644
index 0000000000..993e9b96a5
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatPreviewView.kt
@@ -0,0 +1,10 @@
+package chat.simplex.app.views.chatlist
+
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import chat.simplex.app.model.Chat
+
+@Composable
+fun ChatPreviewView(chat: Chat) {
+ Text("ChatPreviewView")
+}