mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 20:08:57 +00:00
android, desktop: allow for chat profile selection on new chat screen (#4741)
* add api and types * basic ui * add search on profiles * profile images on select chat profile * incognito adjustments * basic api connection * handling errors * add loading state * header to scroll * selected profile on top (profile or incognito) * adjust share profile copy * avoid list moving around on selection commit * bigger profile pick * info icon interactive area * thumbs to match contacts list size * incognito sizes matching icons * title to section padding * add chevron * align borders and other chevron icon * prevent click on self * only prevent selection * update * selectable item area * no need for oninfo to be composable * simplify * wrap apis in try * remove redundant derivedStateOf * closure fns capital naming * simplify current user null check --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
043a4ed915
commit
e582d2d742
+2
-1
@@ -784,7 +784,8 @@ object ChatModel {
|
||||
data class ShowingInvitation(
|
||||
val connId: String,
|
||||
val connReq: String,
|
||||
val connChatUsed: Boolean
|
||||
val connChatUsed: Boolean,
|
||||
val conn: PendingContactConnection
|
||||
)
|
||||
|
||||
enum class ChatType(val type: String) {
|
||||
|
||||
+32
-3
@@ -1132,9 +1132,30 @@ object ChatController {
|
||||
|
||||
suspend fun apiSetConnectionIncognito(rh: Long?, connId: Long, incognito: Boolean): PendingContactConnection? {
|
||||
val r = sendCmd(rh, CC.ApiSetConnectionIncognito(connId, incognito))
|
||||
if (r is CR.ConnectionIncognitoUpdated) return r.toConnection
|
||||
Log.e(TAG, "apiSetConnectionIncognito bad response: ${r.responseType} ${r.details}")
|
||||
return null
|
||||
|
||||
return when (r) {
|
||||
is CR.ConnectionIncognitoUpdated -> r.toConnection
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiSetConnectionIncognito", generalGetString(MR.strings.error_sending_message), r)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiChangeConnectionUser(rh: Long?, connId: Long, userId: Long): PendingContactConnection? {
|
||||
val r = sendCmd(rh, CC.ApiChangeConnectionUser(connId, userId))
|
||||
|
||||
return when (r) {
|
||||
is CR.ConnectionUserChanged -> r.toConnection
|
||||
else -> {
|
||||
if (!(networkErrorAlert(r))) {
|
||||
apiErrorAlert("apiChangeConnectionUser", generalGetString(MR.strings.error_sending_message), r)
|
||||
}
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun apiConnectPlan(rh: Long?, connReq: String): ConnectionPlan? {
|
||||
@@ -2916,6 +2937,7 @@ sealed class CC {
|
||||
class APIVerifyGroupMember(val groupId: Long, val groupMemberId: Long, val connectionCode: String?): CC()
|
||||
class APIAddContact(val userId: Long, val incognito: Boolean): CC()
|
||||
class ApiSetConnectionIncognito(val connId: Long, val incognito: Boolean): CC()
|
||||
class ApiChangeConnectionUser(val connId: Long, val userId: Long): CC()
|
||||
class APIConnectPlan(val userId: Long, val connReq: String): CC()
|
||||
class APIConnect(val userId: Long, val incognito: Boolean, val connReq: String): CC()
|
||||
class ApiConnectContactViaAddress(val userId: Long, val incognito: Boolean, val contactId: Long): CC()
|
||||
@@ -3071,6 +3093,7 @@ sealed class CC {
|
||||
is APIVerifyGroupMember -> "/_verify code #$groupId $groupMemberId" + if (connectionCode != null) " $connectionCode" else ""
|
||||
is APIAddContact -> "/_connect $userId incognito=${onOff(incognito)}"
|
||||
is ApiSetConnectionIncognito -> "/_set incognito :$connId ${onOff(incognito)}"
|
||||
is ApiChangeConnectionUser -> "/_set conn user :$connId $userId"
|
||||
is APIConnectPlan -> "/_connect plan $userId $connReq"
|
||||
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} $connReq"
|
||||
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
|
||||
@@ -3213,6 +3236,7 @@ sealed class CC {
|
||||
is APIVerifyGroupMember -> "apiVerifyGroupMember"
|
||||
is APIAddContact -> "apiAddContact"
|
||||
is ApiSetConnectionIncognito -> "apiSetConnectionIncognito"
|
||||
is ApiChangeConnectionUser -> "apiChangeConnectionUser"
|
||||
is APIConnectPlan -> "apiConnectPlan"
|
||||
is APIConnect -> "apiConnect"
|
||||
is ApiConnectContactViaAddress -> "apiConnectContactViaAddress"
|
||||
@@ -4757,6 +4781,7 @@ sealed class CR {
|
||||
@Serializable @SerialName("connectionVerified") class ConnectionVerified(val user: UserRef, val verified: Boolean, val expectedCode: String): CR()
|
||||
@Serializable @SerialName("invitation") class Invitation(val user: UserRef, val connReqInvitation: String, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionIncognitoUpdated") class ConnectionIncognitoUpdated(val user: UserRef, val toConnection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("connectionUserChanged") class ConnectionUserChanged(val user: UserRef, val fromConnection: PendingContactConnection, val toConnection: PendingContactConnection, val newUser: UserRef): CR()
|
||||
@Serializable @SerialName("connectionPlan") class CRConnectionPlan(val user: UserRef, val connectionPlan: ConnectionPlan): CR()
|
||||
@Serializable @SerialName("sentConfirmation") class SentConfirmation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@Serializable @SerialName("sentInvitation") class SentInvitation(val user: UserRef, val connection: PendingContactConnection): CR()
|
||||
@@ -4935,6 +4960,7 @@ sealed class CR {
|
||||
is ConnectionVerified -> "connectionVerified"
|
||||
is Invitation -> "invitation"
|
||||
is ConnectionIncognitoUpdated -> "connectionIncognitoUpdated"
|
||||
is ConnectionUserChanged -> "ConnectionUserChanged"
|
||||
is CRConnectionPlan -> "connectionPlan"
|
||||
is SentConfirmation -> "sentConfirmation"
|
||||
is SentInvitation -> "sentInvitation"
|
||||
@@ -5103,6 +5129,7 @@ sealed class CR {
|
||||
is ConnectionVerified -> withUser(user, "verified: $verified\nconnectionCode: $expectedCode")
|
||||
is Invitation -> withUser(user, "connReqInvitation: $connReqInvitation\nconnection: $connection")
|
||||
is ConnectionIncognitoUpdated -> withUser(user, json.encodeToString(toConnection))
|
||||
is ConnectionUserChanged -> withUser(user, "fromConnection: ${json.encodeToString(fromConnection)}\ntoConnection: ${json.encodeToString(toConnection)}\nnewUser: ${json.encodeToString(newUser)}" )
|
||||
is CRConnectionPlan -> withUser(user, json.encodeToString(connectionPlan))
|
||||
is SentConfirmation -> withUser(user, json.encodeToString(connection))
|
||||
is SentInvitation -> withUser(user, json.encodeToString(connection))
|
||||
@@ -5553,6 +5580,7 @@ sealed class ChatErrorType {
|
||||
is AgentCommandError -> "agentCommandError"
|
||||
is InvalidFileDescription -> "invalidFileDescription"
|
||||
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
||||
is ConnectionUserChangeProhibited -> "connectionUserChangeProhibited"
|
||||
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
|
||||
is InternalError -> "internalError"
|
||||
is CEException -> "exception $message"
|
||||
@@ -5630,6 +5658,7 @@ sealed class ChatErrorType {
|
||||
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
||||
@Serializable @SerialName("connectionUserChangeProhibited") object ConnectionUserChangeProhibited: ChatErrorType()
|
||||
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
|
||||
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
||||
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ fun ContactConnectionInfoView(
|
||||
) {
|
||||
LaunchedEffect(connReqInvitation) {
|
||||
if (connReqInvitation != null) {
|
||||
chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(contactConnection.id, connReqInvitation, false, conn = contactConnection)
|
||||
}
|
||||
}
|
||||
/** When [AddContactLearnMore] is open, we don't need to drop [ChatModel.showingInvitation].
|
||||
|
||||
+322
-16
@@ -2,20 +2,25 @@ package chat.simplex.common.views.newchat
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionItemView
|
||||
import SectionSpacer
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import TextIconSpaced
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -32,6 +37,7 @@ import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.usersettings.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URI
|
||||
|
||||
@@ -43,7 +49,7 @@ enum class NewChatOption {
|
||||
fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRCodeScanner: Boolean = false, close: () -> Unit) {
|
||||
val selection = remember { stateGetOrPut("selection") { selection } }
|
||||
val showQRCodeScanner = remember { stateGetOrPut("showQRCodeScanner") { showQRCodeScanner } }
|
||||
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(null) }
|
||||
val contactConnection: MutableState<PendingContactConnection?> = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(chatModel.showingInvitation.value?.conn) }
|
||||
val connReqInvitation by remember { derivedStateOf { chatModel.showingInvitation.value?.connReq ?: "" } }
|
||||
val creatingConnReq = rememberSaveable { mutableStateOf(false) }
|
||||
val pastedLink = rememberSaveable { mutableStateOf("") }
|
||||
@@ -177,6 +183,15 @@ private fun CreatingLinkProgressView() {
|
||||
DefaultProgressView(stringResource(MR.strings.creating_link))
|
||||
}
|
||||
|
||||
private fun updateShownConnection(conn: PendingContactConnection) {
|
||||
chatModel.showingInvitation.value = chatModel.showingInvitation.value?.copy(
|
||||
conn = conn,
|
||||
connId = conn.id,
|
||||
connReq = conn.connReqInv ?: "",
|
||||
connChatUsed = true
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RetryButton(onClick: () -> Unit) {
|
||||
Column(
|
||||
@@ -192,6 +207,248 @@ private fun RetryButton(onClick: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProfilePickerOption(
|
||||
title: String,
|
||||
selected: Boolean,
|
||||
disabled: Boolean,
|
||||
onSelected: () -> Unit,
|
||||
image: @Composable () -> Unit,
|
||||
onInfo: (() -> Unit)? = null
|
||||
) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.sizeIn(minHeight = DEFAULT_MIN_SECTION_ITEM_HEIGHT + 8.dp)
|
||||
.clickable(enabled = !disabled, onClick = onSelected)
|
||||
.padding(horizontal = DEFAULT_PADDING, vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
image()
|
||||
TextIconSpaced(false)
|
||||
Text(title, modifier = Modifier.align(Alignment.CenterVertically))
|
||||
if (onInfo != null) {
|
||||
Spacer(Modifier.padding(6.dp))
|
||||
Column(Modifier
|
||||
.size(48.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable(
|
||||
enabled = !disabled,
|
||||
onClick = { ModalManager.start.showModal { IncognitoView() } }
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
painterResource(MR.images.ic_info),
|
||||
stringResource(MR.strings.incognito),
|
||||
tint = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (selected) {
|
||||
Icon(
|
||||
painterResource(
|
||||
MR.images.ic_check
|
||||
),
|
||||
title,
|
||||
Modifier.size(20.dp),
|
||||
tint = MaterialTheme.colors.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Divider(
|
||||
Modifier.padding(
|
||||
start = DEFAULT_PADDING_HALF,
|
||||
end = DEFAULT_PADDING_HALF,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun filteredProfiles(users: List<User>, searchTextOrPassword: String): List<User> {
|
||||
val s = searchTextOrPassword.trim()
|
||||
val lower = s.lowercase()
|
||||
return users.filter { u ->
|
||||
if ((u.activeUser || !u.hidden) && (s == "" || u.anyNameContains(lower))) {
|
||||
true
|
||||
} else {
|
||||
correctPassword(u, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ActiveProfilePicker(
|
||||
search: MutableState<String>,
|
||||
contactConnection: PendingContactConnection?,
|
||||
close: () -> Unit,
|
||||
rhId: Long?
|
||||
) {
|
||||
val switchingProfile = remember { mutableStateOf(false) }
|
||||
val incognito = remember {
|
||||
chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get()
|
||||
}
|
||||
val selectedProfile by remember { chatModel.currentUser }
|
||||
val searchTextOrPassword = rememberSaveable { search }
|
||||
val profiles = remember {
|
||||
chatModel.users.map { it.user }.sortedBy { !it.activeUser }
|
||||
}
|
||||
val filteredProfiles by remember {
|
||||
derivedStateOf { filteredProfiles(profiles, searchTextOrPassword.value) }
|
||||
}
|
||||
|
||||
var progressByTimeout by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(switchingProfile.value) {
|
||||
progressByTimeout = if (switchingProfile.value) {
|
||||
delay(500)
|
||||
switchingProfile.value
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProfilePickerUserOption(user: User) {
|
||||
val selected = selectedProfile?.userId == user.userId && !incognito
|
||||
|
||||
ProfilePickerOption(
|
||||
title = user.chatViewName,
|
||||
disabled = switchingProfile.value || selected,
|
||||
selected = selected,
|
||||
onSelected = {
|
||||
switchingProfile.value = true
|
||||
withApi {
|
||||
try {
|
||||
if (contactConnection != null) {
|
||||
val conn = controller.apiChangeConnectionUser(rhId, contactConnection.pccConnId, user.userId)
|
||||
if (conn != null) {
|
||||
withChats {
|
||||
updateContactConnection(rhId, conn)
|
||||
updateShownConnection(conn)
|
||||
}
|
||||
controller.changeActiveUser_(
|
||||
rhId = user.remoteHostId,
|
||||
toUserId = user.userId,
|
||||
viewPwd = if (user.hidden) searchTextOrPassword.value else null
|
||||
)
|
||||
|
||||
if (chatModel.currentUser.value?.userId != user.userId) {
|
||||
AlertManager.shared.showAlertMsg(generalGetString(
|
||||
MR.strings.switching_profile_error_title),
|
||||
String.format(generalGetString(MR.strings.switching_profile_error_message), user.chatViewName)
|
||||
)
|
||||
}
|
||||
|
||||
withChats {
|
||||
updateContactConnection(user.remoteHostId, conn)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
switchingProfile.value = false
|
||||
}
|
||||
}
|
||||
},
|
||||
image = { ProfileImage(size = 42.dp, image = user.image) }
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IncognitoUserOption() {
|
||||
ProfilePickerOption(
|
||||
disabled = switchingProfile.value,
|
||||
title = stringResource(MR.strings.incognito),
|
||||
selected = incognito,
|
||||
onSelected = {
|
||||
if (!incognito) {
|
||||
switchingProfile.value = true
|
||||
withApi {
|
||||
try {
|
||||
if (contactConnection != null) {
|
||||
val conn = controller.apiSetConnectionIncognito(rhId, contactConnection.pccConnId, true)
|
||||
|
||||
if (conn != null) {
|
||||
withChats {
|
||||
updateContactConnection(rhId, conn)
|
||||
updateShownConnection(conn)
|
||||
}
|
||||
close.invoke()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
switchingProfile.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
image = {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(
|
||||
painterResource(MR.images.ic_theater_comedy_filled),
|
||||
contentDescription = stringResource(MR.strings.incognito),
|
||||
Modifier.size(32.dp),
|
||||
tint = Indigo,
|
||||
)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
},
|
||||
onInfo = { ModalManager.start.showModal { IncognitoView() } },
|
||||
)
|
||||
}
|
||||
|
||||
BoxWithConstraints {
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.alpha(if (progressByTimeout) 0.6f else 1f)
|
||||
) {
|
||||
LazyColumnWithScrollBar(userScrollEnabled = !switchingProfile.value) {
|
||||
item {
|
||||
AppBarTitle(stringResource(MR.strings.select_chat_profile), hostDevice(rhId), bottomPadding = DEFAULT_PADDING)
|
||||
}
|
||||
val activeProfile = filteredProfiles.firstOrNull { it.activeUser }
|
||||
|
||||
if (activeProfile != null) {
|
||||
val otherProfiles = filteredProfiles.filter { it.userId != activeProfile.userId }
|
||||
|
||||
if (incognito) {
|
||||
item {
|
||||
IncognitoUserOption()
|
||||
}
|
||||
item {
|
||||
ProfilePickerUserOption(activeProfile)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
ProfilePickerUserOption(activeProfile)
|
||||
}
|
||||
item {
|
||||
IncognitoUserOption()
|
||||
}
|
||||
}
|
||||
|
||||
itemsIndexed(otherProfiles) { _, p ->
|
||||
ProfilePickerUserOption(p)
|
||||
}
|
||||
} else {
|
||||
item {
|
||||
IncognitoUserOption()
|
||||
}
|
||||
itemsIndexed(filteredProfiles) { _, p ->
|
||||
ProfilePickerUserOption(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (progressByTimeout) {
|
||||
DefaultProgressView("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection: MutableState<PendingContactConnection?>) {
|
||||
SectionView(stringResource(MR.strings.share_this_1_time_link).uppercase(), headerBottomPadding = 5.dp) {
|
||||
@@ -204,23 +461,72 @@ private fun InviteView(rhId: Long?, connReqInvitation: String, contactConnection
|
||||
SimpleXLinkQRCode(connReqInvitation, onShare = { chatModel.markShowingInvitationUsed() })
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(10.dp))
|
||||
val incognito = remember { mutableStateOf(controller.appPrefs.incognito.get()) }
|
||||
IncognitoToggle(controller.appPrefs.incognito, incognito) {
|
||||
ModalManager.start.showModal { IncognitoView() }
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
val incognito by remember(chatModel.showingInvitation.value?.conn?.incognito, controller.appPrefs.incognito.get()) {
|
||||
derivedStateOf {
|
||||
chatModel.showingInvitation.value?.conn?.incognito ?: controller.appPrefs.incognito.get()
|
||||
}
|
||||
}
|
||||
KeyChangeEffect(incognito.value) {
|
||||
withBGApi {
|
||||
val contactConn = contactConnection.value ?: return@withBGApi
|
||||
val conn = controller.apiSetConnectionIncognito(rhId, contactConn.pccConnId, incognito.value) ?: return@withBGApi
|
||||
withChats {
|
||||
contactConnection.value = conn
|
||||
updateContactConnection(rhId, conn)
|
||||
val currentUser = remember { chatModel.currentUser }.value
|
||||
|
||||
if (currentUser != null) {
|
||||
SectionView(stringResource(MR.strings.new_chat_share_profile).uppercase(), headerBottomPadding = 5.dp) {
|
||||
SectionItemView(
|
||||
padding = PaddingValues(
|
||||
top = 0.dp,
|
||||
bottom = 0.dp,
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
),
|
||||
click = {
|
||||
ModalManager.start.showCustomModal { close ->
|
||||
val search = rememberSaveable { mutableStateOf("") }
|
||||
ModalView(
|
||||
{ close() },
|
||||
endButtons = {
|
||||
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
||||
},
|
||||
content = {
|
||||
ActiveProfilePicker(
|
||||
search = search,
|
||||
close = close,
|
||||
rhId = rhId,
|
||||
contactConnection = contactConnection.value
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
) {
|
||||
if (incognito) {
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Icon(
|
||||
painterResource(MR.images.ic_theater_comedy_filled),
|
||||
contentDescription = stringResource(MR.strings.incognito),
|
||||
tint = Indigo,
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
Spacer(Modifier.width(2.dp))
|
||||
} else {
|
||||
ProfileImage(size = 42.dp, image = currentUser.image)
|
||||
}
|
||||
TextIconSpaced(false)
|
||||
Text(
|
||||
text = if (incognito) stringResource(MR.strings.incognito) else currentUser.chatViewName,
|
||||
color = MaterialTheme.colors.onBackground
|
||||
)
|
||||
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.End) {
|
||||
Icon(
|
||||
painter = painterResource(MR.images.ic_arrow_forward_ios),
|
||||
contentDescription = stringResource(MR.strings.new_chat_share_profile),
|
||||
tint = MaterialTheme.colors.secondary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
chatModel.markShowingInvitationUsed()
|
||||
if (incognito) {
|
||||
SectionTextFooter(generalGetString(MR.strings.connect__a_new_random_profile_will_be_shared))
|
||||
}
|
||||
}
|
||||
SectionTextFooter(sharedProfileInfo(chatModel, incognito.value))
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -366,7 +672,7 @@ private fun createInvitation(
|
||||
if (r != null) {
|
||||
withChats {
|
||||
updateContactConnection(rhId, r.second)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false)
|
||||
chatModel.showingInvitation.value = ShowingInvitation(connId = r.second.id, connReq = simplexChatLink(r.first), connChatUsed = false, conn = r.second)
|
||||
contactConnection.value = r.second
|
||||
}
|
||||
} else {
|
||||
|
||||
+2
-2
@@ -303,7 +303,7 @@ private fun ProfileActionView(action: UserProfileAction, user: User, doAction: (
|
||||
}
|
||||
}
|
||||
|
||||
private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List<User> {
|
||||
fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List<User> {
|
||||
val s = searchTextOrPassword.trim()
|
||||
val lower = s.lowercase()
|
||||
return m.users.filter { u ->
|
||||
@@ -317,7 +317,7 @@ private fun filteredUsers(m: ChatModel, searchTextOrPassword: String): List<User
|
||||
|
||||
private fun visibleUsersCount(m: ChatModel): Int = m.users.filter { u -> !u.user.hidden }.size
|
||||
|
||||
private fun correctPassword(user: User, pwd: String): Boolean {
|
||||
fun correctPassword(user: User, pwd: String): Boolean {
|
||||
val ph = user.viewPwdHash
|
||||
return ph != null && pwd != "" && chatPasswordHash(pwd, ph.salt) == ph.hash
|
||||
}
|
||||
|
||||
@@ -665,6 +665,10 @@
|
||||
<string name="one_time_link_short">1-time link</string>
|
||||
<string name="simplex_address">SimpleX address</string>
|
||||
<string name="or_show_this_qr_code">Or show this code</string>
|
||||
<string name="new_chat_share_profile">Share profile</string>
|
||||
<string name="select_chat_profile">Select chat profile</string>
|
||||
<string name="switching_profile_error_title">Error switching profile</string>
|
||||
<string name="switching_profile_error_message">Your connection was moved to %s but an unexpected error occurred while redirecting you to the profile.</string>
|
||||
<string name="or_scan_qr_code">Or scan QR code</string>
|
||||
<string name="keep_unused_invitation_question">Keep unused invitation?</string>
|
||||
<string name="you_can_view_invitation_link_again">You can view invitation link again in connection details.</string>
|
||||
|
||||
Reference in New Issue
Block a user