From 1cfdb42e040dabfc0a3d037d59a63730aab25eec Mon Sep 17 00:00:00 2001 From: dom4k Date: Mon, 16 Mar 2026 20:29:49 +0000 Subject: [PATCH] Refine NNNet UI and rename Android package --- README.md | 14 +- android/app/build.gradle.kts | 4 +- android/app/src/main/AndroidManifest.xml | 9 +- .../com/schoolmesh/messenger/MainActivity.kt | 487 ------------------ .../java/pro/nnnteam/nnnet/ChatActivity.kt | 218 ++++++++ .../java/pro/nnnteam/nnnet/MainActivity.kt | 356 +++++++++++++ .../pro/nnnteam/nnnet/SettingsActivity.kt | 98 ++++ .../nnnteam/nnnet}/data/ChatSummary.kt | 2 +- .../nnnteam/nnnet}/data/MeshDatabase.kt | 2 +- .../nnnteam/nnnet}/data/MeshRepository.kt | 6 +- .../nnnteam/nnnet}/data/MessageDao.kt | 2 +- .../nnnteam/nnnet}/data/MessageEntity.kt | 2 +- .../nnnteam/nnnet}/data/OutboundQueueDao.kt | 2 +- .../nnnet}/data/OutboundQueueEntity.kt | 2 +- .../nnnteam/nnnet}/mesh/BleMeshManager.kt | 36 +- .../nnnet}/mesh/MeshForegroundService.kt | 24 +- .../nnnteam/nnnet}/mesh/MeshPacket.kt | 2 +- .../nnnteam/nnnet}/mesh/MeshPacketCodec.kt | 2 +- .../nnnteam/nnnet}/mesh/MeshQueueProcessor.kt | 4 +- .../nnnet}/mesh/MeshServiceContract.kt | 10 +- .../nnnteam/nnnet}/mesh/PacketType.kt | 2 +- .../nnnteam/nnnet}/mesh/SeenPacketCache.kt | 2 +- .../pro/nnnteam/nnnet/ui/ChatListAdapter.kt | 50 ++ .../nnnteam/nnnet/ui/MessageListAdapter.kt | 71 +++ .../pro/nnnteam/nnnet/update/UpdateManager.kt | 53 ++ .../src/main/res/drawable/bg_chat_avatar.xml | 4 + .../main/res/drawable/bg_message_incoming.xml | 5 + .../main/res/drawable/bg_message_input.xml | 5 + .../main/res/drawable/bg_message_outgoing.xml | 5 + .../src/main/res/drawable/bg_send_button.xml | 4 + .../main/res/drawable/bg_settings_card.xml | 5 + .../main/res/drawable/bg_status_offline.xml | 5 + .../main/res/drawable/bg_status_online.xml | 5 + .../app/src/main/res/layout/activity_chat.xml | 120 +++++ .../app/src/main/res/layout/activity_main.xml | 320 ++++-------- .../src/main/res/layout/activity_settings.xml | 72 +++ .../src/main/res/layout/item_chat_summary.xml | 70 +++ .../app/src/main/res/layout/item_message.xml | 38 ++ android/app/src/main/res/menu/main_menu.xml | 6 + android/app/src/main/res/values/colors.xml | 14 +- android/app/src/main/res/values/strings.xml | 34 +- android/app/src/main/res/values/themes.xml | 11 +- docs/ARCHITECTURE.md | 14 +- website/assets/js/app.js | 2 +- website/index.html | 8 +- 45 files changed, 1430 insertions(+), 777 deletions(-) delete mode 100644 android/app/src/main/java/com/schoolmesh/messenger/MainActivity.kt create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/ChatActivity.kt create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/MainActivity.kt create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/SettingsActivity.kt rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/ChatSummary.kt (80%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/MeshDatabase.kt (95%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/MeshRepository.kt (96%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/MessageDao.kt (97%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/MessageEntity.kt (91%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/OutboundQueueDao.kt (96%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/data/OutboundQueueEntity.kt (90%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/BleMeshManager.kt (91%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/MeshForegroundService.kt (87%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/MeshPacket.kt (92%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/MeshPacketCodec.kt (96%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/MeshQueueProcessor.kt (96%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/MeshServiceContract.kt (54%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/PacketType.kt (62%) rename android/app/src/main/java/{com/schoolmesh/messenger => pro/nnnteam/nnnet}/mesh/SeenPacketCache.kt (92%) create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/ui/ChatListAdapter.kt create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/ui/MessageListAdapter.kt create mode 100644 android/app/src/main/java/pro/nnnteam/nnnet/update/UpdateManager.kt create mode 100644 android/app/src/main/res/drawable/bg_chat_avatar.xml create mode 100644 android/app/src/main/res/drawable/bg_message_incoming.xml create mode 100644 android/app/src/main/res/drawable/bg_message_input.xml create mode 100644 android/app/src/main/res/drawable/bg_message_outgoing.xml create mode 100644 android/app/src/main/res/drawable/bg_send_button.xml create mode 100644 android/app/src/main/res/drawable/bg_settings_card.xml create mode 100644 android/app/src/main/res/drawable/bg_status_offline.xml create mode 100644 android/app/src/main/res/drawable/bg_status_online.xml create mode 100644 android/app/src/main/res/layout/activity_chat.xml create mode 100644 android/app/src/main/res/layout/activity_settings.xml create mode 100644 android/app/src/main/res/layout/item_chat_summary.xml create mode 100644 android/app/src/main/res/layout/item_message.xml create mode 100644 android/app/src/main/res/menu/main_menu.xml diff --git a/README.md b/README.md index 676ffe0..48aec05 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ ## Текущее состояние - BLE discovery + advertising работают. - Реализован минимальный GATT transport для обмена mesh-пакетами. -- Есть foreground service, Room-хранилище, ACK/retry очередь и базовый Telegram-подобный UI. -- Реализованы список чатов, окно диалога, вкладка настроек, ручная проверка обновлений и опциональная автопроверка через `version.json`. +- Есть foreground service, Room-хранилище, ACK/retry очередь и UI в стиле Telegram. +- Реализованы главный экран со списком чатов, отдельный экран диалога, меню `три точки -> Настройки`, ручная проверка обновлений и опциональная автопроверка через `version.json`. - При выключенном Bluetooth приложение запрашивает его включение перед запуском mesh. - Публикация APK и сайта автоматизирована через `Makefile`. +- Проект и Android-приложение приведены к имени `NNNet`, пакет приложения: `pro.nnnteam.nnnet`. ## Стек - Android приложение: **Kotlin** @@ -42,7 +43,7 @@ 3. **Messaging Layer** - личные сообщения; - - список чатов и окно диалога; + - список чатов и отдельный экран диалога; - статусы доставки (queued/sent/relayed/delivered). 4. **Data Layer** @@ -78,6 +79,7 @@ - [x] Добавить защиту от дубликатов по `messageId` (in-memory cache, базово). - [x] Реализовать mesh-forwarding с ограничением TTL (routing action layer, базово). - [x] Добавить список чатов и базовый UI окна сообщений. +- [x] Перенести настройки в меню `три точки` и убрать debug-лог из пользовательского интерфейса. - [x] Подключить Room и базовую схему хранения. - [x] Добавить логирование сети и debug-экран маршрутов. - [x] Добавить ручную проверку обновлений и опциональную автопроверку клиента. @@ -107,3 +109,9 @@ ## Ближайший следующий шаг Добавить профили пользователей, шифрование payload и инструментальные тесты BLE-обмена между несколькими устройствами. + +## Ограничения сети +- Выделенный хост для NNNet не нужен: сеть строится как P2P mesh между устройствами. +- Все узлы равноправны на уровне текущей архитектуры: каждое устройство может обнаруживать соседей, принимать и ретранслировать пакеты. +- Количество пользователей не бесконечно. Практический предел зависит от плотности устройств, качества BLE-эфира, числа одновременных соединений, частоты ретрансляции и ограничений батареи Android. +- Для школы такая схема подходит как офлайн-сеть без интернета, но для больших нагрузок всё равно понадобятся дополнительные оптимизации маршрутизации, дедупликации и доставки. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index fe5cce9..12540f8 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -5,11 +5,11 @@ plugins { } android { - namespace = "com.schoolmesh.messenger" + namespace = "pro.nnnteam.nnnet" compileSdk = 34 defaultConfig { - applicationId = "com.schoolmesh.messenger" + applicationId = "pro.nnnteam.nnnet" minSdk = 26 targetSdk = 34 versionCode = 3 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 12cafb2..3bdb227 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -24,7 +24,14 @@ android:label="@string/app_name" android:roundIcon="@android:drawable/sym_def_app_icon" android:supportsRtl="true" - android:theme="@style/Theme.SchoolMeshMessenger"> + android:theme="@style/Theme.NNNet"> + + diff --git a/android/app/src/main/java/com/schoolmesh/messenger/MainActivity.kt b/android/app/src/main/java/com/schoolmesh/messenger/MainActivity.kt deleted file mode 100644 index 4b235d0..0000000 --- a/android/app/src/main/java/com/schoolmesh/messenger/MainActivity.kt +++ /dev/null @@ -1,487 +0,0 @@ -package com.schoolmesh.messenger - -import android.Manifest -import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothManager -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.widget.ArrayAdapter -import android.widget.Button -import android.widget.EditText -import android.widget.ListView -import android.widget.TextView -import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope -import com.google.android.material.switchmaterial.SwitchMaterial -import com.schoolmesh.messenger.data.ChatSummary -import com.schoolmesh.messenger.data.MeshDatabase -import com.schoolmesh.messenger.data.MeshRepository -import com.schoolmesh.messenger.mesh.MeshForegroundService -import com.schoolmesh.messenger.mesh.MeshServiceContract -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.json.JSONObject -import java.net.HttpURLConnection -import java.net.URL -import java.text.SimpleDateFormat -import java.util.ArrayDeque -import java.util.Date -import java.util.Locale - -class MainActivity : AppCompatActivity() { - private lateinit var repository: MeshRepository - private lateinit var statusText: TextView - private lateinit var peersText: TextView - private lateinit var logsText: TextView - private lateinit var activeChatTitle: TextView - private lateinit var targetInput: EditText - private lateinit var messageInput: EditText - private lateinit var chatListView: ListView - private lateinit var messageListView: ListView - private lateinit var chatsScreen: android.view.View - private lateinit var settingsScreen: android.view.View - private lateinit var autoUpdateSwitch: SwitchMaterial - - private val peers = linkedSetOf() - private val logs = ArrayDeque() - private val chatSummaries = mutableListOf() - private val chatItems = mutableListOf() - private val messageItems = mutableListOf() - - private lateinit var chatAdapter: ArrayAdapter - private lateinit var messageAdapter: ArrayAdapter - - private var activePeerId: String? = null - private var pendingSend: PendingSend? = null - private var pendingStartRequested = false - - private val prefs by lazy { - getSharedPreferences("nnnet_settings", Context.MODE_PRIVATE) - } - - private val meshEventReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - if (intent?.action != MeshServiceContract.ACTION_EVENT) return - val eventType = intent.getStringExtra(MeshServiceContract.EXTRA_EVENT_TYPE) ?: return - val value = intent.getStringExtra(MeshServiceContract.EXTRA_EVENT_VALUE) ?: return - - when (eventType) { - MeshServiceContract.EVENT_STATUS -> updateStatus(value) - MeshServiceContract.EVENT_PEER -> addPeer(value) - MeshServiceContract.EVENT_LOG -> appendLog(value) - MeshServiceContract.EVENT_MESSAGES_CHANGED -> { - refreshChats() - refreshMessages() - } - } - } - } - - private val permissionLauncher = registerForActivityResult( - ActivityResultContracts.RequestMultiplePermissions() - ) { result -> - val allGranted = result.values.all { it } - if (allGranted) { - ensureBluetoothEnabledAndContinue() - } else { - updateStatus("Нет BLE-разрешений") - appendLog("Permissions denied by user") - Toast.makeText(this, "Permissions denied", Toast.LENGTH_SHORT).show() - } - } - - private val enableBluetoothLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult() - ) { - if (bluetoothAdapter()?.isEnabled == true) { - continueAfterBluetoothReady() - } else { - updateStatus("Bluetooth is disabled") - appendLog("Bluetooth enable request denied") - Toast.makeText(this, "Bluetooth is required", Toast.LENGTH_SHORT).show() - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - - val database = MeshDatabase.getInstance(applicationContext) - repository = MeshRepository(database.messageDao(), database.outboundQueueDao()) - - statusText = findViewById(R.id.statusText) - peersText = findViewById(R.id.peersText) - logsText = findViewById(R.id.logsText) - activeChatTitle = findViewById(R.id.activeChatTitle) - targetInput = findViewById(R.id.targetInput) - messageInput = findViewById(R.id.messageInput) - chatListView = findViewById(R.id.chatListView) - messageListView = findViewById(R.id.messageListView) - chatsScreen = findViewById(R.id.chatsScreen) - settingsScreen = findViewById(R.id.settingsScreen) - autoUpdateSwitch = findViewById(R.id.autoUpdateSwitch) - - chatAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, chatItems) - messageAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, messageItems) - chatListView.adapter = chatAdapter - messageListView.adapter = messageAdapter - - findViewById