mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-29 03:18:37 +00:00
kotlin ui
This commit is contained in:
+19
-2
@@ -1294,6 +1294,7 @@ data class User(
|
||||
override val displayName: String get() = profile.displayName
|
||||
override val fullName: String get() = profile.fullName
|
||||
override val shortDescr: String? get() = profile.shortDescr
|
||||
override val profileDescription: String? get() = profile.description
|
||||
override val image: String? get() = profile.image
|
||||
override val localAlias: String = ""
|
||||
|
||||
@@ -1366,6 +1367,9 @@ interface NamedChat {
|
||||
val displayName: String
|
||||
val fullName: String
|
||||
val shortDescr: String?
|
||||
// the full profile description (contacts/business/bot addresses); null for chats that have none.
|
||||
// named distinctly from shortDescr and from PendingContactConnection.description (a connection-status string).
|
||||
val profileDescription: String? get() = null
|
||||
val image: String?
|
||||
val localAlias: String
|
||||
val chatViewName: String
|
||||
@@ -1491,6 +1495,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val displayName get() = contact.displayName
|
||||
override val fullName get() = contact.fullName
|
||||
override val shortDescr get() = contact.profile.shortDescr
|
||||
override val profileDescription get() = contact.profile.description
|
||||
override val image get() = contact.image
|
||||
override val localAlias: String get() = contact.localAlias
|
||||
override fun anyNameContains(searchAnyCase: String): Boolean = contact.anyNameContains(searchAnyCase)
|
||||
@@ -1573,6 +1578,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val displayName get() = contactRequest.displayName
|
||||
override val fullName get() = contactRequest.fullName
|
||||
override val shortDescr get() = contactRequest.profile.shortDescr
|
||||
override val profileDescription get() = contactRequest.profile.description
|
||||
override val image get() = contactRequest.image
|
||||
override val localAlias get() = contactRequest.localAlias
|
||||
|
||||
@@ -1863,6 +1869,7 @@ data class Contact(
|
||||
override val displayName get() = localAlias.ifEmpty { profile.displayName }
|
||||
override val fullName get() = profile.fullName
|
||||
override val shortDescr get() = profile.shortDescr
|
||||
override val profileDescription get() = profile.description
|
||||
override val image get() = profile.image
|
||||
val contactLink: String? = profile.contactLink
|
||||
override val localAlias get() = profile.localAlias
|
||||
@@ -2032,6 +2039,7 @@ data class Profile(
|
||||
override val displayName: String,
|
||||
override val fullName: String,
|
||||
override val shortDescr: String?,
|
||||
val description: String? = null,
|
||||
override val image: String? = null,
|
||||
override val localAlias : String = "",
|
||||
val contactLink: String? = null,
|
||||
@@ -2042,12 +2050,14 @@ data class Profile(
|
||||
val badge: BadgeProof? = null,
|
||||
val contactDomain: SimplexDomainClaim? = null
|
||||
): NamedChat {
|
||||
override val profileDescription: String? get() = description
|
||||
|
||||
val profileViewName: String
|
||||
get() {
|
||||
return if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)"
|
||||
}
|
||||
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
fun toLocalProfile(profileId: Long): LocalProfile = LocalProfile(profileId, displayName, fullName, shortDescr, description, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
|
||||
companion object {
|
||||
val sampleData = Profile(
|
||||
@@ -2064,6 +2074,7 @@ data class LocalProfile(
|
||||
override val displayName: String,
|
||||
override val fullName: String,
|
||||
override val shortDescr: String?,
|
||||
val description: String? = null,
|
||||
override val image: String? = null,
|
||||
override val localAlias: String,
|
||||
val contactLink: String? = null,
|
||||
@@ -2073,9 +2084,11 @@ data class LocalProfile(
|
||||
val contactDomain: SimplexDomainClaim? = null,
|
||||
val contactDomainVerified: Boolean? = null
|
||||
): NamedChat {
|
||||
override val profileDescription: String? get() = description
|
||||
|
||||
val profileViewName: String = localAlias.ifEmpty { if (fullName == "" || displayName == fullName) displayName else "$displayName ($fullName)" }
|
||||
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
fun toProfile(): Profile = Profile(displayName, fullName, shortDescr, description, image, localAlias, contactLink, preferences, peerType, contactDomain = contactDomain)
|
||||
|
||||
companion object {
|
||||
val sampleData = LocalProfile(
|
||||
@@ -2553,6 +2566,7 @@ data class GroupMember (
|
||||
}
|
||||
override val fullName: String get() = memberProfile.fullName
|
||||
override val shortDescr: String? get() = memberProfile.shortDescr
|
||||
override val profileDescription: String? get() = memberProfile.description
|
||||
override val image: String? get() = memberProfile.image
|
||||
val contactLink: String? = memberProfile.contactLink
|
||||
val verified get() = activeConn?.connectionCode != null
|
||||
@@ -4858,6 +4872,8 @@ sealed class Format {
|
||||
@Serializable @SerialName("simplexName") class SimplexName(val nameInfo: SimplexNameInfo): Format()
|
||||
@Serializable @SerialName("command") class Command(val commandStr: String): Format()
|
||||
@Serializable @SerialName("mention") class Mention(val memberName: String): Format()
|
||||
// app-only span (not sent over the wire): a tappable "modal" reference; the renderer resolves modalName to content in the current context
|
||||
@Serializable @SerialName("modal") class Modal(val modalName: String): Format()
|
||||
@Serializable @SerialName("email") class Email: Format()
|
||||
@Serializable @SerialName("phone") class Phone: Format()
|
||||
@Serializable @SerialName("unknown") class Unknown: Format()
|
||||
@@ -4878,6 +4894,7 @@ sealed class Format {
|
||||
is Mention -> SpanStyle(fontWeight = FontWeight.Medium)
|
||||
is Email -> linkStyle
|
||||
is Phone -> linkStyle
|
||||
is Modal -> linkStyle
|
||||
is Unknown -> SpanStyle()
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -370,7 +370,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDesc
|
||||
withBGApi {
|
||||
val rhId = chatModel.remoteHostId()
|
||||
val user = chatModel.controller.apiCreateActiveUser(
|
||||
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image)
|
||||
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image = image)
|
||||
) ?: return@withBGApi
|
||||
chatModel.currentUser.value = user
|
||||
if (chatModel.users.isEmpty()) {
|
||||
@@ -389,7 +389,7 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDesc
|
||||
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||
withBGApi {
|
||||
chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser(
|
||||
null, Profile(displayName.trim(), "", null, null)
|
||||
null, Profile(displayName.trim(), "", shortDescr = null)
|
||||
) ?: return@withBGApi
|
||||
chatModel.localUserCreated.value = true
|
||||
// new users don't need the local file encryption indicator (all files are encrypted); existing users keep it on
|
||||
|
||||
+61
-11
@@ -775,19 +775,69 @@ fun ChatInfoDescription(c: NamedChat, displayName: String, copyNameToClipboard:
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF).combinedClickable(onClick = copyFullName, onLongClick = copyFullName).onRightClick(copyFullName)
|
||||
)
|
||||
}
|
||||
val descr = c.shortDescr?.trim()
|
||||
if (descr != null && descr != "") {
|
||||
ProfileDescriptionText(
|
||||
shortDescr = c.shortDescr,
|
||||
description = c.profileDescription,
|
||||
style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center),
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF)
|
||||
)
|
||||
}
|
||||
|
||||
// A compact profile description: shows shortDescr, or a truncated first line of the full description,
|
||||
// with a tappable "Read more" that opens the full description in a modal. Used for contacts and, later,
|
||||
// business/bot addresses. Groups keep showing only shortDescr (their welcome message is shown separately).
|
||||
@Composable
|
||||
fun ProfileDescriptionText(shortDescr: String?, description: String?, style: TextStyle, modifier: Modifier = Modifier) {
|
||||
val short = shortDescr?.trim()?.ifEmpty { null }
|
||||
val descr = description?.trim()?.ifEmpty { null }
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val linkMode = chatModel.simplexLinkMode.value
|
||||
if (descr == null) {
|
||||
// no full description: render shortDescr as before (or nothing)
|
||||
if (short != null) {
|
||||
MarkdownText(
|
||||
short, parseToMarkdown(short), toggleSecrets = true, style = style, maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis, uriHandler = uriHandler, modifier = modifier, linkMode = linkMode
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
val firstLine = descr.lineSequence().first()
|
||||
val truncated = firstLine.length > 100
|
||||
val multiline = descr.length > firstLine.length
|
||||
// when there is no shortDescr and the whole description already fits on one short line, show it inline
|
||||
if (short == null && !truncated && !multiline) {
|
||||
MarkdownText(
|
||||
descr,
|
||||
parseToMarkdown(descr),
|
||||
toggleSecrets = true,
|
||||
style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center),
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
uriHandler = LocalUriHandler.current,
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF),
|
||||
linkMode = chatModel.simplexLinkMode.value
|
||||
descr, parseToMarkdown(descr), toggleSecrets = true, style = style, maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis, uriHandler = uriHandler, modifier = modifier, linkMode = linkMode
|
||||
)
|
||||
return
|
||||
}
|
||||
val teaser = short ?: (if (truncated) firstLine.take(100).trimEnd() + "…" else firstLine + "…")
|
||||
val readMore = stringResource(MR.strings.whats_new_read_more)
|
||||
val formatted = (parseToMarkdown(teaser) ?: FormattedText.plain(teaser)) +
|
||||
FormattedText(" ") + FormattedText(readMore, Format.Modal("description"))
|
||||
MarkdownText(
|
||||
"$teaser $readMore", formatted, toggleSecrets = true, style = style, maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis, uriHandler = uriHandler, modifier = modifier, linkMode = linkMode,
|
||||
onModalClick = { showFullProfileDescription(descr) }
|
||||
)
|
||||
}
|
||||
|
||||
private fun showFullProfileDescription(description: String) {
|
||||
ModalManager.end.showModalCloseable { _ ->
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.profile_description__field))
|
||||
MarkdownText(
|
||||
description,
|
||||
parseToMarkdown(description),
|
||||
toggleSecrets = true,
|
||||
style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.onBackground, lineHeight = 22.sp),
|
||||
uriHandler = LocalUriHandler.current,
|
||||
linkMode = chatModel.simplexLinkMode.value,
|
||||
modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-14
@@ -2328,20 +2328,12 @@ fun BoxScope.ChatItemsList(
|
||||
)
|
||||
}
|
||||
|
||||
val descr = chatInfo.shortDescr?.trim()
|
||||
if (descr != null && descr != "") {
|
||||
MarkdownText(
|
||||
descr,
|
||||
parseToMarkdown(descr),
|
||||
toggleSecrets = true,
|
||||
style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center),
|
||||
maxLines = 4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
uriHandler = LocalUriHandler.current,
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF),
|
||||
linkMode = linkMode
|
||||
)
|
||||
}
|
||||
ProfileDescriptionText(
|
||||
shortDescr = chatInfo.shortDescr,
|
||||
description = chatInfo.profileDescription,
|
||||
style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.onBackground, lineHeight = 21.sp, textAlign = TextAlign.Center),
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF)
|
||||
)
|
||||
|
||||
when (chatInfo) {
|
||||
is ChatInfo.Direct -> ContactSimplexNameView(chatInfo.contact, verifiable = false)
|
||||
|
||||
+19
-2
@@ -113,6 +113,7 @@ fun MarkdownText (
|
||||
linkMode: SimplexLinkMode,
|
||||
inlineContent: Pair<AnnotatedString.Builder.() -> Unit, Map<String, InlineTextContent>>? = null,
|
||||
onLinkLongClick: (link: String) -> Unit = {},
|
||||
onModalClick: ((modalName: String) -> Unit)? = null,
|
||||
showViaProxy: Boolean = false,
|
||||
showTimestamp: Boolean = true,
|
||||
prefix: AnnotatedString? = null,
|
||||
@@ -193,6 +194,7 @@ fun MarkdownText (
|
||||
var hasLinks = false
|
||||
var hasSecrets = false
|
||||
var hasCommands = false
|
||||
var hasModals = false
|
||||
val annotatedText = buildAnnotatedString {
|
||||
inlineContent?.first?.invoke(this)
|
||||
appendSender(this, sender, senderBold)
|
||||
@@ -302,6 +304,17 @@ fun MarkdownText (
|
||||
withStyle(ftStyle) { append(ft.text) }
|
||||
}
|
||||
}
|
||||
is Format.Modal -> {
|
||||
if (onModalClick == null) {
|
||||
append(ft.text)
|
||||
} else {
|
||||
hasModals = true
|
||||
val ftStyle = Format.linkStyle
|
||||
withAnnotation(tag = "MODAL", annotation = ft.format.modalName) {
|
||||
withStyle(ftStyle) { append(ft.text) }
|
||||
}
|
||||
}
|
||||
}
|
||||
is Format.Unknown -> append(ft.text)
|
||||
}
|
||||
}
|
||||
@@ -315,7 +328,7 @@ fun MarkdownText (
|
||||
else */if (meta != null) withStyle(reserveTimestampStyle) { append(reserve) }
|
||||
}
|
||||
val clampedRange = selectionRange?.let { it.first .. minOf(it.last, selectableEnd) }
|
||||
if ((hasLinks && uriHandler != null) || hasSecrets || (hasCommands && sendCommandMsg != null)) {
|
||||
if ((hasLinks && uriHandler != null) || hasSecrets || (hasCommands && sendCommandMsg != null) || (hasModals && onModalClick != null)) {
|
||||
val icon = remember { mutableStateOf(PointerIcon.Text) }
|
||||
ClickableText(annotatedText, style = style, selectionRange = clampedRange, modifier = modifier.pointerHoverIcon(icon.value), maxLines = maxLines, overflow = overflow,
|
||||
onLongClick = { offset ->
|
||||
@@ -353,11 +366,14 @@ fun MarkdownText (
|
||||
if (hasCommands && sendCommandMsg != null) {
|
||||
withAnnotation("COMMAND") { a -> sendCommandMsg("/${a.item}") }
|
||||
}
|
||||
if (hasModals && onModalClick != null) {
|
||||
withAnnotation("MODAL") { a -> onModalClick(a.item) }
|
||||
}
|
||||
},
|
||||
onHover = { offset ->
|
||||
val hasAnnotation: (String) -> Boolean = { tag -> annotatedText.hasStringAnnotations(tag, start = offset, end = offset) }
|
||||
icon.value =
|
||||
if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND")) {
|
||||
if (hasAnnotation("WEB_URL") || hasAnnotation("SIMPLEX_URL") || hasAnnotation("OTHER_URL") || hasAnnotation("SIMPLEX_NAME") || hasAnnotation("SECRET") || hasAnnotation("COMMAND") || hasAnnotation("MODAL")) {
|
||||
PointerIcon.Hand
|
||||
} else {
|
||||
PointerIcon.Text
|
||||
@@ -367,6 +383,7 @@ fun MarkdownText (
|
||||
annotatedText.hasStringAnnotations(tag = "WEB_URL", start = offset, end = offset)
|
||||
|| annotatedText.hasStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset)
|
||||
|| annotatedText.hasStringAnnotations(tag = "OTHER_URL", start = offset, end = offset)
|
||||
|| annotatedText.hasStringAnnotations(tag = "MODAL", start = offset, end = offset)
|
||||
},
|
||||
onTextLayout = { onTextLayoutResult?.invoke(it) }
|
||||
)
|
||||
|
||||
+44
-7
@@ -24,6 +24,7 @@ import chat.simplex.common.views.onboarding.ReadableText
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.views.*
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URI
|
||||
|
||||
@@ -40,9 +41,9 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
||||
UserProfileLayout(
|
||||
profile = profile,
|
||||
close,
|
||||
saveProfile = { displayName, fullName, shortDescr, image ->
|
||||
saveProfile = { displayName, fullName, shortDescr, description, image ->
|
||||
withBGApi {
|
||||
val updatedProfile = profile.copy(displayName = displayName.trim(), fullName = fullName.trim(), shortDescr = shortDescr.trim().ifEmpty { null }, image = image)
|
||||
val updatedProfile = profile.copy(displayName = displayName.trim(), fullName = fullName.trim(), shortDescr = shortDescr.trim().ifEmpty { null }, description = description.trim().ifEmpty { null }, image = image)
|
||||
val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, updatedProfile)
|
||||
if (updated != null) {
|
||||
val (newProfile, _) = updated
|
||||
@@ -60,12 +61,13 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
||||
fun UserProfileLayout(
|
||||
profile: Profile,
|
||||
close: () -> Unit,
|
||||
saveProfile: (String, String, String, String?) -> Unit,
|
||||
saveProfile: (String, String, String, String, String?) -> Unit,
|
||||
) {
|
||||
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||
val displayName = remember { mutableStateOf(profile.displayName) }
|
||||
val fullName = remember { mutableStateOf(profile.fullName) }
|
||||
val shortDescr = remember { mutableStateOf(profile.shortDescr ?: "") }
|
||||
val description = remember { mutableStateOf(profile.description ?: "") }
|
||||
val chosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
|
||||
val profileImage = rememberSaveable { mutableStateOf(profile.image) }
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -90,12 +92,13 @@ fun UserProfileLayout(
|
||||
displayName.value.trim() == profile.displayName &&
|
||||
fullName.value.trim() == profile.fullName &&
|
||||
shortDescr.value.trim() == (profile.shortDescr ?: "") &&
|
||||
description.value.trim() == (profile.description ?: "") &&
|
||||
profile.image == profileImage.value
|
||||
val closeWithAlert = {
|
||||
if (dataUnchanged || !canSaveProfile(displayName.value, shortDescr.value, profile)) {
|
||||
close()
|
||||
} else {
|
||||
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }, close)
|
||||
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, description.value, profileImage.value) }, close)
|
||||
}
|
||||
}
|
||||
ModalView(close = closeWithAlert) {
|
||||
@@ -167,9 +170,20 @@ fun UserProfileLayout(
|
||||
}
|
||||
ProfileNameField(shortDescr)
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
// opens the description editor on top of the profile editor; the edited value is saved
|
||||
// together with the rest of the profile by the "Save and notify contacts" button below.
|
||||
Text(
|
||||
stringResource(if (description.value.isBlank()) MR.strings.add_description else MR.strings.edit_description),
|
||||
color = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.clickable {
|
||||
ModalManager.start.showModalCloseable { ProfileDescriptionEditor(description) }
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
val enabled = !dataUnchanged && canSaveProfile(displayName.value, shortDescr.value, profile)
|
||||
val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }
|
||||
val saveModifier: Modifier = Modifier.clickable(enabled) { saveProfile(displayName.value, fullName.value, shortDescr.value, description.value, profileImage.value) }
|
||||
val saveColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||
Text(
|
||||
stringResource(MR.strings.save_and_notify_contacts),
|
||||
@@ -218,6 +232,29 @@ fun DeleteImageButton(click: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
// Editor for the full profile description, opened on top of the profile editor. It edits the shared
|
||||
// `description` state in place; there is no separate save here — the profile "Save and notify contacts"
|
||||
// button persists it together with the rest of the profile.
|
||||
@Composable
|
||||
private fun ProfileDescriptionEditor(description: MutableState<String>) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle(stringResource(MR.strings.profile_description__field), withPadding = false)
|
||||
TextEditor(
|
||||
description,
|
||||
Modifier.heightIn(min = 100.dp),
|
||||
placeholder = stringResource(MR.strings.enter_description_optional),
|
||||
contentPadding = PaddingValues(),
|
||||
focusRequester = focusRequester,
|
||||
)
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
delay(200)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
|
||||
AlertManager.shared.showAlertDialogStacked(
|
||||
title = generalGetString(MR.strings.save_preferences_question),
|
||||
@@ -248,7 +285,7 @@ fun PreviewUserProfileLayoutEditOff() {
|
||||
UserProfileLayout(
|
||||
profile = Profile.sampleData,
|
||||
close = {},
|
||||
saveProfile = { _, _, _, _ -> }
|
||||
saveProfile = { _, _, _, _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -264,7 +301,7 @@ fun PreviewUserProfileLayoutEditOn() {
|
||||
UserProfileLayout(
|
||||
profile = Profile.sampleData,
|
||||
close = {},
|
||||
saveProfile = { _, _, _, _ -> }
|
||||
saveProfile = { _, _, _, _, _ -> }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,6 +1247,10 @@
|
||||
<string name="full_name__field">Full name:</string>
|
||||
<string name="short_descr__field">Bio:</string>
|
||||
<string name="bio_too_large">Bio too large</string>
|
||||
<string name="profile_description__field">Description</string>
|
||||
<string name="add_description">Add description</string>
|
||||
<string name="edit_description">Edit description</string>
|
||||
<string name="enter_description_optional">Enter description (optional)</string>
|
||||
<string name="your_current_profile">Your current profile</string>
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.</string>
|
||||
<string name="edit_image">Edit image</string>
|
||||
|
||||
Reference in New Issue
Block a user