list of chats and chat messages (#327)

This commit is contained in:
Evgeny Poberezkin
2022-02-17 20:30:21 +00:00
committed by GitHub
parent 423f54e95d
commit 290a88fd90
11 changed files with 245 additions and 53 deletions

View File

@@ -13,9 +13,10 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import chat.simplex.app.views.DetailView
import chat.simplex.app.views.TerminalView
import chat.simplex.app.views.WelcomeView
import chat.simplex.app.model.ChatModel
import chat.simplex.app.views.*
import chat.simplex.app.views.chat.ChatView
import chat.simplex.app.views.chatlist.*
class MainActivity: ComponentActivity() {
@@ -25,7 +26,7 @@ class MainActivity: ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
SimpleXTheme {
Navigation(viewModel=viewModel)
Navigation(viewModel.chatModel)
}
}
}
@@ -36,18 +37,28 @@ class SimplexViewModel(application: Application) : AndroidViewModel(application)
}
@Composable
fun Navigation(viewModel: SimplexViewModel) {
val navController = rememberNavController()
fun Navigation(chatModel: ChatModel) {
val nav = rememberNavController()
NavHost(navController=navController, startDestination=Pages.Home.route){
NavHost(navController = nav, startDestination=Pages.Home.route){
composable(route=Pages.Home.route){
MainPage(vm = viewModel, navController = navController)
MainPage(chatModel, nav)
}
composable(route = Pages.Welcome.route){
WelcomeView(vm = viewModel) {navController.navigate(Pages.Home.route) { popUpTo(Pages.Home.route) { inclusive = true }}}
WelcomeView(chatModel) {
nav.navigate(Pages.Home.route) {
popUpTo(Pages.Home.route) { inclusive = true }
}
}
}
composable(route = Pages.Chats.route) {
ChatListView(chatModel, nav)
}
composable(route = Pages.Chat.route) {
ChatView(chatModel, nav)
}
composable(route = Pages.Terminal.route) {
TerminalView(chatModel = viewModel.chatModel, navController = navController)
TerminalView(chatModel, navController = nav)
}
composable(
Pages.TerminalItemDetails.route + "/{identifier}",
@@ -56,7 +67,7 @@ fun Navigation(viewModel: SimplexViewModel) {
type = NavType.LongType
}
)
) { entry -> DetailView( entry.arguments!!.getLong("identifier"), viewModel.chatModel.terminalItems, navController) }
) { entry -> DetailView( entry.arguments!!.getLong("identifier"), chatModel.terminalItems, nav) }
}
}
@@ -65,4 +76,6 @@ sealed class Pages(val route: String) {
object Terminal : Pages("terminal")
object Welcome : Pages("welcome")
object TerminalItemDetails : Pages("details")
object Chats: Pages("chats")
object Chat: Pages("chat")
}

View File

