mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-26 23:55:53 +00:00
android: user address; fix add and connect contact views dark mode; chat list view styling (#359)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
@@ -22,8 +22,7 @@ import chat.simplex.app.views.chat.ChatView
|
||||
import chat.simplex.app.views.chatlist.ChatListView
|
||||
import chat.simplex.app.views.helpers.withApi
|
||||
import chat.simplex.app.views.newchat.*
|
||||
import chat.simplex.app.views.usersettings.HelpView
|
||||
import chat.simplex.app.views.usersettings.UserProfileView
|
||||
import chat.simplex.app.views.usersettings.*
|
||||
import com.google.accompanist.insets.ExperimentalAnimatedInsets
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@@ -54,6 +53,7 @@ class SimplexViewModel(application: Application): AndroidViewModel(application)
|
||||
val chatModel = getApplication<SimplexApp>().chatModel
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@DelicateCoroutinesApi
|
||||
@ExperimentalPermissionsApi
|
||||
@ExperimentalMaterialApi
|
||||
@@ -116,6 +116,9 @@ fun Navigation(chatModel: ChatModel) {
|
||||
composable(route = Pages.UserProfile.route) {
|
||||
UserProfileView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.UserAddress.route) {
|
||||
UserAddressView(chatModel, nav)
|
||||
}
|
||||
composable(route = Pages.Help.route) {
|
||||
HelpView(chatModel, nav)
|
||||
}
|
||||
@@ -136,6 +139,7 @@ sealed class Pages(val route: String) {
|
||||
object Connect: Pages("connect")
|
||||
object ChatInfo: Pages("chat_info")
|
||||
object UserProfile: Pages("user_profile")
|
||||
object UserAddress: Pages("user_address")
|
||||
object Help: Pages("help")
|
||||
}
|
||||
|
||||
|
||||
@@ -58,8 +58,40 @@ class SimplexApp: Application() {
|
||||
alertView.value = null
|
||||
}
|
||||
|
||||
fun showAlertMsg(title: String, text: String? = null,
|
||||
confirmText: String = "Ok", onConfirm: (() -> Unit)? = null) {
|
||||
fun showAlertDialog(
|
||||
title: String,
|
||||
text: String? = null,
|
||||
confirmText: String = "Ok",
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
dismissText: String = "Cancel",
|
||||
onDismiss: (() -> 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) }
|
||||
},
|
||||
dismissButton = {
|
||||
Button(onClick = {
|
||||
onDismiss?.invoke()
|
||||
hideAlert()
|
||||
}) { Text(dismissText) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
|
||||
@@ -23,6 +23,7 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
|
||||
var connReqInvitation: String? = null
|
||||
var terminalItems = mutableStateListOf<TerminalItem>()
|
||||
var userAddress = mutableStateOf<String?>(null)
|
||||
// set when app is opened via contact or invitation URI
|
||||
var appOpenUrl = mutableStateOf<Uri?>(null)
|
||||
|
||||
@@ -60,14 +61,15 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
|
||||
}
|
||||
}
|
||||
|
||||
// func replaceChat(_ id: String, _ chat: Chat) {
|
||||
// if let i = getChatIndex(id) {
|
||||
// chats[i] = chat
|
||||
// } else {
|
||||
// // invalid state, correcting
|
||||
// chats.insert(chat, at: 0)
|
||||
// }
|
||||
// }
|
||||
fun replaceChat(id: String, chat: Chat) {
|
||||
val i = getChatIndex(id)
|
||||
if (i >= 0) {
|
||||
chats[i] = chat
|
||||
} else {
|
||||
// invalid state, correcting
|
||||
chats.add(index = 0, chat)
|
||||
}
|
||||
}
|
||||
|
||||
fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||
// update previews
|
||||
|
||||
@@ -23,6 +23,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
Log.d("SIMPLEX (user)", u.toString())
|
||||
try {
|
||||
apiStartChat()
|
||||
chatModel.userAddress.value = apiGetUserAddress()
|
||||
chatModel.chats.addAll(apiGetChats())
|
||||
chatModel.chatsLoaded.value = true
|
||||
startReceiver()
|
||||
@@ -221,7 +222,12 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected())
|
||||
// NtfManager.shared.notifyContactConnected(contact)
|
||||
}
|
||||
// is CR.ReceivedContactRequest -> return
|
||||
is CR.ReceivedContactRequest -> {
|
||||
val contactRequest = r.contactRequest
|
||||
val cInfo = ChatInfo.ContactRequest(contactRequest)
|
||||
chatModel.addChat(Chat(chatInfo = cInfo, chatItems = listOf()))
|
||||
// NtfManager.shared.notifyContactRequest(contactRequest)
|
||||
}
|
||||
is CR.ContactUpdated -> {
|
||||
val cInfo = ChatInfo.Direct(r.toContact)
|
||||
if (chatModel.hasChat(r.toContact.id)) {
|
||||
@@ -257,15 +263,6 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
|
||||
chatModel.addChatItem(cInfo, cItem)
|
||||
// NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
||||
}
|
||||
|
||||
// switch res {
|
||||
// case let .receivedContactRequest(contactRequest):
|
||||
// chatModel.addChat(Chat(
|
||||
// chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest),
|
||||
// chatItems: []
|
||||
// ))
|
||||
// NtfManager.shared.notifyContactRequest(contactRequest)
|
||||
//
|
||||
// case let .chatItemUpdated(aChatItem):
|
||||
// let cInfo = aChatItem.chatInfo
|
||||
// let cItem = aChatItem.chatItem
|
||||
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
package chat.simplex.app.views.chatlist
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.ExperimentalTextApi
|
||||
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.helpers.withApi
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Composable
|
||||
fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel, nav: NavController) {
|
||||
ChatListNavLink(
|
||||
chat = chat,
|
||||
action = {
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct -> chatNavLink(chat, chatModel, nav)
|
||||
is ChatInfo.Group -> chatNavLink(chat, chatModel, nav)
|
||||
is ChatInfo.ContactRequest -> contactRequestNavLink(chat.chatInfo, chatModel, nav)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
fun chatNavLink(chatPreview: Chat, chatModel: ChatModel, navController: NavController) {
|
||||
withApi {
|
||||
val chatInfo = chatPreview.chatInfo
|
||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
||||
if (chat != null) {
|
||||
chatModel.chatId.value = chatInfo.id
|
||||
chatModel.chatItems = chat.chatItems.toMutableStateList()
|
||||
navController.navigate(Pages.Chat.route)
|
||||
} else {
|
||||
// TODO show error? or will apiGetChat show it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
fun contactRequestNavLink(contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel, navController: NavController) {
|
||||
chatModel.alertManager.showAlertDialog(
|
||||
title = "Accept connection request?",
|
||||
text = "If you choose to reject sender will NOT be notified",
|
||||
confirmText = "Accept",
|
||||
onConfirm = {
|
||||
withApi {
|
||||
val contact = chatModel.controller.apiAcceptContactRequest(contactRequest.apiId)
|
||||
if (contact != null) {
|
||||
val chat = Chat(ChatInfo.Direct(contact), listOf())
|
||||
chatModel.replaceChat(contactRequest.id, chat)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissText = "Reject",
|
||||
onDismiss = {
|
||||
withApi {
|
||||
chatModel.controller.apiRejectContactRequest(contactRequest.apiId)
|
||||
chatModel.removeChat(contactRequest.id)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Composable
|
||||
fun ChatListNavLink(chat: Chat, action: () -> Unit) {
|
||||
ChatListNavLinkLayout(
|
||||
content = {
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct -> ChatPreviewView(chat)
|
||||
is ChatInfo.Group -> ChatPreviewView(chat)
|
||||
is ChatInfo.ContactRequest -> ContactRequestView(chat)
|
||||
}
|
||||
},
|
||||
action = action
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatListNavLinkLayout(content: (@Composable () -> Unit), action: () -> Unit) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = action)
|
||||
.height(88.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.padding(start = 8.dp)
|
||||
.padding(end = 12.dp),
|
||||
verticalAlignment = Alignment.Top,
|
||||
// TODO?
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
content.invoke()
|
||||
}
|
||||
}
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Preview
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewChatListNavLinkDirect() {
|
||||
SimpleXTheme {
|
||||
ChatListNavLink(
|
||||
chat = Chat(
|
||||
chatInfo = ChatInfo.Direct.sampleData,
|
||||
chatItems = listOf(
|
||||
ChatItem.getSampleData(
|
||||
1,
|
||||
CIDirection.DirectSnd(),
|
||||
Clock.System.now(),
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
)
|
||||
),
|
||||
chatStats = Chat.ChatStats()
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Preview
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewChatListNavLinkGroup() {
|
||||
SimpleXTheme {
|
||||
ChatListNavLink(
|
||||
chat = Chat(
|
||||
chatInfo = ChatInfo.Group.sampleData,
|
||||
chatItems = listOf(
|
||||
ChatItem.getSampleData(
|
||||
1,
|
||||
CIDirection.DirectSnd(),
|
||||
Clock.System.now(),
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
)
|
||||
),
|
||||
chatStats = Chat.ChatStats()
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Preview
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewChatListNavLinkContactRequest() {
|
||||
SimpleXTheme {
|
||||
ChatListNavLink(
|
||||
chat = Chat(
|
||||
chatInfo = ChatInfo.ContactRequest.sampleData,
|
||||
chatItems = listOf(),
|
||||
chatStats = Chat.ChatStats()
|
||||
),
|
||||
action = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,15 +14,12 @@ 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.ExperimentalTextApi
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import chat.simplex.app.Pages
|
||||
import chat.simplex.app.model.Chat
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.views.chat.ChatHelpView
|
||||
import chat.simplex.app.views.helpers.withApi
|
||||
import chat.simplex.app.views.newchat.NewChatSheet
|
||||
import chat.simplex.app.views.usersettings.SettingsView
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
@@ -67,6 +64,7 @@ fun scaffoldController(): ScaffoldController {
|
||||
return ctrl
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@DelicateCoroutinesApi
|
||||
@ExperimentalPermissionsApi
|
||||
@ExperimentalMaterialApi
|
||||
@@ -180,29 +178,16 @@ fun ChatListToolbar(scaffoldCtrl: ScaffoldController) {
|
||||
}
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
fun goToChat(chatPreview: Chat, chatModel: ChatModel, navController: NavController) {
|
||||
withApi {
|
||||
val cInfo = chatPreview.chatInfo
|
||||
val chat = chatModel.controller.apiGetChat(cInfo.chatType, cInfo.apiId)
|
||||
if (chat != null) {
|
||||
chatModel.chatId.value = cInfo.id
|
||||
chatModel.chatItems = chat.chatItems.toMutableStateList()
|
||||
navController.navigate(Pages.Chat.route)
|
||||
} else {
|
||||
// TODO show error? or will apiGetChat show it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@DelicateCoroutinesApi
|
||||
@Composable
|
||||
fun ChatList(chatModel: ChatModel, navController: NavController) {
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
items(chatModel.chats) { chat ->
|
||||
ChatPreviewView(chat) { goToChat(chat, chatModel, navController) }
|
||||
ChatListNavLinkView(chat, chatModel, navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +1,76 @@
|
||||
package chat.simplex.app.views.chatlist
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.ExperimentalTextApi
|
||||
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 androidx.compose.ui.unit.sp
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.model.Chat
|
||||
import chat.simplex.app.model.getTimestampText
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chat.item.MarkdownText
|
||||
import chat.simplex.app.views.helpers.ChatInfoImage
|
||||
import chat.simplex.app.views.helpers.badgeLayout
|
||||
import kotlinx.datetime.Clock
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Composable
|
||||
fun ChatPreviewView(chat: Chat, goToChat: () -> Unit) {
|
||||
Surface(
|
||||
border = BorderStroke(0.5.dp, MaterialTheme.colors.secondary),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = goToChat)
|
||||
.height(88.dp)
|
||||
) {
|
||||
Row(
|
||||
fun ChatPreviewView(chat: Chat) {
|
||||
Row {
|
||||
ChatInfoImage(chat, size = 72.dp)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.padding(start = 8.dp)
|
||||
.padding(end = 12.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
ChatInfoImage(chat, size = 72.dp)
|
||||
Column(modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.weight(1F)) {
|
||||
Text(
|
||||
chat.chatInfo.chatViewName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
if (chat.chatItems.count() > 0) {
|
||||
MarkdownText(
|
||||
chat.chatItems.last(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt)
|
||||
Column(Modifier.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Top) {
|
||||
Text(ts,
|
||||
color = HighOrLowlight,
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier.padding(bottom = 5.dp)
|
||||
)
|
||||
.weight(1F)
|
||||
) {
|
||||
Text(
|
||||
chat.chatInfo.chatViewName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
val n = chat.chatStats.unreadCount
|
||||
if (n > 0) {
|
||||
Text(
|
||||
if (n < 1000) "$n" else "${n / 1000}k",
|
||||
color = MaterialTheme.colors.onPrimary,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary, shape = CircleShape)
|
||||
.align(Alignment.End)
|
||||
.badgeLayout()
|
||||
.padding(horizontal = 3.dp)
|
||||
.padding(vertical = 1.dp)
|
||||
)
|
||||
}
|
||||
if (chat.chatItems.count() > 0) {
|
||||
MarkdownText(
|
||||
chat.chatItems.last(),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
val ts = chat.chatItems.lastOrNull()?.timestampText ?: getTimestampText(chat.chatInfo.createdAt)
|
||||
Column(
|
||||
Modifier.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Text(
|
||||
ts,
|
||||
color = HighOrLowlight,
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier.padding(bottom = 5.dp)
|
||||
)
|
||||
val n = chat.chatStats.unreadCount
|
||||
if (n > 0) {
|
||||
Text(
|
||||
if (n < 1000) "$n" else "${n / 1000}k",
|
||||
color = MaterialTheme.colors.onPrimary,
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary, shape = CircleShape)
|
||||
.align(Alignment.End)
|
||||
.badgeLayout()
|
||||
.padding(horizontal = 3.dp)
|
||||
.padding(vertical = 1.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalTextApi
|
||||
@Preview
|
||||
@Composable
|
||||
fun ChatPreviewViewExample() {
|
||||
SimpleXTheme {
|
||||
ChatPreviewView(
|
||||
chat = Chat(
|
||||
chatInfo = ChatInfo.Direct.sampleData,
|
||||
chatItems = listOf(ChatItem.getSampleData(
|
||||
1,
|
||||
CIDirection.DirectSnd(),
|
||||
Clock.System.now(),
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
)),
|
||||
chatStats = Chat.ChatStats(unreadCount = 3)
|
||||
),
|
||||
goToChat = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package chat.simplex.app.views.chatlist
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.model.Chat
|
||||
import chat.simplex.app.model.getTimestampText
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.views.helpers.ChatInfoImage
|
||||
|
||||
@Composable
|
||||
fun ContactRequestView(chat: Chat) {
|
||||
Row {
|
||||
ChatInfoImage(chat, size = 72.dp)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.weight(1F)
|
||||
) {
|
||||
Text(
|
||||
chat.chatInfo.chatViewName,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.primary
|
||||
)
|
||||
Text(
|
||||
"wants to connect to you!",
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
val ts = getTimestampText(chat.chatInfo.createdAt)
|
||||
Column(
|
||||
Modifier.fillMaxHeight(),
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
Text(
|
||||
ts,
|
||||
color = HighOrLowlight,
|
||||
style = MaterialTheme.typography.body2,
|
||||
modifier = Modifier.padding(bottom = 5.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package chat.simplex.app.views.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
fun shareText(cxt: Context, text: String) {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
cxt.startActivity(shareIntent)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package chat.simplex.app.views.newchat
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
@@ -22,6 +21,7 @@ import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.ui.theme.SimpleButton
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.helpers.CloseSheetBar
|
||||
import chat.simplex.app.views.helpers.shareText
|
||||
|
||||
@Composable
|
||||
fun AddContactView(chatModel: ChatModel, nav: NavController) {
|
||||
@@ -40,54 +40,53 @@ fun AddContactView(chatModel: ChatModel, nav: NavController) {
|
||||
fun AddContactLayout(connReq: String, close: () -> Unit, share: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colors.background),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.background(MaterialTheme.colors.background)
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
CloseSheetBar(close)
|
||||
Text(
|
||||
"Add contact",
|
||||
style = MaterialTheme.typography.h1,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Text(
|
||||
"Show QR code to your contact\nto scan from the app",
|
||||
style = MaterialTheme.typography.h2,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
QRCode(connReq)
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append("If you cannot meet in person, you can ")
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) {
|
||||
append("If you cannot meet in person, you can ")
|
||||
}
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) {
|
||||
append("scan QR code in the video call")
|
||||
}
|
||||
append(", or you can share the invitation link via any other channel.")
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) {
|
||||
append(", or you can share the invitation link via any other channel.")
|
||||
}
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 8.dp)
|
||||
.padding(bottom = 16.dp)
|
||||
)
|
||||
SimpleButton("Share invitation link", icon = Icons.Outlined.Share, click = share)
|
||||
}
|
||||
}
|
||||
|
||||
fun shareText(cxt: Context, text: String) {
|
||||
val sendIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_TEXT, text)
|
||||
type = "text/plain"
|
||||
}
|
||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||
cxt.startActivity(shareIntent)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewAddContactView() {
|
||||
SimpleXTheme {
|
||||
|
||||
+27
-13
@@ -1,5 +1,6 @@
|
||||
package chat.simplex.app.views.newchat
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
@@ -30,7 +31,7 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) {
|
||||
withUriAction(chatModel, uri) { action ->
|
||||
connectViaUri(chatModel, action, uri)
|
||||
}
|
||||
} catch(e: RuntimeException) {
|
||||
} catch (e: RuntimeException) {
|
||||
chatModel.alertManager.showAlertMsg(
|
||||
title = "Invalid QR code",
|
||||
text = "This QR code is not a link!"
|
||||
@@ -44,8 +45,10 @@ fun ConnectContactView(chatModel: ChatModel, nav: NavController) {
|
||||
}
|
||||
|
||||
@DelicateCoroutinesApi
|
||||
fun withUriAction(chatModel: ChatModel, uri: Uri,
|
||||
run: suspend (String) -> Unit) {
|
||||
fun withUriAction(
|
||||
chatModel: ChatModel, uri: Uri,
|
||||
run: suspend (String) -> Unit
|
||||
) {
|
||||
val action = uri.path?.drop(1)
|
||||
if (action == "contact" || action == "invitation") {
|
||||
withApi { run(action) }
|
||||
@@ -74,46 +77,57 @@ suspend fun connectViaUri(chatModel: ChatModel, action: String, uri: Uri) {
|
||||
fun ConnectContactLayout(qrCodeScanner: @Composable () -> Unit, close: () -> Unit) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colors.background),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.background(MaterialTheme.colors.background)
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
CloseSheetBar(close)
|
||||
Text(
|
||||
"Scan QR code",
|
||||
style = MaterialTheme.typography.h1,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Text(
|
||||
"Your chat profile will be sent\nto your contact",
|
||||
style = MaterialTheme.typography.h2,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
color = MaterialTheme.colors.onBackground,
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
)
|
||||
Box (
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(ratio = 1F)
|
||||
) { qrCodeScanner() }
|
||||
Text(
|
||||
buildAnnotatedString {
|
||||
append("If you cannot meet in person, you can ")
|
||||
withStyle(SpanStyle(fontWeight = FontWeight.Bold)) {
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) {
|
||||
append("If you cannot meet in person, you can ")
|
||||
}
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground, fontWeight = FontWeight.Bold)) {
|
||||
append("scan QR code in the video call")
|
||||
}
|
||||
append(", or you can create the invitation link.")
|
||||
withStyle(SpanStyle(color = MaterialTheme.colors.onBackground)) {
|
||||
append(", or you can create the invitation link.")
|
||||
}
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.caption,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(top = 16.dp)
|
||||
.padding(top = 4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewConnectContactLayout() {
|
||||
SimpleXTheme {
|
||||
|
||||
@@ -21,7 +21,6 @@ import chat.simplex.app.Pages
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.model.Profile
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
|
||||
@Composable
|
||||
@@ -49,6 +48,7 @@ fun SettingsLayout(
|
||||
.fillMaxSize()
|
||||
// .background(MaterialTheme.colors.background)
|
||||
.padding(8.dp)
|
||||
.padding(top = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
"Your Settings",
|
||||
@@ -87,15 +87,15 @@ fun SettingsLayout(
|
||||
Icon(
|
||||
Icons.Outlined.QrCode,
|
||||
contentDescription = "Address",
|
||||
tint = HighOrLowlight,
|
||||
tint = MaterialTheme.colors.onBackground,
|
||||
)
|
||||
Spacer(Modifier.padding(horizontal = 4.dp))
|
||||
Text(
|
||||
"Your SimpleX contact address",
|
||||
color = HighOrLowlight
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
},
|
||||
func = { println("navigate to address") }
|
||||
func = { navigate(Pages.UserAddress.route) }
|
||||
)
|
||||
Spacer(Modifier.height(24.dp))
|
||||
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package chat.simplex.app.views.usersettings
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
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.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.ui.theme.SimpleButton
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.QRCode
|
||||
|
||||
@Composable
|
||||
fun UserAddressView(chatModel: ChatModel, nav: NavController) {
|
||||
val cxt = LocalContext.current
|
||||
UserAddressLayout(
|
||||
userAddress = chatModel.userAddress.value,
|
||||
back = { nav.popBackStack() },
|
||||
createAddress = {
|
||||
withApi {
|
||||
chatModel.userAddress.value = chatModel.controller.apiCreateUserAddress()
|
||||
}
|
||||
},
|
||||
share = { userAddress: String -> shareText(cxt, userAddress) },
|
||||
deleteAddress = {
|
||||
chatModel.alertManager.showAlertMsg(
|
||||
title = "Delete address?",
|
||||
text = "All your contacts will remain connected",
|
||||
confirmText = "Delete",
|
||||
onConfirm = {
|
||||
withApi {
|
||||
chatModel.controller.apiDeleteUserAddress()
|
||||
chatModel.userAddress.value = null
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserAddressLayout(
|
||||
userAddress: String?,
|
||||
back: () -> Unit,
|
||||
createAddress: () -> Unit,
|
||||
share: (String) -> Unit,
|
||||
deleteAddress: () -> Unit
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(MaterialTheme.colors.background)
|
||||
.padding(horizontal = 8.dp),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.Top
|
||||
) {
|
||||
CloseSheetBar(back)
|
||||
Text(
|
||||
"Your chat address",
|
||||
Modifier.padding(bottom = 24.dp),
|
||||
style = MaterialTheme.typography.h1,
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Text(
|
||||
"You can share your address as a link or as a QR code - anybody will be able to connect to you, " +
|
||||
"and if you later delete it - you won't lose your contacts.",
|
||||
Modifier.padding(bottom = 24.dp),
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
if (userAddress == null) {
|
||||
SimpleButton("Create address", icon = Icons.Outlined.QrCode, click = createAddress)
|
||||
} else {
|
||||
QRCode(userAddress)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
SimpleButton(
|
||||
"Share link",
|
||||
icon = Icons.Outlined.Share,
|
||||
click = { share(userAddress) })
|
||||
SimpleButton(
|
||||
"Delete address",
|
||||
icon = Icons.Outlined.Delete,
|
||||
color = Color.Red,
|
||||
click = deleteAddress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewUserAddressLayoutNoAddress() {
|
||||
SimpleXTheme {
|
||||
UserAddressLayout(
|
||||
userAddress = null,
|
||||
back = {},
|
||||
createAddress = {},
|
||||
share = { _ -> },
|
||||
deleteAddress = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
@Composable
|
||||
fun PreviewUserAddressLayoutAddressCreated() {
|
||||
SimpleXTheme {
|
||||
UserAddressLayout(
|
||||
userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D",
|
||||
back = {},
|
||||
createAddress = {},
|
||||
share = { _ -> },
|
||||
deleteAddress = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -179,7 +179,6 @@ fun UserProfileLayout(
|
||||
showBackground = true,
|
||||
name = "Dark Mode"
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PreviewUserProfileLayoutEditOff() {
|
||||
SimpleXTheme {
|
||||
|
||||
@@ -66,7 +66,11 @@ struct UserAddress_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let chatModel = ChatModel()
|
||||
chatModel.userAddress = "https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
|
||||
return UserAddress()
|
||||
.environmentObject(chatModel)
|
||||
return Group {
|
||||
UserAddress()
|
||||
.environmentObject(chatModel)
|
||||
UserAddress()
|
||||
.environmentObject(ChatModel())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user