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/build.gradle.kts b/app/build.gradle.kts index 4e595641e6c287342877b35407595987399c176f..22a6be9698b639175bf34bc77ce64a106999b863 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,6 +50,7 @@ android { } val ktor_version = "3.0.1" dependencies { + implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) @@ -92,4 +93,5 @@ dependencies { implementation("com.google.code.gson:gson:2.10.1") implementation("androidx.room:room-runtime:2.6.1") ksp ("androidx.room:room-compiler:2.6.1") + implementation ("androidx.room:room-runtime:2.4.2") } \ No newline at end of file diff --git a/app/src/main/java/com/example/radio2/AppDatabase.kt b/app/src/main/java/com/example/radio2/AppDatabase.kt new file mode 100644 index 0000000000000000000000000000000000000000..c4d1852801b2218b426d962c58d5d17e6978d4b7 --- /dev/null +++ b/app/src/main/java/com/example/radio2/AppDatabase.kt @@ -0,0 +1,74 @@ +package com.example.radio2 + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import android.content.Context +import androidx.room.Dao +import androidx.room.Entity +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query + +// Определение сущности Chat +@Entity(tableName = "Chats") +data class Chat( + @PrimaryKey val id: Long, + val title: String +) + +// Определение сущности Message +@Entity(tableName = "Messages") +data class Message( + @PrimaryKey(autoGenerate = true) val id: Long = 0, + val chatId: Long, + val text: String +) + +// DAO для работы СЃ чатами +@Dao +interface ChatDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertChat(chat: Chat) + + @Query("SELECT * FROM Chats WHERE id = :chatId LIMIT 1") + suspend fun getChatById(chatId: Long): Chat? + + @Query("SELECT * FROM Chats WHERE title = :title LIMIT 1") + suspend fun getChatByTitle(title: String): Chat? +} + +// DAO для работы СЃ сообщениями +@Dao +interface MessageDao { + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertMessage(message: Message) + + @Query("SELECT * FROM Messages WHERE chatId = :chatId") + suspend fun getMessagesForChat(chatId: Long): List<Message> +} + +// База данных +@Database(entities = [Chat::class, Message::class], version = 1, exportSchema = false) +abstract class AppDatabase : RoomDatabase() { + abstract fun chatDao(): ChatDao + abstract fun messageDao(): MessageDao + + companion object { + @Volatile + private var INSTANCE: AppDatabase? = null + + fun getDatabase(context: Context): AppDatabase { + return INSTANCE ?: synchronized(this) { + val instance = Room.databaseBuilder( + context.applicationContext, + AppDatabase::class.java, + "chat_database" + ).build() + INSTANCE = instance + instance + } + } + } +} diff --git a/app/src/main/java/com/example/radio2/ChatActivity.kt b/app/src/main/java/com/example/radio2/ChatActivity.kt index f3917ba440dd139a489222c7b213dcacee912c4c..29d9d175d3178f30f4a569db652cd7b5fe2f1f7c 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 @@ -13,24 +15,32 @@ import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.room.Room import com.example.radio2.Data.userDataStorage -import com.example.radio2.com.example.radio2.AppDatabase -import com.example.radio2.com.example.radio2.ChatRepository +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 sharedPreferences: SharedPreferences - private lateinit var database: AppDatabase - private lateinit var chatRepository: ChatRepository - private var chatId: String? = null // chatId теперь строка - private var userId: String? = null // userId теперь строка + 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?) { @@ -39,7 +49,6 @@ class ChatActivity : AppCompatActivity() { webSocketService?.setCurrentActivity(this@ChatActivity) } - override fun onServiceDisconnected(name: ComponentName?) { webSocketService = null } @@ -50,18 +59,7 @@ class ChatActivity : AppCompatActivity() { Intent(this, WebSocketService::class.java).also { intent -> bindService(intent, connection, BIND_AUTO_CREATE) } - val database = AppDatabase.getInstance(this) - chatRepository = ChatRepository( - userDao = database.userDao(), - chatDao = database.chatDao(), - chatMemberDao = database.chatMemberDao(), - messageDao = database.messageDao() - ) - - // Загрузка истории чата - getMessagesForChat() - - // Загружаем данные пользователя Рё чата + loadUserData() } @@ -70,27 +68,56 @@ class ChatActivity : AppCompatActivity() { unbindService(connection) } + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) setContentView(R.layout.activity_chat) - + val db = Room.databaseBuilder(applicationContext, AppDatabase::class.java, "chat_database").build() + chatRepository = ChatRepository(db) recyclerView = findViewById(R.id.recycler_message) messageInput = findViewById<TextInputEditText>(R.id.et_chat) val userId = intent.getStringExtra("USER_ID") - val userName = intent.getStringExtra("USER_NAME") + 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 { finish() @@ -113,86 +140,99 @@ class ChatActivity : AppCompatActivity() { private fun loadUserData() { lifecycleScope.launch { try { - // Получаем текущего пользователя - val userIdString = userDataStorage.userId // Получаем userId как строку - if (userIdString.isNullOrBlank()) { - Log.e("ChatActivity", "Ошибка: userId РЅРµ найден или РїСѓСЃС‚") - return@launch - } - - userId = userIdString // userId теперь строка - Log.d("ChatActivity", "Успешно получен userId: $userId") - - // Получаем ID чата - chatId = getChatIdForUser(userId!!) - if (chatId.isNullOrBlank()) { - Log.e("ChatActivity", "Ошибка: chatId РЅРµ найден для userId: $userId") - return@launch + val userName = intent.getStringExtra("USER_NAME") ?: "Неизвестный пользователь" + userId = intent.getLongExtra("USER_ID", 0) // Получаем userId РёР· Intent + + // Проверяем наличие чата Рё создаем его, если необходимо + chatId = chatRepository.getChatForUser (userId!!) ?: run { + // Создаем новый чат + val newChat = Chat(id = System.currentTimeMillis(), title = userName) // Рспользуем текущее время как уникальный ID + chatRepository.insertChat(newChat) + newChat.id // Возвращаем ID РЅРѕРІРѕРіРѕ чата } Log.d("ChatActivity", "Успешно получен chatId: $chatId") - val userName = getUserName(userId!!) - Log.d("ChatActivity", "Получено РёРјСЏ пользователя: $userName") - - loadChat(userId!!, userName) + loadChat(userId!!.toString(), userName) getMessagesForChat() - // Логирование базы данных - logAllUsers() - logAllChats() - } catch (e: Exception) { Log.e("ChatActivity", "Ошибка РїСЂРё загрузке данных: ${e.message}") } } } - private suspend fun getChatIdForUser(userId: String): String? { - return chatRepository.getChatForUser(userId) + private fun loadChat(userId: String, userName: String) { + lifecycleScope.launch { + // Генерация ID чата РЅР° РѕСЃРЅРѕРІРµ имени пользователя + val chatId = generateChatId(userName) + val chat = chatRepository.getChatById(chatId) + + 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 + 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 suspend fun getUserName(userId: String): String { - return chatRepository.getUserName(userId) ?: "Неизвестный пользователь" + 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 fun loadChat(userId: String, userName: String) { - val name = findViewById<TextView>(R.id.userName) - name.text = userName + 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() + } } private fun sendMessage() { val text = messageInput.text.toString().trim() if (text.isNotEmpty()) { addUserMessage(text) - webSocketService?.sendMessage(text, "0", "23", "0", "test", "test", "0") + + +// webSocketService?.sendMessage( +// +// text, +// userId ?: "0", +// chatId ?: "0", +// "0", +// "test", +// "test", +// "0" +// ) + + messageInput.hint = "Message" messageInput.text = null } else { messageInput.hint = "Enter Message" } } - private fun getMessagesForChat() { - lifecycleScope.launch { - val currentUserId = userId - val currentChatId = chatId - - if (currentUserId != null && currentChatId != null) { - try { - val chatMessages = - chatRepository.getMessagesForChat(currentChatId, currentUserId) - messages.clear() - messages.addAll(chatMessages.map { MessageTo(it.text, MessageType.RECEIVED) }) - chatAdapter.notifyDataSetChanged() - recyclerView.scrollToPosition(messages.size - 1) - } catch (e: Exception) { - Log.e("ChatActivity", "Ошибка РїСЂРё загрузке сообщений: ${e.message}") - } - } else { - Log.e("ChatActivity", "Ошибка: userId или chatId РЅРµ инициализированы") - } - } - } + private fun addUserMessage(message: String) { messages.add(MessageTo(message, MessageType.SENT)) chatAdapter.notifyItemInserted(messages.size - 1) @@ -200,151 +240,24 @@ class ChatActivity : AppCompatActivity() { messageInput.setText("") } - private fun logAllUsers() { - lifecycleScope.launch { - val users = chatRepository.getAllUsers() - if (users.isEmpty()) { - Log.d("Database", "Р’ базе данных нет пользователей") - } else { - users.forEach { user -> - Log.d("Database", "User: id=${user.id}, name=${user.username}") - } - } - } - } - - private fun logAllChats() { - lifecycleScope.launch { - val chats = chatRepository.getAllChats() - if (chats.isEmpty()) { - Log.d("Database", "Р’ базе данных нет чатов") - } else { - chats.forEach { chat -> - Log.d("Database", "Chat: id=${chat.id}, title=${chat.title}") - } - } - } - } - -// val editor = otherChatsPreferences.edit() -// val key = "messages_$senderName" -// Log.d("chat_prefs2", "Сохраняем для: $key, $senderName") -// -// -// val existingMessages = otherChatsPreferences.getString(key, "") ?: "" -// val updatedMessages = if (existingMessages.isNotEmpty()) { -// "$existingMessages,$message" -// } else { -// message -// } -// -// editor.putString(key, updatedMessages) -// editor.apply() -// } - //private fun loadMessages() { - - //val userName = intent.getStringExtra("USER_NAME") ?: return - //val key = "messages_$userName" -// uploadLoadMessages(key) - //val savedMessages = currentChatPreferences.getString(key, "") - //savedMessages?.let { - // if (it.isNotEmpty()) { - //val loadedMessages = it.split(",").map { msg -> - //val parts = msg.split(":") -// if (parts.size == 2) { - // val type = MessageType.valueOf(parts[0]) - // val text = parts[1] - // MessageTo(text, type) -// } else null - //} - - // messages.clear() - // messages.addAll(loadedMessages) - - // } - //} - - // chatAdapter.setList(messages) - //chatAdapter.notifyItemInserted(messages.size - 1) - // recyclerView.scrollToPosition(messages.size - 1) - //} - - // private fun uploadLoadMessages(key: String) { - - //val serializedMessages = otherChatsPreferences.getString(key, "") - //serializedMessages?.let { - //if (it.isNotEmpty()) { - // val loadedMessages = it.split(",").map { msg -> - //val parts = msg.split(":") - //val type = MessageType.valueOf(parts[0]) - // val text = parts[1] - // MessageTo(text, type) - - // } - // - // } - // } - // } - -// val otherMessages = otherChatsPreferences.getStringSet(key, emptySet()) ?: emptySet() -// -// // Сохранение сообщений РІ текущем чате -// val currentMessages = currentChatPreferences.getStringSet(key, mutableSetOf()) ?: mutableSetOf() -// currentMessages.addAll(otherMessages) -// -// // Сохранение обновленных сообщений РІ текущем чате -// currentChatPreferences.edit().putStringSet(key, currentMessages).apply() - -// removeData(key) - - -// private fun removeData(key: String) { -// otherChatsPreferences.edit().remove(key).apply() -// } override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { finish() true } + else -> super.onOptionsItemSelected(item) } } - - -// fun addReceived1Message(message: String) {messages.add(MessageTo(message, MessageType.RECEIVED))} - fun addReceivedMessage(message: String ) { -// val selectedUserName = intent.getStringExtra("USER_NAME")?: return -// Log.d("chat_prefs3", "Received message from $senderName: $message") - // addReceived1Message(message) - -// if (selectedUserName == senderName) { - - messages.add(MessageTo(message, MessageType.RECEIVED)) - chatAdapter.notifyItemInserted(messages.size - 1) - recyclerView.scrollToPosition(messages.size - 1) -// saveMessages() -// Log.d("chat_prefs2", "Added message to current chat: $message") - - -// } - -// else { -// otherSaveMessages(senderName) -// Log.d("chat_prefs3", "???: $message") -// } - - - } -// fun addUser1Message(message: String) {messages.add(MessageTo(message, MessageType.SENT))} -// fun addUserMessage(message: String) { -// -//// addUser1Message(message) -// messages.add(MessageTo(message, MessageType.SENT)) -// chatAdapter.notifyItemInserted(messages.size - 1) -// recyclerView.scrollToPosition(messages.size - 1) -// messageInput.setText("") -// saveMessages() + 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/ChatRepository.kt b/app/src/main/java/com/example/radio2/ChatRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8ee8461ac8b6fdd2ddfbab1eb6b8e0fd6f17f4e --- /dev/null +++ b/app/src/main/java/com/example/radio2/ChatRepository.kt @@ -0,0 +1,53 @@ +package com.example.radio2 + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class ChatRepository(private val db: AppDatabase) { + + // Вставка РЅРѕРІРѕРіРѕ чата + suspend fun insertChat(chat: Chat) { + withContext(Dispatchers.IO) { + db.chatDao().insertChat(chat) + } + } + + // Получение чата РїРѕ ID + suspend fun getChatById(chatId: Long): Chat? { + return withContext(Dispatchers.IO) { + db.chatDao().getChatById(chatId) + } + } + + // Получение чата РїРѕ названию + suspend fun getChatByTitle(title: String): Chat? { + return withContext(Dispatchers.IO) { + db.chatDao().getChatByTitle(title) + } + } + + // Вставка РЅРѕРІРѕРіРѕ сообщения + suspend fun insertMessage(message: Message) { + withContext(Dispatchers.IO) { + db.messageDao().insertMessage(message) + } + } + + // Получение сообщений для конкретного чата + suspend fun getMessagesForChat(chatId: Long): List<Message> { + return withContext(Dispatchers.IO) { + db.messageDao().getMessagesForChat(chatId) + } + } + suspend fun getChatForUser (userId: Long): Long? { + return withContext(Dispatchers.IO) { + val chat = db.chatDao().getChatByTitle(userId.toString()) + chat?.id + } + } + suspend fun createChat(chatId: Long, title: String): Long { + val chat = Chat(id = chatId, title = title) + db.chatDao().insertChat(chat) + return chat.id + } +} diff --git a/app/src/main/java/com/example/radio2/DataBase.kt b/app/src/main/java/com/example/radio2/DataBase.kt index fcd7af25ee19eb736954ae614a3ec109507088df..40e33df5edb3ad7973cf0e4974f0c5ee255e7f2a 100644 --- a/app/src/main/java/com/example/radio2/DataBase.kt +++ b/app/src/main/java/com/example/radio2/DataBase.kt @@ -1,336 +1,335 @@ -package com.example.radio2.com.example.radio2 - -import android.content.Context -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import androidx.room.Dao -import androidx.room.Database -import androidx.room.Entity -import androidx.room.Insert -import androidx.room.PrimaryKey -import androidx.room.Query -import androidx.room.ForeignKey -import androidx.room.OnConflictStrategy -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.migration.Migration -import androidx.sqlite.db.SupportSQLiteDatabase -import kotlinx.coroutines.launch -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import java.util.UUID - -// Таблица юзера -@Entity(tableName = "users") -data class User( - @PrimaryKey val id: String, // id теперь строка - val username: String // Название юзера -) - -//Таблица СЃ чатами -@Entity(tableName = "chats") -data class Chat( - @PrimaryKey val id: String, // chatId теперь строка - val title: String? = null // Название чата -) - -// Таблица СЃ участниками чата, РіРґРµ главные id чата Рё id юзера -@Entity( - tableName = "chat_members", - primaryKeys = ["chatId", "userId"], - foreignKeys = [ - ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]), - ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["userId"]) - ] -) -data class ChatMember( - val chatId: String, // chatId теперь строка - val userId: String // userId теперь строка -) - -@Entity( - tableName = "messages", - foreignKeys = [ - ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]) - ] -) -data class Message( - @PrimaryKey(autoGenerate = true) val id: Int = 0, // уникальный id сообщения - val chatId: String, // chatId теперь строка - val senderId: String, // senderId теперь строка - val text: String, // Сам текст - val timestamp: Long = System.currentTimeMillis() // Метка СЃРѕ временем -) - - -//Data Access Objects - управление пользователями -@Dao -interface UserDao { - @Insert - suspend fun insertUser(user: User): Long - - @Query("SELECT * FROM users WHERE id = :userId") - suspend fun getUserById(userId: String): User // userId теперь строка - - @Query("SELECT * FROM users") - suspend fun getAllUsers(): List<User> - - @Query("SELECT username FROM users WHERE id = :userId LIMIT 1") - suspend fun getUserNameById(userId: String): String? // userId теперь строка -} - -//Data Access Objects - управление чатами -@Dao -interface ChatDao { - @Insert - suspend fun insertChat(chat: Chat): Long - - @Query("SELECT * FROM chats WHERE id = :chatId") - suspend fun getChatById(chatId: Int): Chat - - @Query("SELECT * FROM chats") - suspend fun getAllChats(): List<Chat> -} - -//Data Access Objects - управление юзерами чата -@Dao -interface ChatMemberDao { - @Insert - suspend fun addMember(chatMember: ChatMember) - - @Query(""" - SELECT * FROM chats - WHERE id IN (SELECT chatId FROM chat_members WHERE userId = :userId) - """) - suspend fun getChatsForUser(userId: String): List<Chat> // userId теперь строка - - @Query(""" - SELECT * FROM users - WHERE id IN (SELECT userId FROM chat_members WHERE chatId = :chatId) - """) - suspend fun getUsersInChat(chatId: String): List<User> // chatId теперь строка - - @Query("SELECT chatId FROM chat_members WHERE userId = :userId LIMIT 1") - suspend fun getChatIdByUserId(userId: String): String? // userId теперь строка -} - -//Data Access Objects - управление сообщениями -@Dao -interface MessageDao { - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun insertMessage(message: Message) - - @Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY timestamp ASC") - suspend fun getMessagesForChat(chatId: String): List<Message> // chatId теперь строка -} - -val MIGRATION_1_2 = object : Migration(1, 2) { - override fun migrate(database: SupportSQLiteDatabase) { - // Миграция для users - database.execSQL(""" - CREATE TABLE IF NOT EXISTS `users_temp` ( - `id` TEXT NOT NULL PRIMARY KEY, - `username` TEXT NOT NULL - ) - """) - database.execSQL(""" - INSERT INTO `users_temp` (`id`, `username`) - SELECT `id`, `username` FROM `users` - """) - database.execSQL("DROP TABLE `users`") - database.execSQL("ALTER TABLE `users_temp` RENAME TO `users`") - - // Миграция для chats - database.execSQL(""" - CREATE TABLE IF NOT EXISTS `chats_temp` ( - `id` TEXT NOT NULL PRIMARY KEY, - `title` TEXT - ) - """) - database.execSQL(""" - INSERT INTO `chats_temp` (`id`, `title`) - SELECT `id`, `title` FROM `chats` - """) - database.execSQL("DROP TABLE `chats`") - database.execSQL("ALTER TABLE `chats_temp` RENAME TO `chats`") - - // Миграция для chat_members - database.execSQL(""" - CREATE TABLE IF NOT EXISTS `chat_members_temp` ( - `chatId` TEXT NOT NULL, - `userId` TEXT NOT NULL, - PRIMARY KEY(`chatId`, `userId`), - FOREIGN KEY(`chatId`) REFERENCES `chats`(`id`) ON DELETE NO ACTION, - FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON DELETE NO ACTION - ) - """) - - // Копируем данные РёР· старой таблицы РІ РЅРѕРІСѓСЋ для chat_members - database.execSQL(""" - INSERT INTO `chat_members_temp` (`chatId`, `userId`) - SELECT `chatId`, `userId` FROM `chat_members` - """) - - // Удаляем старую таблицу - database.execSQL("DROP TABLE `chat_members`") - - // Переименовываем РЅРѕРІСѓСЋ таблицу РІ старое РёРјСЏ - database.execSQL("ALTER TABLE `chat_members_temp` RENAME TO `chat_members`") - - // Миграция для messages - database.execSQL(""" - CREATE TABLE IF NOT EXISTS `messages_temp` ( - `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - `chatId` TEXT NOT NULL, - `senderId` TEXT NOT NULL, - `text` TEXT NOT NULL, - `timestamp` INTEGER NOT NULL, - FOREIGN KEY(`chatId`) REFERENCES `chats`(`id`) ON DELETE NO ACTION - ) - """) - - // Копируем данные РёР· старой таблицы РІ РЅРѕРІСѓСЋ для messages - database.execSQL(""" - INSERT INTO `messages_temp` (`id`, `chatId`, `senderId`, `text`, `timestamp`) - SELECT `id`, `chatId`, `senderId`, `text`, `timestamp` FROM `messages` - """) - - // Удаляем старую таблицу - database.execSQL("DROP TABLE `messages`") - - // Переименовываем РЅРѕРІСѓСЋ таблицу РІ старое РёРјСЏ - database.execSQL("ALTER TABLE `messages_temp` RENAME TO `messages`") - } -} - -// Сама база данных -@Database(entities = [User::class, Chat::class, ChatMember::class, Message::class], version = 2) -abstract class AppDatabase : RoomDatabase() { - abstract fun userDao(): UserDao - abstract fun chatDao(): ChatDao - abstract fun chatMemberDao(): ChatMemberDao - abstract fun messageDao(): MessageDao - - companion object { - @Volatile - private var INSTANCE: AppDatabase? = null - - fun getInstance(context: Context): AppDatabase { - return INSTANCE ?: synchronized(this) { - val instance = Room.databaseBuilder( - context.applicationContext, - AppDatabase::class.java, - "app_database" - ).build() - INSTANCE = instance - instance - } - } - } -} - -class ChatRepository( - private val userDao: UserDao, - private val chatDao: ChatDao, - private val chatMemberDao: ChatMemberDao, - private val messageDao: MessageDao -) { - - // 1. Создать РЅРѕРІРѕРіРѕ пользователя - suspend fun createUser(username: String): Long { - return withContext(Dispatchers.IO) { - val user = User(id = generateRandomId(), username = username) // Генерация id теперь строка - userDao.insertUser(user) - } - } - - // Создать новый чат между несколькими пользователями - suspend fun createChat(chatTitle: String?, userIds: List<String>): String { // chatId теперь строка - return withContext(Dispatchers.IO) { - // Делаем чат - val chatId = generateRandomId() // Генерация chatId теперь строка - - // Добавляем чат РІ базу - chatDao.insertChat(Chat(id = chatId, title = chatTitle)) - - // Добавляем юзера РІ чат - userIds.forEach { userId -> - chatMemberDao.addMember(ChatMember(chatId = chatId, userId = userId)) - } - chatId - } - } - - - // Отправить сообщение РІ чат - suspend fun sendMessage(chatId: String, senderId: String, text: String) { // chatId Рё senderId теперь строки - withContext(Dispatchers.IO) { - // Проверяем, является ли отправитель юзером чата - val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } - if (senderId !in usersInChat) { - throw IllegalArgumentException("Пользователь СЃ ID $senderId РЅРµ является участником этого диалога") - } - - // Добавить сообщение - messageDao.insertMessage( - Message(chatId = chatId, senderId = senderId, text = text) - ) - } - } - - // Получить СЃРїРёСЃРѕРє чатов для юзера - suspend fun getChatsForUser(userId: String): List<Chat> { // userId теперь строка - return withContext(Dispatchers.IO) { - chatMemberDao.getChatsForUser(userId) - } - } - - // Получить сообщения для чата - suspend fun getMessagesForChat(chatId: String, userId: String): List<Message> { // chatId Рё userId теперь строки - return withContext(Dispatchers.IO) { - // Проверяем, является ли юзер участником чата - val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } - if (userId !in usersInChat) { - throw IllegalArgumentException("Пользователь СЃ ID $userId РЅРµ имеет доступа Рє этому чату") - } - - // Возврат - messageDao.getMessagesForChat(chatId) - } - } - - // Если надо добавить участника РІ СѓР¶Рµ существующий чат - suspend fun addUserToChat(chatId: String, userId: String) { // chatId Рё userId теперь строки - withContext(Dispatchers.IO) { - // Проверка, есть ли юзер РІ этом чате - val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } - if (userId in usersInChat) { - throw IllegalArgumentException("Пользователь СЃ ID $userId СѓР¶Рµ участник чата") - } - - // Добавление - chatMemberDao.addMember(ChatMember(chatId = chatId, userId = userId)) - } - } - - suspend fun getChatForUser(userId: String): String? { // userId теперь строка - return chatMemberDao.getChatIdByUserId(userId) - } - - suspend fun getUserName(userId: String): String? { // userId теперь строка - return userDao.getUserNameById(userId) - } - - suspend fun getAllUsers(): List<User> { - return userDao.getAllUsers() - } - - suspend fun getAllChats(): List<Chat> { - return chatDao.getAllChats() - } - - // Функция для генерации случайного строкового ID - fun generateRandomId(): String { - return UUID.randomUUID().toString() - } -} \ No newline at end of file +//package com.example.radio2 +// +//import android.content.Context +//import androidx.room.Dao +//import androidx.room.Database +//import androidx.room.Entity +//import androidx.room.Insert +//import androidx.room.PrimaryKey +//import androidx.room.Query +//import androidx.room.ForeignKey +//import androidx.room.OnConflictStrategy +//import androidx.room.Room +//import androidx.room.RoomDatabase +//import androidx.room.migration.Migration +//import androidx.sqlite.db.SupportSQLiteDatabase +//import kotlinx.coroutines.Dispatchers +//import kotlinx.coroutines.withContext +//import java.util.UUID +// +//// Таблица юзера +//@Entity(tableName = "users") +//data class User( +// @PrimaryKey val id: String, // id теперь строка +// val username: String // Название юзера +//) +// +////Таблица СЃ чатами +//@Entity(tableName = "chats") +//data class Chat( +// @PrimaryKey val id: String, // chatId теперь строка +// val title: String? = null // Название чата +//) +// +//// Таблица СЃ участниками чата, РіРґРµ главные id чата Рё id юзера +//@Entity( +// tableName = "chat_members", +// primaryKeys = ["chatId", "userId"], +// foreignKeys = [ +// ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]), +// ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["userId"]) +// ] +//) +//data class ChatMember( +// val chatId: String, // chatId теперь строка +// val userId: String // userId теперь строка +//) +// +//@Entity( +// tableName = "messages", +// foreignKeys = [ +// ForeignKey(entity = Chat::class, parentColumns = ["id"], childColumns = ["chatId"]) +// ] +//) +//data class Message( +// @PrimaryKey(autoGenerate = true) val id: Int = 0, // уникальный id сообщения +// val chatId: String, // chatId теперь строка +// val senderId: String, // senderId теперь строка +// val text: String, // Сам текст +// val timestamp: Long = System.currentTimeMillis() // Метка СЃРѕ временем +//) +// +// +////Data Access Objects - управление пользователями +//@Dao +//interface UserDao { +// @Insert +// suspend fun insertUser(user: User): Long +// +// @Query("SELECT * FROM users WHERE id = :userId") +// suspend fun getUserById(userId: String): User // userId теперь строка +// +// @Query("SELECT * FROM users") +// suspend fun getAllUsers(): List<User> +// +// @Query("SELECT username FROM users WHERE id = :userId LIMIT 1") +// suspend fun getUserNameById(userId: String): String? // userId теперь строка +//} +// +////Data Access Objects - управление чатами +//@Dao +//interface ChatDao { +// @Insert +// suspend fun insertChat(chat: Chat): Long +// +// @Query("SELECT * FROM chats WHERE id = :chatId") +// suspend fun getChatById(chatId: Int): Chat +// +// @Query("SELECT * FROM chats") +// suspend fun getAllChats(): List<Chat> +//} +// +////Data Access Objects - управление юзерами чата +//@Dao +//interface ChatMemberDao { +// @Insert +// suspend fun addMember(chatMember: ChatMember) +// +// @Query(""" +// SELECT * FROM chats +// WHERE id IN (SELECT chatId FROM chat_members WHERE userId = :userId) +// """) +// suspend fun getChatsForUser(userId: String): List<Chat> // userId теперь строка +// +// @Query(""" +// SELECT * FROM users +// WHERE id IN (SELECT userId FROM chat_members WHERE chatId = :chatId) +// """) +// suspend fun getUsersInChat(chatId: String): List<User> // chatId теперь строка +// +// @Query("SELECT chatId FROM chat_members WHERE userId = :userId LIMIT 1") +// suspend fun getChatIdByUserId(userId: String): String? // userId теперь строка +//} +// +////Data Access Objects - управление сообщениями +//@Dao +//interface MessageDao { +// @Insert(onConflict = OnConflictStrategy.REPLACE) +// suspend fun insertMessage(message: Message) +// +// @Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY timestamp ASC") +// suspend fun getMessagesForChat(chatId: String): List<Message> // chatId теперь строка +//} +// +//val MIGRATION_1_2 = object : Migration(1, 2) { +// override fun migrate(database: SupportSQLiteDatabase) { +// // Миграция для users +// database.execSQL(""" +// CREATE TABLE IF NOT EXISTS `users_temp` ( +// `id` TEXT NOT NULL PRIMARY KEY, +// `username` TEXT NOT NULL +// ) +// """) +// database.execSQL(""" +// INSERT INTO `users_temp` (`id`, `username`) +// SELECT `id`, `username` FROM `users` +// """) +// database.execSQL("DROP TABLE `users`") +// database.execSQL("ALTER TABLE `users_temp` RENAME TO `users`") +// +// // Миграция для chats +// database.execSQL(""" +// CREATE TABLE IF NOT EXISTS `chats_temp` ( +// `id` TEXT NOT NULL PRIMARY KEY, +// `title` TEXT +// ) +// """) +// database.execSQL(""" +// INSERT INTO `chats_temp` (`id`, `title`) +// SELECT `id`, `title` FROM `chats` +// """) +// database.execSQL("DROP TABLE `chats`") +// database.execSQL("ALTER TABLE `chats_temp` RENAME TO `chats`") +// +// // Миграция для chat_members +// database.execSQL(""" +// CREATE TABLE IF NOT EXISTS `chat_members_temp` ( +// `chatId` TEXT NOT NULL, +// `userId` TEXT NOT NULL, +// PRIMARY KEY(`chatId`, `userId`), +// FOREIGN KEY(`chatId`) REFERENCES `chats`(`id`) ON DELETE NO ACTION, +// FOREIGN KEY(`userId`) REFERENCES `users`(`id`) ON DELETE NO ACTION +// ) +// """) +// +// // Копируем данные РёР· старой таблицы РІ РЅРѕРІСѓСЋ для chat_members +// database.execSQL(""" +// INSERT INTO `chat_members_temp` (`chatId`, `userId`) +// SELECT `chatId`, `userId` FROM `chat_members` +// """) +// +// // Удаляем старую таблицу +// database.execSQL("DROP TABLE `chat_members`") +// +// // Переименовываем РЅРѕРІСѓСЋ таблицу РІ старое РёРјСЏ +// database.execSQL("ALTER TABLE `chat_members_temp` RENAME TO `chat_members`") +// +// // Миграция для messages +// database.execSQL(""" +// CREATE TABLE IF NOT EXISTS `messages_temp` ( +// `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, +// `chatId` TEXT NOT NULL, +// `senderId` TEXT NOT NULL, +// `text` TEXT NOT NULL, +// `timestamp` INTEGER NOT NULL, +// FOREIGN KEY(`chatId`) REFERENCES `chats`(`id`) ON DELETE NO ACTION +// ) +// """) +// +// // Копируем данные РёР· старой таблицы РІ РЅРѕРІСѓСЋ для messages +// database.execSQL(""" +// INSERT INTO `messages_temp` (`id`, `chatId`, `senderId`, `text`, `timestamp`) +// SELECT `id`, `chatId`, `senderId`, `text`, `timestamp` FROM `messages` +// """) +// +// // Удаляем старую таблицу +// database.execSQL("DROP TABLE `messages`") +// +// // Переименовываем РЅРѕРІСѓСЋ таблицу РІ старое РёРјСЏ +// database.execSQL("ALTER TABLE `messages_temp` RENAME TO `messages`") +// } +//} +// +//// Сама база данных +//@Database(entities = [User::class, Chat::class, ChatMember::class, Message::class], version = 2) +//abstract class AppDatabase : RoomDatabase() { +// abstract fun userDao(): UserDao +// abstract fun chatDao(): ChatDao +// abstract fun chatMemberDao(): ChatMemberDao +// abstract fun messageDao(): MessageDao +// +// companion object { +// @Volatile +// private var INSTANCE: AppDatabase? = null +// +// fun getInstance(context: Context): AppDatabase { +// return INSTANCE ?: synchronized(this) { +// val instance = Room.databaseBuilder( +// context.applicationContext, +// AppDatabase::class.java, +// "app_database" +// ).build() +// INSTANCE = instance +// instance +// } +// } +// } +//} +// +//class ChatRepository( +// private val userDao: UserDao, +// private val chatDao: ChatDao, +// private val chatMemberDao: ChatMemberDao, +// private val messageDao: MessageDao +//) { +// +// // 1. Создать РЅРѕРІРѕРіРѕ пользователя +// suspend fun createUser(username: String): Long { +// return withContext(Dispatchers.IO) { +// val user = User(id = generateRandomId(), username = username) // Генерация id теперь строка +// userDao.insertUser(user) +// } +// } +// +// // Создать новый чат между несколькими пользователями +// suspend fun createChat(chatTitle: String?,userId: String) +// { // chatId теперь строка +// return withContext(Dispatchers.IO) { +// // Делаем чат +// val chatId = generateRandomId() // Генерация chatId теперь строка +// +// +// // Добавляем чат РІ базу +// chatDao.insertChat(Chat(id = chatId, title = chatTitle)) +// +// // Добавляем юзера РІ чат +// +// chatMemberDao.addMember(ChatMember(chatId = chatId, userId = userId)) +// +// chatId +// } +// } +// +// +// // Отправить сообщение РІ чат +// suspend fun sendMessage(chatId: String, senderId: String, text: String) { // chatId Рё senderId теперь строки +// withContext(Dispatchers.IO) { +// // Проверяем, является ли отправитель юзером чата +// val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } +// if (senderId !in usersInChat) { +// throw IllegalArgumentException("Пользователь СЃ ID $senderId РЅРµ является участником этого диалога") +// } +// +// // Добавить сообщение +// messageDao.insertMessage( +// Message(chatId = chatId, senderId = senderId, text = text) +// ) +// } +// } +// +// // Получить СЃРїРёСЃРѕРє чатов для юзера +// suspend fun getChatsForUser(userId: String): List<Chat> { // userId теперь строка +// return withContext(Dispatchers.IO) { +// chatMemberDao.getChatsForUser(userId) +// } +// } +// +// // Получить сообщения для чата +// suspend fun getMessagesForChat(chatId: String, userId: String): List<Message> { // chatId Рё userId теперь строки +// return withContext(Dispatchers.IO) { +// // Проверяем, является ли юзер участником чата +// val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } +// if (userId !in usersInChat) { +// throw IllegalArgumentException("Пользователь СЃ ID $userId РЅРµ имеет доступа Рє этому чату") +// } +// +// // Возврат +// messageDao.getMessagesForChat(chatId) +// } +// } +// +// // Если надо добавить участника РІ СѓР¶Рµ существующий чат +// suspend fun addUserToChat(chatId: String, userId: String) { // chatId Рё userId теперь строки +// withContext(Dispatchers.IO) { +// // Проверка, есть ли юзер РІ этом чате +// val usersInChat = chatMemberDao.getUsersInChat(chatId).map { it.id } +// if (userId in usersInChat) { +// throw IllegalArgumentException("Пользователь СЃ ID $userId СѓР¶Рµ участник чата") +// } +// +// // Добавление +// chatMemberDao.addMember(ChatMember(chatId = chatId, userId = userId)) +// } +// } +// +// suspend fun getChatForUser(userId: String): String? { // userId теперь строка +// return chatMemberDao.getChatIdByUserId(userId) +// } +// +// suspend fun getUserName(userId: String): String? { // userId теперь строка +// return userDao.getUserNameById(userId) +// } +// +// suspend fun getAllUsers(): List<User> { +// return userDao.getAllUsers() +// } +// +// suspend fun getAllChats(): List<Chat> { +// return chatDao.getAllChats() +// } +// +// // Функция для генерации случайного строкового ID +// fun generateRandomId(): String { +// return UUID.randomUUID().toString() +// } +//} \ 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 69a2524e6627e7b3a6e30d679833cf5d9f4b88b6..e14a4468ce6361ceeaf947cb3a91c06f03bfcb2a 100644 --- a/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt +++ b/app/src/main/java/com/example/radio2/Rtp/AudioProcessor.kt @@ -1,18 +1,29 @@ package com.example.radio2.Rtp +import android.Manifest +import android.content.Context import android.media.AudioAttributes import android.media.AudioFormat import android.media.AudioFormat.CHANNEL_OUT_MONO import android.media.AudioFormat.ENCODING_PCM_16BIT import android.media.AudioManager +import android.media.AudioRecord import android.media.AudioTrack +import android.media.MediaRecorder import android.util.Log +import androidx.core.content.ContextCompat import com.example.radio2.Data.AudioConfigStorage import com.example.radio2.Rtp.ArrayUtils.chunkFloatArrayWithPadding import com.example.radio2.service.WebSocketService +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch import org.example.radio2.Rtp.AlawMulawEncoder import org.json.JSONArray import org.json.JSONObject +import java.nio.ByteBuffer +import java.nio.ByteOrder import java.util.Base64 import java.util.Base64.getEncoder import kotlin.math.PI @@ -43,64 +54,76 @@ interface VoicePacketSender { fun sendVoicePacket(base64Str: String) } -class AudioProcessor(private val packetSender: VoicePacketSender?, private val sampleRate: Int = 26000, private val bufferSize: Int = 1440) { - val frequency = 1000 // Частота 20 кГц - val duration = 1.0 // Длительность 0.5 секунды - +class AudioProcessor( + private val packetSender: VoicePacketSender?, + private val context: Context, + private val sampleRate: Int = 26000, + private val bufferSize: Int = 1440 +) { + private var isRecording = false + private lateinit var recordingThread: Thread + private var audioRecord: AudioRecord? = null fun startRecording() { - Log.d("AudioRecorder ssrc", "ssrc: $ssrc") - val signal = generateToneArray(frequency, duration, sampleRate) - /*Log.d("AudioRecorder signal", "signal: ${signal.contentToString()}")*/ - - val chunks = chunkFloatArrayWithPadding(signal, bufferSamplesCount) + if (isRecording) return + + isRecording = true + recordingThread = Thread { + val minBufferSize = AudioRecord.getMinBufferSize( + sampleRate, + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT + ) + + val recordBufferSize = maxOf(minBufferSize, bufferSize * 2) + + if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != + android.content.pm.PackageManager.PERMISSION_GRANTED + ) { + throw SecurityException("Permission RECORD_AUDIO is not granted.") + } - /*Log.d( - TAG, - """ - Количество чанков: ${chunks.size} - Чанки: - ${chunks.joinToString("\n") { "Длина: ${it.size} | Данные: ${formatChunkData(it)}" }} - """.trimIndent() - )*/ + audioRecord = AudioRecord( + MediaRecorder.AudioSource.MIC, + sampleRate, + AudioFormat.CHANNEL_IN_MONO, + AudioFormat.ENCODING_PCM_16BIT, + recordBufferSize + ) - /*playBuffer(signal, sampleRate)*/ + audioRecord?.startRecording() - Log.d(TAG, "Длина РёСЃС…РѕРґРЅРѕРіРѕ массива: ${signal.size}") + val buffer = ShortArray(bufferSize) - val encodedChunks = mutableListOf<String>() + while (isRecording) { + val bytesRead = audioRecord?.read(buffer, 0, bufferSize) ?: 0 - for (chunk in chunks) { - // Преобразуем FloatArray РІ ShortArray - val chunkShort = ShortArray(chunk.size) - for (i in chunk.indices) { - chunkShort[i] = (chunk[i] * 32767).toInt().toShort() + if (bytesRead > 0) { + val (base64Packet, _) = processMicrophoneFrame(buffer) + packetSender?.sendVoicePacket(base64Packet) + } } - // Кодируем чанк Рё добавляем РІ СЃРїРёСЃРѕРє - val (base64Packet, voicePacket) = processMicrophoneFrame(chunkShort) - encodedChunks.add(base64Packet) - - packetSender?.sendVoicePacket(base64Packet) + audioRecord?.stop() + audioRecord?.release() + audioRecord = null } + recordingThread.start() + } - /*val decodedSignal = mutableListOf<Float>() - for (base64Packet in encodedChunks) { - val decodedBase64 = voicePacketProcessor.processVoicePacket(base64Packet) - val decodedFloatArray = voicePacketProcessor.convertPCMToFloatArray(decodedBase64) - decodedSignal.addAll(decodedFloatArray.toList()) + fun stopRecording() { + isRecording = false + try { + if (::recordingThread.isInitialized) { + recordingThread.join(500) // Даем потоку 500ms РЅР° завершение + } + } catch (e: InterruptedException) { + Log.e(TAG, "Error stopping recording thread", e) } - - val finalDecodedSignal = decodedSignal.toFloatArray() - - - Thread.sleep(1000) - playBuffer(finalDecodedSignal, sampleRate)*/ } - - fun processMicrophoneFrame(wave: ShortArray) : Pair<String, JSONArray> { + fun processMicrophoneFrame(wave: ShortArray): Pair<String, JSONArray> { udpPacketBuffer[0] = (2 shl 6).toByte() udpPacketBuffer[1] = 106.toByte() sequenceNumber++ @@ -117,7 +140,7 @@ class AudioProcessor(private val packetSender: VoicePacketSender?, private val s val copySize = encodedWave.size.coerceAtMost(maxPayloadSize) System.arraycopy(encodedWave, 0, udpPacketBuffer, 12, copySize) - val base64Str = Base64.getEncoder().encodeToString(udpPacketBuffer.copyOf(12 + copySize)) + val base64Str = Base64.getEncoder().encodeToString(udpPacketBuffer.copyOf(12 + copySize)) val voicePacket = JSONArray().apply { put(JSONObject().apply { put("MessageID", "VOICE_PACKET") @@ -158,7 +181,8 @@ class AudioProcessor(private val packetSender: VoicePacketSender?, private val s val byteBuffer = ByteArray(bufferSize) for (i in signal.indices) { - val sample = (signal[i] * Short.MAX_VALUE).toInt().coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()) + val sample = (signal[i] * Short.MAX_VALUE).toInt() + .coerceIn(Short.MIN_VALUE.toInt(), Short.MAX_VALUE.toInt()) byteBuffer[i * 2] = sample.toByte() // Младший байт byteBuffer[i * 2 + 1] = (sample shr 8).toByte() // Старший байт @@ -184,4 +208,51 @@ class AudioProcessor(private val packetSender: VoicePacketSender?, private val s "[$firstThree, ..., $lastThree]" } } + + fun recordAudio(duration: Double, sampleRate: Int): FloatArray { + val channelConfig = AudioFormat.CHANNEL_IN_MONO + val audioFormat = AudioFormat.ENCODING_PCM_16BIT + val minBufferSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) + val bufferSize = maxOf(minBufferSize, (sampleRate * duration * 2).toInt()) + + if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) != + android.content.pm.PackageManager.PERMISSION_GRANTED + ) { + throw SecurityException("Permission RECORD_AUDIO is not granted.") + } + + + val audioRecord = AudioRecord( + MediaRecorder.AudioSource.MIC, + sampleRate, + channelConfig, + audioFormat, + bufferSize + ) + + val buffer = ByteArray(bufferSize) + audioRecord.startRecording() + + var bytesRead = 0 + while (bytesRead < bufferSize) { + val read = audioRecord.read(buffer, bytesRead, bufferSize - bytesRead) + if (read == AudioRecord.ERROR_INVALID_OPERATION || read == AudioRecord.ERROR_BAD_VALUE) break + bytesRead += read + } + + audioRecord.stop() + audioRecord.release() + + // Конвертация ByteArray РІ FloatArray + return ByteBuffer.wrap(buffer) + .order(ByteOrder.LITTLE_ENDIAN) + .asShortBuffer() + .let { shorts -> + FloatArray(shorts.remaining()) { + shorts[it] / 32768f // Нормализация РІ диапазон [-1.0, 1.0] + } + } + + } + } 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 f853d23608990d5f60aae50d267cb6d1b144c6fb..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) - 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 7b1dd0972b5f5393e2a8f9e28a7193890e34115e..cb7ebcd89d1a70abaceed1f1ef68ed334dae232b 100644 --- a/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt +++ b/app/src/main/java/com/example/radio2/SubscriberGroupActivity.kt @@ -166,20 +166,25 @@ class SubscriberGroupActivity : ComponentActivity(), VoicePacketSender { enableEdgeToEdge() val button = findViewById<Button>(R.id.activateVoice) var buttonActionCount = 0 - audioProcessor = AudioProcessor(this) + audioProcessor = AudioProcessor(this, this) button.setOnTouchListener { view, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { buttonActionCount++ - if(buttonActionCount == 1) { - webSocketService?.startPTT(selectedUser!!.ID, "User") + if (buttonActionCount == 1) { + if (selectedUser != null) { + webSocketService?.startPTT(selectedUser!!.ID, "User") + } else if (selectedGroup != null) { + webSocketService?.startPTT(selectedGroup!!.ID, "Group") + } } audioProcessor.startRecording() true } MotionEvent.ACTION_UP -> { buttonActionCount = 0 + audioProcessor.stopRecording() webSocketService?.stopPTT(selectedUser!!.ID, "User") true } @@ -189,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 2c71ea5f37b318888012d658370af69c0b3a743e..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() { @@ -383,12 +377,12 @@ class WebSocketService : Service() { put("MessageID", "STORAGE_JOB_REQUEST") put("JobID", generateJobId()) put("ConversationID", generateJobId()) - put("Sequence", sequence.toInt()) + put("Sequence", sequence.toIntOrNull()?:0) put("FromDeviceID", id) put("FromUserID", userId) put("ToDeviceID", "Y2w6TGPtWaO5SsKDLMMZqw==") put("ToUserID", "EREREREREREREREREREREQ==") - put("JobType", jobType.toInt()) + put("JobType", jobType.toIntOrNull()?:0) put("JobState", 20) put("FileName", "") put("Title", title) @@ -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