@@ -2,13 +2,15 @@ package chat.simplex.app
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
@Composable
fun MainPage(vm: SimplexViewModel, navController: NavController) {
if (vm.chatModel.currentUser.value == null) WelcomeView(vm=vm) {
navController.navigate(Pages.Terminal.route)
fun MainPage(chatModel: ChatModel, nav: NavController) {
if (chatModel.currentUser.value == null) WelcomeView(chatModel) {
nav.navigate(Pages.Chats.route)
}
else TerminalView(vm.chatModel, navController)
else ChatListView(chatModel, nav)
}

View File

@@ -3,6 +3,8 @@ 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
@@ -35,19 +37,7 @@ class SimplexApp: Application() {
GlobalScope.launch {
withContext(Dispatchers.Main) {
var user = controller.apiGetActiveUser()
controller.setCurrentUser(user)
if (user == null) {
user = controller.apiCreateActiveUser(Profile("android", "Android test"))
}
Log.d("SIMPLEX (user)", user.toString())
chatModel.currentUser = user
try {
controller.apiStartChat()
Log.d("SIMPLEX", "started chat")
} catch(e: Error) {
Log.d("SIMPLEX", "failed starting chat $e")
// throw e
}
if (user != null) controller.startChat(user)
}
}
}

View File

@@ -2,14 +2,15 @@ package chat.simplex.app.model
import androidx.compose.runtime.*
import kotlinx.serialization.*
import java.util.*
class ChatModel(val controller: ChatController) {
var currentUser = mutableStateOf<User?>(null)
var terminalItems = mutableStateListOf<TerminalItem>()
var chats = mutableStateListOf<Chat>()
var chatId = mutableStateOf<String?>(null)
var chatItems = mutableStateListOf<ChatItem>()
fun setCurrentUser(u: User?){
currentUser = mutableStateOf<User?>(u)
}
var terminalItems = mutableStateListOf<TerminalItem>()
companion object {
val sampleData: ChatModel get() {
@@ -36,7 +37,7 @@ class User (
val localDisplayName: String,
val profile: Profile,
val activeUser: Boolean
) : NamedChat {
): NamedChat {
override val displayName: String get() = profile.displayName
override val fullName: String get() = profile.fullName
@@ -61,6 +62,7 @@ interface NamedChat {
}
interface SomeChat {
val chatType: ChatType
val localDisplayName: String
val id: ChatId
val apiId: Long
@@ -102,6 +104,7 @@ class Chat (
sealed class ChatInfo: SomeChat, NamedChat {
@Serializable @SerialName("direct")
class Direct(val contact: Contact): ChatInfo() {
override val chatType get() = ChatType.Direct
override val localDisplayName get() = contact.localDisplayName
override val id get() = contact.id
override val apiId get() = contact.apiId
@@ -116,6 +119,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
@Serializable @SerialName("group")
class Group(val groupInfo: GroupInfo): ChatInfo() {
override val chatType get() = ChatType.Group
override val localDisplayName get() = groupInfo.localDisplayName
override val id get() = groupInfo.id
override val apiId get() = groupInfo.apiId
@@ -130,6 +134,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
@Serializable @SerialName("contactRequest")
class ContactRequest(val contactRequest: UserContactRequest): ChatInfo() {
override val chatType get() = ChatType.ContactRequest
override val localDisplayName get() = contactRequest.localDisplayName
override val id get() = contactRequest.id
override val apiId get() = contactRequest.apiId
@@ -153,6 +158,7 @@ class Contact(
// no serializer for type Date?
// val createdAt: Date
): SomeChat, NamedChat {
override val chatType get() = ChatType.Direct
override val id get() = "@$contactId"
override val apiId get() = contactId
override val ready get() = activeConn.connStatus == "ready" || activeConn.connStatus == "snd-ready"
@@ -197,6 +203,7 @@ class GroupInfo (
val groupProfile: GroupProfile,
// var createdAt: Date
): SomeChat, NamedChat {
override val chatType get() = ChatType.Group
override val id get() = "#$groupId"
override val apiId get() = groupId
override val ready get() = true
@@ -257,6 +264,7 @@ class UserContactRequest (
val profile: Profile
// val createdAt: Date
): SomeChat, NamedChat {
override val chatType get() = ChatType.ContactRequest
override val id get() = "<@$contactRequestId"
override val apiId get() = contactRequestId
override val ready get() = true

View File

@@ -1,6 +1,9 @@
package chat.simplex.app.model
import android.util.Log
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.toMutableStateList
import chat.simplex.app.chatRecvMsg
import chat.simplex.app.chatSendCmd
import kotlinx.coroutines.Dispatchers
@@ -21,8 +24,18 @@ open class ChatController(val ctrl: ChatCtrl) {
fun setModel(m: ChatModel) {
chatModel = m
}
suspend fun setCurrentUser(u: User?){
chatModel!!.setCurrentUser(u)
suspend fun startChat(u: User) {
chatModel!!.currentUser = mutableStateOf(u)
Log.d("SIMPLEX (user)", u.toString())
apiStartChat()
try {
Log.d("SIMPLEX", "started chat")
chatModel!!.chats = apiGetChats().toMutableStateList()
} catch(e: Error) {
Log.d("SIMPLEX", "failed starting chat $e")
throw e
}
}
fun startReceiver() {
@@ -75,12 +88,26 @@ open class ChatController(val ctrl: ChatCtrl) {
throw Error("failed starting chat: ${r.toString()}")
}
suspend fun apiGetChats() {
suspend fun apiGetChats(): List<Chat> {
val r = sendCmd(CC.ApiGetChats())
if (r is CR.ApiChats ) return
if (r is CR.ApiChats ) return r.chats
throw Error("failed getting the list of chats: ${r.toString()}")
}
suspend fun apiGetChat(type: ChatType, id: Long): Chat? {
val r = sendCmd(CC.ApiGetChat(type, id))
if (r is CR.ApiChat ) return r.chat
Log.d("SIMPLEX", "apiGetChat bad response: ${r.toString()}")
return null
}
suspend fun apiSendMessage(type: ChatType, id: Long, mc: MsgContent): AChatItem? {
val r = sendCmd(CC.ApiSendMessage(type, id, mc))
if (r is CR.NewChatItem ) return r.chatItem
Log.d("SIMPLEX", "apiSendMessage bad response: ${r.toString()}")
return null
}
class Mock: ChatController(0) {}
}
@@ -125,7 +152,7 @@ abstract class CC {
}
companion object {
fun chatRef(type: ChatType, id: Long) = "${type}${id}"
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
}
}

View File

@@ -31,6 +31,9 @@ import kotlinx.coroutines.withContext
@Composable
fun TerminalView(chatModel: ChatModel, navController: NavController) {
Column {
Button(onClick = { navController.popBackStack() }) {
Text("Back")
}
TerminalLog(chatModel.terminalItems, navController)
SendMsgView(sendMessage = { cmd ->
GlobalScope.launch {
@@ -59,7 +62,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>, navController: NavController)
@Composable
fun DetailView(identifier: Long, terminalItems: List<TerminalItem>, navController: NavController){
Column(
modifier=Modifier.verticalScroll(rememberScrollState())
modifier = Modifier.verticalScroll(rememberScrollState())
) {
Button(onClick = { navController.popBackStack() }) {
Text("Back")

View File

@@ -14,6 +14,7 @@ import androidx.compose.material.Button
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Modifier
import chat.simplex.app.SimplexViewModel
import chat.simplex.app.model.ChatModel
import chat.simplex.app.model.Profile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
@@ -21,7 +22,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun WelcomeView(vm: SimplexViewModel, routeHome: () -> Unit) {
fun WelcomeView(chatModel: ChatModel, routeHome: () -> Unit) {
Column(
modifier = Modifier.verticalScroll(rememberScrollState())
) {
@@ -33,12 +34,12 @@ fun WelcomeView(vm: SimplexViewModel, routeHome: () -> Unit) {
Spacer(Modifier.height(8.dp))
Text("We don't store any of your contacts or messages (once delivered) on the servers.")
Spacer(Modifier.height(24.dp))
CreateProfilePanel(vm, routeHome)
CreateProfilePanel(chatModel, routeHome)
}
}
@Composable
fun CreateProfilePanel(vm: SimplexViewModel, routeHome: () -> Unit) {
fun CreateProfilePanel(chatModel: ChatModel, routeHome: () -> Unit) {
var displayName by remember { mutableStateOf("") }
var fullName by remember { mutableStateOf("") }
@@ -52,10 +53,10 @@ fun CreateProfilePanel(vm: SimplexViewModel, routeHome: () -> Unit) {
Button(onClick={
GlobalScope.launch {
withContext(Dispatchers.Main) {
val user = vm.chatModel.controller.apiCreateActiveUser(
val user = chatModel.controller.apiCreateActiveUser(
Profile(displayName, fullName)
)
vm.chatModel.setCurrentUser(user)
chatModel.controller.startChat(user)
routeHome()
}
}

View File

@@ -1,10 +1,66 @@
package chat.simplex.app.views.chat
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import chat.simplex.app.model.Chat
import androidx.compose.runtime.MutableState
import androidx.navigation.NavController
import chat.simplex.app.model.*
import chat.simplex.app.views.chat.item.ChatItemView
import chat.simplex.app.views.chatlist.ChatPreviewView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun ChatView(chat: Chat) {
Text("ChatView")
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 }
Column {
ChatInfoToolbar(chat, nav)
ChatItemsList(chatModel.chatItems)
SendMsgView(sendMessage = { msg ->
GlobalScope.launch {
withContext(Dispatchers.Main) {
// 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"
// TODO add new item
}
}
})
}
}
}
@Composable
fun ChatInfoToolbar(chat: Chat, nav: NavController) {
Row {
Button(onClick = { nav.popBackStack() }) {
Text("Back")
}
Column {
Text(chat.chatInfo.displayName)
Text(chat.chatInfo.fullName)
}
}
}
@Composable
fun ChatItemsList(chatItems: List<ChatItem>) {
LazyColumn {
items(chatItems) { cItem ->
ChatItemView(cItem)
}
}
}

View File

@@ -6,5 +6,5 @@ import chat.simplex.app.model.ChatItem
@Composable
fun ChatItemView(chatItem: ChatItem) {
Text("ChatItemView")
Text(chatItem.content.text)
}

View File

@@ -1,10 +1,78 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import chat.simplex.app.model.ChatModel
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Modifier
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.TerminalView
import chat.simplex.app.views.chat.SendMsgView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Composable
fun ChatListView(chatModel: ChatModel) {
Text("ChatListView")
fun ChatListView(chatModel: ChatModel, navController: NavController) {
Column(modifier = Modifier.padding(all = 8.dp)) {
ChatListToolbar()
Button (onClick = { navController.navigate(Pages.Terminal.route) }) {
Text("Terminal")
}
ChatList(chatModel, navController)
}
}
@Composable
fun ChatListToolbar() {
Text("ChatListToolbar")
}
@Composable
fun ChatList(chatModel: ChatModel, navController: NavController) {
LazyColumn {
items(chatModel.chats) { chat ->
Button(onClick = {
GlobalScope.launch {
withContext(Dispatchers.Main) {
val cInfo = chat.chatInfo
val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId)
if (chat != null ) {
chatModel.chatId = mutableStateOf(cInfo.id)
chatModel.chatItems = chat.chatItems.toMutableStateList()
navController.navigate(Pages.Chat.route)
} else {
// TODO show error? or will apiGetChat show it
}
}
}
}) {
ChatPreviewView(chat)
}
}
}
}
//@Preview
//@Composable
//fun PreviewSendMsgView() {
// SimpleXTheme {
// ChatListView(
// chats = listOf(
// Chat()
// ),
//
// )
// }
//}

View File

@@ -1,10 +1,34 @@
package chat.simplex.app.views.chatlist
import androidx.compose.foundation.layout.*
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import chat.simplex.app.model.Chat
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.ui.theme.SimpleXTheme
@Composable
fun ChatPreviewView(chat: Chat) {
Text("ChatPreviewView")
Column(modifier = Modifier.padding(all = 8.dp)) {
Text(chat.chatInfo.chatViewName)
if (chat.chatItems.count() > 0) {
Text(chat.chatItems.last().content.text)
}
}
}
@Preview
@Composable
fun ChatPreviewView() {
SimpleXTheme {
ChatPreviewView(
chat = Chat(
chatInfo = ChatInfo.Direct.sampleData,
chatItems = listOf(),
chatStats = Chat.ChatStats()
)
)
}
}