mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-06-03 14:56:20 +00:00
Merge branch 'master' into master-android
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+4
-3
@@ -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
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+18
-9
@@ -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 = { _ -> }
|
||||
)
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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}}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")])
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user