ios, android: direct messages in group - types, TODOs

This commit is contained in:
spaced4ndy 2023-09-08 19:07:04 +04:00
parent 281d9c7f79
commit 1682577ede
13 changed files with 193 additions and 81 deletions

View File

@ -315,11 +315,13 @@ func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -
throw r
}
func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool = false, ttl: Int? = nil) async -> ChatItem? {
func apiSendMessage(sendRef: SendRef, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool = false, ttl: Int? = nil) async -> ChatItem? {
let chatModel = ChatModel.shared
let cmd: ChatCommand = .apiSendMessage(type: type, id: id, file: file, quotedItemId: quotedItemId, msg: msg, live: live, ttl: ttl)
let cmd: ChatCommand = .apiSendMessage(sendRef: sendRef, file: file, quotedItemId: quotedItemId, msg: msg, live: live, ttl: ttl)
let r: ChatResponse
if type == .direct {
switch sendRef {
// TODO group-direct: re-use direct logic for directMember
case .direct:
var cItem: ChatItem? = nil
let endTask = beginBGTask({
if let cItem = cItem {
@ -341,7 +343,7 @@ func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId:
}
endTask()
return nil
} else {
default:
r = await chatSendCmd(cmd, bgDelay: msgDelay)
if case let .newChatItem(_, aChatItem) = r {
return aChatItem.chatItem

View File

@ -41,7 +41,7 @@ struct CIRcvDecryptionError: View {
.onAppear {
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
if case let .group(groupInfo) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir {
case let .groupRcv(groupMember, _) = chatItem.chatDir {
do {
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
if let s = stats {
@ -78,7 +78,7 @@ struct CIRcvDecryptionError: View {
basicDecryptionErrorItem()
}
} else if case let .group(groupInfo) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir,
case let .groupRcv(groupMember, _) = chatItem.chatDir,
let modelMember = ChatModel.shared.groupMembers.first(where: { $0.id == groupMember.id }),
let memberStats = modelMember.activeConn?.connectionStats {
if memberStats.ratchetSyncAllowed {

View File

@ -33,7 +33,7 @@ struct DeletedItemView_Previews: PreviewProvider {
static var previews: some View {
Group {
DeletedItemView(chatItem: ChatItem.getDeletedContentSample())
DeletedItemView(chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData)))
DeletedItemView(chatItem: ChatItem.getDeletedContentSample(dir: .groupRcv(groupMember: GroupMember.sampleData, messageScope: .msGroup)))
}
.previewLayout(.fixed(width: 360, height: 200))
}

File diff suppressed because one or more lines are too long

View File

@ -428,7 +428,8 @@ struct ChatView: View {
}
@ViewBuilder private func chatItemView(_ ci: ChatItem, _ maxWidth: CGFloat) -> some View {
if case let .groupRcv(member) = ci.chatDir,
// TODO group-direct: display message as sent/received privately
if case let .groupRcv(member, _) = ci.chatDir,
case let .group(groupInfo) = chat.chatInfo {
let (prevItem, nextItem) = chatModel.getChatItemNeighbors(ci)
if ci.memberConnected != nil && nextItem?.memberConnected != nil {
@ -874,10 +875,11 @@ struct ChatView: View {
}
}
// TODO group-direct: show image if scope changes from group to private or vice versa
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
switch (prevItem?.chatDir) {
case .groupSnd: return true
case let .groupRcv(prevMember): return prevMember.groupMemberId != member.groupMemberId
case let .groupRcv(prevMember, _): return prevMember.groupMemberId != member.groupMemberId
default: return false
}
}

View File

@ -747,25 +747,37 @@ struct ComposeView: View {
}
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? {
if let chatItem = await apiSendMessage(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
file: file,
quotedItemId: quoted,
msg: mc,
live: live,
ttl: ttl
) {
await MainActor.run {
chatModel.removeLiveDummy(animated: false)
chatModel.addChatItem(chat.chatInfo, chatItem)
// TODO group-direct: directMember in compose state
var sendRef: SendRef?
let chatId = chat.chatInfo.apiId
switch chat.chatInfo.chatType {
case .direct: sendRef = .direct(contactId: chatId)
case .group: sendRef = .group(groupId: chatId, directMemberId: nil)
default: sendRef = nil
}
if let sendRef = sendRef {
if let chatItem = await apiSendMessage(
sendRef: sendRef,
file: file,
quotedItemId: quoted,
msg: mc,
live: live,
ttl: ttl
) {
await MainActor.run {
chatModel.removeLiveDummy(animated: false)
chatModel.addChatItem(chat.chatInfo, chatItem)
}
return chatItem
}
return chatItem
if let file = file {
removeFile(file.filePath)
}
return nil
} else {
logger.error("ComposeView send: sendRef is nil")
return nil
}
if let file = file {
removeFile(file.filePath)
}
return nil
}
func checkLinkPreview() -> MsgContent {

View File

@ -39,7 +39,7 @@ public enum ChatCommand {
case apiGetChats(userId: Int64)
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
case apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?)
case apiSendMessage(sendRef: SendRef, file: CryptoFile?, quotedItemId: Int64?, msg: MsgContent, live: Bool, ttl: Int?)
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64)
@ -156,10 +156,10 @@ public enum ChatCommand {
case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" +
(search == "" ? "" : " search=\(search)")
case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)"
case let .apiSendMessage(type, id, file, quotedItemId, mc, live, ttl):
case let .apiSendMessage(sendRef, file, quotedItemId, mc, live, ttl):
let msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc))
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)"
return "/_send \(sendRefStr(sendRef)) live=\(onOff(live)) ttl=\(ttlStr) json \(msg)"
case let .apiUpdateChatItem(type, id, itemId, mc, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(mc.cmdString)"
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)"
@ -365,6 +365,14 @@ public enum ChatCommand {
"\(type.rawValue)\(id)"
}
func sendRefStr(_ sendRef: SendRef) -> String {
switch sendRef {
case let .direct(contactId): return "@\(contactId)"
case let .group(groupId, .none): return "#\(groupId)"
case let .group(groupId, .some(directMemberId)): return "#\(groupId) @\(directMemberId)"
}
}
func protoServersStr(_ servers: [ServerCfg]) -> String {
encodeJSON(ProtoServersConfig(servers: servers))
}
@ -825,6 +833,11 @@ public enum ChatResponse: Decodable, Error {
}
}
public enum SendRef {
case direct(contactId: Int64)
case group(groupId: Int64, directMemberId: Int64?)
}
public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? {
switch chatResponse {
case let .chatCmdError(_, .error(error)): return error
@ -1454,6 +1467,7 @@ public enum ChatErrorType: Decodable {
case agentCommandError(message: String)
case invalidFileDescription(message: String)
case connectionIncognitoChangeProhibited
case peerChatVRangeIncompatible
case internalError(message: String)
case exception(message: String)
}
@ -1468,6 +1482,7 @@ public enum StoreError: Decodable {
case userNotFoundByContactRequestId(contactRequestId: Int64)
case contactNotFound(contactId: Int64)
case contactNotFoundByName(contactName: ContactName)
case contactNotFoundByMemberId(groupMemberId: Int64)
case contactNotReady(contactName: ContactName)
case duplicateContactLink
case userContactLinkNotFound
@ -1495,6 +1510,7 @@ public enum StoreError: Decodable {
case rcvFileNotFoundXFTP(agentRcvFileId: String)
case connectionNotFound(agentConnId: String)
case connectionNotFoundById(connId: Int64)
case connectionNotFoundByMemberId(groupMemberId: Int64)
case pendingConnectionNotFound(connId: Int64)
case introNotFound
case uniqueID

View File

@ -1968,7 +1968,7 @@ public struct AChatItem: Decodable {
public var chatItem: ChatItem
public var chatId: String {
if case let .groupRcv(groupMember) = chatItem.chatDir {
if case let .groupRcv(groupMember, _) = chatItem.chatDir {
return groupMember.id
}
return chatInfo.id
@ -2041,7 +2041,7 @@ public struct ChatItem: Identifiable, Decodable {
public var memberConnected: GroupMember? {
switch chatDir {
case .groupRcv(let groupMember):
case let .groupRcv(groupMember, _):
switch content {
case .rcvGroupEvent(rcvGroupEvent: .memberConnected): return groupMember
default: return nil
@ -2125,7 +2125,7 @@ public struct ChatItem: Identifiable, Decodable {
public var memberDisplayName: String? {
get {
if case let .groupRcv(groupMember) = chatDir {
if case let .groupRcv(groupMember, _) = chatDir {
return groupMember.displayName
} else {
return nil
@ -2133,9 +2133,10 @@ public struct ChatItem: Identifiable, Decodable {
}
}
// TODO group-direct: prohibit moderation if directMember is present
public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember)? {
switch (chatInfo, chatDir) {
case let (.group(groupInfo), .groupRcv(groupMember)):
case let (.group(groupInfo), .groupRcv(groupMember, _)):
let m = groupInfo.membership
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
? (groupInfo, groupMember)
@ -2246,9 +2247,10 @@ public struct ChatItem: Identifiable, Decodable {
)
}
// TODO group-direct: possibly this has to take sendRef as parameter instead (for directMember)
public static func liveDummy(_ chatType: ChatType) -> ChatItem {
var item = ChatItem(
chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd,
chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd(directMember: nil),
meta: CIMeta(
itemId: -2,
itemTs: .now,
@ -2280,11 +2282,34 @@ public struct ChatItem: Identifiable, Decodable {
}
}
public enum MessageScope: String, Decodable {
case msGroup = "group"
case msPrivate = "private"
}
public enum CIDirection: Decodable {
case directSnd
case directRcv
case groupSnd
case groupRcv(groupMember: GroupMember)
case groupSnd(directMember: GroupMember?)
case groupRcv(groupMember: GroupMember, messageScope: MessageScope)
public var sent: Bool {
get {
switch self {
case .directSnd: return true
case .directRcv: return false
case .groupSnd: return true
case .groupRcv: return false
}
}
}
}
public enum CIQDirection: Decodable {
case directSnd
case directRcv
case groupSnd(messageScope: MessageScope)
case groupRcv(groupMember: GroupMember, messageScope: MessageScope)
public var sent: Bool {
get {
@ -2592,7 +2617,7 @@ public enum MsgDecryptError: String, Decodable {
}
public struct CIQuote: Decodable, ItemContent {
public var chatDir: CIDirection?
public var chatDir: CIQDirection?
public var itemId: Int64?
var sharedMsgId: String? = nil
public var sentAt: Date
@ -2606,17 +2631,18 @@ public struct CIQuote: Decodable, ItemContent {
}
}
// TODO group-direct: "<sender> privately" possibly here?
public func getSender(_ membership: GroupMember?) -> String? {
switch (chatDir) {
case .directSnd: return "you"
case .directRcv: return nil
case .groupSnd: return membership?.displayName ?? "you"
case let .groupRcv(member): return member.displayName
case let .groupRcv(member, _): return member.displayName
case nil: return nil
}
}
public static func getSample(_ itemId: Int64?, _ sentAt: Date, _ text: String, chatDir: CIDirection?, image: String? = nil) -> CIQuote {
public static func getSample(_ itemId: Int64?, _ sentAt: Date, _ text: String, chatDir: CIQDirection?, image: String? = nil) -> CIQuote {
let mc: MsgContent
if let image = image {
mc = .image(text: text, image: image)

View File

@ -59,7 +59,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact)
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember, _) = cItem.chatDir {
title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: previewMode == .hidden)
} else {
title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):"

View File

@ -1588,8 +1588,9 @@ data class ChatItem (
file = null
)
// TODO group-direct: possibly this has to take sendRef as parameter instead (for directMember)
fun liveDummy(direct: Boolean): ChatItem = ChatItem(
chatDir = if (direct) CIDirection.DirectSnd() else CIDirection.GroupSnd(),
chatDir = if (direct) CIDirection.DirectSnd() else CIDirection.GroupSnd(directMember = null),
meta = CIMeta(
itemId = TEMP_LIVE_CHAT_ITEM_ID,
itemTs = Clock.System.now(),
@ -1621,12 +1622,33 @@ data class ChatItem (
}
}
@Serializable
enum class MessageScope(val messageScope: String) {
@SerialName("group") MSGroup("group"),
@SerialName("private") MSPrivate("private");
}
@Serializable
sealed class CIDirection {
@Serializable @SerialName("directSnd") class DirectSnd: CIDirection()
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
@Serializable @SerialName("groupSnd") class GroupSnd: CIDirection()
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember): CIDirection()
@Serializable @SerialName("groupSnd") class GroupSnd(val directMember: GroupMember? = null): CIDirection()
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember, val messageScope: MessageScope): CIDirection()
val sent: Boolean get() = when(this) {
is DirectSnd -> true
is DirectRcv -> false
is GroupSnd -> true
is GroupRcv -> false
}
}
@Serializable
sealed class CIQDirection {
@Serializable @SerialName("directSnd") class DirectSnd: CIQDirection()
@Serializable @SerialName("directRcv") class DirectRcv: CIQDirection()
@Serializable @SerialName("groupSnd") class GroupSnd(val messageScope: MessageScope): CIQDirection()
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember, val messageScope: MessageScope): CIQDirection()
val sent: Boolean get() = when(this) {
is DirectSnd -> true
@ -1921,7 +1943,7 @@ enum class MsgDecryptError {
@Serializable
class CIQuote (
val chatDir: CIDirection? = null,
val chatDir: CIQDirection? = null,
val itemId: Long? = null,
val sharedMsgId: String? = null,
val sentAt: Instant,
@ -1937,15 +1959,15 @@ class CIQuote (
fun sender(membership: GroupMember?): String? = when (chatDir) {
is CIDirection.DirectSnd -> generalGetString(MR.strings.sender_you_pronoun)
is CIDirection.DirectRcv -> null
is CIDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
is CIDirection.GroupRcv -> chatDir.groupMember.displayName
is CIQDirection.DirectSnd -> generalGetString(MR.strings.sender_you_pronoun)
is CIQDirection.DirectRcv -> null
is CIQDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
is CIQDirection.GroupRcv -> chatDir.groupMember.displayName
null -> null
}
companion object {
fun getSample(itemId: Long?, sentAt: Instant, text: String, chatDir: CIDirection?): CIQuote =
fun getSample(itemId: Long?, sentAt: Instant, text: String, chatDir: CIQDirection?): CIQuote =
CIQuote(chatDir = chatDir, itemId = itemId, sentAt = sentAt, content = MsgContent.MCText(text))
}
}

View File

@ -586,8 +586,8 @@ object ChatController {
return null
}
suspend fun apiSendMessage(type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? {
val cmd = CC.ApiSendMessage(type, id, file, quotedItemId, mc, live, ttl)
suspend fun apiSendMessage(sendRef: SendRef, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? {
val cmd = CC.ApiSendMessage(sendRef, file, quotedItemId, mc, live, ttl)
val r = sendCmd(cmd)
return when (r) {
is CR.NewChatItem -> r.chatItem
@ -1805,7 +1805,7 @@ sealed class CC {
class ApiGetChats(val userId: Long): CC()
class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC()
class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC()
class ApiSendMessage(val type: ChatType, val id: Long, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC()
class ApiSendMessage(val sendRef: SendRef, val file: CryptoFile?, val quotedItemId: Long?, val mc: MsgContent, val live: Boolean, val ttl: Int?): CC()
class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val mc: MsgContent, val live: Boolean): CC()
class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemId: Long, val mode: CIDeleteMode): CC()
class ApiDeleteMemberChatItem(val groupId: Long, val groupMemberId: Long, val itemId: Long): CC()
@ -1909,7 +1909,7 @@ sealed class CC {
is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId"
is ApiSendMessage -> {
val ttlStr = if (ttl != null) "$ttl" else "default"
"/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}"
"/_send ${sendRefStr(sendRef)} live=${onOff(live)} ttl=${ttlStr} json ${json.encodeToString(ComposedMessage(file, quotedItemId, mc))}"
}
is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}"
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
@ -2105,10 +2105,27 @@ sealed class CC {
companion object {
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
fun sendRefStr(sendRef: SendRef): String {
return when (sendRef) {
is SendRef.Direct -> "@${sendRef.contactId}"
is SendRef.Group -> {
when (sendRef.directMemberId) {
null -> "#${sendRef.groupId}"
else -> "#${sendRef.groupId} @${sendRef.directMemberId}"
}
}
}
}
fun protoServersStr(servers: List<ServerCfg>) = json.encodeToString(ProtoServersConfig(servers))
}
}
sealed class SendRef {
class Direct(val contactId: Long): SendRef()
class Group(val groupId: Long, val directMemberId: Long?): SendRef()
}
@Serializable
data class NewUser(
val profile: Profile?,
@ -3820,6 +3837,7 @@ sealed class ChatErrorType {
is AgentCommandError -> "agentCommandError"
is InvalidFileDescription -> "invalidFileDescription"
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
is InternalError -> "internalError"
is CEException -> "exception $message"
}
@ -3894,6 +3912,7 @@ sealed class ChatErrorType {
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
}
@ -3911,6 +3930,7 @@ sealed class StoreError {
is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId"
is ContactNotFound -> "contactNotFound"
is ContactNotFoundByName -> "contactNotFoundByName"
is ContactNotFoundByMemberId -> "contactNotFoundByMemberId"
is ContactNotReady -> "contactNotReady"
is DuplicateContactLink -> "duplicateContactLink"
is UserContactLinkNotFound -> "userContactLinkNotFound"
@ -3938,6 +3958,7 @@ sealed class StoreError {
is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP"
is ConnectionNotFound -> "connectionNotFound"
is ConnectionNotFoundById -> "connectionNotFoundById"
is ConnectionNotFoundByMemberId -> "connectionNotFoundByMemberId"
is PendingConnectionNotFound -> "pendingConnectionNotFound"
is IntroNotFound -> "introNotFound"
is UniqueID -> "uniqueID"
@ -3966,6 +3987,7 @@ sealed class StoreError {
@Serializable @SerialName("userNotFoundByContactRequestId") class UserNotFoundByContactRequestId(val contactRequestId: Long): StoreError()
@Serializable @SerialName("contactNotFound") class ContactNotFound(val contactId: Long): StoreError()
@Serializable @SerialName("contactNotFoundByName") class ContactNotFoundByName(val contactName: String): StoreError()
@Serializable @SerialName("contactNotFoundByMemberId") class ContactNotFoundByMemberId(val groupMemberId: Long): StoreError()
@Serializable @SerialName("contactNotReady") class ContactNotReady(val contactName: String): StoreError()
@Serializable @SerialName("duplicateContactLink") object DuplicateContactLink: StoreError()
@Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: StoreError()
@ -3993,6 +4015,7 @@ sealed class StoreError {
@Serializable @SerialName("rcvFileNotFoundXFTP") class RcvFileNotFoundXFTP(val agentRcvFileId: String): StoreError()
@Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError()
@Serializable @SerialName("connectionNotFoundById") class ConnectionNotFoundById(val connId: Long): StoreError()
@Serializable @SerialName("connectionNotFoundByMemberId") class ConnectionNotFoundByMemberId(val groupMemberId: Long): StoreError()
@Serializable @SerialName("pendingConnectionNotFound") class PendingConnectionNotFound(val connId: Long): StoreError()
@Serializable @SerialName("introNotFound") object IntroNotFound: StoreError()
@Serializable @SerialName("uniqueID") object UniqueID: StoreError()

View File

@ -1286,20 +1286,20 @@ fun PreviewGroupChatLayout() {
SimpleXTheme {
val chatItems = listOf(
ChatItem.getSampleData(
1, CIDirection.GroupSnd(), Clock.System.now(), "hello"
1, CIDirection.GroupSnd(directMember = null), Clock.System.now(), "hello"
),
ChatItem.getSampleData(
2, CIDirection.GroupRcv(GroupMember.sampleData), Clock.System.now(), "hello"
2, CIDirection.GroupRcv(GroupMember.sampleData, MessageScope.MSGroup), Clock.System.now(), "hello"
),
ChatItem.getDeletedContentSampleData(3),
ChatItem.getSampleData(
4, CIDirection.GroupRcv(GroupMember.sampleData), Clock.System.now(), "hello"
4, CIDirection.GroupRcv(GroupMember.sampleData, MessageScope.MSGroup), Clock.System.now(), "hello"
),
ChatItem.getSampleData(
5, CIDirection.GroupSnd(), Clock.System.now(), "hello"
5, CIDirection.GroupSnd(directMember = null), Clock.System.now(), "hello"
),
ChatItem.getSampleData(
6, CIDirection.GroupRcv(GroupMember.sampleData), Clock.System.now(), "hello"
6, CIDirection.GroupRcv(GroupMember.sampleData, MessageScope.MSGroup), Clock.System.now(), "hello"
)
)
val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) }

View File

@ -318,25 +318,34 @@ fun ComposeView(
}
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
val aChatItem = chatModel.controller.apiSendMessage(
type = cInfo.chatType,
id = cInfo.apiId,
file = file,
quotedItemId = quoted,
mc = mc,
live = live,
ttl = ttl
)
if (aChatItem != null) {
chatModel.addChatItem(cInfo, aChatItem.chatItem)
return aChatItem.chatItem
// TODO group-direct: directMember in compose state
val chatId = chat.chatInfo.apiId
val sendRef = when (chat.chatInfo.chatType) {
ChatType.Direct -> SendRef.Direct(contactId = chatId)
ChatType.Group -> SendRef.Group(groupId = chatId, directMemberId = null)
else -> null
}
if (sendRef != null) {
val aChatItem = chatModel.controller.apiSendMessage(
sendRef = sendRef,
file = file,
quotedItemId = quoted,
mc = mc,
live = live,
ttl = ttl
)
if (aChatItem != null) {
chatModel.addChatItem(cInfo, aChatItem.chatItem)
return aChatItem.chatItem
}
if (file != null) removeFile(file.filePath)
return null
} else {
Log.e(TAG, "ComposeView send: sendRef is null")
return null
}
if (file != null) removeFile(file.filePath)
return null
}
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? {
val cInfo = chat.chatInfo
val cs = composeState.value