mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-28 18:40:09 +00:00
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>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
739990c732
commit
1edf60362e
@@ -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>
|
||||
@@ -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<SimplexApp>().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
|
||||
|
||||
@@ -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<User?>(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<Uri?>(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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -118,9 +118,9 @@ fun ChatItemsList(chatItems: List<ChatItem>) {
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun PreviewChatViewLayout() {
|
||||
fun PreviewChatLayout() {
|
||||
SimpleXTheme {
|
||||
val chatItems = listOf(
|
||||
ChatItem.getSampleData(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+69
-46
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+191
@@ -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 = { _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user