feat: Enhance search functionality and user experience

- Added local account metadata handling in SearchScreen for improved "Saved Messages" search fallback.
- Updated search logic to include username and account name checks when searching for the user.
- Introduced search logging in SearchUsersViewModel for better debugging and tracking of search queries.
- Refactored image download process in AttachmentComponents to include detailed logging for debugging.
- Created AttachmentDownloadDebugLogger to manage and display download logs.
- Improved DeviceVerificationBanner UI for better user engagement during device verification.
- Adjusted OtherProfileScreen layout to enhance information visibility and user interaction.
- Updated network security configuration to include new Let's Encrypt certificate for CDN.
This commit is contained in:
2026-02-19 17:34:16 +05:00
parent cacd6dc029
commit 53d0e44ef8
26 changed files with 972 additions and 613 deletions

View File

@@ -35,6 +35,16 @@ class PacketSearch : Packet() {
stream.writeInt16(getPacketId())
stream.writeString(privateKey)
stream.writeString(search)
// Desktop parity: packet always includes users count block.
// For client requests this is 0, but field must be present for server parser.
stream.writeInt16(users.size)
users.forEach { user ->
stream.writeString(user.username)
stream.writeString(user.title)
stream.writeString(user.publicKey)
stream.writeInt8(user.verified)
stream.writeInt8(user.online)
}
return stream
}
}

View File

@@ -4,6 +4,7 @@ import android.content.Context
import android.os.Build
import com.rosetta.messenger.data.AccountManager
import com.rosetta.messenger.data.MessageRepository
import com.rosetta.messenger.data.isPlaceholderAccountName
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -277,7 +278,7 @@ object ProtocolManager {
// Если это наш own profile — сохраняем username/name в AccountManager
if (user.publicKey == ownPublicKey && appContext != null) {
val accountManager = AccountManager(appContext!!)
if (user.title.isNotBlank()) {
if (user.title.isNotBlank() && !isPlaceholderAccountName(user.title)) {
accountManager.updateAccountName(user.publicKey, user.title)
}
if (user.username.isNotBlank()) {
@@ -333,6 +334,24 @@ object ProtocolManager {
TransportManager.requestTransportServer()
fetchOwnProfile()
requestSynchronize()
subscribePushTokenIfAvailable()
}
private fun subscribePushTokenIfAvailable() {
val context = appContext ?: return
val token =
context.getSharedPreferences("rosetta_prefs", Context.MODE_PRIVATE)
.getString("fcm_token", null)
?.trim()
.orEmpty()
if (token.isEmpty()) return
val packet = PacketPushNotification().apply {
notificationsToken = token
action = PushNotificationAction.SUBSCRIBE
}
send(packet)
addLog("🔔 Push token subscribe requested on AUTHENTICATED")
}
private fun requestSynchronize() {

View File

@@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.withContext
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.IOException
import java.util.concurrent.TimeUnit
import kotlin.coroutines.resume
@@ -29,12 +28,8 @@ data class TransportState(
* Совместимо с desktop версией (TransportProvider)
*/
object TransportManager {
private const val TAG = "TransportManager"
private const val MAX_RETRIES = 3
private const val INITIAL_BACKOFF_MS = 1000L
// Fallback transport server (CDN)
private const val FALLBACK_TRANSPORT_SERVER = "https://cdn.rosetta-im.com"
private var transportServer: String? = null
@@ -54,20 +49,32 @@ object TransportManager {
* Установить адрес транспортного сервера
*/
fun setTransportServer(server: String) {
transportServer = server
val normalized = server.trim().trimEnd('/')
transportServer = normalized.ifBlank { null }
}
/**
* Получить адрес транспортного сервера (с fallback)
* Получить текущий адрес транспортного сервера
*/
fun getTransportServer(): String = transportServer ?: FALLBACK_TRANSPORT_SERVER
fun getTransportServer(): String? = transportServer
/**
* Получить активный сервер для скачивания/загрузки
* Получить активный сервер для скачивания/загрузки.
* Desktop parity: ждём сервер из PacketRequestTransport (0x0F), а не используем hardcoded CDN.
*/
private fun getActiveServer(): String {
val server = transportServer ?: FALLBACK_TRANSPORT_SERVER
return server
private suspend fun getActiveServer(): String {
transportServer?.let { return it }
requestTransportServer()
repeat(40) { // 10s total
val server = transportServer
if (!server.isNullOrBlank()) {
return server
}
delay(250)
}
throw IOException("Transport server is not set")
}
/**