ios, android: direct messages in group - types, TODOs
This commit is contained in:
parent
281d9c7f79
commit
1682577ede
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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):"
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 }) }
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user