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
|
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 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
|
let r: ChatResponse
|
||||||
if type == .direct {
|
switch sendRef {
|
||||||
|
// TODO group-direct: re-use direct logic for directMember
|
||||||
|
case .direct:
|
||||||
var cItem: ChatItem? = nil
|
var cItem: ChatItem? = nil
|
||||||
let endTask = beginBGTask({
|
let endTask = beginBGTask({
|
||||||
if let cItem = cItem {
|
if let cItem = cItem {
|
||||||
@ -341,7 +343,7 @@ func apiSendMessage(type: ChatType, id: Int64, file: CryptoFile?, quotedItemId:
|
|||||||
}
|
}
|
||||||
endTask()
|
endTask()
|
||||||
return nil
|
return nil
|
||||||
} else {
|
default:
|
||||||
r = await chatSendCmd(cmd, bgDelay: msgDelay)
|
r = await chatSendCmd(cmd, bgDelay: msgDelay)
|
||||||
if case let .newChatItem(_, aChatItem) = r {
|
if case let .newChatItem(_, aChatItem) = r {
|
||||||
return aChatItem.chatItem
|
return aChatItem.chatItem
|
||||||
|
@ -41,7 +41,7 @@ struct CIRcvDecryptionError: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
|
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
|
||||||
if case let .group(groupInfo) = chat.chatInfo,
|
if case let .group(groupInfo) = chat.chatInfo,
|
||||||
case let .groupRcv(groupMember) = chatItem.chatDir {
|
case let .groupRcv(groupMember, _) = chatItem.chatDir {
|
||||||
do {
|
do {
|
||||||
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
|
let (member, stats) = try apiGroupMemberInfo(groupInfo.apiId, groupMember.groupMemberId)
|
||||||
if let s = stats {
|
if let s = stats {
|
||||||
@ -78,7 +78,7 @@ struct CIRcvDecryptionError: View {
|
|||||||
basicDecryptionErrorItem()
|
basicDecryptionErrorItem()
|
||||||
}
|
}
|
||||||
} else if case let .group(groupInfo) = chat.chatInfo,
|
} 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 modelMember = ChatModel.shared.groupMembers.first(where: { $0.id == groupMember.id }),
|
||||||
let memberStats = modelMember.activeConn?.connectionStats {
|
let memberStats = modelMember.activeConn?.connectionStats {
|
||||||
if memberStats.ratchetSyncAllowed {
|
if memberStats.ratchetSyncAllowed {
|
||||||
|
@ -33,7 +33,7 @@ struct DeletedItemView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
Group {
|
Group {
|
||||||
DeletedItemView(chatItem: ChatItem.getDeletedContentSample())
|
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))
|
.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 {
|
@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 {
|
case let .group(groupInfo) = chat.chatInfo {
|
||||||
let (prevItem, nextItem) = chatModel.getChatItemNeighbors(ci)
|
let (prevItem, nextItem) = chatModel.getChatItemNeighbors(ci)
|
||||||
if ci.memberConnected != nil && nextItem?.memberConnected != nil {
|
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 {
|
private func showMemberImage(_ member: GroupMember, _ prevItem: ChatItem?) -> Bool {
|
||||||
switch (prevItem?.chatDir) {
|
switch (prevItem?.chatDir) {
|
||||||
case .groupSnd: return true
|
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
|
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? {
|
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?) async -> ChatItem? {
|
||||||
if let chatItem = await apiSendMessage(
|
// TODO group-direct: directMember in compose state
|
||||||
type: chat.chatInfo.chatType,
|
var sendRef: SendRef?
|
||||||
id: chat.chatInfo.apiId,
|
let chatId = chat.chatInfo.apiId
|
||||||
file: file,
|
switch chat.chatInfo.chatType {
|
||||||
quotedItemId: quoted,
|
case .direct: sendRef = .direct(contactId: chatId)
|
||||||
msg: mc,
|
case .group: sendRef = .group(groupId: chatId, directMemberId: nil)
|
||||||
live: live,
|
default: sendRef = nil
|
||||||
ttl: ttl
|
}
|
||||||
) {
|
if let sendRef = sendRef {
|
||||||
await MainActor.run {
|
if let chatItem = await apiSendMessage(
|
||||||
chatModel.removeLiveDummy(animated: false)
|
sendRef: sendRef,
|
||||||
chatModel.addChatItem(chat.chatInfo, chatItem)
|
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 {
|
func checkLinkPreview() -> MsgContent {
|
||||||
|
@ -39,7 +39,7 @@ public enum ChatCommand {
|
|||||||
case apiGetChats(userId: Int64)
|
case apiGetChats(userId: Int64)
|
||||||
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
|
case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String)
|
||||||
case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
|
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 apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent, live: Bool)
|
||||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
||||||
case apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64)
|
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)" +
|
case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" +
|
||||||
(search == "" ? "" : " search=\(search)")
|
(search == "" ? "" : " search=\(search)")
|
||||||
case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)"
|
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 msg = encodeJSON(ComposedMessage(fileSource: file, quotedItemId: quotedItemId, msgContent: mc))
|
||||||
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
|
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 .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 .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)"
|
case let .apiDeleteMemberChatItem(groupId, groupMemberId, itemId): return "/_delete member item #\(groupId) \(groupMemberId) \(itemId)"
|
||||||
@ -365,6 +365,14 @@ public enum ChatCommand {
|
|||||||
"\(type.rawValue)\(id)"
|
"\(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 {
|
func protoServersStr(_ servers: [ServerCfg]) -> String {
|
||||||
encodeJSON(ProtoServersConfig(servers: servers))
|
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? {
|
public func chatError(_ chatResponse: ChatResponse) -> ChatErrorType? {
|
||||||
switch chatResponse {
|
switch chatResponse {
|
||||||
case let .chatCmdError(_, .error(error)): return error
|
case let .chatCmdError(_, .error(error)): return error
|
||||||
@ -1454,6 +1467,7 @@ public enum ChatErrorType: Decodable {
|
|||||||
case agentCommandError(message: String)
|
case agentCommandError(message: String)
|
||||||
case invalidFileDescription(message: String)
|
case invalidFileDescription(message: String)
|
||||||
case connectionIncognitoChangeProhibited
|
case connectionIncognitoChangeProhibited
|
||||||
|
case peerChatVRangeIncompatible
|
||||||
case internalError(message: String)
|
case internalError(message: String)
|
||||||
case exception(message: String)
|
case exception(message: String)
|
||||||
}
|
}
|
||||||
@ -1468,6 +1482,7 @@ public enum StoreError: Decodable {
|
|||||||
case userNotFoundByContactRequestId(contactRequestId: Int64)
|
case userNotFoundByContactRequestId(contactRequestId: Int64)
|
||||||
case contactNotFound(contactId: Int64)
|
case contactNotFound(contactId: Int64)
|
||||||
case contactNotFoundByName(contactName: ContactName)
|
case contactNotFoundByName(contactName: ContactName)
|
||||||
|
case contactNotFoundByMemberId(groupMemberId: Int64)
|
||||||
case contactNotReady(contactName: ContactName)
|
case contactNotReady(contactName: ContactName)
|
||||||
case duplicateContactLink
|
case duplicateContactLink
|
||||||
case userContactLinkNotFound
|
case userContactLinkNotFound
|
||||||
@ -1495,6 +1510,7 @@ public enum StoreError: Decodable {
|
|||||||
case rcvFileNotFoundXFTP(agentRcvFileId: String)
|
case rcvFileNotFoundXFTP(agentRcvFileId: String)
|
||||||
case connectionNotFound(agentConnId: String)
|
case connectionNotFound(agentConnId: String)
|
||||||
case connectionNotFoundById(connId: Int64)
|
case connectionNotFoundById(connId: Int64)
|
||||||
|
case connectionNotFoundByMemberId(groupMemberId: Int64)
|
||||||
case pendingConnectionNotFound(connId: Int64)
|
case pendingConnectionNotFound(connId: Int64)
|
||||||
case introNotFound
|
case introNotFound
|
||||||
case uniqueID
|
case uniqueID
|
||||||
|
@ -1968,7 +1968,7 @@ public struct AChatItem: Decodable {
|
|||||||
public var chatItem: ChatItem
|
public var chatItem: ChatItem
|
||||||
|
|
||||||
public var chatId: String {
|
public var chatId: String {
|
||||||
if case let .groupRcv(groupMember) = chatItem.chatDir {
|
if case let .groupRcv(groupMember, _) = chatItem.chatDir {
|
||||||
return groupMember.id
|
return groupMember.id
|
||||||
}
|
}
|
||||||
return chatInfo.id
|
return chatInfo.id
|
||||||
@ -2041,7 +2041,7 @@ public struct ChatItem: Identifiable, Decodable {
|
|||||||
|
|
||||||
public var memberConnected: GroupMember? {
|
public var memberConnected: GroupMember? {
|
||||||
switch chatDir {
|
switch chatDir {
|
||||||
case .groupRcv(let groupMember):
|
case let .groupRcv(groupMember, _):
|
||||||
switch content {
|
switch content {
|
||||||
case .rcvGroupEvent(rcvGroupEvent: .memberConnected): return groupMember
|
case .rcvGroupEvent(rcvGroupEvent: .memberConnected): return groupMember
|
||||||
default: return nil
|
default: return nil
|
||||||
@ -2125,7 +2125,7 @@ public struct ChatItem: Identifiable, Decodable {
|
|||||||
|
|
||||||
public var memberDisplayName: String? {
|
public var memberDisplayName: String? {
|
||||||
get {
|
get {
|
||||||
if case let .groupRcv(groupMember) = chatDir {
|
if case let .groupRcv(groupMember, _) = chatDir {
|
||||||
return groupMember.displayName
|
return groupMember.displayName
|
||||||
} else {
|
} else {
|
||||||
return nil
|
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)? {
|
public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember)? {
|
||||||
switch (chatInfo, chatDir) {
|
switch (chatInfo, chatDir) {
|
||||||
case let (.group(groupInfo), .groupRcv(groupMember)):
|
case let (.group(groupInfo), .groupRcv(groupMember, _)):
|
||||||
let m = groupInfo.membership
|
let m = groupInfo.membership
|
||||||
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
|
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
|
||||||
? (groupInfo, groupMember)
|
? (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 {
|
public static func liveDummy(_ chatType: ChatType) -> ChatItem {
|
||||||
var item = ChatItem(
|
var item = ChatItem(
|
||||||
chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd,
|
chatDir: chatType == ChatType.direct ? CIDirection.directSnd : CIDirection.groupSnd(directMember: nil),
|
||||||
meta: CIMeta(
|
meta: CIMeta(
|
||||||
itemId: -2,
|
itemId: -2,
|
||||||
itemTs: .now,
|
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 {
|
public enum CIDirection: Decodable {
|
||||||
case directSnd
|
case directSnd
|
||||||
case directRcv
|
case directRcv
|
||||||
case groupSnd
|
case groupSnd(directMember: GroupMember?)
|
||||||
case groupRcv(groupMember: 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 {
|
public var sent: Bool {
|
||||||
get {
|
get {
|
||||||
@ -2592,7 +2617,7 @@ public enum MsgDecryptError: String, Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct CIQuote: Decodable, ItemContent {
|
public struct CIQuote: Decodable, ItemContent {
|
||||||
public var chatDir: CIDirection?
|
public var chatDir: CIQDirection?
|
||||||
public var itemId: Int64?
|
public var itemId: Int64?
|
||||||
var sharedMsgId: String? = nil
|
var sharedMsgId: String? = nil
|
||||||
public var sentAt: Date
|
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? {
|
public func getSender(_ membership: GroupMember?) -> String? {
|
||||||
switch (chatDir) {
|
switch (chatDir) {
|
||||||
case .directSnd: return "you"
|
case .directSnd: return "you"
|
||||||
case .directRcv: return nil
|
case .directRcv: return nil
|
||||||
case .groupSnd: return membership?.displayName ?? "you"
|
case .groupSnd: return membership?.displayName ?? "you"
|
||||||
case let .groupRcv(member): return member.displayName
|
case let .groupRcv(member, _): return member.displayName
|
||||||
case nil: return nil
|
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
|
let mc: MsgContent
|
||||||
if let image = image {
|
if let image = image {
|
||||||
mc = .image(text: text, 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 {
|
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem) -> UNMutableNotificationContent {
|
||||||
let previewMode = ntfPreviewModeGroupDefault.get()
|
let previewMode = ntfPreviewModeGroupDefault.get()
|
||||||
var title: String
|
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)
|
title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: previewMode == .hidden)
|
||||||
} else {
|
} else {
|
||||||
title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):"
|
title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):"
|
||||||
|
@ -1588,8 +1588,9 @@ data class ChatItem (
|
|||||||
file = null
|
file = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO group-direct: possibly this has to take sendRef as parameter instead (for directMember)
|
||||||
fun liveDummy(direct: Boolean): ChatItem = ChatItem(
|
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(
|
meta = CIMeta(
|
||||||
itemId = TEMP_LIVE_CHAT_ITEM_ID,
|
itemId = TEMP_LIVE_CHAT_ITEM_ID,
|
||||||
itemTs = Clock.System.now(),
|
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
|
@Serializable
|
||||||
sealed class CIDirection {
|
sealed class CIDirection {
|
||||||
@Serializable @SerialName("directSnd") class DirectSnd: CIDirection()
|
@Serializable @SerialName("directSnd") class DirectSnd: CIDirection()
|
||||||
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
|
@Serializable @SerialName("directRcv") class DirectRcv: CIDirection()
|
||||||
@Serializable @SerialName("groupSnd") class GroupSnd: CIDirection()
|
@Serializable @SerialName("groupSnd") class GroupSnd(val directMember: GroupMember? = null): CIDirection()
|
||||||
@Serializable @SerialName("groupRcv") class GroupRcv(val groupMember: GroupMember): 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) {
|
val sent: Boolean get() = when(this) {
|
||||||
is DirectSnd -> true
|
is DirectSnd -> true
|
||||||
@ -1921,7 +1943,7 @@ enum class MsgDecryptError {
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class CIQuote (
|
class CIQuote (
|
||||||
val chatDir: CIDirection? = null,
|
val chatDir: CIQDirection? = null,
|
||||||
val itemId: Long? = null,
|
val itemId: Long? = null,
|
||||||
val sharedMsgId: String? = null,
|
val sharedMsgId: String? = null,
|
||||||
val sentAt: Instant,
|
val sentAt: Instant,
|
||||||
@ -1937,15 +1959,15 @@ class CIQuote (
|
|||||||
|
|
||||||
|
|
||||||
fun sender(membership: GroupMember?): String? = when (chatDir) {
|
fun sender(membership: GroupMember?): String? = when (chatDir) {
|
||||||
is CIDirection.DirectSnd -> generalGetString(MR.strings.sender_you_pronoun)
|
is CIQDirection.DirectSnd -> generalGetString(MR.strings.sender_you_pronoun)
|
||||||
is CIDirection.DirectRcv -> null
|
is CIQDirection.DirectRcv -> null
|
||||||
is CIDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
|
is CIQDirection.GroupSnd -> membership?.displayName ?: generalGetString(MR.strings.sender_you_pronoun)
|
||||||
is CIDirection.GroupRcv -> chatDir.groupMember.displayName
|
is CIQDirection.GroupRcv -> chatDir.groupMember.displayName
|
||||||
null -> null
|
null -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
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))
|
CIQuote(chatDir = chatDir, itemId = itemId, sentAt = sentAt, content = MsgContent.MCText(text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -586,8 +586,8 @@ object ChatController {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiSendMessage(type: ChatType, id: Long, file: CryptoFile? = null, quotedItemId: Long? = null, mc: MsgContent, live: Boolean = false, ttl: Int? = null): AChatItem? {
|
suspend fun apiSendMessage(sendRef: SendRef, 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)
|
val cmd = CC.ApiSendMessage(sendRef, file, quotedItemId, mc, live, ttl)
|
||||||
val r = sendCmd(cmd)
|
val r = sendCmd(cmd)
|
||||||
return when (r) {
|
return when (r) {
|
||||||
is CR.NewChatItem -> r.chatItem
|
is CR.NewChatItem -> r.chatItem
|
||||||
@ -1805,7 +1805,7 @@ sealed class CC {
|
|||||||
class ApiGetChats(val userId: Long): CC()
|
class ApiGetChats(val userId: Long): CC()
|
||||||
class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): 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 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 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 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()
|
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 ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId"
|
||||||
is ApiSendMessage -> {
|
is ApiSendMessage -> {
|
||||||
val ttlStr = if (ttl != null) "$ttl" else "default"
|
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 ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${mc.cmdString}"
|
||||||
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
|
is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} $itemId ${mode.deleteMode}"
|
||||||
@ -2105,10 +2105,27 @@ sealed class CC {
|
|||||||
companion object {
|
companion object {
|
||||||
fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
|
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))
|
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
|
@Serializable
|
||||||
data class NewUser(
|
data class NewUser(
|
||||||
val profile: Profile?,
|
val profile: Profile?,
|
||||||
@ -3820,6 +3837,7 @@ sealed class ChatErrorType {
|
|||||||
is AgentCommandError -> "agentCommandError"
|
is AgentCommandError -> "agentCommandError"
|
||||||
is InvalidFileDescription -> "invalidFileDescription"
|
is InvalidFileDescription -> "invalidFileDescription"
|
||||||
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
|
||||||
|
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
|
||||||
is InternalError -> "internalError"
|
is InternalError -> "internalError"
|
||||||
is CEException -> "exception $message"
|
is CEException -> "exception $message"
|
||||||
}
|
}
|
||||||
@ -3894,6 +3912,7 @@ sealed class ChatErrorType {
|
|||||||
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
|
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
|
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
|
||||||
|
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
|
||||||
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
|
||||||
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
|
||||||
}
|
}
|
||||||
@ -3911,6 +3930,7 @@ sealed class StoreError {
|
|||||||
is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId"
|
is UserNotFoundByContactRequestId -> "userNotFoundByContactRequestId"
|
||||||
is ContactNotFound -> "contactNotFound"
|
is ContactNotFound -> "contactNotFound"
|
||||||
is ContactNotFoundByName -> "contactNotFoundByName"
|
is ContactNotFoundByName -> "contactNotFoundByName"
|
||||||
|
is ContactNotFoundByMemberId -> "contactNotFoundByMemberId"
|
||||||
is ContactNotReady -> "contactNotReady"
|
is ContactNotReady -> "contactNotReady"
|
||||||
is DuplicateContactLink -> "duplicateContactLink"
|
is DuplicateContactLink -> "duplicateContactLink"
|
||||||
is UserContactLinkNotFound -> "userContactLinkNotFound"
|
is UserContactLinkNotFound -> "userContactLinkNotFound"
|
||||||
@ -3938,6 +3958,7 @@ sealed class StoreError {
|
|||||||
is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP"
|
is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP"
|
||||||
is ConnectionNotFound -> "connectionNotFound"
|
is ConnectionNotFound -> "connectionNotFound"
|
||||||
is ConnectionNotFoundById -> "connectionNotFoundById"
|
is ConnectionNotFoundById -> "connectionNotFoundById"
|
||||||
|
is ConnectionNotFoundByMemberId -> "connectionNotFoundByMemberId"
|
||||||
is PendingConnectionNotFound -> "pendingConnectionNotFound"
|
is PendingConnectionNotFound -> "pendingConnectionNotFound"
|
||||||
is IntroNotFound -> "introNotFound"
|
is IntroNotFound -> "introNotFound"
|
||||||
is UniqueID -> "uniqueID"
|
is UniqueID -> "uniqueID"
|
||||||
@ -3966,6 +3987,7 @@ sealed class StoreError {
|
|||||||
@Serializable @SerialName("userNotFoundByContactRequestId") class UserNotFoundByContactRequestId(val contactRequestId: Long): StoreError()
|
@Serializable @SerialName("userNotFoundByContactRequestId") class UserNotFoundByContactRequestId(val contactRequestId: Long): StoreError()
|
||||||
@Serializable @SerialName("contactNotFound") class ContactNotFound(val contactId: Long): StoreError()
|
@Serializable @SerialName("contactNotFound") class ContactNotFound(val contactId: Long): StoreError()
|
||||||
@Serializable @SerialName("contactNotFoundByName") class ContactNotFoundByName(val contactName: String): 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("contactNotReady") class ContactNotReady(val contactName: String): StoreError()
|
||||||
@Serializable @SerialName("duplicateContactLink") object DuplicateContactLink: StoreError()
|
@Serializable @SerialName("duplicateContactLink") object DuplicateContactLink: StoreError()
|
||||||
@Serializable @SerialName("userContactLinkNotFound") object UserContactLinkNotFound: 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("rcvFileNotFoundXFTP") class RcvFileNotFoundXFTP(val agentRcvFileId: String): StoreError()
|
||||||
@Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError()
|
@Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError()
|
||||||
@Serializable @SerialName("connectionNotFoundById") class ConnectionNotFoundById(val connId: Long): 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("pendingConnectionNotFound") class PendingConnectionNotFound(val connId: Long): StoreError()
|
||||||
@Serializable @SerialName("introNotFound") object IntroNotFound: StoreError()
|
@Serializable @SerialName("introNotFound") object IntroNotFound: StoreError()
|
||||||
@Serializable @SerialName("uniqueID") object UniqueID: StoreError()
|
@Serializable @SerialName("uniqueID") object UniqueID: StoreError()
|
||||||
|
@ -1286,20 +1286,20 @@ fun PreviewGroupChatLayout() {
|
|||||||
SimpleXTheme {
|
SimpleXTheme {
|
||||||
val chatItems = listOf(
|
val chatItems = listOf(
|
||||||
ChatItem.getSampleData(
|
ChatItem.getSampleData(
|
||||||
1, CIDirection.GroupSnd(), Clock.System.now(), "hello"
|
1, CIDirection.GroupSnd(directMember = null), Clock.System.now(), "hello"
|
||||||
),
|
),
|
||||||
ChatItem.getSampleData(
|
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.getDeletedContentSampleData(3),
|
||||||
ChatItem.getSampleData(
|
ChatItem.getSampleData(
|
||||||
4, CIDirection.GroupRcv(GroupMember.sampleData), Clock.System.now(), "hello"
|
4, CIDirection.GroupRcv(GroupMember.sampleData, MessageScope.MSGroup), Clock.System.now(), "hello"
|
||||||
),
|
),
|
||||||
ChatItem.getSampleData(
|
ChatItem.getSampleData(
|
||||||
5, CIDirection.GroupSnd(), Clock.System.now(), "hello"
|
5, CIDirection.GroupSnd(directMember = null), Clock.System.now(), "hello"
|
||||||
),
|
),
|
||||||
ChatItem.getSampleData(
|
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 }) }
|
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? {
|
suspend fun send(cInfo: ChatInfo, mc: MsgContent, quoted: Long?, file: CryptoFile? = null, live: Boolean = false, ttl: Int?): ChatItem? {
|
||||||
val aChatItem = chatModel.controller.apiSendMessage(
|
// TODO group-direct: directMember in compose state
|
||||||
type = cInfo.chatType,
|
val chatId = chat.chatInfo.apiId
|
||||||
id = cInfo.apiId,
|
val sendRef = when (chat.chatInfo.chatType) {
|
||||||
file = file,
|
ChatType.Direct -> SendRef.Direct(contactId = chatId)
|
||||||
quotedItemId = quoted,
|
ChatType.Group -> SendRef.Group(groupId = chatId, directMemberId = null)
|
||||||
mc = mc,
|
else -> null
|
||||||
live = live,
|
}
|
||||||
ttl = ttl
|
if (sendRef != null) {
|
||||||
)
|
val aChatItem = chatModel.controller.apiSendMessage(
|
||||||
if (aChatItem != null) {
|
sendRef = sendRef,
|
||||||
chatModel.addChatItem(cInfo, aChatItem.chatItem)
|
file = file,
|
||||||
return aChatItem.chatItem
|
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? {
|
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? {
|
||||||
val cInfo = chat.chatInfo
|
val cInfo = chat.chatInfo
|
||||||
val cs = composeState.value
|
val cs = composeState.value
|
||||||
|
Loading…
Reference in New Issue
Block a user