Browse Source

android: model channel reply drafts and native message references

master
evgeny 4 days ago
parent
commit
22e3c1b0a5
  1. 12
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChannelReply.kt
  2. 7
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt
  3. 35
      tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt

12
tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChannelReply.kt

@ -0,0 +1,12 @@
package com.utun.chat.data
/** Снимок выбранного ответа: revision отличает повторный выбор того же сообщения. */
data class ChannelReply(val channelId: String, val message: Message, val revision: Long)
fun messageIdBytes(id: String): ByteArray {
if (id.isEmpty()) return byteArrayOf()
require(id.length == 64 && id.all { it in '0'..'9' || it in 'a'..'f' }) { "Invalid message ID" }
return ByteArray(32) { id.substring(it * 2, it * 2 + 2).toInt(16).toByte() }
}
fun validMessageId(id: String): Boolean = id.length == 64 && id.all { it in '0'..'9' || it in 'a'..'f' } && id.any { it != '0' }

7
tools/chatgui-android/app/src/main/java/com/utun/chat/data/ChatRepository.kt

@ -68,7 +68,7 @@ data class Message(val id: Long, val author: String, val text: String, val ts: L
val videoWidth: Int = 0, val videoHeight: Int = 0, val videoDurationMs: Int = 0,
val downloadState: String = "", /* "", "downloading", "downloaded", "error" */
val deliveryStatusRes: Int = 0, val inviteGroupName: String? = null, val authorNodeId: Long = 0,
val messageId: String = "", val feedback: FeedbackState? = null)
val messageId: String = "", val feedback: FeedbackState? = null, val replyTo: String = "")
data class ChatMember(val nodeId: Long, val name: String, val online: Boolean = false,
val isAdmin: Boolean = false, val isSuper: Boolean = false,
@ -290,10 +290,10 @@ class ChatRepository {
return 0
}
fun getMessages(channelId: String, limit: Int = 50): List<Message> {
fun getMessages(channelId: String, limit: Int = 50, anchor: String = "", exact: Boolean = false): List<Message> {
val list = mutableListOf<Message>()
try {
val arr = JSONArray(NativeLib.getMessages(channelId, limit))
val arr = JSONArray(NativeLib.getMessages(channelId, limit, anchor, exact))
for (i in 0 until arr.length()) {
val obj = arr.getJSONObject(i)
val text = obj.optString("text", "")
@ -385,6 +385,7 @@ class ChatRepository {
list.add(Message(
id = obj.getLong("id"),
messageId = obj.optString("messageId"),
replyTo = obj.optString("replyTo").takeIf(::validMessageId).orEmpty(),
author = obj.getString("author"),
authorNodeId = obj.optLong("authorNodeId"),
text = displayText,

35
tools/chatgui-android/app/src/main/java/com/utun/chat/data/NativeLib.kt

@ -84,7 +84,8 @@ object NativeLib {
/* ── External network activity (ConnectivityManager) ── */
fun notifyNetworkActive() { nativeNotifyNetworkActive() }
fun sendMessage(channelId: String, text: String) { nativeSendMessage(channelId, text) }
fun sendMessage(channelId: String, text: String, replyTo: String = "", requestId: Long = 0): Boolean =
nativeSendMessage(channelId, text.toByteArray(Charsets.UTF_8), messageIdBytes(replyTo), requestId)
fun createChannel(name: String, channelId: String) { nativeCreateChannel(name, channelId) }
fun connectNode(address: String, port: Int, pubkeyHex: String) { nativeConnectNode(address, port, pubkeyHex) }
fun joinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int) {
@ -96,7 +97,8 @@ object NativeLib {
fun connectChannel(channelId: String) { nativeConnectChannel(channelId) }
fun connectMemberNode(channelId: String, nodeId: Long) { nativeConnectMemberNode(channelId, nodeId) }
fun getChannels(): String = nativeGetChannels() ?: "[]"
fun getMessages(channelId: String, limit: Int = 50): String = nativeGetMessages(channelId, limit) ?: "[]"
fun getMessages(channelId: String, limit: Int = 50, anchor: String = "", exact: Boolean = false): String =
nativeGetMessages(channelId, limit, messageIdBytes(anchor), exact) ?: "[]"
fun feedbackCommand(json: String, requestId: Long = 0): Boolean = nativeFeedbackCommand(json.toByteArray(Charsets.UTF_8), requestId)
fun clearDatabase(): Boolean = nativeClearDatabase()
fun setDebugLevel(category: String, level: String) { nativeSetDebugLevel(category, level) }
@ -151,7 +153,8 @@ object NativeLib {
fun getLocalSockets(): String = nativeGetLocalSockets() ?: "[]"
/* ── Voice recording ── */
fun voiceStart(channelId: String): Boolean = nativeVoiceStart(channelId)
fun voiceStart(channelId: String, replyTo: String = "", requestId: Long = 0): Boolean =
nativeVoiceStart(channelId, messageIdBytes(replyTo), requestId)
fun voiceFeed(samples: ShortArray): Boolean = nativeVoiceFeed(samples)
fun voiceStop(): Int = nativeVoiceStop()
fun voiceCancel() { nativeVoiceCancel() }
@ -167,16 +170,18 @@ object NativeLib {
}
/* ── Attachment ── */
fun attachmentSend(channelId: String, filePath: String): Boolean = nativeAttachmentSend(channelId, filePath)
fun attachmentSend(channelId: String, filePath: String, replyTo: String = "", requestId: Long = 0): Boolean =
nativeAttachmentSend(channelId, filePath, messageIdBytes(replyTo), requestId)
fun attachmentDownload(channelId: String, msgId: Long): Boolean = nativeAttachmentDownload(channelId, msgId)
/* ── Video message ── */
fun videoSend(channelId: String, filePath: String, durationMs: Int, width: Int, height: Int): Boolean =
nativeVideoSend(channelId, filePath, durationMs, width, height)
fun videoSend(channelId: String, filePath: String, durationMs: Int, width: Int, height: Int,
replyTo: String = "", requestId: Long = 0): Boolean =
nativeVideoSend(channelId, filePath, durationMs, width, height, messageIdBytes(replyTo), requestId)
/* ── Photo message ── */
fun photoSend(channelId: String, filePath: String, width: Int, height: Int): Boolean =
nativePhotoSend(channelId, filePath, width, height)
fun photoSend(channelId: String, filePath: String, width: Int, height: Int, replyTo: String = "", requestId: Long = 0): Boolean =
nativePhotoSend(channelId, filePath, width, height, messageIdBytes(replyTo), requestId)
/* ── Voice playback ── */
fun voiceDecodeOpen(filePath: String, outInfo: IntArray): Long = nativeVoiceDecodeOpen(filePath, outInfo)
@ -230,7 +235,7 @@ object NativeLib {
private external fun nativeStop()
private external fun nativeSetEventCallback(cb: Any)
private external fun nativeDestroy()
private external fun nativeSendMessage(channelId: String, text: String)
private external fun nativeSendMessage(channelId: String, text: ByteArray, replyTo: ByteArray, requestId: Long): Boolean
private external fun nativeCreateChannel(name: String, channelId: String)
private external fun nativeConnectNode(address: String, port: Int, pubkeyHex: String)
private external fun nativeJoinChannel(channelId: Long, nodeId: Long, joinKey: Long, pubkey: ByteArray, addrs: ByteArray, addrCount: Int)
@ -238,7 +243,7 @@ object NativeLib {
private external fun nativeConnectChannel(channelId: String)
private external fun nativeConnectMemberNode(channelId: String, nodeId: Long)
private external fun nativeGetChannels(): String?
private external fun nativeGetMessages(channelId: String, limit: Int): String?
private external fun nativeGetMessages(channelId: String, limit: Int, anchor: ByteArray, exact: Boolean): String?
private external fun nativeFeedbackCommand(json: ByteArray, requestId: Long): Boolean
private external fun nativeSetDebugLevel(category: String, level: String)
private external fun nativeSetChatSetting(name: String, value: String)
@ -279,7 +284,7 @@ object NativeLib {
private external fun nativeGetLocalSockets(): String?
/* ── Voice recording JNI ── */
private external fun nativeVoiceStart(channelId: String): Boolean
private external fun nativeVoiceStart(channelId: String, replyTo: ByteArray, requestId: Long): Boolean
private external fun nativeVoiceFeed(samples: ShortArray): Boolean
private external fun nativeVoiceStop(): Int
private external fun nativeVoiceCancel()
@ -295,14 +300,16 @@ object NativeLib {
)
/* ── Attachment JNI ── */
private external fun nativeAttachmentSend(channelId: String, filePath: String): Boolean
private external fun nativeAttachmentSend(channelId: String, filePath: String, replyTo: ByteArray, requestId: Long): Boolean
private external fun nativeAttachmentDownload(channelId: String, msgId: Long): Boolean
/* ── Video message JNI ── */
private external fun nativeVideoSend(channelId: String, filePath: String, durationMs: Int, width: Int, height: Int): Boolean
private external fun nativeVideoSend(channelId: String, filePath: String, durationMs: Int, width: Int, height: Int,
replyTo: ByteArray, requestId: Long): Boolean
/* ── Photo message JNI ── */
private external fun nativePhotoSend(channelId: String, filePath: String, width: Int, height: Int): Boolean
private external fun nativePhotoSend(channelId: String, filePath: String, width: Int, height: Int,
replyTo: ByteArray, requestId: Long): Boolean
/* ── Voice playback JNI ── */
private external fun nativeVoiceDecodeOpen(filePath: String, outInfo: IntArray): Long

Loading…
Cancel
Save