Merge branch 'master-ghc8107' into master-android
This commit is contained in:
commit
9ed723bafa
@ -720,8 +720,9 @@ func apiUpdateProfile(profile: Profile) async throws -> (Profile, [Contact])? {
|
|||||||
let userId = try currentUserId("apiUpdateProfile")
|
let userId = try currentUserId("apiUpdateProfile")
|
||||||
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
|
let r = await chatSendCmd(.apiUpdateProfile(userId: userId, profile: profile))
|
||||||
switch r {
|
switch r {
|
||||||
case .userProfileNoChange: return nil
|
case .userProfileNoChange: return (profile, [])
|
||||||
case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts)
|
case let .userProfileUpdated(_, _, toProfile, updateSummary): return (toProfile, updateSummary.changedContacts)
|
||||||
|
case .chatCmdError(_, .errorStore(.duplicateName)): return nil;
|
||||||
default: throw r
|
default: throw r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,7 +242,7 @@ struct ChatInfoView: View {
|
|||||||
}
|
}
|
||||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||||
if contact.ready && contact.active {
|
if contact.ready && contact.active {
|
||||||
ActionSheet(
|
return ActionSheet(
|
||||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||||
buttons: [
|
buttons: [
|
||||||
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
|
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
|
||||||
@ -251,7 +251,7 @@ struct ChatInfoView: View {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ActionSheet(
|
return ActionSheet(
|
||||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||||
buttons: [
|
buttons: [
|
||||||
.destructive(Text("Delete")) { deleteContact() },
|
.destructive(Text("Delete")) { deleteContact() },
|
||||||
|
@ -78,7 +78,7 @@ struct ChatListNavLink: View {
|
|||||||
.frame(height: rowHeights[dynamicTypeSize])
|
.frame(height: rowHeights[dynamicTypeSize])
|
||||||
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
||||||
if contact.ready && contact.active {
|
if contact.ready && contact.active {
|
||||||
ActionSheet(
|
return ActionSheet(
|
||||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||||
buttons: [
|
buttons: [
|
||||||
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
|
.destructive(Text("Delete and notify contact")) { Task { await deleteChat(chat, notify: true) } },
|
||||||
@ -87,7 +87,7 @@ struct ChatListNavLink: View {
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
ActionSheet(
|
return ActionSheet(
|
||||||
title: Text("Delete contact?\nThis cannot be undone!"),
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
||||||
buttons: [
|
buttons: [
|
||||||
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
|
.destructive(Text("Delete")) { Task { await deleteChat(chat) } },
|
||||||
|
@ -174,11 +174,13 @@ struct UserProfile: View {
|
|||||||
chatModel.updateCurrentUser(newProfile)
|
chatModel.updateCurrentUser(newProfile)
|
||||||
profile = newProfile
|
profile = newProfile
|
||||||
}
|
}
|
||||||
|
editProfile = false
|
||||||
|
} else {
|
||||||
|
alert = .duplicateUserError
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
|
logger.error("UserProfile apiUpdateProfile error: \(responseError(error))")
|
||||||
}
|
}
|
||||||
editProfile = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +83,9 @@ public enum AppState: String {
|
|||||||
|
|
||||||
public var canSuspend: Bool {
|
public var canSuspend: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .active: true
|
case .active: return true
|
||||||
case .bgRefresh: true
|
case .bgRefresh: return true
|
||||||
default: false
|
default: return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,7 @@ fun processExternalIntent(intent: Intent?) {
|
|||||||
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
val text = intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
if (uri.scheme != "content") return showNonContentUriAlert()
|
||||||
// Shared file that contains plain text, like `*.log` file
|
// Shared file that contains plain text, like `*.log` file
|
||||||
chatModel.sharedContent.value = SharedContent.File(text ?: "", uri.toURI())
|
chatModel.sharedContent.value = SharedContent.File(text ?: "", uri.toURI())
|
||||||
} else if (text != null) {
|
} else if (text != null) {
|
||||||
@ -153,12 +154,14 @@ fun processExternalIntent(intent: Intent?) {
|
|||||||
isMediaIntent(intent) -> {
|
isMediaIntent(intent) -> {
|
||||||
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
if (uri.scheme != "content") return showNonContentUriAlert()
|
||||||
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri.toURI()))
|
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", listOf(uri.toURI()))
|
||||||
} // All other mime types
|
} // All other mime types
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
val uri = intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
|
if (uri.scheme != "content") return showNonContentUriAlert()
|
||||||
chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri.toURI())
|
chatModel.sharedContent.value = SharedContent.File(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uri.toURI())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,6 +176,7 @@ fun processExternalIntent(intent: Intent?) {
|
|||||||
isMediaIntent(intent) -> {
|
isMediaIntent(intent) -> {
|
||||||
val uris = intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM) as? List<Uri>
|
val uris = intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM) as? List<Uri>
|
||||||
if (uris != null) {
|
if (uris != null) {
|
||||||
|
if (uris.any { it.scheme != "content" }) return showNonContentUriAlert()
|
||||||
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris.map { it.toURI() })
|
chatModel.sharedContent.value = SharedContent.Media(intent.getStringExtra(Intent.EXTRA_TEXT) ?: "", uris.map { it.toURI() })
|
||||||
} // All other mime types
|
} // All other mime types
|
||||||
}
|
}
|
||||||
@ -185,6 +189,13 @@ fun processExternalIntent(intent: Intent?) {
|
|||||||
fun isMediaIntent(intent: Intent): Boolean =
|
fun isMediaIntent(intent: Intent): Boolean =
|
||||||
intent.type?.startsWith("image/") == true || intent.type?.startsWith("video/") == true
|
intent.type?.startsWith("image/") == true || intent.type?.startsWith("video/") == true
|
||||||
|
|
||||||
|
private fun showNonContentUriAlert() {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title = generalGetString(MR.strings.non_content_uri_alert_title),
|
||||||
|
text = generalGetString(MR.strings.non_content_uri_alert_text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
//fun testJson() {
|
//fun testJson() {
|
||||||
// val str: String = """
|
// val str: String = """
|
||||||
// """.trimIndent()
|
// """.trimIndent()
|
||||||
|
@ -943,6 +943,9 @@ object ChatController {
|
|||||||
val r = sendCmd(CC.ApiUpdateProfile(userId, profile))
|
val r = sendCmd(CC.ApiUpdateProfile(userId, profile))
|
||||||
if (r is CR.UserProfileNoChange) return profile to emptyList()
|
if (r is CR.UserProfileNoChange) return profile to emptyList()
|
||||||
if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts
|
if (r is CR.UserProfileUpdated) return r.toProfile to r.updateSummary.changedContacts
|
||||||
|
if (r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName) {
|
||||||
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.failed_to_create_user_duplicate_title), generalGetString(MR.strings.failed_to_create_user_duplicate_desc))
|
||||||
|
}
|
||||||
Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}")
|
Log.e(TAG, "apiUpdateProfile bad response: ${r.responseType} ${r.details}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -324,8 +324,26 @@ fun ComposeView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteUnusedFiles() {
|
fun deleteUnusedFiles() {
|
||||||
chatModel.filesToDelete.forEach { it.delete() }
|
val shared = chatModel.sharedContent.value
|
||||||
chatModel.filesToDelete.clear()
|
if (shared == null) {
|
||||||
|
chatModel.filesToDelete.forEach { it.delete() }
|
||||||
|
chatModel.filesToDelete.clear()
|
||||||
|
} else {
|
||||||
|
val sharedPaths = when (shared) {
|
||||||
|
is SharedContent.Media -> shared.uris.map { it.toString() }
|
||||||
|
is SharedContent.File -> listOf(shared.uri.toString())
|
||||||
|
is SharedContent.Text -> emptyList()
|
||||||
|
}
|
||||||
|
// When sharing a file and pasting it in SimpleX itself, the file shouldn't be deleted before sending or before leaving the chat after sharing
|
||||||
|
chatModel.filesToDelete.removeAll { file ->
|
||||||
|
if (sharedPaths.any { it.endsWith(file.name) }) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
file.delete()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
|
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
|
||||||
|
@ -42,8 +42,8 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
val (newProfile, _) = updated
|
val (newProfile, _) = updated
|
||||||
chatModel.updateCurrentUser(newProfile)
|
chatModel.updateCurrentUser(newProfile)
|
||||||
profile = newProfile
|
profile = newProfile
|
||||||
|
close()
|
||||||
}
|
}
|
||||||
close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
<!-- MainActivity.kt -->
|
<!-- MainActivity.kt -->
|
||||||
<string name="opening_database">Opening database…</string>
|
<string name="opening_database">Opening database…</string>
|
||||||
|
<string name="non_content_uri_alert_title">Invalid file path</string>
|
||||||
|
<string name="non_content_uri_alert_text">You shared an invalid file path. Report the issue to the app developers.</string>
|
||||||
|
|
||||||
<!-- Server info - ChatModel.kt -->
|
<!-- Server info - ChatModel.kt -->
|
||||||
<string name="server_connected">connected</string>
|
<string name="server_connected">connected</string>
|
||||||
|
@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: 6e2bb080263b9d899e2bd369559dbd4f6978b4cc
|
tag: bba7ad349459dc212782517bce68a60299bbb827
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."6e2bb080263b9d899e2bd369559dbd4f6978b4cc" = "0rwamck6jvky16g1323h1b0355yw79v81557wxwjpzni97k418s9";
|
"https://github.com/simplex-chat/simplexmq.git"."bba7ad349459dc212782517bce68a60299bbb827" = "0qsiv83zvz2q4g9rhjys57yaj14hvjl0wp71dmckvqayyz7mfqqa";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895";
|
"https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";
|
||||||
|
@ -1502,13 +1502,15 @@ processChatCommand = \case
|
|||||||
chatRef <- getChatRef user chatName
|
chatRef <- getChatRef user chatName
|
||||||
chatItemId <- getChatItemIdByText user chatRef msg
|
chatItemId <- getChatItemIdByText user chatRef msg
|
||||||
processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction
|
processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction
|
||||||
APINewGroup userId gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
|
APINewGroup userId incognito gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
|
||||||
checkValidName displayName
|
checkValidName displayName
|
||||||
gVar <- asks idsDrg
|
gVar <- asks idsDrg
|
||||||
groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile
|
-- [incognito] generate incognito profile for group membership
|
||||||
|
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||||
|
groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile incognitoProfile
|
||||||
pure $ CRGroupCreated user groupInfo
|
pure $ CRGroupCreated user groupInfo
|
||||||
NewGroup gProfile -> withUser $ \User {userId} ->
|
NewGroup incognito gProfile -> withUser $ \User {userId} ->
|
||||||
processChatCommand $ APINewGroup userId gProfile
|
processChatCommand $ APINewGroup userId incognito gProfile
|
||||||
APIAddMember groupId contactId memRole -> withUser $ \user -> withChatLock "addMember" $ do
|
APIAddMember groupId contactId memRole -> withUser $ \user -> withChatLock "addMember" $ do
|
||||||
-- TODO for large groups: no need to load all members to determine if contact is a member
|
-- TODO for large groups: no need to load all members to determine if contact is a member
|
||||||
(group, contact) <- withStore $ \db -> (,) <$> getGroup db user groupId <*> getContact db user contactId
|
(group, contact) <- withStore $ \db -> (,) <$> getGroup db user groupId <*> getContact db user contactId
|
||||||
@ -5707,8 +5709,8 @@ chatCommandP =
|
|||||||
("/help settings" <|> "/hs") $> ChatHelp HSSettings,
|
("/help settings" <|> "/hs") $> ChatHelp HSSettings,
|
||||||
("/help db" <|> "/hd") $> ChatHelp HSDatabase,
|
("/help db" <|> "/hd") $> ChatHelp HSDatabase,
|
||||||
("/help" <|> "/h") $> ChatHelp HSMain,
|
("/help" <|> "/h") $> ChatHelp HSMain,
|
||||||
("/group " <|> "/g ") *> char_ '#' *> (NewGroup <$> groupProfile),
|
("/group" <|> "/g") *> (NewGroup <$> incognitoP <* A.space <* char_ '#' <*> groupProfile),
|
||||||
"/_group " *> (APINewGroup <$> A.decimal <* A.space <*> jsonP),
|
"/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP),
|
||||||
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayName <* A.space <* char_ '@' <*> displayName <*> (memberRole <|> pure GRMember)),
|
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayName <* A.space <* char_ '@' <*> displayName <*> (memberRole <|> pure GRMember)),
|
||||||
("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayName),
|
("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayName),
|
||||||
("/member role " <|> "/mr ") *> char_ '#' *> (MemberRole <$> displayName <* A.space <* char_ '@' <*> displayName <*> memberRole),
|
("/member role " <|> "/mr ") *> char_ '#' *> (MemberRole <$> displayName <* A.space <* char_ '@' <*> displayName <*> memberRole),
|
||||||
|
@ -363,8 +363,8 @@ data ChatCommand
|
|||||||
| EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text}
|
| EditMessage {chatName :: ChatName, editedMsg :: Text, message :: Text}
|
||||||
| UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text}
|
| UpdateLiveMessage {chatName :: ChatName, chatItemId :: ChatItemId, liveMessage :: Bool, message :: Text}
|
||||||
| ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text}
|
| ReactToMessage {add :: Bool, reaction :: MsgReaction, chatName :: ChatName, reactToMessage :: Text}
|
||||||
| APINewGroup UserId GroupProfile
|
| APINewGroup UserId IncognitoEnabled GroupProfile
|
||||||
| NewGroup GroupProfile
|
| NewGroup IncognitoEnabled GroupProfile
|
||||||
| AddMember GroupName ContactName GroupMemberRole
|
| AddMember GroupName ContactName GroupMemberRole
|
||||||
| JoinGroup GroupName
|
| JoinGroup GroupName
|
||||||
| MemberRole GroupName ContactName GroupMemberRole
|
| MemberRole GroupName ContactName GroupMemberRole
|
||||||
|
@ -188,17 +188,6 @@ createIncognitoProfile db User {userId} p = do
|
|||||||
createdAt <- getCurrentTime
|
createdAt <- getCurrentTime
|
||||||
createIncognitoProfile_ db userId createdAt p
|
createIncognitoProfile_ db userId createdAt p
|
||||||
|
|
||||||
createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
|
|
||||||
createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
|
|
||||||
DB.execute
|
|
||||||
db
|
|
||||||
[sql|
|
|
||||||
INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at)
|
|
||||||
VALUES (?,?,?,?,?,?,?)
|
|
||||||
|]
|
|
||||||
(displayName, fullName, image, userId, Just True, createdAt, createdAt)
|
|
||||||
insertedRowId db
|
|
||||||
|
|
||||||
createDirectContact :: DB.Connection -> User -> Connection -> Profile -> ExceptT StoreError IO Contact
|
createDirectContact :: DB.Connection -> User -> Connection -> Profile -> ExceptT StoreError IO Contact
|
||||||
createDirectContact db user@User {userId} activeConn@Connection {connId, localAlias} p@Profile {preferences} = do
|
createDirectContact db user@User {userId} activeConn@Connection {connId, localAlias} p@Profile {preferences} = do
|
||||||
createdAt <- liftIO getCurrentTime
|
createdAt <- liftIO getCurrentTime
|
||||||
|
@ -278,11 +278,12 @@ getGroupAndMember db User {userId, userContactId} groupMemberId =
|
|||||||
in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection connRow})
|
in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection connRow})
|
||||||
|
|
||||||
-- | creates completely new group with a single member - the current user
|
-- | creates completely new group with a single member - the current user
|
||||||
createNewGroup :: DB.Connection -> TVar ChaChaDRG -> User -> GroupProfile -> ExceptT StoreError IO GroupInfo
|
createNewGroup :: DB.Connection -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo
|
||||||
createNewGroup db gVar user@User {userId} groupProfile = ExceptT $ do
|
createNewGroup db gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do
|
||||||
let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile
|
let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile
|
||||||
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
|
customUserProfileId <- mapM (createIncognitoProfile_ db userId currentTs) incognitoProfile
|
||||||
withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do
|
withLocalDisplayName db userId displayName $ \ldn -> runExceptT $ do
|
||||||
groupId <- liftIO $ do
|
groupId <- liftIO $ do
|
||||||
DB.execute
|
DB.execute
|
||||||
@ -296,7 +297,7 @@ createNewGroup db gVar user@User {userId} groupProfile = ExceptT $ do
|
|||||||
(ldn, userId, profileId, True, currentTs, currentTs, currentTs)
|
(ldn, userId, profileId, True, currentTs, currentTs, currentTs)
|
||||||
insertedRowId db
|
insertedRowId db
|
||||||
memberId <- liftIO $ encodedRandomBytes gVar 12
|
memberId <- liftIO $ encodedRandomBytes gVar 12
|
||||||
membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs
|
membership <- createContactMemberInv_ db user groupId user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser customUserProfileId currentTs
|
||||||
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
|
let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False}
|
||||||
pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}
|
pure GroupInfo {groupId, localDisplayName = ldn, groupProfile, fullGroupPreferences, membership, hostConnCustomUserProfileId = Nothing, chatSettings, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs}
|
||||||
|
|
||||||
|
@ -182,6 +182,17 @@ createConnection_ db userId connType entityId acId peerChatVRange@(VersionRange
|
|||||||
where
|
where
|
||||||
ent ct = if connType == ct then entityId else Nothing
|
ent ct = if connType == ct then entityId else Nothing
|
||||||
|
|
||||||
|
createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
|
||||||
|
createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
|
||||||
|
DB.execute
|
||||||
|
db
|
||||||
|
[sql|
|
||||||
|
INSERT INTO contact_profiles (display_name, full_name, image, user_id, incognito, created_at, updated_at)
|
||||||
|
VALUES (?,?,?,?,?,?,?)
|
||||||
|
|]
|
||||||
|
(displayName, fullName, image, userId, Just True, createdAt, createdAt)
|
||||||
|
insertedRowId db
|
||||||
|
|
||||||
setPeerChatVRange :: DB.Connection -> Int64 -> VersionRange -> IO ()
|
setPeerChatVRange :: DB.Connection -> Int64 -> VersionRange -> IO ()
|
||||||
setPeerChatVRange db connId (VersionRange minVer maxVer) =
|
setPeerChatVRange db connId (VersionRange minVer maxVer) =
|
||||||
DB.execute
|
DB.execute
|
||||||
|
@ -131,7 +131,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
|
|||||||
CRUserContactLink u UserContactLink {connReqContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connReqContact <> autoAcceptStatus_ autoAccept
|
CRUserContactLink u UserContactLink {connReqContact, autoAccept} -> ttyUser u $ connReqContact_ "Your chat address:" connReqContact <> autoAcceptStatus_ autoAccept
|
||||||
CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept
|
CRUserContactLinkUpdated u UserContactLink {autoAccept} -> ttyUser u $ autoAcceptStatus_ autoAccept
|
||||||
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
|
||||||
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g
|
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
|
||||||
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
|
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
|
||||||
CRGroupsList u gs -> ttyUser u $ viewGroupsList gs
|
CRGroupsList u gs -> ttyUser u $ viewGroupsList gs
|
||||||
CRSentGroupInvitation u g c _ ->
|
CRSentGroupInvitation u g c _ ->
|
||||||
@ -789,11 +789,22 @@ viewReceivedContactRequest c Profile {fullName} =
|
|||||||
"to reject: " <> highlight ("/rc " <> viewName c) <> " (the sender will NOT be notified)"
|
"to reject: " <> highlight ("/rc " <> viewName c) <> " (the sender will NOT be notified)"
|
||||||
]
|
]
|
||||||
|
|
||||||
viewGroupCreated :: GroupInfo -> [StyledString]
|
viewGroupCreated :: GroupInfo -> Bool -> [StyledString]
|
||||||
viewGroupCreated g =
|
viewGroupCreated g testView =
|
||||||
[ "group " <> ttyFullGroup g <> " is created",
|
case incognitoMembershipProfile g of
|
||||||
"to add members use " <> highlight ("/a " <> viewGroupName g <> " <name>") <> " or " <> highlight ("/create link #" <> viewGroupName g)
|
Just localProfile
|
||||||
]
|
| testView -> incognitoProfile' profile : message
|
||||||
|
| otherwise -> message
|
||||||
|
where
|
||||||
|
profile = fromLocalProfile localProfile
|
||||||
|
message =
|
||||||
|
[ "group " <> ttyFullGroup g <> " is created, your incognito profile for this group is " <> incognitoProfile' profile,
|
||||||
|
"to add members use " <> highlight ("/create link #" <> viewGroupName g)
|
||||||
|
]
|
||||||
|
Nothing ->
|
||||||
|
[ "group " <> ttyFullGroup g <> " is created",
|
||||||
|
"to add members use " <> highlight ("/a " <> viewGroupName g <> " <name>") <> " or " <> highlight ("/create link #" <> viewGroupName g)
|
||||||
|
]
|
||||||
|
|
||||||
viewCannotResendInvitation :: GroupInfo -> ContactName -> [StyledString]
|
viewCannotResendInvitation :: GroupInfo -> ContactName -> [StyledString]
|
||||||
viewCannotResendInvitation g c =
|
viewCannotResendInvitation g c =
|
||||||
@ -1668,7 +1679,7 @@ viewChatError logLevel = \case
|
|||||||
_ -> ": you have insufficient permissions for this action, the required role is " <> plain (strEncode role)
|
_ -> ": you have insufficient permissions for this action, the required role is " <> plain (strEncode role)
|
||||||
CEGroupMemberInitialRole g role -> [ttyGroup' g <> ": initial role for group member cannot be " <> plain (strEncode role) <> ", use member or observer"]
|
CEGroupMemberInitialRole g role -> [ttyGroup' g <> ": initial role for group member cannot be " <> plain (strEncode role) <> ", use member or observer"]
|
||||||
CEContactIncognitoCantInvite -> ["you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito"]
|
CEContactIncognitoCantInvite -> ["you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito"]
|
||||||
CEGroupIncognitoCantInvite -> ["you've connected to this group using an incognito profile - prohibited to invite contacts"]
|
CEGroupIncognitoCantInvite -> ["you are using an incognito profile for this group - prohibited to invite contacts"]
|
||||||
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
|
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
|
||||||
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> viewGroupName g)]
|
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> viewGroupName g)]
|
||||||
CEGroupMemberNotActive -> ["your group connection is not active yet, try later"]
|
CEGroupMemberNotActive -> ["your group connection is not active yet, try later"]
|
||||||
|
@ -49,7 +49,7 @@ extra-deps:
|
|||||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||||
# - ../simplexmq
|
# - ../simplexmq
|
||||||
- github: simplex-chat/simplexmq
|
- github: simplex-chat/simplexmq
|
||||||
commit: 6e2bb080263b9d899e2bd369559dbd4f6978b4cc
|
commit: bba7ad349459dc212782517bce68a60299bbb827
|
||||||
- github: kazu-yamamoto/http2
|
- github: kazu-yamamoto/http2
|
||||||
commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517
|
commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517
|
||||||
# - ../direct-sqlcipher
|
# - ../direct-sqlcipher
|
||||||
|
@ -23,6 +23,7 @@ chatGroupTests = do
|
|||||||
describe "chat groups" $ do
|
describe "chat groups" $ do
|
||||||
it "add contacts, create group and send/receive messages" testGroup
|
it "add contacts, create group and send/receive messages" testGroup
|
||||||
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
|
it "add contacts, create group and send/receive messages, check messages" testGroupCheckMessages
|
||||||
|
it "create group with incognito membership" testNewGroupIncognito
|
||||||
it "create and join group with 4 members" testGroup2
|
it "create and join group with 4 members" testGroup2
|
||||||
it "create and delete group" testGroupDelete
|
it "create and delete group" testGroupDelete
|
||||||
it "create group with the same displayName" testGroupSameName
|
it "create group with the same displayName" testGroupSameName
|
||||||
@ -277,6 +278,56 @@ testGroupShared alice bob cath checkMessages = do
|
|||||||
alice #$> ("/_unread chat #1 on", id, "ok")
|
alice #$> ("/_unread chat #1 on", id, "ok")
|
||||||
alice #$> ("/_unread chat #1 off", id, "ok")
|
alice #$> ("/_unread chat #1 off", id, "ok")
|
||||||
|
|
||||||
|
testNewGroupIncognito :: HasCallStack => FilePath -> IO ()
|
||||||
|
testNewGroupIncognito =
|
||||||
|
testChat2 aliceProfile bobProfile $
|
||||||
|
\alice bob -> do
|
||||||
|
connectUsers alice bob
|
||||||
|
|
||||||
|
-- alice creates group with incognito membership
|
||||||
|
alice ##> "/g i team"
|
||||||
|
aliceIncognito <- getTermLine alice
|
||||||
|
alice <## ("group #team is created, your incognito profile for this group is " <> aliceIncognito)
|
||||||
|
alice <## "to add members use /create link #team"
|
||||||
|
|
||||||
|
-- alice invites bob
|
||||||
|
alice ##> "/a team bob"
|
||||||
|
alice <## "you are using an incognito profile for this group - prohibited to invite contacts"
|
||||||
|
|
||||||
|
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..."
|
||||||
|
_ <- getTermLine alice
|
||||||
|
concurrentlyN_
|
||||||
|
[ do
|
||||||
|
alice <## ("bob_1 (Bob): contact is connected, your incognito profile for this contact is " <> aliceIncognito)
|
||||||
|
alice <## "use /i bob_1 to print out this incognito profile again"
|
||||||
|
alice <## "bob_1 invited to group #team via your group link"
|
||||||
|
alice <## "#team: bob_1 joined the group",
|
||||||
|
do
|
||||||
|
bob <## (aliceIncognito <> ": contact is connected")
|
||||||
|
bob <## "#team: you joined the group"
|
||||||
|
]
|
||||||
|
|
||||||
|
alice <##> bob
|
||||||
|
|
||||||
|
alice ?#> "@bob_1 hi, I'm incognito"
|
||||||
|
bob <# (aliceIncognito <> "> hi, I'm incognito")
|
||||||
|
bob #> ("@" <> aliceIncognito <> " hey, I'm bob")
|
||||||
|
alice ?<# "bob_1> hey, I'm bob"
|
||||||
|
|
||||||
|
alice ?#> "#team hello"
|
||||||
|
bob <# ("#team " <> aliceIncognito <> "> hello")
|
||||||
|
bob #> "#team hi there"
|
||||||
|
alice ?<# "#team bob_1> hi there"
|
||||||
|
|
||||||
|
alice ##> "/gs"
|
||||||
|
alice <## "i #team (2 members)"
|
||||||
|
bob ##> "/gs"
|
||||||
|
bob <## "#team (2 members)"
|
||||||
|
|
||||||
testGroup2 :: HasCallStack => FilePath -> IO ()
|
testGroup2 :: HasCallStack => FilePath -> IO ()
|
||||||
testGroup2 =
|
testGroup2 =
|
||||||
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
|
testChatCfg4 testCfgCreateGroupDirect aliceProfile bobProfile cathProfile danProfile $
|
||||||
|
@ -1085,7 +1085,7 @@ testJoinGroupIncognito =
|
|||||||
]
|
]
|
||||||
-- cath cannot invite to the group because her membership is incognito
|
-- cath cannot invite to the group because her membership is incognito
|
||||||
cath ##> "/a secret_club dan"
|
cath ##> "/a secret_club dan"
|
||||||
cath <## "you've connected to this group using an incognito profile - prohibited to invite contacts"
|
cath <## "you are using an incognito profile for this group - prohibited to invite contacts"
|
||||||
-- alice invites dan
|
-- alice invites dan
|
||||||
alice ##> "/a secret_club dan admin"
|
alice ##> "/a secret_club dan admin"
|
||||||
concurrentlyN_
|
concurrentlyN_
|
||||||
|
Loading…
Reference in New Issue
Block a user