This commit is contained in:
Evgeny @ SimpleX Chat
2026-07-14 20:46:37 +00:00
parent 7787e6119c
commit 78de359db1
7 changed files with 41 additions and 40 deletions
@@ -1367,8 +1367,6 @@ 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
@@ -4872,7 +4870,6 @@ 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()
@@ -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(), "", shortDescr = null)
null, Profile(displayName.trim(), "", null, 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
@@ -783,9 +783,6 @@ fun ChatInfoDescription(c: NamedChat, displayName: String, copyNameToClipboard:
)
}
// 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 }
@@ -76,8 +76,6 @@ fun UserProfileLayout(
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
val focusRequester = remember { FocusRequester() }
val descrFocusRequester = remember { FocusRequester() }
// the description is edited on a separate screen shown in place of the profile form (same composition,
// so the edited value survives). It is persisted together with the rest of the profile by the save button.
var editingDescription by remember { mutableStateOf(false) }
ModalBottomSheetLayout(
scrimColor = Color.Black.copy(alpha = 0.12F),
@@ -195,8 +193,6 @@ fun UserProfileLayout(
ProfileNameField(shortDescr)
Spacer(Modifier.height(DEFAULT_PADDING))
// shows the description editor in place of this form; 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,
+24 -18
View File
@@ -1265,35 +1265,41 @@ memberInfo g m@GroupMember {memberId, memberRole, memberProfile, memberPubKey, a
redactedMemberProfile :: GroupInfo -> GroupMember -> Profile -> Profile
redactedMemberProfile g m Profile {displayName, fullName, shortDescr, description, image, contactLink = lnk, peerType, badge, contactDomain} =
Profile {displayName, fullName, shortDescr = removeSimplexLink =<< shortDescr, description = redactGroupDescription g m =<< description, image, contactLink, preferences = Nothing, peerType, badge, contactDomain = redactedDomain}
Profile {displayName, fullName, shortDescr = redactLinkedText dropWholeOnLink allowSimplexLinks =<< shortDescr, description = redactGroupDescription g m =<< description, image, contactLink, preferences = Nothing, peerType, badge, contactDomain = redactedDomain}
where
contactLink = if allowSimplexLinks then lnk else Nothing
redactedDomain = if allowDirect then (\d -> d {proof = Nothing} :: SimplexDomainClaim) <$> contactDomain else Nothing
allowDirect = groupFeatureMemberAllowed SGFDirectMessages m g
allowSimplexLinks = groupFeatureMemberAllowed SGFSimplexLinks m g && allowDirect
removeSimplexLink s
| allowSimplexLinks = Just s
| hasObfuscatedSimplexLink s = Nothing
| otherwise = maybe (Just s) (\fts -> if any ftIsSimplexLink fts then Nothing else Just s) $ parseMaybeMarkdownList s
allowSimplexLinks = memberLinksAllowed g m
-- redact only the description of an incoming member profile (receive side), per the group's link/name policy
redactMemberProfileDescription :: GroupInfo -> GroupMember -> Profile -> Profile
redactMemberProfileDescription g m p@Profile {description} = p {description = redactGroupDescription g m =<< description}
redactMemberProfileDescription g m p@Profile {description} =
p {description = redactGroupDescription g m =<< description}
-- strip link/name spans inline from a group member's description (keeping the prose), dropping the whole field only on an obfuscated link
redactGroupDescription :: GroupInfo -> GroupMember -> Text -> Maybe Text
redactGroupDescription g m s
redactGroupDescription g m = redactLinkedText stripLinkSpans (memberLinksAllowed g m)
memberLinksAllowed :: GroupInfo -> GroupMember -> Bool
memberLinksAllowed g m = groupFeatureMemberAllowed SGFSimplexLinks m g && groupFeatureMemberAllowed SGFDirectMessages m g
redactLinkedText :: (Text -> [FormattedText] -> Maybe Text) -> Bool -> Text -> Maybe Text
redactLinkedText fromSpans allowSimplexLinks s
| allowSimplexLinks = Just s
| hasObfuscatedSimplexLink s = Nothing
| otherwise = case parseMaybeMarkdownList s of
Nothing -> Just s
Just fts ->
let kept = T.concat [t | FormattedText f t <- fts, not (maybe False isLinkOrName f)]
in if T.null (T.strip kept) then Nothing else Just kept
| otherwise = maybe (Just s) (fromSpans s) $ parseMaybeMarkdownList s
dropWholeOnLink :: Text -> [FormattedText] -> Maybe Text
dropWholeOnLink s fts
| any ftIsSimplexLink fts = Nothing
| otherwise = Just s
stripLinkSpans :: Text -> [FormattedText] -> Maybe Text
stripLinkSpans _ fts
| T.null (T.strip kept) = Nothing
| otherwise = Just kept
where
allowDirect = groupFeatureMemberAllowed SGFDirectMessages m g
allowSimplexLinks = groupFeatureMemberAllowed SGFSimplexLinks m g && allowDirect
isLinkOrName f = isLink f || case f of Mention {} -> True; _ -> False
kept = T.concat [t | FormattedText f t <- fts, not (maybe False linkOrMention f)]
linkOrMention f = isLink f || case f of Mention {} -> True; _ -> False
-- Roles carried by the roster; owners are on the link, not the roster.
isRosterRole :: GroupMemberRole -> Bool
+3 -3
View File
@@ -353,9 +353,9 @@ updateUserProfile db user p'
DB.execute db "UPDATE users SET user_member_profile_updated_at = ? WHERE user_id = ?" (currentTs, userId)
pure $ Just currentTs
| otherwise = pure userMemberProfileUpdatedAt
userMemberProfileChanged = newName /= displayName || fn' /= fullName || d' /= shortDescr || img' /= image
User {userId, userContactId, localDisplayName, profile = LocalProfile {profileId, displayName, fullName, shortDescr, image, localBadge, localAlias}, userMemberProfileUpdatedAt} = user
Profile {displayName = newName, fullName = fn', shortDescr = d', image = img', preferences} = p'
userMemberProfileChanged = newName /= displayName || fn' /= fullName || d' /= shortDescr || desc' /= description || img' /= image
User {userId, userContactId, localDisplayName, profile = LocalProfile {profileId, displayName, fullName, shortDescr, description, image, localBadge, localAlias}, userMemberProfileUpdatedAt} = user
Profile {displayName = newName, fullName = fn', shortDescr = d', description = desc', image = img', preferences} = p'
fullPreferences = fullPreferences' preferences
-- own profile field update; leaves the badge columns alone (the credential is owned by setUserBadge/addUserBadge)
+13 -8
View File
@@ -1959,14 +1959,15 @@ viewSwitchPhase = \case
SPCompleted -> "changed address"
viewUserProfileUpdated :: Profile -> Profile -> UserProfileUpdateSummary -> [StyledString]
viewUserProfileUpdated Profile {displayName = n, fullName, shortDescr, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', shortDescr = shortDescr', image = image', contactLink = contactLink', preferences = prefs'} summary =
viewUserProfileUpdated Profile {displayName = n, fullName, shortDescr, description, image, contactLink, preferences} Profile {displayName = n', fullName = fullName', shortDescr = shortDescr', description = description', image = image', contactLink = contactLink', preferences = prefs'} summary =
profileUpdated <> viewPrefsUpdated preferences prefs'
where
UserProfileUpdateSummary {updateSuccesses = s, updateFailures = f} = summary
profileUpdated
| n == n' && fullName == fullName' && shortDescr == shortDescr' && image == image' && contactLink == contactLink' = []
| n == n' && fullName == fullName' && shortDescr == shortDescr' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"]
| n == n' && fullName == fullName' && shortDescr == shortDescr' = [if isNothing image' then "profile image removed" else "profile image updated"]
| n == n' && fullName == fullName' && shortDescr == shortDescr' && description == description' && image == image' && contactLink == contactLink' = []
| n == n' && fullName == fullName' && shortDescr == shortDescr' && description == description' && image == image' = [if isNothing contactLink' then "contact address removed" else "new contact address set"]
| n == n' && fullName == fullName' && shortDescr == shortDescr' && description == description' = [if isNothing image' then "profile image removed" else "profile image updated"]
| n == n' && fullName == fullName' && shortDescr == shortDescr' = ["user description " <> (if maybe True T.null description' then "removed" else "changed to " <> maybe "" plain description') <> notified]
| n == n' && fullName == fullName' = ["user bio " <> (if maybe True T.null shortDescr' then "removed" else "changed to " <> maybe "" plain shortDescr') <> notified]
| n == n' = ["user full name " <> (if T.null fullName' || fullName' == n' then "removed" else "changed to " <> plain fullName') <> notified]
| otherwise = ["user profile is changed to " <> ttyFullName n' fullName' shortDescr' <> notified]
@@ -2257,13 +2258,17 @@ viewConnectionPlan ChatConfig {logLevel, testView} _connLink = \case
viewContactUpdated :: Contact -> Contact -> [StyledString]
viewContactUpdated
Contact {localDisplayName = n, profile = LocalProfile {fullName, shortDescr, contactLink}}
Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', shortDescr = shortDescr', contactLink = contactLink'}}
| n == n' && fullName == fullName' && shortDescr == shortDescr' && contactLink == contactLink' = []
| n == n' && fullName == fullName' && shortDescr == shortDescr' =
Contact {localDisplayName = n, profile = LocalProfile {fullName, shortDescr, description, contactLink}}
Contact {localDisplayName = n', profile = LocalProfile {fullName = fullName', shortDescr = shortDescr', description = description', contactLink = contactLink'}}
| n == n' && fullName == fullName' && shortDescr == shortDescr' && description == description' && contactLink == contactLink' = []
| n == n' && fullName == fullName' && shortDescr == shortDescr' && description == description' =
if isNothing contactLink'
then [ttyContact n <> " removed contact address"]
else [ttyContact n <> " set new contact address, use " <> highlight ("/info " <> n) <> " to view"]
| n == n' && fullName == fullName' && shortDescr == shortDescr' =
if maybe True T.null description'
then ["contact " <> ttyContact n <> " removed description"]
else ["contact " <> ttyContact n <> " updated description: " <> maybe "" plain description']
| n == n' && fullName == fullName' =
if maybe True T.null shortDescr'
then ["contact " <> ttyContact n <> " removed bio"]