From 1edf60362e111fb79820929ba465f43bae72a835 Mon Sep 17 00:00:00 2001
From: Efim Poberezkin <8711996+efim-poberezkin@users.noreply.github.com>
Date: Tue, 22 Feb 2022 00:09:51 +0400
Subject: [PATCH] android: UserProfileView (#341)
* android: update user profile view logic
* indentation
* format
* UserProfileView
* remove prints
* empty line
* undo format
* change by value
* separate layout
* layout
* unconditionally editProfile = false
* add header and close button to profile page, add links to "settings"
* use generic navigate in settings, remove terminal button from the list of chats
Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
---
.../.idea/codeStyles/codeStyleConfig.xml | 1 +
.../java/chat/simplex/app/MainActivity.kt | 20 +-
.../java/chat/simplex/app/model/ChatModel.kt | 13 +-
.../java/chat/simplex/app/model/SimpleXAPI.kt | 23 ++-
.../chat/simplex/app/views/chat/ChatView.kt | 4 +-
.../app/views/chatlist/ChatListView.kt | 19 +-
.../app/views/usersettings/SettingsView.kt | 115 ++++++-----
.../app/views/usersettings/UserProfileView.kt | 191 ++++++++++++++++++
8 files changed, 306 insertions(+), 80 deletions(-)
create mode 100644 apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt
diff --git a/apps/android/.idea/codeStyles/codeStyleConfig.xml b/apps/android/.idea/codeStyles/codeStyleConfig.xml
index 79ee123c2b..6e6eec1148 100644
--- a/apps/android/.idea/codeStyles/codeStyleConfig.xml
+++ b/apps/android/.idea/codeStyles/codeStyleConfig.xml
@@ -1,5 +1,6 @@
+
\ No newline at end of file
diff --git a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
index ac74e93a0d..ed237c9df2 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/MainActivity.kt
@@ -21,6 +21,8 @@ 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.SettingsView
+import chat.simplex.app.views.usersettings.UserProfileView
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import kotlinx.coroutines.DelicateCoroutinesApi
@@ -42,7 +44,7 @@ class MainActivity: ComponentActivity() {
}
@DelicateCoroutinesApi
-class SimplexViewModel(application: Application) : AndroidViewModel(application) {
+class SimplexViewModel(application: Application): AndroidViewModel(application) {
val chatModel = getApplication().chatModel
}
@@ -103,6 +105,12 @@ fun Navigation(chatModel: ChatModel) {
}
)
) { entry -> DetailView(entry.arguments!!.getLong("identifier"), chatModel.terminalItems, nav) }
+ composable(route = Pages.Settings.route) {
+ SettingsView(chatModel, nav)
+ }
+ composable(route = Pages.UserProfile.route) {
+ UserProfileView(chatModel, nav)
+ }
}
val am = chatModel.alertManager
if (am.presentAlert.value) am.alertView.value?.invoke()
@@ -110,15 +118,17 @@ fun Navigation(chatModel: ChatModel) {
}
sealed class Pages(val route: String) {
- object Home : Pages("home")
- object Terminal : Pages("terminal")
- object Welcome : Pages("welcome")
- object TerminalItemDetails : Pages("details")
+ object Home: Pages("home")
+ object Terminal: Pages("terminal")
+ object Welcome: Pages("welcome")
+ object TerminalItemDetails: Pages("details")
object ChatList: Pages("chats")
object Chat: Pages("chat")
object AddContact: Pages("add_contact")
object Connect: Pages("connect")
object ChatInfo: Pages("chat_info")
+ object Settings: Pages("settings")
+ object UserProfile: Pages("user_profile")
}
@DelicateCoroutinesApi
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 cddc588055..f634e1101b 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
@@ -7,10 +7,6 @@ import chat.simplex.app.SimplexApp
import kotlinx.datetime.*
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(null)
@@ -23,6 +19,13 @@ class ChatModel(val controller: ChatController, val alertManager: SimplexApp.Ale
// set when app is opened via contact or invitation URI
var appOpenUrl = mutableStateOf(null)
+ fun updateUserProfile(profile: Profile) {
+ val user = currentUser.value
+ if (user != null) {
+ currentUser.value = user.copy(profile = profile)
+ }
+ }
+
fun hasChat(id: String): Boolean = chats.firstOrNull() { it.id == id } != null
fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id }
private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id }
@@ -178,7 +181,7 @@ enum class ChatType(val type: String) {
}
@Serializable
-class User (
+data class User(
val userId: Long,
val userContactId: Long,
val localDisplayName: String,
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 c005dfd02e..b513098587 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
@@ -211,25 +211,28 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
fun processReceivedMsg(r: CR) {
chatModel.terminalItems.add(TerminalItem.resp(r))
- when {
- r is CR.ContactConnected -> chatModel.updateContact(r.contact)
-// r is CR.ReceivedContactRequest -> return
- r is CR.ContactUpdated -> {
+ when (r) {
+ is CR.ContactConnected -> {
+ chatModel.updateContact(r.contact)
+ chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected())
+// NtfManager.shared.notifyContactConnected(contact)
+ }
+// is CR.ReceivedContactRequest -> return
+ is CR.ContactUpdated -> {
val cInfo = ChatInfo.Direct(r.toContact)
if (chatModel.hasChat(r.toContact.id)) {
chatModel.updateChatInfo(cInfo)
}
}
-
- r is CR.ContactSubscribed -> {
+ is CR.ContactSubscribed -> {
chatModel.updateContact(r.contact)
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Connected())
}
- r is CR.ContactDisconnected -> {
+ is CR.ContactDisconnected -> {
chatModel.updateContact(r.contact)
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Disconnected())
}
- r is CR.ContactSubError -> {
+ is CR.ContactSubError -> {
chatModel.updateContact(r.contact)
val e = r.chatError
val err: String =
@@ -244,7 +247,7 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
else e.string
chatModel.updateNetworkStatus(r.contact, Chat.NetworkStatus.Error(err))
}
- r is CR.NewChatItem -> {
+ is CR.NewChatItem -> {
val cInfo = r.chatItem.chatInfo
val cItem = r.chatItem.chatItem
chatModel.addChatItem(cInfo, cItem)
@@ -252,8 +255,6 @@ open class ChatController(val ctrl: ChatCtrl, val alertManager: SimplexApp.Alert
}
// switch res {
-// chatModel.updateNetworkStatus(contact, .connected)
-// NtfManager.shared.notifyContactConnected(contact)
// case let .receivedContactRequest(contactRequest):
// chatModel.addChat(Chat(
// chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest),
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
index f5e93b8258..b2bb96331c 100644
--- 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
@@ -118,9 +118,9 @@ fun ChatItemsList(chatItems: List) {
}
}
-@Preview
+@Preview(showBackground = true)
@Composable
-fun PreviewChatViewLayout() {
+fun PreviewChatLayout() {
SimpleXTheme {
val chatItems = listOf(
ChatItem.getSampleData(
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
index 38a8736056..9b52c930ae 100644
--- 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
@@ -63,13 +63,9 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) {
.fillMaxSize()
.background(MaterialTheme.colors.background)
) {
- ChatListToolbar(newChatCtrl)
- Button(
- onClick = { nav.navigate(Pages.Terminal.route) },
- modifier = Modifier.padding(14.dp)
- ) {
- Text("Terminal")
- }
+ ChatListToolbar(
+ newChatCtrl,
+ settings = { nav.navigate(Pages.Settings.route) })
ChatList(chatModel, nav)
}
if (newChatCtrl.state.bottomSheetState.isExpanded) {
@@ -85,7 +81,7 @@ fun ChatListView(chatModel: ChatModel, nav: NavController) {
@ExperimentalMaterialApi
@Composable
-fun ChatListToolbar(newChatSheetCtrl: ScaffoldController) {
+fun ChatListToolbar(newChatSheetCtrl: ScaffoldController, settings: () -> Unit) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
@@ -96,7 +92,9 @@ fun ChatListToolbar(newChatSheetCtrl: ScaffoldController) {
Icons.Outlined.Settings,
"Settings Cog",
tint = MaterialTheme.colors.primary,
- modifier = Modifier.padding(horizontal = 10.dp)
+ modifier = Modifier
+ .padding(horizontal = 10.dp)
+ .clickable(onClick = settings)
)
Text(
"Your chats",
@@ -115,14 +113,13 @@ fun ChatListToolbar(newChatSheetCtrl: 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 = mutableStateOf(cInfo.id)
+ chatModel.chatId.value = cInfo.id
chatModel.chatItems = chat.chatItems.toMutableStateList()
navController.navigate(Pages.Chat.route)
} else {
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
index 527494be95..b0e3fde03e 100644
--- a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/SettingsView.kt
@@ -4,55 +4,78 @@ import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalUriHandler
+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.ChatModel
import chat.simplex.app.model.Profile
+import chat.simplex.app.ui.theme.SimpleXTheme
-//@Preview(showBackground = true)
@Composable
-fun SettingsView(profile: Profile) {
- Column() {
- Text("Your Settings")
- Spacer(Modifier.height(4.dp))
- Text("YOU", style= MaterialTheme.typography.h4)
- Button(
- onClick = { println(profile.displayName) }
- ) {
- Text(profile.displayName)
- }
- Button(
- onClick = { println(profile.hashCode()) }
- ) {
- Text("Your SimpleX contact address", style= MaterialTheme.typography.body1)
- }
- Spacer(Modifier.height(10.dp))
- Text("HELP", style= MaterialTheme.typography.h4)
- Button(
- onClick = { println("navigate to help") }
- ) {
- Text("How to use SimpleX Chat")
- }
- Button(
- onClick = { println("start help chat") }
- ) {
- Text("Get help & advice via chat")
- }
- Button(
- onClick = { println("navigate to email") }
- ) {
- Text("Ask questions via email")
- }
- Spacer(Modifier.height(10.dp))
- Text("DEVELOP", style= MaterialTheme.typography.h4)
- Button(
- onClick = { println("navigate to console") }
- ) {
- Text("Chat console")
- }
- Button(
- onClick = { println("navigate to github") }
- ) {
- Text("Install SimpleX for terminal")
- }
- }
+fun SettingsView(chatModel: ChatModel, nav: NavController) {
+ val user = chatModel.currentUser.value
+ if (user != null) {
+ SettingsLayout(
+ profile = user.profile,
+ back = nav::popBackStack,
+ navigate = nav::navigate
+ )
+ }
}
+val simplexTeamUri = "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D"
+
+@Composable
+fun SettingsLayout(
+ profile: Profile,
+ back: () -> Unit,
+ navigate: (String) -> Unit
+) {
+ val uriHandler = LocalUriHandler.current
+ Column() {
+ Button(onClick = back) {
+ Text("Back")
+ }
+ Text("Your Settings")
+ Spacer(Modifier.height(4.dp))
+
+ Text("YOU", style = MaterialTheme.typography.h4)
+ Button(onClick = { navigate(Pages.UserProfile.route) }) {
+ Text(profile.displayName)
+ }
+
+ Text("HELP", style = MaterialTheme.typography.h4)
+ Button(onClick = { println("navigate to help") }) {
+ Text("How to use SimpleX Chat")
+ }
+ Button(onClick = { uriHandler.openUri(simplexTeamUri) }) {
+ Text("Get help & advice via chat")
+ }
+ Button(onClick = { uriHandler.openUri("mailto:chat@simplex.chat") }) {
+ Text("Ask questions via email")
+ }
+ Spacer(Modifier.height(10.dp))
+
+ Text("DEVELOP", style = MaterialTheme.typography.h4)
+ Button(onClick = { navigate(Pages.Terminal.route) }) {
+ Text("Chat console")
+ }
+ Button(onClick = { uriHandler.openUri("https://github.com/simplex-chat/simplex-chat") }) {
+ Text("Install SimpleX for terminal")
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewSettingsLayout() {
+ SimpleXTheme {
+ SettingsLayout(
+ profile = Profile.sampleData,
+ back = {},
+ navigate = {}
+ )
+ }
+}
diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt
new file mode 100644
index 0000000000..dc646d3246
--- /dev/null
+++ b/apps/android/app/src/main/java/chat/simplex/app/views/usersettings/UserProfileView.kt
@@ -0,0 +1,191 @@
+package chat.simplex.app.views.usersettings
+
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavController
+import chat.simplex.app.model.ChatModel
+import chat.simplex.app.model.Profile
+import chat.simplex.app.ui.theme.SimpleXTheme
+import chat.simplex.app.views.helpers.CloseSheetBar
+import chat.simplex.app.views.helpers.withApi
+
+@Composable
+fun UserProfileView(chatModel: ChatModel, nav: NavController) {
+ val user = chatModel.currentUser.value
+ if (user != null) {
+ var editProfile by remember { mutableStateOf(false) }
+ var profile by remember { mutableStateOf(user.profile) }
+ UserProfileLayout(
+ editProfile = editProfile,
+ profile = profile,
+ back = { nav.popBackStack() },
+ editProfileOff = { editProfile = false },
+ editProfileOn = { editProfile = true },
+ saveProfile = { displayName: String, fullName: String ->
+ withApi {
+ val newProfile = chatModel.controller.apiUpdateProfile(
+ profile = Profile(displayName, fullName)
+ )
+ if (newProfile != null) {
+ chatModel.updateUserProfile(newProfile)
+ profile = newProfile
+ }
+ editProfile = false
+ }
+ }
+ )
+ }
+}
+
+@Composable
+fun UserProfileLayout(
+ editProfile: Boolean,
+ profile: Profile,
+ back: () -> Unit,
+ editProfileOff: () -> Unit,
+ editProfileOn: () -> Unit,
+ saveProfile: (String, String) -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 8.dp)
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.Start
+ ) {
+ CloseSheetBar(back)
+ Text("Your chat profile",
+ Modifier.padding(bottom = 24.dp),
+ style = MaterialTheme.typography.h1
+ )
+ Text(
+ "Your profile is stored on your device and shared only with your contacts.\n" +
+ "SimpleX servers cannot see your profile.",
+ Modifier.padding(bottom = 24.dp)
+ )
+ if (editProfile) {
+ var displayName by remember { mutableStateOf(profile.displayName) }
+ var fullName by remember { mutableStateOf(profile.fullName) }
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.Start
+ ) {
+ // TODO hints
+ BasicTextField(
+ value = displayName,
+ onValueChange = { displayName = it },
+ modifier = Modifier
+ .padding(bottom = 24.dp)
+ .fillMaxWidth(),
+ textStyle = TextStyle(fontSize = 16.sp),
+ keyboardOptions = KeyboardOptions(
+ capitalization = KeyboardCapitalization.None,
+ autoCorrect = false
+ ),
+ singleLine = true
+ )
+ BasicTextField(
+ value = fullName,
+ onValueChange = { fullName = it },
+ modifier = Modifier
+ .padding(bottom = 24.dp)
+ .fillMaxWidth(),
+ textStyle = TextStyle(fontSize = 16.sp),
+ keyboardOptions = KeyboardOptions(
+ capitalization = KeyboardCapitalization.None,
+ autoCorrect = false
+ ),
+ singleLine = true
+ )
+ Row {
+ Text(
+ "Cancel",
+ color = MaterialTheme.colors.primary,
+ modifier = Modifier
+ .clickable(onClick = editProfileOff)
+ )
+ Spacer(Modifier.padding(horizontal = 8.dp))
+ Text(
+ "Save (and notify contacts)",
+ color = MaterialTheme.colors.primary,
+ modifier = Modifier
+ .clickable(onClick = { saveProfile(displayName, fullName) })
+ )
+ }
+ }
+ } else {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.Start
+ ) {
+ Row(
+ Modifier.padding(bottom = 24.dp)
+ ) {
+ Text("Display name:")
+ Spacer(Modifier.padding(horizontal = 4.dp))
+ Text(
+ profile.displayName,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ Row(
+ Modifier.padding(bottom = 24.dp)
+ ) {
+ Text("Full name:")
+ Spacer(Modifier.padding(horizontal = 4.dp))
+ Text(
+ profile.fullName,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ Text(
+ "Edit",
+ color = MaterialTheme.colors.primary,
+ modifier = Modifier
+ .clickable(onClick = editProfileOn)
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewUserProfileLayoutEditOff() {
+ SimpleXTheme {
+ UserProfileLayout(
+ profile = Profile.sampleData,
+ editProfile = false,
+ back = {},
+ editProfileOff = {},
+ editProfileOn = {},
+ saveProfile = { _, _ -> }
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun PreviewUserProfileLayoutEditOn() {
+ SimpleXTheme {
+ UserProfileLayout(
+ profile = Profile.sampleData,
+ editProfile = true,
+ back = {},
+ editProfileOff = {},
+ editProfileOn = {},
+ saveProfile = { _, _ -> }
+ )
+ }
+}