Merge branch 'master' into xftp
This commit is contained in:
commit
858f0f2650
@ -496,6 +496,14 @@ data class Chat (
|
|||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val userIsObserver: Boolean get() = when(chatInfo) {
|
||||||
|
is ChatInfo.Group -> {
|
||||||
|
val m = chatInfo.groupInfo.membership
|
||||||
|
m.memberActive && m.memberRole == GroupMemberRole.Observer
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
val id: String get() = chatInfo.id
|
val id: String get() = chatInfo.id
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -942,7 +950,7 @@ data class GroupMember (
|
|||||||
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
|
fun canChangeRoleTo(groupInfo: GroupInfo): List<GroupMemberRole>? =
|
||||||
if (!canBeRemoved(groupInfo)) null
|
if (!canBeRemoved(groupInfo)) null
|
||||||
else groupInfo.membership.memberRole.let { userRole ->
|
else groupInfo.membership.memberRole.let { userRole ->
|
||||||
GroupMemberRole.values().filter { it <= userRole }
|
GroupMemberRole.values().filter { it <= userRole && it != GroupMemberRole.Observer }
|
||||||
}
|
}
|
||||||
|
|
||||||
val memberIncognito = memberProfile.profileId != memberContactProfileId
|
val memberIncognito = memberProfile.profileId != memberContactProfileId
|
||||||
|
@ -83,6 +83,7 @@ fun TerminalLayout(
|
|||||||
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = false,
|
allowedVoiceByPrefs = false,
|
||||||
|
userIsObserver = false,
|
||||||
userCanSend = true,
|
userCanSend = true,
|
||||||
allowVoiceToContact = {},
|
allowVoiceToContact = {},
|
||||||
sendMessage = sendCommand,
|
sendMessage = sendCommand,
|
||||||
|
@ -648,6 +648,7 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val userCanSend = rememberUpdatedState(chat.userCanSend)
|
val userCanSend = rememberUpdatedState(chat.userCanSend)
|
||||||
|
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
contextItemView()
|
contextItemView()
|
||||||
@ -744,6 +745,7 @@ fun ComposeView(
|
|||||||
needToAllowVoiceToContact,
|
needToAllowVoiceToContact,
|
||||||
allowedVoiceByPrefs,
|
allowedVoiceByPrefs,
|
||||||
allowVoiceToContact = ::allowVoiceToContact,
|
allowVoiceToContact = ::allowVoiceToContact,
|
||||||
|
userIsObserver = userIsObserver.value,
|
||||||
userCanSend = userCanSend.value,
|
userCanSend = userCanSend.value,
|
||||||
sendMessage = {
|
sendMessage = {
|
||||||
sendMessage()
|
sendMessage()
|
||||||
|
@ -60,6 +60,7 @@ fun SendMsgView(
|
|||||||
liveMessageAlertShown: SharedPreference<Boolean>,
|
liveMessageAlertShown: SharedPreference<Boolean>,
|
||||||
needToAllowVoiceToContact: Boolean,
|
needToAllowVoiceToContact: Boolean,
|
||||||
allowedVoiceByPrefs: Boolean,
|
allowedVoiceByPrefs: Boolean,
|
||||||
|
userIsObserver: Boolean,
|
||||||
userCanSend: Boolean,
|
userCanSend: Boolean,
|
||||||
allowVoiceToContact: () -> Unit,
|
allowVoiceToContact: () -> Unit,
|
||||||
sendMessage: () -> Unit,
|
sendMessage: () -> Unit,
|
||||||
@ -75,7 +76,7 @@ fun SendMsgView(
|
|||||||
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
|
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
|
||||||
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
|
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
|
||||||
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
|
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
|
||||||
NativeKeyboard(composeState, textStyle, showDeleteTextButton, userCanSend, onMessageChange)
|
NativeKeyboard(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange)
|
||||||
// Disable clicks on text field
|
// Disable clicks on text field
|
||||||
if (cs.preview is ComposePreview.VoicePreview || !userCanSend) {
|
if (cs.preview is ComposePreview.VoicePreview || !userCanSend) {
|
||||||
Box(Modifier
|
Box(Modifier
|
||||||
@ -182,7 +183,7 @@ private fun NativeKeyboard(
|
|||||||
composeState: MutableState<ComposeState>,
|
composeState: MutableState<ComposeState>,
|
||||||
textStyle: MutableState<TextStyle>,
|
textStyle: MutableState<TextStyle>,
|
||||||
showDeleteTextButton: MutableState<Boolean>,
|
showDeleteTextButton: MutableState<Boolean>,
|
||||||
userCanSend: Boolean,
|
userIsObserver: Boolean,
|
||||||
onMessageChange: (String) -> Unit
|
onMessageChange: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
@ -262,16 +263,23 @@ private fun NativeKeyboard(
|
|||||||
}
|
}
|
||||||
showDeleteTextButton.value = it.lineCount >= 4
|
showDeleteTextButton.value = it.lineCount >= 4
|
||||||
}
|
}
|
||||||
if (composeState.value.preview is ComposePreview.VoicePreview || !userCanSend) {
|
if (composeState.value.preview is ComposePreview.VoicePreview) {
|
||||||
Text(
|
ComposeOverlay(R.string.voice_message_send_text, textStyle, padding)
|
||||||
if (composeState.value.preview is ComposePreview.VoicePreview) generalGetString(R.string.voice_message_send_text) else generalGetString(R.string.you_are_observer),
|
} else if (userIsObserver) {
|
||||||
Modifier.padding(padding),
|
ComposeOverlay(R.string.you_are_observer, textStyle, padding)
|
||||||
color = HighOrLowlight,
|
|
||||||
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ComposeOverlay(textId: Int, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
|
||||||
|
Text(
|
||||||
|
generalGetString(textId),
|
||||||
|
Modifier.padding(padding),
|
||||||
|
color = HighOrLowlight,
|
||||||
|
style = textStyle.value.copy(fontStyle = FontStyle.Italic)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>) {
|
private fun BoxScope.DeleteTextButton(composeState: MutableState<ComposeState>) {
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -581,6 +589,7 @@ fun PreviewSendMsgView() {
|
|||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
|
userIsObserver = false,
|
||||||
userCanSend = true,
|
userCanSend = true,
|
||||||
allowVoiceToContact = {},
|
allowVoiceToContact = {},
|
||||||
sendMessage = {},
|
sendMessage = {},
|
||||||
@ -610,6 +619,7 @@ fun PreviewSendMsgViewEditing() {
|
|||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
|
userIsObserver = false,
|
||||||
userCanSend = true,
|
userCanSend = true,
|
||||||
allowVoiceToContact = {},
|
allowVoiceToContact = {},
|
||||||
sendMessage = {},
|
sendMessage = {},
|
||||||
@ -639,6 +649,7 @@ fun PreviewSendMsgViewInProgress() {
|
|||||||
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
|
||||||
needToAllowVoiceToContact = false,
|
needToAllowVoiceToContact = false,
|
||||||
allowedVoiceByPrefs = true,
|
allowedVoiceByPrefs = true,
|
||||||
|
userIsObserver = false,
|
||||||
userCanSend = true,
|
userCanSend = true,
|
||||||
allowVoiceToContact = {},
|
allowVoiceToContact = {},
|
||||||
sendMessage = {},
|
sendMessage = {},
|
||||||
|
@ -166,7 +166,7 @@ private fun RoleSelectionRow(groupInfo: GroupInfo, selectedRole: MutableState<Gr
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
) {
|
) {
|
||||||
val values = GroupMemberRole.values().filter { it <= groupInfo.membership.memberRole }.map { it to it.text }
|
val values = GroupMemberRole.values().filter { it <= groupInfo.membership.memberRole && it != GroupMemberRole.Observer }.map { it to it.text }
|
||||||
ExposedDropDownSettingRow(
|
ExposedDropDownSettingRow(
|
||||||
generalGetString(R.string.new_member_role),
|
generalGetString(R.string.new_member_role),
|
||||||
values,
|
values,
|
||||||
|
@ -120,9 +120,9 @@ fun GroupLinkLayout(
|
|||||||
if (groupLink == null) {
|
if (groupLink == null) {
|
||||||
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
|
SimpleButton(stringResource(R.string.button_create_group_link), icon = Icons.Outlined.AddLink, disabled = creatingLink, click = createLink)
|
||||||
} else {
|
} else {
|
||||||
SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
|
// SectionItemView(padding = PaddingValues(bottom = DEFAULT_PADDING)) {
|
||||||
RoleSelectionRow(groupInfo, groupLinkMemberRole)
|
// RoleSelectionRow(groupInfo, groupLinkMemberRole)
|
||||||
}
|
// }
|
||||||
var initialLaunch by remember { mutableStateOf(true) }
|
var initialLaunch by remember { mutableStateOf(true) }
|
||||||
LaunchedEffect(groupLinkMemberRole.value) {
|
LaunchedEffect(groupLinkMemberRole.value) {
|
||||||
if (!initialLaunch) {
|
if (!initialLaunch) {
|
||||||
|
@ -555,6 +555,15 @@ final class Chat: ObservableObject, Identifiable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userIsObserver: Bool {
|
||||||
|
switch chatInfo {
|
||||||
|
case let .group(groupInfo):
|
||||||
|
let m = groupInfo.membership
|
||||||
|
return m.memberActive && m.memberRole == .observer
|
||||||
|
default: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var id: ChatId { get { chatInfo.id } }
|
var id: ChatId { get { chatInfo.id } }
|
||||||
|
|
||||||
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
|
||||||
|
@ -291,7 +291,7 @@ struct ComposeView: View {
|
|||||||
.background(.background)
|
.background(.background)
|
||||||
.disabled(!chat.userCanSend)
|
.disabled(!chat.userCanSend)
|
||||||
|
|
||||||
if (!chat.userCanSend) {
|
if chat.userIsObserver {
|
||||||
Text("you are observer")
|
Text("you are observer")
|
||||||
.italic()
|
.italic()
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
@ -140,7 +140,7 @@ struct AddGroupMembersView: View {
|
|||||||
private func rolePicker() -> some View {
|
private func rolePicker() -> some View {
|
||||||
Picker("New member role", selection: $selectedRole) {
|
Picker("New member role", selection: $selectedRole) {
|
||||||
ForEach(GroupMemberRole.allCases) { role in
|
ForEach(GroupMemberRole.allCases) { role in
|
||||||
if role <= groupInfo.membership.memberRole {
|
if role <= groupInfo.membership.memberRole && role != .observer {
|
||||||
Text(role.text)
|
Text(role.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,15 @@ struct GroupLinkView: View {
|
|||||||
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
Text("You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.")
|
||||||
.padding(.bottom)
|
.padding(.bottom)
|
||||||
if let groupLink = groupLink {
|
if let groupLink = groupLink {
|
||||||
HStack {
|
// HStack {
|
||||||
Text("Initial role")
|
// Text("Initial role")
|
||||||
Picker("Initial role", selection: $groupLinkMemberRole) {
|
// Picker("Initial role", selection: $groupLinkMemberRole) {
|
||||||
ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
|
// ForEach([GroupMemberRole.member, GroupMemberRole.observer]) { role in
|
||||||
Text(role.text)
|
// Text(role.text)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
// .frame(maxWidth: .infinity, alignment: .leading)
|
||||||
QRCode(uri: groupLink)
|
QRCode(uri: groupLink)
|
||||||
HStack {
|
HStack {
|
||||||
Button {
|
Button {
|
||||||
|
@ -1517,7 +1517,7 @@ public struct GroupMember: Identifiable, Decodable {
|
|||||||
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
|
public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? {
|
||||||
if !canBeRemoved(groupInfo: groupInfo) { return nil }
|
if !canBeRemoved(groupInfo: groupInfo) { return nil }
|
||||||
let userRole = groupInfo.membership.memberRole
|
let userRole = groupInfo.membership.memberRole
|
||||||
return GroupMemberRole.allCases.filter { $0 <= userRole }
|
return GroupMemberRole.allCases.filter { $0 <= userRole && $0 != .observer }
|
||||||
}
|
}
|
||||||
|
|
||||||
public var memberIncognito: Bool {
|
public var memberIncognito: Bool {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 4.5.3.1
|
version: 4.5.4.2
|
||||||
#synopsis:
|
#synopsis:
|
||||||
#description:
|
#description:
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
|
@ -5,7 +5,7 @@ cabal-version: 1.12
|
|||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 4.5.3.1
|
version: 4.5.4.2
|
||||||
category: Web, System, Services, Cryptography
|
category: Web, System, Services, Cryptography
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
author: simplex.chat
|
author: simplex.chat
|
||||||
|
@ -3439,9 +3439,9 @@ processAgentMessageConn user@User {userId} corrId agentConnId agentMessage = do
|
|||||||
messageError $ eventName <> ": wrong call state " <> T.pack (show $ callStateTag callState)
|
messageError $ eventName <> ": wrong call state " <> T.pack (show $ callStateTag callState)
|
||||||
|
|
||||||
mergeContacts :: Contact -> Contact -> m ()
|
mergeContacts :: Contact -> Contact -> m ()
|
||||||
mergeContacts to from = do
|
mergeContacts c1 c2 = do
|
||||||
withStore' $ \db -> mergeContactRecords db userId to from
|
withStore' $ \db -> mergeContactRecords db userId c1 c2
|
||||||
toView $ CRContactsMerged user to from
|
toView $ CRContactsMerged user c1 c2
|
||||||
|
|
||||||
saveConnInfo :: Connection -> ConnInfo -> m ()
|
saveConnInfo :: Connection -> ConnInfo -> m ()
|
||||||
saveConnInfo activeConn connInfo = do
|
saveConnInfo activeConn connInfo = do
|
||||||
|
@ -1622,8 +1622,17 @@ matchSentProbe db user@User {userId} _from@Contact {contactId} (Probe probe) = d
|
|||||||
cId : _ -> eitherToMaybe <$> runExceptT (getContact db user cId)
|
cId : _ -> eitherToMaybe <$> runExceptT (getContact db user cId)
|
||||||
|
|
||||||
mergeContactRecords :: DB.Connection -> UserId -> Contact -> Contact -> IO ()
|
mergeContactRecords :: DB.Connection -> UserId -> Contact -> Contact -> IO ()
|
||||||
mergeContactRecords db userId Contact {contactId = toContactId} Contact {contactId = fromContactId, localDisplayName} = do
|
mergeContactRecords db userId ct1 ct2 = do
|
||||||
|
let (toCt, fromCt) = toFromContacts ct1 ct2
|
||||||
|
Contact {contactId = toContactId} = toCt
|
||||||
|
Contact {contactId = fromContactId, localDisplayName} = fromCt
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
|
-- TODO next query fixes incorrect unused contacts deletion; consider more thorough fix
|
||||||
|
when (contactDirect toCt && not (contactUsed toCt)) $
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
"UPDATE contacts SET contact_used = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?"
|
||||||
|
(currentTs, userId, toContactId)
|
||||||
DB.execute
|
DB.execute
|
||||||
db
|
db
|
||||||
"UPDATE connections SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
|
"UPDATE connections SET contact_id = ?, updated_at = ? WHERE contact_id = ? AND user_id = ?"
|
||||||
@ -1659,6 +1668,17 @@ mergeContactRecords db userId Contact {contactId = toContactId} Contact {contact
|
|||||||
deleteContactProfile_ db userId fromContactId
|
deleteContactProfile_ db userId fromContactId
|
||||||
DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId)
|
DB.execute db "DELETE FROM contacts WHERE contact_id = ? AND user_id = ?" (fromContactId, userId)
|
||||||
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId)
|
DB.execute db "DELETE FROM display_names WHERE local_display_name = ? AND user_id = ?" (localDisplayName, userId)
|
||||||
|
where
|
||||||
|
toFromContacts :: Contact -> Contact -> (Contact, Contact)
|
||||||
|
toFromContacts c1 c2
|
||||||
|
| d1 && not d2 = (c1, c2)
|
||||||
|
| d2 && not d1 = (c2, c1)
|
||||||
|
| ctCreatedAt c1 <= ctCreatedAt c2 = (c1, c2)
|
||||||
|
| otherwise = (c2, c1)
|
||||||
|
where
|
||||||
|
d1 = directOrUsed c1
|
||||||
|
d2 = directOrUsed c2
|
||||||
|
ctCreatedAt Contact {createdAt} = createdAt
|
||||||
|
|
||||||
getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
|
getConnectionEntity :: DB.Connection -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity
|
||||||
getConnectionEntity db user@User {userId, userContactId} agentConnId = do
|
getConnectionEntity db user@User {userId, userContactId} agentConnId = do
|
||||||
|
@ -159,9 +159,12 @@ contactConnId = aConnId . contactConn
|
|||||||
contactConnIncognito :: Contact -> Bool
|
contactConnIncognito :: Contact -> Bool
|
||||||
contactConnIncognito = connIncognito . contactConn
|
contactConnIncognito = connIncognito . contactConn
|
||||||
|
|
||||||
|
contactDirect :: Contact -> Bool
|
||||||
|
contactDirect Contact {activeConn = Connection {connLevel, viaGroupLink}} = connLevel == 0 && not viaGroupLink
|
||||||
|
|
||||||
directOrUsed :: Contact -> Bool
|
directOrUsed :: Contact -> Bool
|
||||||
directOrUsed Contact {contactUsed, activeConn = Connection {connLevel, viaGroupLink}} =
|
directOrUsed ct@Contact {contactUsed} =
|
||||||
(connLevel == 0 && not viaGroupLink) || contactUsed
|
contactDirect ct || contactUsed
|
||||||
|
|
||||||
anyDirectOrUsed :: Contact -> Bool
|
anyDirectOrUsed :: Contact -> Bool
|
||||||
anyDirectOrUsed Contact {contactUsed, activeConn = Connection {connLevel}} = connLevel == 0 || contactUsed
|
anyDirectOrUsed Contact {contactUsed, activeConn = Connection {connLevel}} = connLevel == 0 || contactUsed
|
||||||
|
@ -47,6 +47,7 @@ chatGroupTests = do
|
|||||||
it "unused host contact is deleted after all groups with it are deleted" testGroupLinkUnusedHostContactDeleted
|
it "unused host contact is deleted after all groups with it are deleted" testGroupLinkUnusedHostContactDeleted
|
||||||
it "leaving groups with unused host contacts deletes incognito profiles" testGroupLinkIncognitoUnusedHostContactsDeleted
|
it "leaving groups with unused host contacts deletes incognito profiles" testGroupLinkIncognitoUnusedHostContactsDeleted
|
||||||
it "group link member role" testGroupLinkMemberRole
|
it "group link member role" testGroupLinkMemberRole
|
||||||
|
it "leaving and deleting the group joined via link should NOT delete previously existing direct contacts" testGroupLinkLeaveDelete
|
||||||
|
|
||||||
testGroup :: HasCallStack => SpecWith FilePath
|
testGroup :: HasCallStack => SpecWith FilePath
|
||||||
testGroup = versionTestMatrix3 runTestGroup
|
testGroup = versionTestMatrix3 runTestGroup
|
||||||
@ -1916,3 +1917,72 @@ testGroupLinkMemberRole =
|
|||||||
concurrently_
|
concurrently_
|
||||||
(alice <# "#team bob> hey now")
|
(alice <# "#team bob> hey now")
|
||||||
(cath <# "#team bob> hey now")
|
(cath <# "#team bob> hey now")
|
||||||
|
|
||||||
|
testGroupLinkLeaveDelete :: HasCallStack => FilePath -> IO ()
|
||||||
|
testGroupLinkLeaveDelete =
|
||||||
|
testChat3 aliceProfile bobProfile cathProfile $
|
||||||
|
\alice bob cath -> do
|
||||||
|
connectUsers alice bob
|
||||||
|
connectUsers cath bob
|
||||||
|
alice ##> "/g team"
|
||||||
|
alice <## "group #team is created"
|
||||||
|
alice <## "to add members use /a team <name> or /create link #team"
|
||||||
|
alice ##> "/create link #team"
|
||||||
|
gLink <- getGroupLink alice "team" GRMember True
|
||||||
|
bob ##> ("/c " <> gLink)
|
||||||
|
bob <## "connection request sent!"
|
||||||
|
alice <## "bob_1 (Bob): accepting request to join group #team..."
|
||||||
|
concurrentlyN_
|
||||||
|
[ alice
|
||||||
|
<### [ "bob_1 (Bob): contact is connected",
|
||||||
|
"contact bob_1 is merged into bob",
|
||||||
|
"use @bob <message> to send messages",
|
||||||
|
EndsWith "invited to group #team via your group link",
|
||||||
|
EndsWith "joined the group"
|
||||||
|
],
|
||||||
|
bob
|
||||||
|
<### [ "alice_1 (Alice): contact is connected",
|
||||||
|
"contact alice_1 is merged into alice",
|
||||||
|
"use @alice <message> to send messages",
|
||||||
|
"#team: you joined the group"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
cath ##> ("/c " <> gLink)
|
||||||
|
cath <## "connection request sent!"
|
||||||
|
alice <## "cath (Catherine): accepting request to join group #team..."
|
||||||
|
concurrentlyN_
|
||||||
|
[ alice
|
||||||
|
<### [ "cath (Catherine): contact is connected",
|
||||||
|
"cath invited to group #team via your group link",
|
||||||
|
"#team: cath joined the group"
|
||||||
|
],
|
||||||
|
cath
|
||||||
|
<### [ "alice (Alice): contact is connected",
|
||||||
|
"#team: you joined the group",
|
||||||
|
"#team: member bob_1 (Bob) is connected",
|
||||||
|
"contact bob_1 is merged into bob",
|
||||||
|
"use @bob <message> to send messages"
|
||||||
|
],
|
||||||
|
bob
|
||||||
|
<### [ "#team: alice added cath_1 (Catherine) to the group (connecting...)",
|
||||||
|
"#team: new member cath_1 is connected",
|
||||||
|
"contact cath_1 is merged into cath",
|
||||||
|
"use @cath <message> to send messages"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
bob ##> "/l team"
|
||||||
|
concurrentlyN_
|
||||||
|
[ do
|
||||||
|
bob <## "#team: you left the group"
|
||||||
|
bob <## "use /d #team to delete the group",
|
||||||
|
alice <## "#team: bob left the group",
|
||||||
|
cath <## "#team: bob left the group"
|
||||||
|
]
|
||||||
|
bob ##> "/contacts"
|
||||||
|
bob <## "alice (Alice)"
|
||||||
|
bob <## "cath (Catherine)"
|
||||||
|
bob ##> "/d #team"
|
||||||
|
bob <## "#team: you deleted the group"
|
||||||
|
bob ##> "/contacts"
|
||||||
|
bob <## "alice (Alice)"
|
||||||
|
bob <## "cath (Catherine)"
|
||||||
|
Loading…
Reference in New Issue
Block a user