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