ui: allow to add/edit profile short description, length limit; allow to set welcome message for address without auto-accept (#6086)

This commit is contained in:
spaced4ndy authored and GitHub committed 2025-07-18 08:39:56 +00:00
1 parent 7d59920399
commit ec061bcbb9
10 files changed
+169 -93

No files matched your search

@@ -34,6 +34,12 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
const val MAX_BIO_LENGTH_BYTES = 160
fun bioFitsLimit(bio: String): Boolean {
return chatJsonLength(bio) <= MAX_BIO_LENGTH_BYTES
}
@Composable
fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
val scope = rememberCoroutineScope()
@@ -68,18 +74,28 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
}
ProfileNameField(displayName, "", { it.trim() == mkValidName(it) }, focusRequester)
// TODO enable in v6.4.1, limit to 160 characters
Spacer(Modifier.height(DEFAULT_PADDING))
// Text(
// stringResource(MR.strings.short_descr),
// fontSize = 16.sp
// )
// ProfileNameField(shortDescr, "")
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.short_descr),
fontSize = 16.sp
)
Spacer(Modifier.height(20.dp))
if (!bioFitsLimit(shortDescr.value)) {
IconButton(
onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.bio_too_large)) },
Modifier.size(20.dp)) {
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
}
}
}
ProfileNameField(shortDescr, "", isValid = { bioFitsLimit(it) })
}
SettingsActionItem(
painterResource(MR.images.ic_check),
stringResource(MR.strings.create_another_profile_button),
disabled = !canCreateProfile(displayName.value),
disabled = !canCreateProfile(displayName.value) || !bioFitsLimit(shortDescr.value),
textColor = MaterialTheme.colors.primary,
iconColor = MaterialTheme.colors.primary,
click = {
@@ -68,7 +68,7 @@ fun GroupProfileLayout(
shortDescr.value.trim() == (groupProfile.shortDescr ?: "") &&
groupProfile.image == profileImage.value
val closeWithAlert = {
if (dataUnchanged || !canUpdateProfile(displayName.value, groupProfile)) {
if (dataUnchanged || !canUpdateProfile(displayName.value, shortDescr.value, groupProfile)) {
close()
} else {
showUnsavedChangesAlert({
@@ -143,18 +143,27 @@ fun GroupProfileLayout(
ProfileNameField(fullName)
}
// TODO enable in v6.4.1, limit to 160 characters
Spacer(Modifier.height(DEFAULT_PADDING))
// Spacer(Modifier.height(DEFAULT_PADDING))
// Text(
// stringResource(MR.strings.group_short_descr_field),
// fontSize = 16.sp,
// modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
// )
// ProfileNameField(shortDescr)
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.group_short_descr_field),
fontSize = 16.sp,
)
if (!bioFitsLimit(shortDescr.value)) {
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
IconButton(
onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.group_descr_too_large)) },
Modifier.size(20.dp)
) {
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
}
}
}
ProfileNameField(shortDescr, "", isValid = { bioFitsLimit(it) })
Spacer(Modifier.height(DEFAULT_PADDING))
val enabled = !dataUnchanged && canUpdateProfile(displayName.value, groupProfile)
val enabled = !dataUnchanged && canUpdateProfile(displayName.value, shortDescr.value, groupProfile)
if (enabled) {
Text(
stringResource(MR.strings.save_group_profile),
@@ -189,8 +198,8 @@ fun GroupProfileLayout(
}
}
private fun canUpdateProfile(displayName: String, groupProfile: GroupProfile): Boolean =
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile)
private fun canUpdateProfile(displayName: String, shortDescr: String, groupProfile: GroupProfile): Boolean =
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, groupProfile) && bioFitsLimit(shortDescr)
private fun isValidNewProfileName(displayName: String, groupProfile: GroupProfile): Boolean =
displayName == groupProfile.displayName || isValidDisplayName(displayName.trim())
@@ -396,19 +396,16 @@ private fun ModalData.UserAddressSettings(
SectionView {
ShareWithContactsButton(shareViaProfile, setProfileAddress)
AutoAcceptToggle(addressSettingsState) { saveAddressSettings(addressSettingsState.value, savedAddressSettingsState) }
if (addressSettingsState.value.autoAccept && !chatModel.addressShortLinkDataSet() && !addressSettingsState.value.businessAddress) {
AcceptIncognitoToggle(addressSettingsState)
}
}
SectionDividerSpaced()
// TODO v6.4.1 move auto-reply editor here
// SectionView(stringResource(MR.strings.address_welcome_message).uppercase()) {
// AutoReplyEditor(addressSettingsState)
// }
// SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
if (addressSettingsState.value.autoAccept) {
AutoAcceptSection(addressSettingsState = addressSettingsState)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
SectionView(stringResource(MR.strings.address_welcome_message).uppercase()) {
AutoReplyEditor(addressSettingsState)
}
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
saveAddressSettingsButton(addressSettingsState.value == savedAddressSettingsState.value) {
saveAddressSettings(addressSettingsState.value, savedAddressSettingsState)
@@ -572,18 +569,6 @@ private class AddressSettingsState {
}
}
@Composable
private fun AutoAcceptSection(addressSettingsState: MutableState<AddressSettingsState>) {
SectionView(stringResource(MR.strings.auto_accept_contact).uppercase()) {
if (!chatModel.addressShortLinkDataSet() && !addressSettingsState.value.businessAddress) {
AcceptIncognitoToggle(addressSettingsState)
}
// TODO v6.4.1 show this message editor even with auto-accept disabled
AutoReplyEditor(addressSettingsState)
SectionTextFooter(stringResource(MR.strings.sent_to_your_contact_after_connection))
}
}
@Composable
private fun AcceptIncognitoToggle(addressSettingsState: MutableState<AddressSettingsState>) {
PreferenceToggleWithIcon(
@@ -92,7 +92,7 @@ fun UserProfileLayout(
shortDescr.value.trim() == (profile.shortDescr ?: "") &&
profile.image == profileImage.value
val closeWithAlert = {
if (dataUnchanged || !canSaveProfile(displayName.value, profile)) {
if (dataUnchanged || !canSaveProfile(displayName.value, shortDescr.value, profile)) {
close()
} else {
showUnsavedChangesAlert({ saveProfile(displayName.value, fullName.value, shortDescr.value, profileImage.value) }, close)
@@ -148,18 +148,27 @@ fun UserProfileLayout(
ProfileNameField(fullName)
}
// TODO enable in v6.4.1, limit to 160 characters
Spacer(Modifier.height(DEFAULT_PADDING))
// Spacer(Modifier.height(DEFAULT_PADDING))
// Text(
// stringResource(MR.strings.short_descr__field),
// fontSize = 16.sp,
// modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
// )
// ProfileNameField(shortDescr)
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.short_descr__field),
fontSize = 16.sp,
)
if (!bioFitsLimit(shortDescr.value)) {
Spacer(Modifier.size(DEFAULT_PADDING_HALF))
IconButton(
onClick = { AlertManager.shared.showAlertMsg(title = generalGetString(MR.strings.bio_too_large)) },
Modifier.size(20.dp)
) {
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.error)
}
}
}
ProfileNameField(shortDescr)
Spacer(Modifier.height(DEFAULT_PADDING))
val enabled = !dataUnchanged && canSaveProfile(displayName.value, profile)
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 saveColor: Color = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
Text(
@@ -225,8 +234,8 @@ private fun isValidNewProfileName(displayName: String, profile: Profile): Boolea
private fun showFullName(profile: Profile): Boolean =
profile.fullName.trim().isNotEmpty() && profile.fullName.trim() != profile.displayName.trim()
private fun canSaveProfile(displayName: String, profile: Profile): Boolean =
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile)
private fun canSaveProfile(displayName: String, shortDescr: String, profile: Profile): Boolean =
displayName.trim().isNotEmpty() && isValidNewProfileName(displayName, profile) && bioFitsLimit(shortDescr)
@Preview/*(
uiMode = Configuration.UI_MODE_NIGHT_YES,
@@ -1108,6 +1108,7 @@
<string name="display_name__field">Profile name:</string>
<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="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>
@@ -1916,6 +1917,7 @@
<string name="group_display_name_field">Enter group name:</string>
<string name="group_full_name_field">Group full name:</string>
<string name="group_short_descr_field">Short description:</string>
<string name="group_descr_too_large">Description too large</string>
<string name="group_main_profile_sent">Your chat profile will be sent to group members</string>
<string name="chat_main_profile_sent">Your chat profile will be sent to chat members</string>
<string name="create_group_button">Create group</string>