Сборка перед кэшированием
This commit is contained in:
+10
@@ -87,3 +87,13 @@ postgres_data/
|
|||||||
tmp/
|
tmp/
|
||||||
|
|
||||||
*.txt
|
*.txt
|
||||||
|
|
||||||
|
# Android / Kotlin Mobile
|
||||||
|
client-mobile/.gradle/
|
||||||
|
client-mobile/.idea/
|
||||||
|
client-mobile/.run/
|
||||||
|
client-mobile/build/
|
||||||
|
client-mobile/.cxx/
|
||||||
|
client-mobile/local.properties
|
||||||
|
client-mobile/*.iml
|
||||||
|
client-mobile/.kotlin/
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
#Tue Apr 14 00:27:15 MSK 2026
|
|
||||||
gradle.version=8.5
|
|
||||||
Binary file not shown.
@@ -1,2 +0,0 @@
|
|||||||
#Tue Apr 14 00:13:58 MSK 2026
|
|
||||||
java.home=C\:\\Program Files\\Android\\Android Studio\\jbr
|
|
||||||
@@ -23,7 +23,6 @@ class ChatRepositoryImpl @Inject constructor(
|
|||||||
private val api: ChatApi,
|
private val api: ChatApi,
|
||||||
private val tokenManager: TokenManager,
|
private val tokenManager: TokenManager,
|
||||||
private val serverConfig: ServerConfig,
|
private val serverConfig: ServerConfig,
|
||||||
private val messageDao: core.database.data.MessageDao,
|
|
||||||
private val hubClient: chats.data.remote.signalr.ChatHubClient
|
private val hubClient: chats.data.remote.signalr.ChatHubClient
|
||||||
) : ChatRepository {
|
) : ChatRepository {
|
||||||
private val gson = com.google.gson.Gson()
|
private val gson = com.google.gson.Gson()
|
||||||
@@ -35,10 +34,8 @@ class ChatRepositoryImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getMessagesFlow(chatId: String): kotlinx.coroutines.flow.Flow<List<Message>> {
|
override fun getMessagesFlow(chatId: String): kotlinx.coroutines.flow.Flow<List<Message>> {
|
||||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
// Кэш отключён - всегда возвращаем пустой поток
|
||||||
return messageDao.getMessages(chatId).map { entities: List<core.database.data.MessageEntity> ->
|
return kotlinx.coroutines.flow.flowOf(emptyList())
|
||||||
entities.map { it.toDomain(baseUrl, gson) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getMessages(chatId: String, cursor: String?, pivot: Long?, limit: Int?): List<Message> {
|
override suspend fun getMessages(chatId: String, cursor: String?, pivot: Long?, limit: Int?): List<Message> {
|
||||||
@@ -93,21 +90,19 @@ class ChatRepositoryImpl @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun markMessagesAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) {
|
override suspend fun markMessagesAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) {
|
||||||
try {
|
try {
|
||||||
android.util.Log.d("ChatRepoImpl", "markMessagesAsRead CALLED FOR $chatId. Caller stack: ${android.util.Log.getStackTraceString(Throwable())}")
|
android.util.Log.d("ChatRepoImpl", "markMessagesAsRead CALLED FOR $chatId")
|
||||||
hubClient.readMessages(ReadMessagesRequest(chatId, lastMessageId, lastReadSequenceId))
|
hubClient.readMessages(ReadMessagesRequest(chatId, lastMessageId, lastReadSequenceId))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("ChatRepo", "Error marking messages as read", e)
|
android.util.Log.e("ChatRepo", "Error marking messages as read", e)
|
||||||
}
|
}
|
||||||
// Обновляем локальную БД
|
|
||||||
messageDao.markMessagesAsRead(chatId, lastReadSequenceId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveMessage(message: Message) {
|
override suspend fun saveMessage(message: Message) {
|
||||||
android.util.Log.d("ChatRepo", "DB cache disabled, skipping save: ${message.id}")
|
// Кэш отключён
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun deleteLocalMessage(messageId: String) {
|
override suspend fun deleteLocalMessage(messageId: String) {
|
||||||
messageDao.deleteMessage(messageId)
|
// Локальное удаление не поддерживается без кэша
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun uploadMedia(file: java.io.File): String {
|
override suspend fun uploadMedia(file: java.io.File): String {
|
||||||
@@ -145,7 +140,6 @@ class ChatRepositoryImpl @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun deleteMessage(messageId: String, forEveryone: Boolean) {
|
override suspend fun deleteMessage(messageId: String, forEveryone: Boolean) {
|
||||||
api.deleteMessage(messageId, forEveryone)
|
api.deleteMessage(messageId, forEveryone)
|
||||||
messageDao.deleteMessage(messageId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun editMessage(messageId: String, content: String): Message {
|
override suspend fun editMessage(messageId: String, content: String): Message {
|
||||||
|
|||||||
@@ -23,24 +23,6 @@ fun ChatDto.toDomain(currentUserId: String, baseUrl: String): Chat {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Message.toEntity(gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
|
||||||
return core.database.data.MessageEntity(
|
|
||||||
id = id,
|
|
||||||
chatId = chatId,
|
|
||||||
senderId = senderId,
|
|
||||||
senderName = senderName,
|
|
||||||
senderAvatar = senderAvatar,
|
|
||||||
content = content,
|
|
||||||
sequenceId = sequenceId,
|
|
||||||
createdAt = createdAt,
|
|
||||||
mediaType = mediaType.name.lowercase(),
|
|
||||||
mediaJson = gson.toJson(media),
|
|
||||||
reactionsJson = gson.toJson(reactions),
|
|
||||||
isRead = isRead,
|
|
||||||
replyToId = replyTo?.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun MessageDto.toDomain(currentUserId: String, baseUrl: String): Message {
|
fun MessageDto.toDomain(currentUserId: String, baseUrl: String): Message {
|
||||||
val domainMediaType = when (type) {
|
val domainMediaType = when (type) {
|
||||||
"gif" -> MediaType.GIF
|
"gif" -> MediaType.GIF
|
||||||
@@ -86,79 +68,6 @@ fun MessageDto.toDomain(currentUserId: String, baseUrl: String): Message {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun MessageDto.toEntity(baseUrl: String, currentUserId: String, gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
|
||||||
return core.database.data.MessageEntity(
|
|
||||||
id = id,
|
|
||||||
chatId = chatId ?: "",
|
|
||||||
senderId = senderId ?: "",
|
|
||||||
senderName = sender?.displayName ?: "Unknown",
|
|
||||||
senderAvatar = sender?.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
|
||||||
content = content,
|
|
||||||
sequenceId = sequenceId ?: 0,
|
|
||||||
createdAt = createdAt ?: "",
|
|
||||||
mediaType = type ?: "text",
|
|
||||||
mediaJson = gson.toJson(media),
|
|
||||||
reactionsJson = gson.toJson(reactions),
|
|
||||||
isRead = senderId == currentUserId,
|
|
||||||
replyToId = replyTo?.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun core.database.data.MessageEntity.toDomain(baseUrl: String, gson: com.google.gson.Gson): Message {
|
|
||||||
val mediaTypeEnum = when (mediaType) {
|
|
||||||
"gif" -> MediaType.GIF
|
|
||||||
"image", "photo" -> MediaType.IMAGE
|
|
||||||
"video" -> MediaType.VIDEO
|
|
||||||
"audio", "voice" -> MediaType.AUDIO
|
|
||||||
"file" -> MediaType.FILE
|
|
||||||
else -> MediaType.TEXT
|
|
||||||
}
|
|
||||||
|
|
||||||
val mediaTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.MediaItemDto>>() {}.type
|
|
||||||
val mediaDtos: List<chats.data.remote.dto.MediaItemDto> = try {
|
|
||||||
gson.fromJson(mediaJson, mediaTypeToken)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
emptyList()
|
|
||||||
} ?: emptyList()
|
|
||||||
|
|
||||||
val reactionsTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.ReactionDto>>() {}.type
|
|
||||||
val reactionDtos: List<chats.data.remote.dto.ReactionDto> = try {
|
|
||||||
gson.fromJson(reactionsJson, reactionsTypeToken)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
try {
|
|
||||||
val mapType = object : com.google.gson.reflect.TypeToken<Map<String, Int>>() {}.type
|
|
||||||
val map: Map<String, Int> = gson.fromJson(reactionsJson, mapType) ?: emptyMap()
|
|
||||||
map.map { chats.data.remote.dto.ReactionDto(it.key, it.value, false) }
|
|
||||||
} catch (innerE: Exception) {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
} ?: emptyList()
|
|
||||||
|
|
||||||
return Message(
|
|
||||||
id = id,
|
|
||||||
chatId = chatId,
|
|
||||||
senderId = senderId,
|
|
||||||
senderName = senderName,
|
|
||||||
senderAvatar = senderAvatar,
|
|
||||||
content = content,
|
|
||||||
sequenceId = sequenceId,
|
|
||||||
createdAt = createdAt,
|
|
||||||
media = mediaDtos.map {
|
|
||||||
chats.domain.model.Media(
|
|
||||||
id = it.id ?: java.util.UUID.randomUUID().toString(),
|
|
||||||
type = it.type ?: "unknown",
|
|
||||||
url = (it.url ?: "").ensureAbsoluteUrl(baseUrl),
|
|
||||||
filename = it.filename,
|
|
||||||
size = it.size,
|
|
||||||
duration = it.duration
|
|
||||||
)
|
|
||||||
},
|
|
||||||
mediaType = mediaTypeEnum,
|
|
||||||
reactions = reactionDtos.associate { it.emoji to it.count },
|
|
||||||
isRead = isRead
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun String.ensureAbsoluteUrl(baseUrl: String): String {
|
fun String.ensureAbsoluteUrl(baseUrl: String): String {
|
||||||
return if (this.startsWith("http")) {
|
return if (this.startsWith("http")) {
|
||||||
this
|
this
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import chats.data.remote.api.ChatApi
|
|||||||
import chats.data.remote.signalr.ChatHubClient
|
import chats.data.remote.signalr.ChatHubClient
|
||||||
import chats.data.repository.ChatRepositoryImpl
|
import chats.data.repository.ChatRepositoryImpl
|
||||||
import chats.domain.repository.ChatRepository
|
import chats.domain.repository.ChatRepository
|
||||||
import core.database.data.MessageDao
|
|
||||||
import core.network.ServerConfig
|
import core.network.ServerConfig
|
||||||
import core.security.TokenManager
|
import core.security.TokenManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
@@ -30,10 +29,9 @@ object ChatModule {
|
|||||||
api: ChatApi,
|
api: ChatApi,
|
||||||
tokenManager: TokenManager,
|
tokenManager: TokenManager,
|
||||||
serverConfig: ServerConfig,
|
serverConfig: ServerConfig,
|
||||||
messageDao: MessageDao,
|
|
||||||
hubClient: chats.data.remote.signalr.ChatHubClient
|
hubClient: chats.data.remote.signalr.ChatHubClient
|
||||||
): ChatRepository {
|
): ChatRepository {
|
||||||
return ChatRepositoryImpl(api, tokenManager, serverConfig, messageDao, hubClient)
|
return ChatRepositoryImpl(api, tokenManager, serverConfig, hubClient)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
package core.database.data
|
|
||||||
|
|
||||||
import androidx.room.*
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
|
|
||||||
@Entity(tableName = "messages")
|
|
||||||
data class MessageEntity(
|
|
||||||
@PrimaryKey val id: String,
|
|
||||||
val chatId: String,
|
|
||||||
val senderId: String,
|
|
||||||
val senderName: String,
|
|
||||||
val senderAvatar: String?,
|
|
||||||
val content: String?,
|
|
||||||
val sequenceId: Int,
|
|
||||||
val createdAt: String,
|
|
||||||
val mediaType: String,
|
|
||||||
val mediaJson: String, // Simplified for now
|
|
||||||
val reactionsJson: String,
|
|
||||||
val isRead: Boolean,
|
|
||||||
val replyToId: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
@Dao
|
|
||||||
interface MessageDao {
|
|
||||||
@Query("SELECT * FROM messages WHERE chatId = :chatId ORDER BY sequenceId ASC")
|
|
||||||
fun getMessages(chatId: String): Flow<List<MessageEntity>>
|
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
||||||
suspend fun insertMessages(messages: List<MessageEntity>)
|
|
||||||
|
|
||||||
@Query("DELETE FROM messages WHERE chatId = :chatId")
|
|
||||||
suspend fun clearChat(chatId: String)
|
|
||||||
|
|
||||||
@Query("DELETE FROM messages WHERE id = :messageId")
|
|
||||||
suspend fun deleteMessage(messageId: String)
|
|
||||||
|
|
||||||
@Query("UPDATE messages SET isRead = 1 WHERE chatId = :chatId AND sequenceId <= :lastReadSequenceId AND isRead = 0")
|
|
||||||
suspend fun markMessagesAsRead(chatId: String, lastReadSequenceId: Int)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Database(entities = [MessageEntity::class], version = 1)
|
|
||||||
abstract class ChatDatabase : RoomDatabase() {
|
|
||||||
abstract fun messageDao(): MessageDao
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package core.di
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.room.Room
|
|
||||||
import core.database.data.ChatDatabase
|
|
||||||
import core.database.data.MessageDao
|
|
||||||
import dagger.Module
|
|
||||||
import dagger.Provides
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Module
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
object DatabaseModule {
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideDatabase(@ApplicationContext context: Context): ChatDatabase {
|
|
||||||
return Room.databaseBuilder(
|
|
||||||
context,
|
|
||||||
ChatDatabase::class.java,
|
|
||||||
"chat_database"
|
|
||||||
).fallbackToDestructiveMigration().build()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
fun provideMessageDao(database: ChatDatabase): MessageDao {
|
|
||||||
return database.messageDao()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user