Add distributed user profiles and username directory
This commit is contained in:
@@ -25,12 +25,12 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import pro.nnnteam.nnnet.data.ChatSummary
|
||||
import pro.nnnteam.nnnet.data.MeshDatabase
|
||||
import pro.nnnteam.nnnet.data.MeshRepository
|
||||
import pro.nnnteam.nnnet.mesh.MeshForegroundService
|
||||
import pro.nnnteam.nnnet.mesh.MeshServiceContract
|
||||
import pro.nnnteam.nnnet.ui.ChatListAdapter
|
||||
import pro.nnnteam.nnnet.ui.ChatListItem
|
||||
import pro.nnnteam.nnnet.update.UpdateInfo
|
||||
import pro.nnnteam.nnnet.update.UpdateManager
|
||||
import java.util.Locale
|
||||
@@ -44,7 +44,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private lateinit var chatListView: ListView
|
||||
|
||||
private val peers = linkedSetOf<String>()
|
||||
private val chatSummaries = mutableListOf<ChatSummary>()
|
||||
private val chatItems = mutableListOf<ChatListItem>()
|
||||
private lateinit var chatAdapter: ChatListAdapter
|
||||
|
||||
private var receiverRegistered = false
|
||||
@@ -63,7 +63,8 @@ class MainActivity : AppCompatActivity() {
|
||||
when (eventType) {
|
||||
MeshServiceContract.EVENT_STATUS -> updateMeshStatus(value)
|
||||
MeshServiceContract.EVENT_PEER -> addPeer(value)
|
||||
MeshServiceContract.EVENT_MESSAGES_CHANGED -> refreshChats()
|
||||
MeshServiceContract.EVENT_MESSAGES_CHANGED,
|
||||
MeshServiceContract.EVENT_PROFILES_CHANGED -> refreshChats()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,8 +72,7 @@ class MainActivity : AppCompatActivity() {
|
||||
private val permissionLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { result ->
|
||||
val allGranted = result.values.all { it }
|
||||
if (allGranted) {
|
||||
if (result.values.all { it }) {
|
||||
ensureBluetoothEnabledAndContinue()
|
||||
} else {
|
||||
Toast.makeText(this, R.string.permissions_denied, Toast.LENGTH_SHORT).show()
|
||||
@@ -94,7 +94,7 @@ class MainActivity : AppCompatActivity() {
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
val database = MeshDatabase.getInstance(applicationContext)
|
||||
repository = MeshRepository(database.messageDao(), database.outboundQueueDao())
|
||||
repository = MeshRepository(database.messageDao(), database.outboundQueueDao(), database.profileDao())
|
||||
|
||||
deviceCountText = findViewById(R.id.deviceCountText)
|
||||
statusBadge = findViewById(R.id.statusBadge)
|
||||
@@ -102,10 +102,10 @@ class MainActivity : AppCompatActivity() {
|
||||
emptyStateText = findViewById(R.id.emptyStateText)
|
||||
chatListView = findViewById(R.id.chatListView)
|
||||
|
||||
chatAdapter = ChatListAdapter(this, chatSummaries)
|
||||
chatAdapter = ChatListAdapter(this, chatItems)
|
||||
chatListView.adapter = chatAdapter
|
||||
chatListView.setOnItemClickListener { _, _, position, _ ->
|
||||
openChat(chatSummaries[position].peerId)
|
||||
openChat(chatItems[position].peerId)
|
||||
}
|
||||
|
||||
statusBadge.setOnClickListener { toggleMesh() }
|
||||
@@ -164,7 +164,7 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun showNewChatDialog() {
|
||||
val input = EditText(this).apply {
|
||||
hint = getString(R.string.hint_peer_id)
|
||||
hint = getString(R.string.hint_chat_target)
|
||||
setSingleLine()
|
||||
setPadding(48, 32, 48, 32)
|
||||
}
|
||||
@@ -172,18 +172,44 @@ class MainActivity : AppCompatActivity() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.new_chat_title)
|
||||
.setView(input)
|
||||
.setPositiveButton(R.string.open_chat) { _, _ ->
|
||||
val peerId = input.text.toString().trim()
|
||||
if (peerId.isNotEmpty()) {
|
||||
openChat(peerId)
|
||||
} else {
|
||||
Toast.makeText(this, R.string.peer_id_required, Toast.LENGTH_SHORT).show()
|
||||
.setPositiveButton(R.string.open_chat, null)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create()
|
||||
.also { dialog ->
|
||||
dialog.setOnShowListener {
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||
val value = input.text.toString().trim()
|
||||
if (value.isEmpty()) {
|
||||
Toast.makeText(this, R.string.peer_id_required, Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
val resolvedPeerId = resolvePeerId(value)
|
||||
if (resolvedPeerId != null) {
|
||||
dialog.dismiss()
|
||||
openChat(resolvedPeerId)
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this@MainActivity,
|
||||
R.string.profile_not_found_locally,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private suspend fun resolvePeerId(value: String): String? {
|
||||
val normalized = value.trim().removePrefix("@").lowercase(Locale.getDefault())
|
||||
return when {
|
||||
':' in value -> value.trim()
|
||||
else -> repository.profileByUsername(normalized)?.peerId?.takeIf { it.isNotBlank() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun openChat(peerId: String) {
|
||||
startActivity(Intent(this, ChatActivity::class.java).putExtra(ChatActivity.EXTRA_PEER_ID, peerId))
|
||||
}
|
||||
@@ -238,10 +264,22 @@ class MainActivity : AppCompatActivity() {
|
||||
private fun refreshChats() {
|
||||
lifecycleScope.launch {
|
||||
val chats = repository.chatSummaries()
|
||||
chatSummaries.clear()
|
||||
chatSummaries.addAll(chats)
|
||||
val mappedItems = chats.map { chat ->
|
||||
val profile = repository.profileByPeerId(chat.peerId)
|
||||
val title = profile?.displayName() ?: chat.peerId
|
||||
val subtitlePrefix = profile?.let { "@${it.username} · " }.orEmpty()
|
||||
ChatListItem(
|
||||
peerId = chat.peerId,
|
||||
title = title,
|
||||
subtitle = subtitlePrefix + chat.lastBody,
|
||||
lastStatus = chat.lastStatus,
|
||||
lastTimestamp = chat.lastTimestamp
|
||||
)
|
||||
}
|
||||
chatItems.clear()
|
||||
chatItems.addAll(mappedItems)
|
||||
chatAdapter.notifyDataSetChanged()
|
||||
emptyStateText.visibility = if (chats.isEmpty()) View.VISIBLE else View.GONE
|
||||
emptyStateText.visibility = if (mappedItems.isEmpty()) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,14 +291,14 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
private fun updateMeshStatus(status: String) {
|
||||
val normalized = status.lowercase(Locale.getDefault())
|
||||
if (normalized.contains("останов")) {
|
||||
if (normalized.contains("останов") || normalized.contains("оффлайн")) {
|
||||
meshEnabled = false
|
||||
peers.clear()
|
||||
} else if (
|
||||
normalized.contains("актив") ||
|
||||
normalized.contains("запуска") ||
|
||||
normalized.contains("в сети") ||
|
||||
normalized.contains("присутствие") ||
|
||||
normalized.contains("устройство") ||
|
||||
normalized.contains("сообщение")
|
||||
) {
|
||||
meshEnabled = true
|
||||
|
||||
Reference in New Issue
Block a user