diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000000000000000000000000000000000000..713686c3887249af92040d13f067198fe10ec161 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +radio2 \ No newline at end of file diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000000000000000000000000000000000000..4a53bee8cb948a6e677a1a320d0ecfbe26d4062e --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="AndroidProjectSystem"> + <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" /> + </component> +</project> \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 545740a654bc72f4e98998fdfe9c657cb0248e56..2bba3d39194e864580b1a25f1a94c54852be8482 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,7 +4,7 @@ <selectionStates> <SelectionState runConfigName="app"> <option name="selectionMode" value="DROPDOWN" /> - <DropdownSelection timestamp="2025-01-16T11:45:29.800994600Z"> + <DropdownSelection timestamp="2025-03-24T15:09:15.860193100Z"> <Target type="DEFAULT_BOOT"> <handle> <DeviceId pluginId="PhysicalDevice" identifier="serial=R58R84SRAZX" /> diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000000000000000000000000000000000..c9b1536a6bae0f38a046f0562d246adc835aed54 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,5 @@ +<project version="4"> + <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/build/classes" /> + </component> +</project> \ No newline at end of file diff --git a/app/src/main/java/com/example/radio2/ChatActivity.kt b/app/src/main/java/com/example/radio2/ChatActivity.kt index 85fe89d9c8bd2c78bb11505a415e7205415ecd8f..bc253813efded2be86d7f132b7f0af35e96df72e 100644 --- a/app/src/main/java/com/example/radio2/ChatActivity.kt +++ b/app/src/main/java/com/example/radio2/ChatActivity.kt @@ -1,10 +1,12 @@ package com.example.radio2 +import android.annotation.SuppressLint import android.content.* import android.os.* import android.util.Log import android.view.KeyEvent import android.view.MenuItem +import android.view.MotionEvent import android.view.inputmethod.EditorInfo import android.widget.Button import android.widget.TextView @@ -18,23 +20,27 @@ import com.example.radio2.Data.userDataStorage import com.example.radio2.AppDatabase import com.example.radio2.ChatRepository import com.example.radio2.service.WebSocketService +import com.example.radio2.Rtp.AudioProcessor +import com.example.radio2.Rtp.VoicePacketSender import com.google.android.material.textfield.TextInputEditText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.security.MessageDigest -class ChatActivity : AppCompatActivity() { +class ChatActivity : AppCompatActivity(), VoicePacketSender { private lateinit var chatRepository: ChatRepository private var webSocketService: WebSocketService? = null private lateinit var chatAdapter: ChatAdapter private val messages = mutableListOf<MessageTo>() private lateinit var recyclerView: RecyclerView private lateinit var messageInput: TextInputEditText - + private lateinit var audioProcessor: AudioProcessor + private var isPttStarted = false private var chatId: Long? = null private var userId: Long? = null + private var selectedUserId: String? = null private val connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { @@ -43,7 +49,6 @@ class ChatActivity : AppCompatActivity() { webSocketService?.setCurrentActivity(this@ChatActivity) } - override fun onServiceDisconnected(name: ComponentName?) { webSocketService = null } @@ -55,7 +60,6 @@ class ChatActivity : AppCompatActivity() { bindService(intent, connection, BIND_AUTO_CREATE) } - loadUserData() } override fun onStop() { @@ -63,6 +67,7 @@ class ChatActivity : AppCompatActivity() { unbindService(connection) } + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -75,16 +80,43 @@ class ChatActivity : AppCompatActivity() { val userId = intent.getStringExtra("USER_ID") val userName = intent.getStringExtra("USER_NAME")?: "Default User" if (userName != null && userId != null) { + selectedUserId = userId loadChat(userId, userName) } - recyclerView.layoutManager = LinearLayoutManager(this) chatAdapter = ChatAdapter(this, messages) recyclerView.adapter = chatAdapter val sendMessageButton = findViewById<Button>(R.id.button) val backButton = findViewById<Button>(R.id.backButton) + val activateVoiceButton = findViewById<Button>(R.id.activateVoice2) + + audioProcessor = AudioProcessor(this, this) + + activateVoiceButton.setOnTouchListener { view, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + if (!isPttStarted) { + isPttStarted = true + selectedUserId?.let { userId -> + webSocketService?.startPTT(userId, "User") + } + } + audioProcessor.startRecording() + true + } + MotionEvent.ACTION_UP -> { + isPttStarted = false + audioProcessor.stopRecording() + selectedUserId?.let { userId -> + webSocketService?.stopPTT(userId, "User") + } + true + } + else -> false + } + } backButton.setOnClickListener { @@ -129,6 +161,7 @@ class ChatActivity : AppCompatActivity() { } } } + private fun loadChat(userId: String, userName: String) { lifecycleScope.launch { // Генерация ID чата РЅР° РѕСЃРЅРѕРІРµ имени пользователя @@ -144,6 +177,46 @@ class ChatActivity : AppCompatActivity() { chatRepository.createChat(chatId, userName) } + // Установка имени пользователя + val name = findViewById<TextView>(R.id.userName) + name.text = userName + Log.d("ChatActivity", "установлен chatId: $chatId") + } + } + + private suspend fun loadMessages(chatId: Long) { + withContext(Dispatchers.IO) { + val messages = chatRepository.getMessagesForChat(chatId) + messages.forEach { message -> + addReceivedMessage(message.text) // Предполагается, что РІС‹ добавите этот метод + } + } + } + + private fun generateChatId(userName: String): Long { + // Генерация С…СЌС€-значения РЅР° РѕСЃРЅРѕРІРµ имени пользователя + val bytes = MessageDigest.getInstance("SHA-256").digest(userName.toByteArray()) + return bytes.fold(0L) { acc, byte -> acc * 31 + byte } + } + + private suspend fun getMessagesForChat() { + chatId?.let { + val messagesList = chatRepository.getMessagesForChat(it) + messages.clear() + messages.addAll(messagesList.map { msg -> MessageTo(msg.text, MessageType.RECEIVED) }) + chatAdapter.notifyDataSetChanged() + } + } + + if (chat != null) { + // Если чат существует, загружаем сообщения + loadMessages(chat.id) + Log.d("ChatActivity", "Успешно получен chatId: $chatId") + } else { + // Если чат РЅРµ существует, создаем новый чат + chatRepository.createChat(chatId, userName) + } + // Установка имени пользователя val name = findViewById<TextView>(R.id.userName) name.text = userName @@ -197,7 +270,6 @@ class ChatActivity : AppCompatActivity() { } } - private fun addUserMessage(message: String) { messages.add(MessageTo(message, MessageType.SENT)) chatAdapter.notifyItemInserted(messages.size - 1) @@ -205,7 +277,6 @@ class ChatActivity : AppCompatActivity() { messageInput.setText("") } - override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { @@ -217,10 +288,13 @@ class ChatActivity : AppCompatActivity() { } } - fun addReceivedMessage(message: String) { messages.add(MessageTo(message, MessageType.RECEIVED)) chatAdapter.notifyItemInserted(messages.size - 1) recyclerView.scrollToPosition(messages.size - 1) } + + override fun sendVoicePacket(base64Str: String) { + webSocketService?.sendVoicePacket(base64Str) + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/radio2/FixDataBase.kt b/app/src/main/java/com/example/radio2/FixDataBase.kt new file mode 100644 index 0000000000000000000000000000000000000000..f7d9f25a5966321d027445214d15e2e42e8c51af --- /dev/null +++ b/app/src/main/java/com/example/radio2/FixDataBase.kt @@ -0,0 +1,84 @@ +package com.example.radio2//package com.example.radio2 +// +//import androidx.room.Dao +//import androidx.room.Entity +//import androidx.room.PrimaryKey +//import androidx.room.ForeignKey +//import androidx.room.Insert +//import androidx.room.Query +// +// +//@Entity(tableName = "Users") +//data class User( +// @PrimaryKey val id: Long, +// val userName: String +//) +// +// +//@Entity(tableName = "Chats") +//data class Chat( +// @PrimaryKey val id: Long, +// val title: String +//) +// +// +//@Entity( +// tableName = "Chat_members", +// foreignKeys = [ +// ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]), +// ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["userId"]) +// ] +//) +//data class ChatMember( +// val chatId: Long, +// val userId: Long +//) +// +// +//@Entity( +// tableName = "Message", +// foreignKeys = [ +// ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]), +// ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["senderId"]) +// ] +//) +//data class Message( +// val chatId: Long, +// val senderId: Long, +// val text: String, +// val timestamp: Long // РњРѕР¶РЅРѕ использовать тип Date, если РІС‹ хотите +//) +//interface ChatDao1 { +// @Insert +// suspend fun insertChat(chat: Chat) +// +// @Query("SELECT * FROM Chats WHERE id = :chatId LIMIT 1") +// suspend fun getChatById(chatId: Long): Chat? +//} +// +//@Dao +//interface MessageDao { +// @Insert +// suspend fun insertMessage(message: Message) +// +// @Query("SELECT * FROM Message WHERE chatId = :chatId") +// suspend fun getMessagesForChat(chatId: Long): List<Message> +//} +// +// +//class ChatRepository(private val db: FixDataBase) { +// +// suspend fun getChatByTitle(title: String): Chat? { +// return db.chatDao().getChatByTitle(title) +// } +// +// suspend fun createChat(title: String): Long { +// val chat = Chat(id = System.currentTimeMillis(), title = title) // Рспользуйте текущее время как ID +// db.chatDao().insertChat(chat) +// return chat.id +// } +// +// suspend fun getMessagesForChat(chatId: Long): List<Message> { +// return db.messageDao().getMessagesForChat(chatId) +// } +//} \ No newline at end of file diff --git a/app/src/main/java/com/example/radio2/Rtp/AlawMulaw.kt b/app/src/main/java/com/example/radio2/Rtp/AlawMulaw.kt index b37fb74f024b68a520844e96b619c2cc7db75a9e..dd9f8dea1e983f9a4c0f84c6c7cd347881c5e6ba 100644 --- a/app/src/main/java/com/example/radio2/Rtp/AlawMulaw.kt +++ b/app/src/main/java/com/example/radio2/Rtp/AlawMulaw.kt @@ -1,77 +1,132 @@ -package com.example.radio2.Rtp +package org.example.radio2.Rtp -class AlawMulaw { +object AlawMulawEncoder1 { + private val muLawCompressTable = shortArrayOf( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + -128, -127, -126, -125, -124, -123, -122, -121, -120, -119, -118, -117, -116, -115, -114, -113, + -112, -111, -110, -109, -108, -107, -106, -105, -104, -103, -102, -101, -100, -99, -98, -97, + -96, -95, -94, -93, -92, -91, -90, -89, -88, -87, -86, -85, -84, -83, -82, -81, + -80, -79, -78, -77, -76, -75, -74, -73, -72, -71, -70, -69, -68, -67, -66, -65, + -64, -63, -62, -61, -60, -59, -58, -57, -56, -55, -54, -53, -52, -51, -50, -49, + -48, -47, -46, -45, -44, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, + -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, + -16, -15, -14, -13, -12, -11, -10, -9, -8, -7, -6, -5, -4, -3, -2, -1 + ) - companion object { - private val k = intArrayOf( - 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 - ) + private val muLawDecompressTable = shortArrayOf( + -32124, -31100, -30076, -29052, -28028, -27004, -25980, -24956, + -23932, -22908, -21884, -20860, -19836, -18812, -17788, -16764, + -15996, -15484, -14972, -14460, -13948, -13436, -12924, -12412, + -11900, -11388, -10876, -10364, -9852, -9340, -8828, -8316, + -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140, + -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092, + -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980, + -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436, + -1372, -1308, -1244, -1180, -1116, -1052, -988, -924, + -876, -844, -812, -780, -748, -716, -684, -652, + -620, -588, -556, -524, -492, -460, -428, -396, + -372, -356, -340, -324, -308, -292, -276, -260, + -244, -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, -64, + -56, -48, -40, -32, -24, -16, -8, 0, + 32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956, + 23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764, + 15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412, + 11900, 11388, 10876, 10364, 9852, 9340, 8828, 8316, + 7932, 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, 4092, + 3900, 3772, 3644, 3516, 3388, 3260, 3132, 3004, + 2876, 2748, 2620, 2492, 2364, 2236, 2108, 1980, + 1884, 1820, 1756, 1692, 1628, 1564, 1500, 1436, + 1372, 1308, 1244, 1180, 1116, 1052, 988, 924, + 876, 844, 812, 780, 748, 716, 684, 652, + 620, 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, 260, + 244, 228, 212, 196, 180, 164, 148, 132, + 120, 112, 104, 96, 88, 80, 72, 64, + 56, 48, 40, 32, 24, 16, 8, 0 + ) - private val l = intArrayOf( - 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 - ) + // Таблицы для A-law (перенесены РёР· companion object) + private val k = intArrayOf( + 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 + ) - private val m = intArrayOf(0, 132, 396, 924, 1980, 4092, 8316, 16764) + private val l = intArrayOf( + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 + ) - fun encodeSample(input: Int): Int { - var a = if (input == -32768) -32767 else input - val c = (a.inv() shr 8) and 128 - if (c == 0) a *= -1 - if (a > 32635) a = 32635 - return if (a >= 256) { - val b = k[(a shr 8) and 127] - (b shl 4) or ((a shr (b + 3)) and 15) - } else { - a shr 4 - } xor c xor 85 - } + private val m = intArrayOf(0, 132, 396, 924, 1980, 4092, 8316, 16764) - fun decodeSample(input: Int): Int { - var a = input xor 85 - val c = if (a and 128 != 0) -1 else 0 - a = if (a and 128 != 0) a and 127 else a - val b = ((a and 240) shr 4) + 4 - return -8 * if (c == 0) { - if (b != 4) { - (1 shl b) or ((a and 15) shl (b - 4)) or (1 shl (b - 5)) - } else { - (a shl 1) or 1 - } - } else { - if (b != 4) { - -((1 shl b) or ((a and 15) shl (b - 4)) or (1 shl (b - 5))) - } else { - -(a shl 1) - 1 - } - } - } + // Ој-law методы + fun encodeMuLaw(data: ShortArray): ByteArray { + return ByteArray(data.size) { encodeSampleMuLaw(data[it].toInt()).toByte() } + } - fun encode(input: ShortArray): ByteArray { - val output = ByteArray(input.size) - for (i in input.indices) { - output[i] = encodeSample(input[i].toInt()).toByte() - } - return output - } + fun decodeMuLaw(data: ByteArray): ShortArray { + return ShortArray(data.size) { decodeSampleMuLaw(data[it]) } + } + + private fun encodeSampleMuLaw(a: Int): Int { + var a = a + val c = a shr 8 and 128 + if (c != 0) a = -a + if (a > 32635) a = 32635 + a += 132 + val b = l[a shr 7 and 255] + return (c or (b shl 4) or (a shr (b + 3) and 15)).inv() + } - fun decode(input: ByteArray): IntArray { - val output = IntArray(input.size) - for (i in input.indices) { - output[i] = decodeSample(input[i].toInt()) - } - return output + private fun decodeSampleMuLaw(encoded: Byte): Short { + val temp = encoded.toInt().inv() + val sign = temp and 0x80 + val exponent = (temp shr 4) and 0x07 + val mantissa = temp and 0x0F + + var sample = (mantissa shl 4) + 0x08 + if (exponent != 0) { + sample = (sample + 0x100) shl (exponent - 1) } + + return if (sign == 0) sample.toShort() else (-sample).toShort() + } + + // A-law методы + fun encodeALaw(data: ShortArray): ByteArray { + return ByteArray(data.size) { encodeSampleALaw(data[it].toInt()).toByte() } + } + + private fun encodeSampleALaw(a: Int): Int { + var a = a + a = if (a == -32768) -32767 else a + val c = if ((a.inv() shr 8) and 128 != 0) 128 else 0 + if (c == 0) a *= -1 + if (a > 32635) a = 32635 + return if (a >= 256) { + val b = k[a shr 8 and 127] + (b shl 4) or (a shr (b + 3) and 15) + } else { + a shr 4 + } xor c xor 85 } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt b/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt index 7194d87806a3d8745af5f261f571590c1f6108e1..e14a4468ce6361ceeaf947cb3a91c06f03bfcb2a 100644 --- a/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt +++ b/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt @@ -78,7 +78,7 @@ class AudioProcessor( val recordBufferSize = maxOf(minBufferSize, bufferSize * 2) if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != - android.content.pm.PackageManager.PERMISSION_GRANTED + android.content.pm.PackageManager.PERMISSION_GRANTED ) { throw SecurityException("Permission RECORD_AUDIO is not granted.") } diff --git a/app/src/main/java/com/example/radio2/Rtp/VoicePacketProcessor.kt b/app/src/main/java/com/example/radio2/Rtp/VoicePacketProcessor.kt index 341c0a82c7b94aa792917bd111b7c259710dbbd7..2ac5487306c3ad33aa7a542bf7b6644b54a400c5 100644 --- a/app/src/main/java/com/example/radio2/Rtp/VoicePacketProcessor.kt +++ b/app/src/main/java/com/example/radio2/Rtp/VoicePacketProcessor.kt @@ -56,7 +56,6 @@ class VoicePacketProcessor { } val pcm = decodeMuLawToPCM(Mulaw) - playPcmData(pcm) return pcm } diff --git a/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt b/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt index bb4b99a5452f063b4d475026f4ae6a2bdfcb5483..cb7ebcd89d1a70abaceed1f1ef68ed334dae232b 100644 --- a/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt +++ b/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt @@ -165,10 +165,9 @@ class SubscriberGroupActivity : ComponentActivity(), VoicePacketSender { setContentView(R.layout.activity_abonents) enableEdgeToEdge() val button = findViewById<Button>(R.id.activateVoice) - var buttonActionCount = 0 + var buttonActionCount = 0 audioProcessor = AudioProcessor(this, this) - button.setOnTouchListener { view, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { @@ -195,6 +194,7 @@ class SubscriberGroupActivity : ComponentActivity(), VoicePacketSender { + val recyclerViewGroup: RecyclerView = findViewById(R.id.groupsRecyclerView) val recyclerViewSubscriber: RecyclerView = findViewById(R.id.subscribersRecyclerView) recyclerViewGroup.layoutManager = LinearLayoutManager(this) diff --git a/app/src/main/java/com/example/radio2/service/WebSocketService.kt b/app/src/main/java/com/example/radio2/service/WebSocketService.kt index 4bce75541a61989da9ccab731c59ec424b128baa..fa067bd4241c197512b35d42f49d33316c475624 100644 --- a/app/src/main/java/com/example/radio2/service/WebSocketService.kt +++ b/app/src/main/java/com/example/radio2/service/WebSocketService.kt @@ -41,6 +41,9 @@ import com.example.radio2.Data.AudioConfigStorage import com.example.radio2.Data.DeviceData2 import kotlinx.coroutines.delay import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.long import java.util.UUID class WebSocketService : Service() { @@ -76,7 +79,7 @@ class WebSocketService : Service() { companion object { private const val NOTIFICATION_CHANNEL_ID = "WebSocketServiceChannel" - private const val NOTIFICATION_ID = 1 + private var notificationId = 0 } override fun onBind(intent: Intent?): IBinder { @@ -170,37 +173,28 @@ class WebSocketService : Service() { when (message) { is Frame.Text -> { val serverMessage = message.readText() + Log.d("WebSocketService", "Received raw message: $serverMessage") handleMessage(serverMessage) val jsonArray = Json.parseToJsonElement(serverMessage).jsonArray jsonArray.forEach() { message -> if (message is kotlinx.serialization.json.JsonObject) { - val messageID = - message["MessageID"]?.jsonPrimitive?.content + val messageID = message["MessageID"]?.jsonPrimitive?.content if (messageID != null) { - val handler = - messageHandlerFactory.getHandler(messageID) + Log.d("WebSocketService", "Processing message with ID: $messageID") + val handler = messageHandlerFactory.getHandler(messageID) if (handler != null) { handler.handle(message, this) } else { - Log.w( - "MessageHandlerFactory", - "No handler found for MessageID: $messageID" - ) + Log.w("MessageHandlerFactory", "No handler found for MessageID: $messageID") } } - } } } - is Frame.Binary -> { - Log.d( - "WebSocketService", - "Received binary frame of size: ${message.data.size}" - ) + Log.d("WebSocketService", "Received binary frame of size: ${message.data.size}") } - else -> Log.w("WebSocketService", "Unhandled frame: $message") } } @@ -208,15 +202,15 @@ class WebSocketService : Service() { } catch (e: Exception) { isConnected = false Log.e("WebSocketService", "Error connecting WebSocket: ${e.message}") - /*retryConnection(url)*/ } } } private fun handleMessage(message: String) { - Log.d("WebSocketService", "Received: $message") + Log.d("WebSocketService", "handleMessage called with: $message") messageCallback?.invoke(message) showNotification(message) + Log.d("WebSocketService", "handleMessage completed") } fun disconnectWebSocket() { @@ -234,7 +228,7 @@ class WebSocketService : Service() { .setSmallIcon(android.R.drawable.stat_notify_sync) .build() - startForeground(NOTIFICATION_ID, notification) + startForeground(notificationId, notification) } private fun createNotificationChannel() { @@ -431,27 +425,63 @@ class WebSocketService : Service() { } private fun showNotification(message: String) { - val notificationIntent = Intent(this, SubscriberGroupActivity::class.java) - notificationIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - val pendingIntent = PendingIntent.getActivity( - this, - 0, - notificationIntent, - PendingIntent.FLAG_IMMUTABLE - ) - - val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) - .setContentTitle("РќРѕРІРѕРµ сообщение") - .setContentText(message) - .setSmallIcon(android.R.drawable.stat_notify_sync) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(pendingIntent) - .setAutoCancel(true) - .build() - - val notificationManager = - getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notificationManager.notify(NOTIFICATION_ID, notification) + try { + Log.d("WebSocketService", "Attempting to show notification for message: $message") + val jsonArray = Json.parseToJsonElement(message).jsonArray + // Обрабатываем только первое сообщение РІ массиве + if (jsonArray.isNotEmpty()) { + val jsonElement = jsonArray[0] + if (jsonElement is JsonObject) { + val messageId = jsonElement["MessageID"]?.jsonPrimitive?.content + Log.d("WebSocketService", "Processing message with ID: $messageId") + + if (messageId == "STORAGE_JOB_REQUEST") { + val fromName = jsonElement["FromName"]?.jsonPrimitive?.content + val title = jsonElement["Title"]?.jsonPrimitive?.content + val jobType = jsonElement["JobType"]?.jsonPrimitive?.int + val jobId = jsonElement["JobID"]?.jsonPrimitive?.content + val time = jsonElement["Time"]?.jsonPrimitive?.long ?: System.currentTimeMillis() + val sequence = jsonElement["Sequence"]?.jsonPrimitive?.int + + if (jobId != null && sequence != null) { + readMessage(jobId, sequence) + } + + Log.d("WebSocketService", "Message details - From: $fromName, Title: $title, JobType: $jobType, JobID: $jobId") + + // Показываем уведомление только для текстовых сообщений (JobType = 0) + if (jobType == 0 && fromName != null && title != null && jobId != null) { + val notificationIntent = Intent(this, SubscriberGroupActivity::class.java) + notificationIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + val pendingIntent = PendingIntent.getActivity( + this, + 0, + notificationIntent, + PendingIntent.FLAG_IMMUTABLE + ) + + val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) + .setContentTitle("Сообщение РѕС‚ $fromName") + .setContentText(title) + .setSmallIcon(android.R.drawable.stat_notify_sync) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(pendingIntent) + .setAutoCancel(true) + .build() + + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val uniqueNotificationId = (time.toString() + jobId).hashCode() + notificationManager.notify(uniqueNotificationId, notification) + Log.d("WebSocketService", "Notification shown with ID: $uniqueNotificationId") + } else { + Log.d("WebSocketService", "Skipping notification - conditions not met") + } + } + } + } + } catch (e: Exception) { + Log.e("WebSocketService", "Error parsing notification message: ${e.message}") + } } diff --git a/h origin working_version b/h origin working_version new file mode 100644 index 0000000000000000000000000000000000000000..736525599ce5e07953d7ac2303a50c9bc9ef8e70 --- /dev/null +++ b/h origin working_version @@ -0,0 +1,170 @@ +[33mcommit 2f3c07e875cdcf72e4984e1f25132776dba0abcc[m[33m ([m[1;36mHEAD[m[33m -> [m[1;32mworking_version[m[33m, [m[1;31morigin/main[m[33m, [m[1;31morigin/HEAD[m[33m, [m[1;32mmain[m[33m, [m[1;32mdatabase[m[33m)[m +Author: алеша <alex288got@gmail.com> +Date: Thu Mar 6 14:32:22 2025 +0300 + + фикс РєРѕРґР° + +[1mdiff --git a/app/src/main/java/com/example/radio2/ChatActivity.kt b/app/src/main/java/com/example/radio2/ChatActivity.kt[m +[1mindex 0ab7dde..f3917ba 100644[m +[1m--- a/app/src/main/java/com/example/radio2/ChatActivity.kt[m +[1m+++ b/app/src/main/java/com/example/radio2/ChatActivity.kt[m +[36m@@ -83,7 +83,7 @@[m [mclass ChatActivity : AppCompatActivity() {[m + if (userName != null && userId != null) {[m + loadChat(userId, userName)[m + }[m +[31m- val sendMessageButton = findViewById<Button>(R.id.button)[m +[32m+[m + [m + recyclerView.layoutManager = LinearLayoutManager(this)[m + chatAdapter = ChatAdapter(this, messages)[m +[36m@@ -172,11 +172,32 @@[m [mclass ChatActivity : AppCompatActivity() {[m + messageInput.hint = "Enter Message"[m + }[m + }[m +[31m-[m +[32m+[m[32m private fun getMessagesForChat() {[m +[32m+[m[32m lifecycleScope.launch {[m +[32m+[m[32m val currentUserId = userId[m +[32m+[m[32m val currentChatId = chatId[m +[32m+[m +[32m+[m[32m if (currentUserId != null && currentChatId != null) {[m +[32m+[m[32m try {[m +[32m+[m[32m val chatMessages =[m +[32m+[m[32m chatRepository.getMessagesForChat(currentChatId, currentUserId)[m +[32m+[m[32m messages.clear()[m +[32m+[m[32m messages.addAll(chatMessages.map { MessageTo(it.text, MessageType.RECEIVED) })[m +[32m+[m[32m chatAdapter.notifyDataSetChanged()[m +[32m+[m[32m recyclerView.scrollToPosition(messages.size - 1)[m +[32m+[m[32m } catch (e: Exception) {[m +[32m+[m[32m Log.e("ChatActivity", "Ошибка РїСЂРё загрузке сообщений: ${e.message}")[m +[32m+[m[32m }[m +[32m+[m[32m } else {[m +[32m+[m[32m Log.e("ChatActivity", "Ошибка: userId или chatId РЅРµ инициализированы")[m +[32m+[m[32m }[m +[32m+[m[32m }[m +[32m+[m[32m }[m + private fun addUserMessage(message: String) {[m + messages.add(MessageTo(message, MessageType.SENT))[m + chatAdapter.notifyItemInserted(messages.size - 1)[m + recyclerView.scrollToPosition(messages.size - 1)[m +[32m+[m[32m messageInput.setText("")[m + }[m + [m + private fun logAllUsers() {[m +[36m@@ -293,7 +314,7 @@[m [mclass ChatActivity : AppCompatActivity() {[m + [m + [m + // fun addReceived1Message(message: String) {messages.add(MessageTo(message, MessageType.RECEIVED))}[m +[31m- fun addReceivedMessage(message: String , senderName: String ) {[m +[32m+[m[32m fun addReceivedMessage(message: String ) {[m + // val selectedUserName = intent.getStringExtra("USER_NAME")?: return[m + // Log.d("chat_prefs3", "Received message from $senderName: $message")[m + // addReceived1Message(message)[m +[36m@@ -317,13 +338,13 @@[m [mclass ChatActivity : AppCompatActivity() {[m + [m + }[m + // fun addUser1Message(message: String) {messages.add(MessageTo(message, MessageType.SENT))}[m +[31m- fun addUserMessage(message: String) {[m +[31m-[m +[31m-// addUser1Message(message)[m +[31m- messages.add(MessageTo(message, MessageType.SENT))[m +[31m- chatAdapter.notifyItemInserted(messages.size - 1)[m +[31m- recyclerView.scrollToPosition(messages.size - 1)[m +[31m- messageInput.setText("")[m +[32m+[m[32m// fun addUserMessage(message: String) {[m +[32m+[m[32m//[m +[32m+[m[32m//// addUser1Message(message)[m +[32m+[m[32m// messages.add(MessageTo(message, MessageType.SENT))[m +[32m+[m[32m// chatAdapter.notifyItemInserted(messages.size - 1)[m +[32m+[m[32m// recyclerView.scrollToPosition(messages.size - 1)[m +[32m+[m[32m// messageInput.setText("")[m + // saveMessages()[m + }[m +[31m-}[m +[41m+[m +[1mdiff --git a/app/src/main/java/com/example/radio2/ChatAdapter.kt b/app/src/main/java/com/example/radio2/ChatAdapter.kt[m +[1mindex 7c943f0..4ff2da8 100644[m +[1m--- a/app/src/main/java/com/example/radio2/ChatAdapter.kt[m +[1m+++ b/app/src/main/java/com/example/radio2/ChatAdapter.kt[m +[36m@@ -33,16 +33,12 @@[m [mclass ChatAdapter(private val context: Context, private var items: MutableList<M[m + holder.blocUserMessage.visibility = View.VISIBLE[m + holder.blocReceivedMessage.visibility = View.GONE[m + holder.userMessage.text = message.text[m +[31m-[m +[31m-[m + }[m + [m +[31m- MessageType.RECEIVED ->{[m +[32m+[m[32m MessageType.RECEIVED -> {[m + holder.blocUserMessage.visibility = View.GONE[m + holder.blocReceivedMessage.visibility = View.VISIBLE[m + holder.receivedMessage.text = message.text[m +[31m-[m +[31m-[m + }[m + }[m + }[m +[36m@@ -59,10 +55,3 @@[m [mdata class MessageTo(val text: String, val type: MessageType, )[m + enum class MessageType () {[m + SENT, RECEIVED[m + }[m +[31m-[m +[31m-[m +[31m-[m +[31m-[m +[31m-[m +[31m-[m +[31m-[m +[1mdiff --git a/app/src/main/java/com/example/radio2/DataBase.kt b/app/src/main/java/com/example/radio2/DataBase.kt[m +[1mindex 5b34958..fcd7af2 100644[m +[1m--- a/app/src/main/java/com/example/radio2/DataBase.kt[m +[1m+++ b/app/src/main/java/com/example/radio2/DataBase.kt[m +[36m@@ -206,10 +206,14 @@[m [mval MIGRATION_1_2 = object : Migration(1, 2) {[m + // Сама база данных[m + @Database(entities = [User::class, Chat::class, ChatMember::class, Message::class], version = 2)[m + abstract class AppDatabase : RoomDatabase() {[m +[32m+[m[32m abstract fun userDao(): UserDao[m +[32m+[m[32m abstract fun chatDao(): ChatDao[m +[32m+[m[32m abstract fun chatMemberDao(): ChatMemberDao[m + abstract fun messageDao(): MessageDao[m + [m + companion object {[m +[31m- @Volatile private var INSTANCE: AppDatabase? = null[m +[32m+[m[32m @Volatile[m +[32m+[m[32m private var INSTANCE: AppDatabase? = null[m + [m + fun getInstance(context: Context): AppDatabase {[m + return INSTANCE ?: synchronized(this) {[m +[36m@@ -217,9 +221,7 @@[m [mabstract class AppDatabase : RoomDatabase() {[m + context.applicationContext,[m + AppDatabase::class.java,[m + "app_database"[m +[31m- )[m +[31m- .addMigrations(MIGRATION_1_2) // Применение миграции[m +[31m- .build()[m +[32m+[m[32m ).build()[m + INSTANCE = instance[m + instance[m + }[m +[36m@@ -258,7 +260,7 @@[m [mclass ChatRepository([m + chatId[m + }[m + }[m +[31m-}[m +[32m+[m + [m + // Отправить сообщение РІ чат[m + suspend fun sendMessage(chatId: String, senderId: String, text: String) { // chatId Рё senderId теперь строки[m +[1mdiff --git a/app/src/main/java/com/example/radio2/MessageHandlers.kt b/app/src/main/java/com/example/radio2/MessageHandlers.kt[m +[1mindex fc04df4..138564c 100644[m +[1m--- a/app/src/main/java/com/example/radio2/MessageHandlers.kt[m +[1m+++ b/app/src/main/java/com/example/radio2/MessageHandlers.kt[m +[36m@@ -250,7 +250,7 @@[m [mclass StorageJobRequestHandler(private val webSocketService: WebSocketService) :[m + webSocketService.executeWhenActivityAvailable { activity ->[m + if (activity is ChatActivity) {[m + activity.runOnUiThread {[m +[31m- activity.addReceivedMessage(title.toString(), fromName.toString())[m +[32m+[m[32m activity.addReceivedMessage(title.toString(),)[m + if (jobId != null) {[m + webSocketService.readMessage(jobId, sequence!!)[m + }[m