Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2026-04-10 09:31:46 +01:00
18 changed files with 160 additions and 58 deletions
@@ -42,7 +42,7 @@ struct GroupProfileView: View {
Section {
HStack {
TextField("Group display name", text: $groupProfile.displayName)
TextField(groupInfo.useRelays ? "Channel display name" : "Group display name", text: $groupProfile.displayName)
.focused($focusDisplayName)
if !validNewProfileName {
Button {
@@ -54,7 +54,7 @@ struct GroupProfileView: View {
}
let fullName = groupInfo.groupProfile.fullName
if fullName != "" && fullName != groupProfile.displayName {
TextField("Group full name (optional)", text: $groupProfile.fullName)
TextField(groupInfo.useRelays ? "Channel full name (optional)" : "Group full name (optional)", text: $groupProfile.fullName)
}
HStack {
TextField("Short description", text: $shortDescr)
@@ -67,7 +67,7 @@ struct GroupProfileView: View {
}
}
} footer: {
Text("Group profile is stored on members' devices, not on the servers.")
Text(groupInfo.useRelays ? "Channel profile is stored on subscribers' devices and on the chat relays." : "Group profile is stored on members' devices, not on the servers.")
}
Section {
@@ -80,11 +80,11 @@ struct GroupProfileView: View {
currentProfileHash == groupProfile.hashValue &&
(groupInfo.groupProfile.shortDescr ?? "") == shortDescr.trimmingCharacters(in: .whitespaces)
)
Button("Save group profile", action: saveProfile)
Button(groupInfo.useRelays ? "Save channel profile" : "Save group profile", action: saveProfile)
.disabled(!canUpdateProfile)
}
}
.confirmationDialog("Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
.confirmationDialog(groupInfo.useRelays ? "Channel image" : "Group image", isPresented: $showChooseSource, titleVisibility: .visible) {
Button("Take picture") {
showTakePhoto = true
}
@@ -130,9 +130,15 @@ struct GroupProfileView: View {
.onDisappear {
if canUpdateProfile {
showAlert(
title: NSLocalizedString("Save group profile?", comment: "alert title"),
message: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"),
buttonTitle: NSLocalizedString("Save (and notify members)", comment: "alert button"),
title: groupInfo.useRelays
? NSLocalizedString("Save channel profile?", comment: "alert title")
: NSLocalizedString("Save group profile?", comment: "alert title"),
message: groupInfo.useRelays
? NSLocalizedString("Channel profile was changed. If you save it, the updated profile will be sent to channel subscribers.", comment: "alert message")
: NSLocalizedString("Group profile was changed. If you save it, the updated profile will be sent to group members.", comment: "alert message"),
buttonTitle: groupInfo.useRelays
? NSLocalizedString("Save (and notify subscribers)", comment: "alert button")
: NSLocalizedString("Save (and notify members)", comment: "alert button"),
buttonAction: saveProfile,
cancelButton: true
)
@@ -142,14 +148,14 @@ struct GroupProfileView: View {
switch a {
case let .saveError(err):
return Alert(
title: Text("Error saving group profile"),
title: Text(groupInfo.useRelays ? "Error saving channel profile" : "Error saving group profile"),
message: Text(err)
)
case let .invalidName(name):
return createInvalidNameAlert(name, $groupProfile.displayName)
}
}
.navigationBarTitle("Group profile")
.navigationBarTitle(groupInfo.useRelays ? "Channel profile" : "Group profile")
.modifier(ThemedBackground(grouped: true))
.navigationBarTitleDisplayMode(focusDisplayName ? .inline : .large)
}
+18 -18
View File
@@ -187,8 +187,8 @@
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */; };
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a */; };
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@@ -563,8 +563,8 @@
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a"; sourceTree = "<group>"; };
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a"; sourceTree = "<group>"; };
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a"; sourceTree = "<group>"; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
@@ -726,8 +726,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a in Frameworks */,
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a in Frameworks */,
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -813,8 +813,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.11-ATEGehbVMVHFTQkduzmQix.a */,
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk-ghc9.6.3.a */,
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.12-ERy6t9H0AqxJf9JR5ehJBk.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -2029,7 +2029,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2079,7 +2079,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2121,7 +2121,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2141,7 +2141,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2166,7 +2166,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2203,7 +2203,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2240,7 +2240,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2291,7 +2291,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2342,7 +2342,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2376,7 +2376,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 323;
CURRENT_PROJECT_VERSION = 324;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2217,18 +2217,19 @@ object ChatController {
return emptyList()
}
suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile): GroupInfo? {
suspend fun apiUpdateGroup(rh: Long?, groupId: Long, groupProfile: GroupProfile, isChannel: Boolean): GroupInfo? {
val r = sendCmd(rh, CC.ApiUpdateGroupProfile(groupId, groupProfile))
val errorTitle = if (isChannel) MR.strings.error_saving_channel_profile else MR.strings.error_saving_group_profile
return when {
r is API.Result && r.res is CR.GroupUpdated -> r.res.toGroup
r is API.Error -> {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_group_profile), "$r.err")
AlertManager.shared.showAlertMsg(generalGetString(errorTitle), "$r.err")
null
}
else -> {
Log.e(TAG, "apiUpdateGroup bad response: ${r.responseType} ${r.details}")
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.error_saving_group_profile),
generalGetString(errorTitle),
"${r.responseType}: ${r.details}"
)
null
@@ -43,7 +43,7 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
fun savePrefs(afterSave: () -> Unit = {}) {
withBGApi {
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp, gInfo.useRelays)
if (g != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, g)
@@ -32,10 +32,11 @@ import java.net.URI
fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, close: () -> Unit) {
GroupProfileLayout(
close = close,
groupInfo = groupInfo,
groupProfile = groupInfo.groupProfile,
saveProfile = { p ->
withBGApi {
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p, groupInfo.useRelays)
if (gInfo != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, gInfo)
@@ -50,9 +51,11 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
@Composable
fun GroupProfileLayout(
close: () -> Unit,
groupInfo: GroupInfo,
groupProfile: GroupProfile,
saveProfile: (GroupProfile) -> Unit,
) {
val isChannel = groupInfo.useRelays
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val displayName = rememberSaveable { mutableStateOf(groupProfile.displayName) }
val fullName = rememberSaveable { mutableStateOf(groupProfile.fullName) }
@@ -71,7 +74,7 @@ fun GroupProfileLayout(
if (dataUnchanged || !canUpdateProfile(displayName.value, shortDescr.value, groupProfile)) {
close()
} else {
showUnsavedChangesAlert({
showUnsavedChangesAlert(isChannel, {
saveProfile(
groupProfile.copy(
displayName = displayName.value.trim(),
@@ -103,7 +106,11 @@ fun GroupProfileLayout(
Modifier.fillMaxWidth()
.padding(horizontal = DEFAULT_PADDING)
) {
ReadableText(MR.strings.group_profile_is_stored_on_members_devices, TextAlign.Center)
ReadableText(
if (isChannel) MR.strings.channel_profile_is_stored_on_subscribers_devices
else MR.strings.group_profile_is_stored_on_members_devices,
TextAlign.Center
)
Box(
Modifier
.fillMaxWidth()
@@ -122,7 +129,7 @@ fun GroupProfileLayout(
}
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(
stringResource(MR.strings.group_display_name_field),
stringResource(if (isChannel) MR.strings.channel_display_name_field else MR.strings.group_display_name_field),
fontSize = 16.sp
)
if (!isValidNewProfileName(displayName.value, groupProfile)) {
@@ -136,7 +143,7 @@ fun GroupProfileLayout(
if (groupProfile.fullName.trim().isNotEmpty() && groupProfile.fullName.trim() != groupProfile.displayName.trim()) {
Spacer(Modifier.height(DEFAULT_PADDING))
Text(
stringResource(MR.strings.group_full_name_field),
stringResource(if (isChannel) MR.strings.channel_full_name_field else MR.strings.group_full_name_field),
fontSize = 16.sp,
modifier = Modifier.padding(bottom = DEFAULT_PADDING_HALF)
)
@@ -164,9 +171,10 @@ fun GroupProfileLayout(
Spacer(Modifier.height(DEFAULT_PADDING))
val enabled = !dataUnchanged && canUpdateProfile(displayName.value, shortDescr.value, groupProfile)
val saveProfileLabel = if (isChannel) MR.strings.save_channel_profile else MR.strings.save_group_profile
if (enabled) {
Text(
stringResource(MR.strings.save_group_profile),
stringResource(saveProfileLabel),
modifier = Modifier.clickable {
saveProfile(
groupProfile.copy(
@@ -181,7 +189,7 @@ fun GroupProfileLayout(
)
} else {
Text(
stringResource(MR.strings.save_group_profile),
stringResource(saveProfileLabel),
color = MaterialTheme.colors.secondary
)
}
@@ -204,10 +212,10 @@ private fun canUpdateProfile(displayName: String, shortDescr: String, groupProfi
private fun isValidNewProfileName(displayName: String, groupProfile: GroupProfile): Boolean =
displayName == groupProfile.displayName || isValidDisplayName(displayName.trim())
private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
private fun showUnsavedChangesAlert(isChannel: Boolean, save: () -> Unit, revert: () -> Unit) {
AlertManager.shared.showAlertDialogStacked(
title = generalGetString(MR.strings.save_preferences_question),
confirmText = generalGetString(MR.strings.save_and_notify_group_members),
confirmText = generalGetString(if (isChannel) MR.strings.save_and_notify_channel_subscribers else MR.strings.save_and_notify_group_members),
dismissText = generalGetString(MR.strings.exit_without_saving),
onConfirm = save,
onDismiss = revert,
@@ -224,6 +232,7 @@ fun PreviewGroupProfileLayout() {
SimpleXTheme {
GroupProfileLayout(
close = {},
groupInfo = GroupInfo.sampleData,
groupProfile = GroupProfile.sampleData,
saveProfile = { _ -> }
)
@@ -34,7 +34,7 @@ fun MemberAdmissionView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
fun saveAdmission(afterSave: () -> Unit = {}) {
withBGApi {
val gp = gInfo.groupProfile.copy(memberAdmission = admission)
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp, gInfo.useRelays)
if (g != null) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, g)
@@ -45,7 +45,7 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
welcome = null
}
val groupProfileUpdated = gInfo.groupProfile.copy(description = welcome)
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated)
val res = m.controller.apiUpdateGroup(rhId, gInfo.groupId, groupProfileUpdated, gInfo.useRelays)
if (res != null) {
gInfo = res
withContext(Dispatchers.Main) {
@@ -1171,6 +1171,7 @@
<string name="save_and_notify_contact">Save and notify contact</string>
<string name="save_and_notify_contacts">Save and notify contacts</string>
<string name="save_and_notify_group_members">Save and notify group members</string>
<string name="save_and_notify_channel_subscribers">Save and notify channel subscribers</string>
<string name="exit_without_saving">Exit without saving</string>
<!-- HiddenProfileView.kt -->
@@ -1999,6 +2000,7 @@
<string name="group_is_decentralized">Fully decentralized visible only to members.</string>
<string name="group_display_name_field">Enter group name:</string>
<string name="group_full_name_field">Group full name:</string>
<string name="channel_full_name_field">Channel 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>
@@ -2007,8 +2009,11 @@
<!-- GroupProfileView.kt -->
<string name="group_profile_is_stored_on_members_devices">Group profile is stored on members\' devices, not on the servers.</string>
<string name="channel_profile_is_stored_on_subscribers_devices">Channel profile is stored on subscribers\' devices and on the chat relays.</string>
<string name="save_group_profile">Save group profile</string>
<string name="save_channel_profile">Save channel profile</string>
<string name="error_saving_group_profile">Error saving group profile</string>
<string name="error_saving_channel_profile">Error saving channel profile</string>
<!-- NetworkAndServers.kt -->
<string name="network_preset_servers_title">Preset servers</string>
+4 -4
View File
@@ -24,13 +24,13 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
android.version_name=6.5-beta.6
android.version_code=338
android.version_name=6.5-beta.7
android.version_code=339
android.bundle=false
desktop.version_name=6.5-beta.6
desktop.version_code=133
desktop.version_name=6.5-beta.7
desktop.version_code=134
kotlin.version=2.1.20
gradle.plugin.version=8.7.0
@@ -1,6 +1,6 @@
{
"name": "@simplex-chat/types",
"version": "0.3.0",
"version": "0.4.0",
"description": "TypeScript types for SimpleX Chat bot libraries",
"main": "dist/index.js",
"types": "dist/index.d.ts",
+1 -1
View File
@@ -813,7 +813,7 @@ export class ChatApi {
* Network usage: no.
*/
async apiCreateActiveUser(profile?: T.Profile): Promise<T.User> {
const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false}}))
const r = await this.sendChatCmd(CC.CreateActiveUser.cmdString({newUser: {profile, pastTimestamp: false, userChatRelay: false}}))
if (r.type === "activeUser") return r.user
throw new ChatCommandError("unexpected response", r)
}
+1 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
version: 6.5.0.12
version: 6.5.0.14
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat
+5 -1
View File
@@ -1195,6 +1195,9 @@ sendHistory user gInfo@GroupInfo {membership} m@GroupMember {activeConn = Just c
where
descrEvent_ :: Maybe (ChatMsgEvent 'Json)
descrEvent_
-- in channels sendHistory runs on the relay, which cannot author XMsgNew (GRRelay < GRObserver);
-- the welcome message reaches new members via the channel link data instead
| useRelays' gInfo = Nothing
| m `supportsVersion` groupHistoryIncludeWelcomeVersion = do
let GroupInfo {groupProfile = GroupProfile {description}} = gInfo
fmap (\descr -> XMsgNew $ MCSimple $ extMsgContent (MCText descr) Nothing) description
@@ -1299,7 +1302,8 @@ setGroupLinkData nm user gInfo gLink = do
(conn, groupRelays) <- withFastStore $ \db ->
(,) <$> getGroupLinkConnection db vr user gInfo <*> liftIO (getConnectedGroupRelays db gInfo)
let (userLinkData, crClientData) = groupLinkData gInfo gLink groupRelays
sLnk <- shortenShortLink' . toShortGroupLink =<< withAgent (\a -> setConnShortLink a nm (aConnId conn) SCMContact userLinkData (Just crClientData))
tagShortLink = if useRelays' gInfo then toShortChannelLink else toShortGroupLink
sLnk <- shortenShortLink' . tagShortLink =<< withAgent (\a -> setConnShortLink a nm (aConnId conn) SCMContact userLinkData (Just crClientData))
withFastStore' $ \db -> setGroupLinkShortLink db gLink sLnk
setGroupLinkDataAsync :: User -> GroupInfo -> GroupLink -> CM ()
+4 -1
View File
@@ -3138,7 +3138,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
(ci, cInfo) <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
groupMsgToView cInfo ci
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g''
void $ forkIO $ void $ setGroupLinkData' NRMBackground user g''
-- in channels, link data is updated by the owner making the change in runUpdateGroupProfile;
-- other owners receiving the update do not refresh the same link
unless (useRelays' g'') $
void $ forkIO $ void $ setGroupLinkData' NRMBackground user g''
Just _ -> updateGroupPrefs_ msgSigned g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
pure $ Just DJSGroup {jobSpec = DJDeliveryJob {includePending = True}}
+1 -1
View File
@@ -209,7 +209,7 @@ chatEventNotification t@ChatTerminal {sendNotification} cc = \case
when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected")
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = n} _ ->
when (userNtf u) $ sendNtf (viewName n <> ">", "wants to connect to you")
CEvtDeletedMemberUser _u g m _withMessages ->
CEvtDeletedMemberUser _u g m _withMessages _signed ->
sendNtf ("#" <> viewGroupName g, viewMemberName m <> " removed you from the group")
_ -> pure ()
where
+1 -1
View File
@@ -2376,7 +2376,7 @@ testDisableCIExpirationOnlyForOneUser ps = do
alice #$> ("/_get chat @6 count=100", chat, [(1,"chat banner"), (1, "alisa 3"), (0, "alisa 4")])
threadDelay 2000000
threadDelay 2500000
-- second user messages are deleted
alice #$> ("/_get chat @6 count=100", chat, [(1,"chat banner")])
+76 -2
View File
@@ -251,6 +251,8 @@ chatGroupTests = do
it "should share same incognito profile with all relays" testChannels2RelaysIncognito
describe "channel operations" $ do
it "should update channel profile (signed)" testChannelUpdateProfileSigned
it "should preserve working link after profile update" testChannelLinkAfterProfileUpdate
it "should preserve working link after welcome message update" testChannelLinkAfterWelcomeUpdate
it "should update channel preferences (signed)" testChannelUpdatePrefsSigned
it "should change member role (signed)" testChannelChangeRoleSigned
it "should block member for all (signed)" testChannelBlockMemberSigned
@@ -8537,7 +8539,7 @@ memberJoinChannel gName relays owners shortLink fullLink member = do
]
]
<> [ do
relay <## (mFullName <> ": accepting request to join group #team...")
relay <## (mFullName <> ": accepting request to join group #" <> gName <> "...")
relay <## ("#" <> gName <> ": " <> mName <> " joined the group")
| relay <- relays
]
@@ -8569,7 +8571,7 @@ memberJoinChannelIncognito gName relays owners shortLink fullLink member = do
]
]
<> [ do
relay <## (memIncognito <> ": accepting request to join group #team...")
relay <## (memIncognito <> ": accepting request to join group #" <> gName <> "...")
relay <## ("#" <> gName <> ": " <> memIncognito <> " joined the group")
| relay <- relays
]
@@ -8770,6 +8772,78 @@ testChannelUpdateProfileSigned ps =
]
alice #$> ("/_get chat #1 count=1", chat, [(1, "group profile updated (signed)")])
testChannelLinkAfterProfileUpdate :: HasCallStack => TestParams -> IO ()
testChannelLinkAfterProfileUpdate ps =
withNewTestChat ps "alice" aliceProfile $ \alice ->
withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob ->
withNewTestChat ps "cath" cathProfile $ \cath ->
withNewTestChat ps "dan" danProfile $ \dan -> do
(shortLink, fullLink) <- prepareChannel1Relay "team" alice bob
memberJoinChannel "team" [bob] [alice] shortLink fullLink cath
-- owner updates channel profile
alice ##> "/gp team my_team My team description"
alice <## "changed to #my_team (My team description)"
concurrentlyN_
[ do
bob <## "alice updated group #team: (signed)"
bob <## "changed to #my_team (My team description)",
do
cath <## "alice updated group #team: (signed)"
cath <## "changed to #my_team (My team description)"
]
alice #$> ("/_get chat #1 count=1", chat, [(1, "group profile updated (signed)")])
-- late subscriber joins via the same channel link after profile update
threadDelay 100000
alice ##> "/show link #my_team"
(shortLink', fullLink') <- getGroupLinks alice "my_team" GRMember False
shortLink' `shouldBe` shortLink
fullLink' `shouldBe` fullLink
memberJoinChannel "my_team" [bob] [alice] shortLink' fullLink' dan
alice #> "#my_team hi"
bob <# "#my_team> hi"
[cath, dan] *<# "#my_team> hi [>>]"
testChannelLinkAfterWelcomeUpdate :: HasCallStack => TestParams -> IO ()
testChannelLinkAfterWelcomeUpdate ps =
withNewTestChat ps "alice" aliceProfile $ \alice ->
withNewTestChatOpts ps relayTestOpts "bob" bobProfile $ \bob ->
withNewTestChat ps "cath" cathProfile $ \cath ->
withNewTestChat ps "dan" danProfile $ \dan -> do
(shortLink, fullLink) <- prepareChannel1Relay "team" alice bob
memberJoinChannel "team" [bob] [alice] shortLink fullLink cath
-- owner updates channel welcome message
alice ##> "/set welcome #team welcome to team"
alice <## "welcome message changed to:"
alice <## "welcome to team"
concurrentlyN_
[ do
bob <## "alice updated group #team: (signed)"
bob <## "welcome message changed to:"
bob <## "welcome to team",
do
cath <## "alice updated group #team: (signed)"
cath <## "welcome message changed to:"
cath <## "welcome to team"
]
alice #$> ("/_get chat #1 count=1", chat, [(1, "group profile updated (signed)")])
-- re-fetch updated link, late subscriber joins
threadDelay 100000
alice ##> "/show link #team"
(shortLink', fullLink') <- getGroupLinks alice "team" GRMember False
shortLink' `shouldBe` shortLink
fullLink' `shouldBe` fullLink
memberJoinChannel "team" [bob] [alice] shortLink' fullLink' dan
dan #$> ("/_get chat #1 count=100", chat, groupFeaturesNoE2E <> [(0, "welcome to team"), (0, e2eeInfoNoPQStr), (0, "connected")])
alice #> "#team hi"
bob <# "#team> hi"
[cath, dan] *<# "#team> hi [>>]"
testChannelUpdatePrefsSigned :: HasCallStack => TestParams -> IO ()
testChannelUpdatePrefsSigned ps =
withNewTestChat ps "alice" aliceProfile $ \alice ->
+2 -2
View File
@@ -56,7 +56,7 @@ validateServersTest = describe "validate user servers" $ do
validateUserServers [duplicateChatRelayName] [] `shouldBe` ([], [])
it "should fail with duplicate chat relay address" $ do
validateUserServers [invalidDuplicateChatRelayAddress] []
`shouldBe` ( [ USEDuplicateChatRelayAddress "chat_relay_1" duplicateAddr,
`shouldBe` ( [ USEDuplicateChatRelayAddress "SimpleX Chat Relay 2" duplicateAddr,
USEDuplicateChatRelayAddress "chat_relay_4" duplicateAddr
],
[]
@@ -180,4 +180,4 @@ invalidDuplicateChatRelayAddress =
}
duplicateAddr :: ShortLinkContact
duplicateAddr = either error id $ strDecode "https://smp111.simplex.im/r#Pz9qz7ZVljMofoRxiDDpL_w2DZSazK8IgafxqnWKv6Y"
duplicateAddr = either error id $ strDecode "https://smp6.simplex.im/r#_qlQfogHGDJ8MAF2wKmkglRBM-xHR142gDJstKiGRQQ"