From 267028e2392272752a7ace6bd033889fbbfc969a Mon Sep 17 00:00:00 2001 From: Ilya Prokofev <87380526+ilyaprofev@users.noreply.github.com> Date: Sun, 2 Feb 2025 22:40:28 +0300 Subject: [PATCH 1/5] added key to friends --- .../meetmapkmp/SocialMap/DataModel/Friend.kt | 2 +- .../SocialMap/ViewModel/Friend_ViewItem.kt | 9 ++++++- .../ilya/platform/DriverFactory.android.kt | 26 +++++++++---------- .../com/ilya/platform/di/FriendsRepository.kt | 9 +++++++ 4 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt index 95eaa37..4860d51 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt @@ -6,5 +6,5 @@ data class Friend( val lastmessage: String, val name: String, val online: Boolean, - + val key: String ) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt index 2ff611a..15c3cba 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt @@ -1,6 +1,8 @@ package com.ilya.meetmapkmp.SocialMap.ViewModel +import android.content.Context +import android.database.sqlite.SQLiteDatabase import android.util.Log import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel @@ -8,14 +10,19 @@ import androidx.lifecycle.viewModelScope import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflect.TypeToken import com.google.gson.Gson import com.ilya.meetmapkmp.SocialMap.DataModel.Friend +import com.ilya.platform.DriverFactory +import com.ilya.platform.di.ChatQueriesImpl import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -class FriendsViewModel : ViewModel() { +class FriendsViewModel() : ViewModel() { val friendsList = mutableStateListOf() // Используем для хранения друзей + // private val driverFactory = DriverFactory(context) + // private val database = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) + // private val chatQueries = ChatQueriesImpl(database) // Функция для обновления данных друзей diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt b/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt index 8d5cb87..0e134e3 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt @@ -52,26 +52,24 @@ actual class DriverFactory(private val context: Context) { fun createFriendsTable(tableName: String): Boolean { val db = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) val createTableQuery = """ - CREATE TABLE IF NOT EXISTS $tableName ( - message_id TEXT PRIMARY KEY, - content TEXT, - profilerIMG TEXT, - messageTime INTEGER, - key TEXT, - senderUsername TEXT, - gifUrls TEXT, - imageUrls TEXT, - videoUrls TEXT, - fileUrls TEXT - ) - """.trimIndent() + CREATE TABLE IF NOT EXISTS $tableName ( + token TEXT PRIMARY KEY, + img TEXT, + lastmessage TEXT, + name TEXT, + online INTEGER + ) + """.trimIndent() return try { db.execSQL(createTableQuery) true } catch (e: Exception) { - Log.e("DriverFactory", "Error creating table: $tableName", e) + Log.e("Database", "Error creating friends table", e) false + } finally { + db.close() // Всегда закрывайте БД после использования } } + } diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt new file mode 100644 index 0000000..9c5ca23 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt @@ -0,0 +1,9 @@ +package com.ilya.platform.di + +import android.database.sqlite.SQLiteDatabase + +class FriendsRepository(private val database: SQLiteDatabase) +{ + + +} \ No newline at end of file From 1076b7bfc3fb2d3a156a38395a85fe279fef14fd Mon Sep 17 00:00:00 2001 From: Ilya Prokofev <87380526+ilyaprofev@users.noreply.github.com> Date: Thu, 6 Feb 2025 22:31:46 +0300 Subject: [PATCH 2/5] =?UTF-8?q?=20=D1=8F=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BB=20=D0=BE=D1=87=D0=B5=D0=BD=D1=8C=20=D0=BC=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../REST => Interface}/Post_invite.kt | 8 +- .../ViewModel/FindFriends_ViewModel.kt | 101 +++++++++++++ .../SocialMap/ViewModel/FriendsViewModel.kt | 25 --- .../SocialMap/ViewModel/WebSocketViewModel.kt | 124 +++++++++++++++ .../SocialMap/ui/UI_Layers/Find_loop_ui.kt~ | 112 ++++++++++++++ .../SocialMap/ui/UI_Layers/FriendsScreen.kt~ | 142 ++++++++++++++++++ .../meetmapkmp/object/AppContextProvider.kt | 22 +++ 7 files changed, 505 insertions(+), 29 deletions(-) rename composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/{DATAServices/REST => Interface}/Post_invite.kt (54%) create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt delete mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FriendsViewModel.kt create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Find_loop_ui.kt~ create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/object/AppContextProvider.kt diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_invite.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt similarity index 54% rename from composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_invite.kt rename to composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt index 477e79d..ff1220c 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_invite.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt @@ -1,13 +1,13 @@ -import okhttp3.ResponseBody +import kotlinx.serialization.json.JsonObject import retrofit2.Response import retrofit2.http.POST import retrofit2.http.Path interface PostInvite { - @POST("/friendrequest/{uid}/{key}/{friend_key}") + @POST("friendrequest/{uid}/{key}/{friend_key}") suspend fun postInvite( @Path("uid") uid: String, @Path("key") key: String, - @Path("friend_key") friend_key: String - ): Response + @Path("friend_key") friendKey: String + ): Response } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt new file mode 100644 index 0000000..72415f6 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt @@ -0,0 +1,101 @@ +package com.ilya.meetmapkmp.SocialMap.ViewModel + +import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends +import Websocket_find_friends +import android.content.Context +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketService +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import org.json.JSONArray +import java.util.concurrent.TimeUnit + +class WebSocketViewModel : ViewModel() { + + // StateFlow для хранения списка друзей + private val _friendsList = MutableStateFlow>(emptyList()) + val friendsList: StateFlow> = _friendsList.asStateFlow() + + + // StateFlow для хранения ошибок + private val _errors = MutableStateFlow("") + val errors: StateFlow = _errors.asStateFlow() + + // Инициализация WebSocketManager + private val webSocketManager = Websocket_find_friends() + + init { + // Установка callback-функций для обработки сообщений и ошибок + webSocketManager.setOnMessageReceivedListener { message -> + viewModelScope.launch { + parseFriendList(message) + } + } + + webSocketManager.setOnErrorOccurredListener { error -> + viewModelScope.launch { + _errors.value = error + } + } + } + + // Метод для подключения к WebSocket + fun connect(uid: String, key: String) { + webSocketManager.connect(uid, key) + } + + // Метод для закрытия WebSocket соединения + fun disconnect() { + webSocketManager.disconnect() + } + + // Метод для отправки команды по WebSocket + fun sendCommand(command: String) { + webSocketManager.sendCommand(command) + } + + // Метод для парсинга полученного списка друзей + private fun parseFriendList(json: String) { + try { + Log.d("WebSocket_friends", "Парсинг списка друзей") + val jsonArray = JSONArray(json) + val friendsList = mutableListOf() + for (i in 0 until jsonArray.length()) { + val jsonObject = jsonArray.getJSONObject(i) + val friend = FindFriends( + key = jsonObject.getString("key"), + name = jsonObject.getString("name"), + img = jsonObject.getString("img"), + friend = jsonObject.getBoolean("friend") + ) + friendsList.add(friend) + } + Log.d("WebSocket_friends", "Получен список друзей: ${friendsList.size} друзей") + _friendsList.value = friendsList + } catch (e: Exception) { + Log.e("WebSocket_friends", "Ошибка парсинга JSON: ${e.message}") + _errors.value = "Ошибка парсинга JSON: ${e.message}" + } + } + + // Метод для получения текущего списка друзей + fun getCurrentFriendsList(): List { + return _friendsList.value + } + + override fun onCleared() { + super.onCleared() + // Закрываем WebSocket при уничтожении ViewModel + disconnect() + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FriendsViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FriendsViewModel.kt deleted file mode 100644 index 9b6377d..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FriendsViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.ilya.meetmapkmp.SocialMap.ViewModel - -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends - - -class FriendsViewModel_data : ViewModel() { - private val _friendsList = mutableStateOf(emptyList()) - val friendsList: State> get() = _friendsList - - var username by mutableStateOf("") - private set // Делаем сеттер приватным, чтобы предотвратить изменение извне - - fun updateUsername(newUsername: String) { - username = newUsername - } - - fun updateFriendsList(friends: List) { - _friendsList.value = friends - } -} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt new file mode 100644 index 0000000..2377133 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt @@ -0,0 +1,124 @@ +package com.ilya.meetmapkmp.SocialMap.ViewModel + +import android.app.Application +import android.content.Context +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketService +import com.ilya.meetmapkmp.SocialMap.DataModel.Friend +import com.ilya.platform.DriverFactory +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.serialization.json.Json + +class WebSocket_getfriendsViewModel(context: Context) : ViewModel() { + + companion object { + private const val TAG = "WebSocketViewModel" // Тег для логов + } + + // StateFlow для хранения состояния WebSocket + private val _webSocketState = MutableStateFlow(WebSocketState.Disconnected) + val webSocketState: StateFlow = _webSocketState.asStateFlow() + private val driverFactory = DriverFactory(context) + + + + + + // StateFlow для хранения списка друзей + private val _friends = MutableStateFlow>(emptyList()) + val friends: StateFlow> = _friends.asStateFlow() + + private val webSocketService = WebSocketService( + onMessageReceived = { jsonString -> + Log.d(TAG, "Message received from server: $jsonString") + // Парсим JSON в список Friend и обновляем StateFlow + parseFriendFromJson(jsonString)?.let { newFriends -> + Log.d(TAG, "Parsed ${newFriends.size} friends from JSON") + _friends.value = newFriends + } + }, + onErrorOccurred = { error -> + Log.e(TAG, "Error occurred: $error") + // Обработка ошибок + _webSocketState.value = WebSocketState.Error(error) + } + ) + + + init{ + driverFactory.createFriendsTable() + } + + + // Подключение к WebSocket + fun connect(uid: String, key: String) { + if (_webSocketState.value is WebSocketState.Connected) { + Log.w(TAG, "Already connected. Ignoring new connection attempt.") + return // Уже подключены + } + Log.d(TAG, "Attempting to connect with uid: $uid, key: $key") + _webSocketState.value = WebSocketState.Connecting + webSocketService.connect(uid, key) + _webSocketState.value = WebSocketState.Connected + Log.d(TAG, "WebSocket connection established.") + } + + // Отключение от WebSocket + fun disconnect() { + Log.d(TAG, "Disconnecting WebSocket...") + webSocketService.disconnect() + _webSocketState.value = WebSocketState.Disconnected + Log.d(TAG, "WebSocket disconnected.") + } + + // Отправка команды через WebSocket + fun sendCommand(command: String) { + if (_webSocketState.value is WebSocketState.Connected) { + Log.d(TAG, "Sending command: $command") + webSocketService.sendCommand(command) + } else { + Log.e(TAG, "WebSocket is not connected. Cannot send command: $command") + _webSocketState.value = WebSocketState.Error("WebSocket is not connected") + } + } + + // Функция для парсинга JSON в объект Friend + private fun parseFriendFromJson(jsonString: String): List? { + return try { + Log.d(TAG, "Parsing JSON string into Friend objects...") + Json.decodeFromString>(jsonString) + } catch (e: Exception) { + Log.e(TAG, "Failed to parse JSON: ${e.message}") + e.printStackTrace() + null + } + } + + // Состояния WebSocket + sealed class WebSocketState { + object Connecting : WebSocketState() + object Connected : WebSocketState() + object Disconnected : WebSocketState() + data class Error(val message: String) : WebSocketState() + } +} + +class ContextViewModelFactory(private val context: Context) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(WebSocket_getfriendsViewModel::class.java)) { + @Suppress("UNCHECKED_CAST") + return WebSocket_getfriendsViewModel(context) as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Find_loop_ui.kt~ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Find_loop_ui.kt~ new file mode 100644 index 0000000..0e57281 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Find_loop_ui.kt~ @@ -0,0 +1,112 @@ +package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers + +import android.widget.Toast +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor + +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.platform.LocalContext +import androidx.navigation.NavController +import com.ilya.meetmapkmp.R + + +@Composable +fun Loop(navController: NavController) { + val context = LocalContext.current + val robotoBold = FontFamily( + Font(R.font.roboto_medium) // Убедитесь, что имя файла правильное и соответствует переименованному файлу + ) + val borderColor = if (isSystemInDarkTheme()) Color.White else Color.Black + val backgroundColor = if (isSystemInDarkTheme()) Color(0xFF262627) else Color(0xFF0088CC) + + Card( + modifier = Modifier + .fillMaxWidth() + .height(100.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + + } + .clip(RoundedCornerShape(0.dp)) + , + shape = RectangleShape, + colors = CardDefaults.cardColors(containerColor = backgroundColor) + ) { + Row(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxHeight() + .padding(start = 40.dp) + .weight(0.8f), + contentAlignment = Alignment.CenterStart // Центровка содержимого внутри Box + ) { + Text( + text = stringResource(id = R.string.app_name), + textAlign = TextAlign.Start, + fontSize = 24.sp, + color = Color.White, + fontFamily = robotoBold + ) + } + + Box( + modifier = Modifier + .fillMaxHeight() + .weight(0.2f), + contentAlignment = Alignment.Center // Центровка содержимого внутри Box + ) { + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search icon", + modifier = Modifier + .size(30.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + navController.navigate("F riendsearch") + }, + tint = Color.White + ) + + + } + } + } +} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ new file mode 100644 index 0000000..acfaf88 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ @@ -0,0 +1,142 @@ +package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers + +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.colorScheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModelProvider +import androidx.navigation.NavController +import coil.compose.AsyncImage +import com.google.api.Context +import com.ilya.meetmapkmp.SocialMap.DataModel.Friend +import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider + + +import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel +import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocket_getfriendsViewModel + + +@Composable +fun FriendsScreen(viewModel: WebSocket_getfriendsViewModel, navController: NavController,context: android.content.Context) { + val backgroundColor = if (isSystemInDarkTheme()) Color.Black else colorScheme.primaryContainer + + + LazyColumn( + modifier = Modifier.fillMaxSize(), + ) { + items(viewModel.friends.value) { friend -> + FriendItem(friend, navController, context) + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .padding(start = 70.dp) + .background(backgroundColor) + ) + } + } +} + +@Composable +fun FriendItem(friend: Friend, navController: NavController, context: android.content.Context) { + + + // Получаем текущую цветовую схему + val colorScheme = MaterialTheme.colorScheme + + // Определяем цвета для текста и фона в зависимости от темы + val backgroundColor = if (isSystemInDarkTheme()) colorScheme.surface else Color.White + val textColor = if (isSystemInDarkTheme()) colorScheme.onSurface else Color.Black + + + + Card( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .clickable( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) { + Log.d("Save_token", "сохраняю токен: ${friend.token}") + val dataProvider = MyDataProvider(context) + dataProvider.saveToken(friend.token) // Store the token + // Переход к чату + navController.navigate("Chat") + }, + colors = CardDefaults.cardColors( + containerColor = backgroundColor + ), + shape = RectangleShape + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + ) { + AsyncImage( + model = friend.img, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(10.dp) + .clip(RoundedCornerShape(30.dp)) + .width(60.dp) + .height(60.dp) + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Column( + verticalArrangement = Arrangement.Top, + modifier = Modifier.fillMaxHeight() + ) { + Text( + text = friend.name, + style = MaterialTheme.typography.bodyLarge, + color = textColor, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Text( + text = friend.lastmessage, + style = MaterialTheme.typography.bodyMedium, + color = textColor.copy(alpha = 0.7f), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + } + } +} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/object/AppContextProvider.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/object/AppContextProvider.kt new file mode 100644 index 0000000..cbdc516 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/object/AppContextProvider.kt @@ -0,0 +1,22 @@ +import android.content.Context + + +object AppContextProvider { + + private lateinit var applicationContext: Context + + // Инициализация контекста приложения + fun initialize(context: Context) { + if (!::applicationContext.isInitialized) { + applicationContext = context.applicationContext + } + } + + // Получение контекста приложения + fun getContext(): Context { + if (!::applicationContext.isInitialized) { + throw IllegalStateException("AppContextProvider not initialized. Call initialize() first.") + } + return applicationContext + } +} \ No newline at end of file From c6067b8dfc8436aab6f9f17f960c726ada58f2f0 Mon Sep 17 00:00:00 2001 From: Ilya Prokofev <87380526+ilyaprofev@users.noreply.github.com> Date: Sat, 8 Feb 2025 00:04:08 +0300 Subject: [PATCH 3/5] =?UTF-8?q?=20=D1=8F=20=D1=81=D0=BC=D0=BE=D0=B3=20?= =?UTF-8?q?=D1=83=D0=B4=D0=BE=D0=BB=D1=8F=D1=82=D1=8C=20=D1=81=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=BC=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BB=D0=B5=D0=B9=20=D0=B8=D0=B7=20db?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ilya/meetmapkmp/Map/Map_Activity.kt | 1 - .../Websocket_find_friends.kt | 97 ++++++------ .../REST/Post_requast_add_friends.kt | 24 ++- .../DATAServices/WebSocketService.kt | 54 +++---- .../meetmapkmp/SocialMap/DataModel/Friend.kt | 5 +- .../Fragment/Chat_with_Friends_fragment.kt | 2 - .../Fragment/Find_friends_fragment.kt | 149 ++++++++++-------- .../SocialMap/Interface/WebSocketCallback.kt | 8 - .../meetmapkmp/SocialMap/SocialMapActivity.kt | 68 +++----- .../ViewModel/FindFriends_ViewModel.kt | 19 +++ .../SocialMap/ViewModel/Friend_ViewItem.kt | 37 ----- .../SocialMap/ViewModel/WebSocketViewModel.kt | 94 ++++++----- .../SocialMap/ui/UI_Layers/FriendsScreen.kt | 86 ++++++---- .../SocialMap/ui/UI_Layers/FriendsScreen.kt~ | 142 ----------------- .../ilya/platform/DriverFactory.android.kt | 5 +- .../com/ilya/platform/di/FriendsRepository.kt | 137 +++++++++++++++- .../res/drawable/photo_error_icon.png | Bin 0 -> 9360 bytes .../androidMain/res/drawable/placeholder.png | Bin 0 -> 14244 bytes 18 files changed, 459 insertions(+), 469 deletions(-) delete mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/WebSocketCallback.kt delete mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt delete mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ create mode 100644 composeApp/src/androidMain/res/drawable/photo_error_icon.png create mode 100644 composeApp/src/androidMain/res/drawable/placeholder.png diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt index e44f6ea..633b5a4 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt @@ -102,7 +102,6 @@ import com.ilya.meetmapkmp.Map.ViewModel.PersonalizedMarkersViewModel import com.ilya.meetmapkmp.Map.ViewModel.PersonalizedMarkersViewModelFactory import com.ilya.meetmapkmp.R import com.ilya.meetmapkmp.SocialMap.SocialMapActivity -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel import com.ilya.reaction.logik.PreferenceHelper.getUserKey import decodePoly import generateUID diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/Find_Frinds_by_websocket/Websocket_find_friends.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/Find_Frinds_by_websocket/Websocket_find_friends.kt index a0e2493..b7b10f2 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/Find_Frinds_by_websocket/Websocket_find_friends.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/Find_Frinds_by_websocket/Websocket_find_friends.kt @@ -1,5 +1,5 @@ import android.util.Log -import com.ilya.meetmapkmp.SocialMap.Interface.WebSocketCallback_frinds + import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends import okhttp3.OkHttpClient import okhttp3.Request @@ -10,71 +10,66 @@ import okio.ByteString import org.json.JSONArray import java.util.concurrent.TimeUnit -class WebSocketFindFriends( - url: String, - private val callback: WebSocketCallback_frinds -) : WebSocketListener() { + + +class Websocket_find_friends( + private var onMessageReceived: ((String) -> Unit)? = null, + private var onErrorOccurred: ((String) -> Unit)? = null +) { + private var webSocket: WebSocket? = null private val client = OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .build() - private val request = Request.Builder() - .url(url) - .build() - - private val webSocket: WebSocket = client.newWebSocket(request, this) + // Метод для подключения к WebSocket + fun connect(uid: String, key: String) { + val url = "wss://meetmap.up.railway.app/findFriends/$uid/$key" + val request = Request.Builder().url(url).build() - // Метод для отправки команды по WebSocket - fun sendCommand(command: String) { - Log.d("WebSocket_friends", "Отправка команды: $command") - webSocket.send(command) - } + webSocket = client.newWebSocket(request, object : WebSocketListener() { + override fun onOpen(webSocket: WebSocket, response: Response) { + Log.d("WebSocket_friends", "Соединение установлено") + webSocket.send("getFriends") // Отправляем команду для получения списка друзей + } - override fun onOpen(webSocket: WebSocket, response: Response) { - Log.d("WebSocket_friends", "Соединение установлено") - } + override fun onMessage(webSocket: WebSocket, text: String) { + Log.d("WebSocket_friends", "Получено сообщение: $text") + onMessageReceived?.invoke(text) // Вызываем функцию обратного вызова для обработки сообщений + } - override fun onMessage(webSocket: WebSocket, text: String) { - Log.d("WebSocket_friends", "Получено сообщение: $text") - parseFriendList(text) - } + override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { + Log.d("WebSocket_friends", "Закрытие соединения: $code / $reason") + webSocket.close(1000, null) + } - override fun onMessage(webSocket: WebSocket, bytes: ByteString) { - Log.d("WebSocket_friends", "Получены байты: ${bytes.hex()}") + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + Log.e("WebSocket_friends", "Ошибка соединения: ${t.message}") + onErrorOccurred?.invoke(t.message ?: "Unknown error") // Вызываем функцию обратного вызова для обработки ошибок + } + }) } - override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - Log.d("WebSocket_friends", "Закрытие соединения: $code / $reason") - webSocket.close(1000, null) + // Метод для закрытия WebSocket соединения + fun disconnect() { + webSocket?.let { + Log.d("WebSocket_friends", "Закрытие WebSocket соединения") + it.close(1000, "Закрытие соединения") + } + client.dispatcher.executorService.shutdown() } - fun stop() { - Log.d("WebSocket_friends", "Закрытие WebSocket соединения") - webSocket.close(1000, "Закрытие соединения") - client.dispatcher.executorService.shutdown() + // Метод для отправки команды по WebSocket + fun sendCommand(command: String) { + webSocket?.send(command) } - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - Log.e("WebSocket_friends", "Ошибка соединения: ${t.message}") + // Методы для установки callback-функций + fun setOnMessageReceivedListener(listener: (String) -> Unit) { + this.onMessageReceived = listener } - // Метод для парсинга полученного списка друзей - private fun parseFriendList(json: String) { - Log.d("WebSocket_friends", "Парсинг списка друзей") - val jsonArray = JSONArray(json) - val friendsList = mutableListOf() - for (i in 0 until jsonArray.length()) { - val jsonObject = jsonArray.getJSONObject(i) - val friend = FindFriends( - key = jsonObject.getString("key"), - name = jsonObject.getString("name"), - img = jsonObject.getString("img"), - friend = jsonObject.getBoolean("friend") - ) - friendsList.add(friend) - } - Log.d("WebSocket_friends", "Получен список друзей: ${friendsList.size} друзей") - callback.onFriendListReceived(friendsList) + fun setOnErrorOccurredListener(listener: (String) -> Unit) { + this.onErrorOccurred = listener } -} +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt index 6dbe639..efdcbd3 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt @@ -1,23 +1,37 @@ +import android.util.Log import okhttp3.ResponseBody import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -suspend fun postRequestAddFriends(uid: String, key: String, friendKey: String): ResponseBody? { +suspend fun postRequestAddFriends(uid: String, key: String, friendKey: String): String? { return try { + // Создаем Retrofit-клиент val retrofit = Retrofit.Builder() .baseUrl("https://meetmap.up.railway.app/") .addConverterFactory(GsonConverterFactory.create()) .build() + // Создаем интерфейс для запроса val service = retrofit.create(PostInvite::class.java) - // Выполняем запрос и получаем результат + // Выполняем запрос val response = service.postInvite(uid, key, friendKey) + // Проверяем успешность запроса if (response.isSuccessful) { - response.body() // Возвращаем тело ответа + // Получаем тело ответа как JSON + val responseBody = response.body() + if (responseBody != null) { + // Извлекаем токен из JSON-ответа + val token = responseBody.get("token")?.toString() + return token + } else { + // Если тело ответа пустое + null + } } else { - // Обработка ошибки + // Обработка ошибки HTTP + Log.e("PostRequestAddFriends", "HTTP error: ${response.code()}") null } } catch (e: Exception) { @@ -25,4 +39,4 @@ suspend fun postRequestAddFriends(uid: String, key: String, friendKey: String): e.printStackTrace() null } -} +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/WebSocketService.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/WebSocketService.kt index 58d27c9..67e2459 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/WebSocketService.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/WebSocketService.kt @@ -11,26 +11,20 @@ import okhttp3.WebSocketListener import okhttp3.logging.HttpLoggingInterceptor import java.util.concurrent.TimeUnit -interface WebSocketListenerCallback { - fun onMessageReceived(message: String) - fun onErrorOccurred(error: String) -} - -class WebSocketService(private val callback: WebSocketListenerCallback, private val context: Context) { +class WebSocketService( + private var onMessageReceived: ((String) -> Unit)? = null, + private var onErrorOccurred: ((String) -> Unit)? = null +) { private var webSocket: WebSocket? = null private var uid: String? = null private var key: String? = null - - companion object { private const val TAG = "WebSocketService" // Тег для логов } - - - + // Метод для подключения к WebSocket fun connect(uid: String, key: String) { this.uid = uid this.key = key @@ -38,17 +32,14 @@ class WebSocketService(private val callback: WebSocketListenerCallback, private connectWebSocket() } - - private fun connectWebSocket() { if (uid == null || key == null) { Log.e(TAG, "UID or Key is null. Cannot connect WebSocket.") - callback.onErrorOccurred("UID or Key is null.") + onErrorOccurred?.invoke("UID or Key is null.") return } - val url = "wss://meetmap.up.railway.app/get-friends/$uid/$key" - //val url = "wss://meetmap.up.railway.app/get-friends/NL85HoOb7FVYP8oDsPu1z9oml1o2/6GkAx0f6cJcWCmihMTpTe41IsqFMIV" + val url = "wss://meetmap.up.railway.app/get-friends/$uid/$key" val request = Request.Builder() .url(url) .build() @@ -60,7 +51,7 @@ class WebSocketService(private val callback: WebSocketListenerCallback, private }) .build() - val webSocketListener = object : WebSocketListener() { + webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) { Log.i(TAG, "WebSocket connection opened. Sending initial request for friends...") webSocket.send("getFriends") @@ -68,36 +59,41 @@ class WebSocketService(private val callback: WebSocketListenerCallback, private override fun onMessage(webSocket: WebSocket, text: String) { Log.d(TAG, "Message received from server: $text") - callback.onMessageReceived(text) // Передача сообщения через интерфейс - + onMessageReceived?.invoke(text) // Вызываем callback для обработки сообщений } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { Log.e(TAG, "WebSocket connection failed: ${t.message}", t) - callback.onErrorOccurred(t.message ?: "Unknown error") // Обработка ошибок через интерфейс + onErrorOccurred?.invoke(t.message ?: "Unknown error") // Вызываем callback для обработки ошибок } override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { Log.w(TAG, "WebSocket closed with code: $code, reason: $reason") } - } + }) - try { - webSocket = client.newWebSocket(request, webSocketListener) - Log.d(TAG, "WebSocket connection initiated to URL: $url") - } catch (e: Exception) { - Log.e(TAG, "Failed to initiate WebSocket connection: ${e.message}", e) - callback.onErrorOccurred("Failed to connect: ${e.message}") - } + Log.d(TAG, "WebSocket connection initiated to URL: $url") } - fun closeWebSocket() { + // Метод для закрытия WebSocket соединения + fun disconnect() { webSocket?.let { Log.d(TAG, "Closing WebSocket connection") it.close(1000, "Service destroyed") } ?: Log.w(TAG, "Attempted to close WebSocket but it was not initialized") } + // Метод для отправки команды по WebSocket + fun sendCommand(command: String) { + webSocket?.send(command) + } + // Методы для установки callback-функций + fun setOnMessageReceivedListener(listener: (String) -> Unit) { + this.onMessageReceived = listener + } + fun setOnErrorOccurredListener(listener: (String) -> Unit) { + this.onErrorOccurred = listener + } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt index 4860d51..8f01e66 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/Friend.kt @@ -1,5 +1,8 @@ package com.ilya.meetmapkmp.SocialMap.DataModel +import kotlinx.serialization.Serializable + +@Serializable data class Friend( val token: String, val img: String, @@ -7,4 +10,4 @@ data class Friend( val name: String, val online: Boolean, val key: String - ) \ No newline at end of file +) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt index 3ede8c1..6b68a1f 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt @@ -27,7 +27,6 @@ import com.google.android.gms.auth.api.identity.Identity import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider import com.ilya.meetmapkmp.SocialMap.ViewModel.ChatViewModel -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel import com.ilya.meetmapkmp.SocialMap.ui.theme.SocialMap import com.ilya.codewithfriends.presentation.profile.ID @@ -49,7 +48,6 @@ class Chat_with_Friends_fragment : Fragment() { ViewModelProvider(this, ChatViewModelFactory(requireContext())).get(ChatViewModel::class.java) } - private val friendsViewModel: FriendsViewModel by viewModels() diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt index 8d83a0f..221464a 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt @@ -2,9 +2,8 @@ package com.example.yourapp.ui -import WebSocketFindFriends + import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -39,6 +38,11 @@ import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.SolidColor @@ -53,11 +57,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.lifecycle.ViewModelProvider - import com.google.android.gms.auth.api.identity.Identity -import com.ilya.meetmapkmp.SocialMap.Interface.WebSocketCallback_frinds + import com.ilya.meetmapkmp.SocialMap.ui.theme.SocialMap import com.ilya.codewithfriends.presentation.profile.ID import com.ilya.codewithfriends.presentation.sign_in.GoogleAuthUiClient @@ -68,14 +70,15 @@ import com.ilya.meetmapkmp.Map.Server_API.POST.addFriends import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends import com.ilya.meetmapkmp.R -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel_data +import com.ilya.meetmapkmp.SocialMap.DataModel.Friend + +import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocketViewModel import com.ilya.meetmapkmp.SocialMap.ui.theme.robotomedium import kotlinx.coroutines.* import postRequestAddFriends -class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { +class Find_friends_fragment : Fragment() { private val googleAuthUiClient by lazy { @@ -86,21 +89,14 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { } - private val friendsViewModelData: FriendsViewModel_data by lazy { - ViewModelProvider(this).get(FriendsViewModel_data::class.java) - } - - private val friendsViewModel: FriendsViewModel by viewModels() - + private val WebSocketViewModel: WebSocketViewModel by viewModels() - private lateinit var webSocketFindFriends: WebSocketFindFriends override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { - return ComposeView(requireContext()).apply { setContent { @@ -119,11 +115,16 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { .height(150.dp) // Занимает только необходимое место .fillMaxWidth() ) { - SearchBar(friendsViewModelData) // Поисковая строка + SearchBar(WebSocketViewModel) // Поисковая строка } + // Собираем friendsList из StateFlow + val friends by WebSocketViewModel.friendsList.collectAsState() + + + // Отображаем список друзей + FriendsList(friends = friends) + - // FriendsList займёт оставшееся пространство - FriendsList(friendsViewModelData.friendsList.value) } } } @@ -134,44 +135,21 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { } - override fun onStart() { - super.onStart() - val uid = ID( - userData = googleAuthUiClient.getSignedInUser() - ) - val key = getUserKey(requireContext()) - if (key != null && uid != null) { - webSocketFindFriends = - WebSocketFindFriends("wss://meetmap.up.railway.app/findFriends/$uid/$key", this) - Log.d( - "Websocket_friends", - "wss://meetmap.up.railway.app/findFriends/$uid/$key" - ) - } - } - override fun onStop() { - super.onStop() - // Отключаемся от WebSocket, как только фрагмент становится невидимым - webSocketFindFriends.stop() - } - - override fun onFriendListReceived(friends: List) { - friendsViewModelData.updateFriendsList(friends) - Log.d("Websocket_friends", friends.toString()) - } @OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class) @Composable - fun SearchBar(viewModel: FriendsViewModel_data) { // Передаем ViewModel в функцию - val keyboardControllers = LocalSoftwareKeyboardController.current + fun SearchBar(webSocketViewModel: WebSocketViewModel) { + val keyboardController = LocalSoftwareKeyboardController.current + var name by remember { mutableStateOf("") } // Текущее значение ввода + var lastSentName by remember { mutableStateOf("") } // Последнее отправленное значение - SocialMap { // Оборачиваем в нашу кастомную тему + SocialMap { Column( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background) // Цвет зависит от темы + .background(MaterialTheme.colorScheme.background) .padding(start = 30.dp, end = 30.dp), ) { Spacer(modifier = Modifier.height(10.dp)) @@ -182,7 +160,6 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { textAlign = TextAlign.Start, modifier = Modifier.fillMaxWidth() ) - // Поле ввода TextField( modifier = Modifier .fillMaxWidth() @@ -194,39 +171,43 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { ), shape = RoundedCornerShape(20.dp) ), - value = viewModel.username, - onValueChange = { - viewModel.updateUsername(it) + value = name, + onValueChange = { newValue -> + name = newValue // Обновляем состояние при изменении }, textStyle = MaterialTheme.typography.bodyLarge, - colors = TextFieldDefaults.colors( - focusedContainerColor = MaterialTheme.colorScheme.surface, - unfocusedContainerColor = MaterialTheme.colorScheme.surface, - focusedIndicatorColor = MaterialTheme.colorScheme.surface, - unfocusedIndicatorColor = MaterialTheme.colorScheme.onSurface, - cursorColor = MaterialTheme.colorScheme.onSurface, - focusedTextColor = MaterialTheme.colorScheme.onSurface - ), + colors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surface, + unfocusedContainerColor = MaterialTheme.colorScheme.surface, + focusedIndicatorColor = MaterialTheme.colorScheme.surface, + unfocusedIndicatorColor = MaterialTheme.colorScheme.onSurface, + cursorColor = MaterialTheme.colorScheme.onSurface, + focusedTextColor = MaterialTheme.colorScheme.onSurface + ), keyboardOptions = KeyboardOptions( imeAction = ImeAction.Search, keyboardType = KeyboardType.Text ), keyboardActions = KeyboardActions( onSearch = { - keyboardControllers?.hide() - webSocketFindFriends.sendCommand("findFriends ${viewModel.username}") + keyboardController?.hide() // Скрываем клавиатуру + if (name.isNotEmpty() && name != lastSentName) { + webSocketViewModel.sendCommand("findFriends $name") // Отправляем команду + lastSentName = name // Обновляем последнее отправленное значение + } } ), ) } - } - LaunchedEffect(key1 = viewModel.username) { - while (isActive) { - delay(3000) // Ждем 3 секунды - // Отправляем команду каждые 3 секунды - if (viewModel.username.isNotEmpty()) { - webSocketFindFriends.sendCommand("findFriends ${viewModel.username}") + // Автоматическая отправка каждые 3 секунды, если поле не пустое и значение изменилось + LaunchedEffect(key1 = name) { + while (isActive) { + delay(3000) // Ждем 3 секунды + if (name.isNotEmpty() && name != lastSentName) { + webSocketViewModel.sendCommand("findFriends $name") // Отправляем команду + lastSentName = name // Обновляем последнее отправленное значение + } } } } @@ -310,7 +291,18 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { .wrapContentHeight(), onClick = { CoroutineScope(Dispatchers.IO).launch { - postRequestAddFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) + + val token = postRequestAddFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) + val data = Friend( + key = friend.key, + name = friend.name, + img = friend.img, + token = token.toString(), + lastmessage = "", + online = false + ) + // friendsRepository.insertOrUpdateFriend(data) + WebSocketViewModel.addfriends_to_bd(data) addFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) } }, @@ -329,4 +321,21 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { } } + + override fun onStart() { + super.onStart() + val uid = ID(userData = googleAuthUiClient.getSignedInUser()) + val key = getUserKey(requireContext()) + if (key != null && uid != null) { + WebSocketViewModel.connect(uid, key) + } + + } + + override fun onDestroy() { + super.onDestroy() + // Отключаемся от WebSocket, как только фрагмент становится невидимым + WebSocketViewModel.disconnect() + } + } diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/WebSocketCallback.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/WebSocketCallback.kt deleted file mode 100644 index 9826fe1..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/WebSocketCallback.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.ilya.meetmapkmp.SocialMap.Interface - -import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends - - -interface WebSocketCallback_frinds { - fun onFriendListReceived(friends: List) -} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/SocialMapActivity.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/SocialMapActivity.kt index 2905d98..d78bebe 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/SocialMapActivity.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/SocialMapActivity.kt @@ -4,6 +4,7 @@ package com.ilya.meetmapkmp.SocialMap import android.content.Intent import android.os.Bundle +import android.util.Log import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.activity.viewModels @@ -15,9 +16,12 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.core.content.ContentProviderCompat.requireContext import androidx.fragment.app.FragmentActivity import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel @@ -32,8 +36,6 @@ import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflec import com.google.gson.Gson import com.ilya.meetmapkmp.SocialMap.DataModel.Friend -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel -import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketListenerCallback import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketService @@ -48,12 +50,15 @@ import com.ilya.meetmapkmp.SocialMap.ui.UI_Layers.MyFragmentContainer import com.ilya.meetmapkmp.SocialMap.ui.theme.SocialMap import com.ilya.codewithfriends.presentation.profile.ID +import com.ilya.codewithfriends.presentation.profile.UID import com.ilya.codewithfriends.presentation.sign_in.GoogleAuthUiClient +import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider +import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocket_getfriendsViewModel import com.ilya.reaction.logik.PreferenceHelper.getUserKey -class SocialMapActivity : FragmentActivity(), WebSocketListenerCallback{ +class SocialMapActivity : FragmentActivity(){ private val googleAuthUiClient by lazy { GoogleAuthUiClient( @@ -62,33 +67,19 @@ class SocialMapActivity : FragmentActivity(), WebSocketListenerCallback{ ) } - private lateinit var webSocketService: WebSocketService - private val friendsViewModel: FriendsViewModel by viewModels() // Используем ViewModel - - + private val WebSocket_getfriendsViewModel: WebSocket_getfriendsViewModel by viewModels() // Используем ViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - - - // Инициализация WebSocketService - webSocketService = WebSocketService(this, this) - - - val uid = ID(userData = googleAuthUiClient.getSignedInUser()) - val key = getUserKey(this) - - webSocketService.connect(uid.toString(), key.toString()) - - - + AppContextProvider.initialize(this) enableEdgeToEdge() setContent { SocialMap { val navController = rememberNavController() + + Column( Modifier .fillMaxSize() @@ -101,16 +92,18 @@ class SocialMapActivity : FragmentActivity(), WebSocketListenerCallback{ composable("Friendsearch") { MyFragmentContainer() } + composable("Chatmenu") { Column(Modifier.fillMaxSize()) { Loop(navController) FriendsScreen( - friendsViewModel.friendsList, + WebSocket_getfriendsViewModel, navController, this@SocialMapActivity ) } } + composable("Chat") { Chat_fragment() } @@ -120,37 +113,24 @@ class SocialMapActivity : FragmentActivity(), WebSocketListenerCallback{ } } - - override fun onStop() { + super.onStop() val intent = Intent(this, WebSocketService::class.java) stopService(intent) // Останавливаем WebSocketService - } - - // Реализация методов интерфейса для обработки сообщений и ошибок - override fun onMessageReceived(message: String) { - val listType = object : TypeToken>() {}.type - val newFriendsList: List = Gson().fromJson(message, listType) + } - // Обновляем данные в ViewModel - friendsViewModel.updateFriends(newFriendsList) - } - - - override fun onErrorOccurred(error: String) { - // Обработка ошибок - println("Error occurred: $error") - // Здесь вы можете уведомить пользователя об ошибке - } - + override fun onStart() { + super.onStart() + val uid = ID(userData = googleAuthUiClient.getSignedInUser()) + val key = getUserKey(this@SocialMapActivity) + WebSocket_getfriendsViewModel.connect(uid.toString(), key.toString()) + } override fun onDestroy() { super.onDestroy() - webSocketService.closeWebSocket() + WebSocket_getfriendsViewModel.disconnect() } - - } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt index 72415f6..c67a451 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt @@ -3,10 +3,14 @@ package com.ilya.meetmapkmp.SocialMap.ViewModel import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends import Websocket_find_friends import android.content.Context +import android.database.sqlite.SQLiteDatabase import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketService +import com.ilya.meetmapkmp.SocialMap.DataModel.Friend +import com.ilya.platform.DriverFactory +import com.ilya.platform.di.FriendsRepository import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -22,6 +26,13 @@ import java.util.concurrent.TimeUnit class WebSocketViewModel : ViewModel() { + private val context = AppContextProvider.getContext() + + private val friendsRepository = FriendsRepository( + SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) + ) + + // StateFlow для хранения списка друзей private val _friendsList = MutableStateFlow>(emptyList()) val friendsList: StateFlow> = _friendsList.asStateFlow() @@ -34,7 +45,10 @@ class WebSocketViewModel : ViewModel() { // Инициализация WebSocketManager private val webSocketManager = Websocket_find_friends() + + init { + DriverFactory(context).createFriendsTable() // Установка callback-функций для обработки сообщений и ошибок webSocketManager.setOnMessageReceivedListener { message -> viewModelScope.launch { @@ -59,6 +73,11 @@ class WebSocketViewModel : ViewModel() { webSocketManager.disconnect() } + fun addfriends_to_bd(friend: Friend){ + friendsRepository.insertOrUpdateFriend(friend) + } + + // Метод для отправки команды по WebSocket fun sendCommand(command: String) { webSocketManager.sendCommand(command) diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt deleted file mode 100644 index 15c3cba..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.ilya.meetmapkmp.SocialMap.ViewModel - - -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.util.Log -import androidx.compose.runtime.mutableStateListOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.google.firebase.crashlytics.buildtools.reloc.com.google.common.reflect.TypeToken -import com.google.gson.Gson -import com.ilya.meetmapkmp.SocialMap.DataModel.Friend -import com.ilya.platform.DriverFactory -import com.ilya.platform.di.ChatQueriesImpl - -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - - -class FriendsViewModel() : ViewModel() { - val friendsList = mutableStateListOf() // Используем для хранения друзей - - // private val driverFactory = DriverFactory(context) - // private val database = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) - // private val chatQueries = ChatQueriesImpl(database) - - - // Функция для обновления данных друзей - fun updateFriends(newFriends: List) { - viewModelScope.launch(Dispatchers.Main) { - friendsList.clear() - friendsList.addAll(newFriends) - } - } - - -} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt index 2377133..b77e596 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt @@ -2,6 +2,7 @@ package com.ilya.meetmapkmp.SocialMap.ViewModel import android.app.Application import android.content.Context +import android.database.sqlite.SQLiteDatabase import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -9,6 +10,7 @@ import androidx.lifecycle.viewModelScope import com.ilya.meetmapkmp.SocialMap.DATAServices.WebSocketService import com.ilya.meetmapkmp.SocialMap.DataModel.Friend import com.ilya.platform.DriverFactory +import com.ilya.platform.di.FriendsRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -17,53 +19,66 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json - -class WebSocket_getfriendsViewModel(context: Context) : ViewModel() { - +class WebSocket_getfriendsViewModel : ViewModel() { companion object { - private const val TAG = "WebSocketViewModel" // Тег для логов + private const val TAG = "WebSocketViewModel" } + private val context = AppContextProvider.getContext() + // StateFlow для хранения состояния WebSocket private val _webSocketState = MutableStateFlow(WebSocketState.Disconnected) val webSocketState: StateFlow = _webSocketState.asStateFlow() - private val driverFactory = DriverFactory(context) - - - - // StateFlow для хранения списка друзей private val _friends = MutableStateFlow>(emptyList()) val friends: StateFlow> = _friends.asStateFlow() + // FriendsRepository для работы с базой данных + private val friendsRepository = FriendsRepository( + SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) + ) + + // WebSocketService для обработки WebSocket сообщений private val webSocketService = WebSocketService( onMessageReceived = { jsonString -> Log.d(TAG, "Message received from server: $jsonString") - // Парсим JSON в список Friend и обновляем StateFlow - parseFriendFromJson(jsonString)?.let { newFriends -> - Log.d(TAG, "Parsed ${newFriends.size} friends from JSON") - _friends.value = newFriends - } + parseAndSaveFriends(jsonString) }, onErrorOccurred = { error -> Log.e(TAG, "Error occurred: $error") - // Обработка ошибок _webSocketState.value = WebSocketState.Error(error) } ) + init { + DriverFactory(context).createFriendsTable() + + // Загрузка начальных данных + loadFriendsFromDatabase() - init{ - driverFactory.createFriendsTable() + // Подписка на изменения в базе данных + friendsRepository.onDatabaseChanged = { + loadFriendsFromDatabase() + } } + private fun loadFriendsFromDatabase() { + viewModelScope.launch { + try { + val allFriends = friendsRepository.getAllFriends() + _friends.value = allFriends + Log.d(TAG, "Loaded ${allFriends.size} friends from database.") + } catch (e: Exception) { + Log.e(TAG, "Error loading friends from database: ${e.message}") + } + } + } - // Подключение к WebSocket fun connect(uid: String, key: String) { if (_webSocketState.value is WebSocketState.Connected) { Log.w(TAG, "Already connected. Ignoring new connection attempt.") - return // Уже подключены + return } Log.d(TAG, "Attempting to connect with uid: $uid, key: $key") _webSocketState.value = WebSocketState.Connecting @@ -72,7 +87,6 @@ class WebSocket_getfriendsViewModel(context: Context) : ViewModel() { Log.d(TAG, "WebSocket connection established.") } - // Отключение от WebSocket fun disconnect() { Log.d(TAG, "Disconnecting WebSocket...") webSocketService.disconnect() @@ -80,7 +94,6 @@ class WebSocket_getfriendsViewModel(context: Context) : ViewModel() { Log.d(TAG, "WebSocket disconnected.") } - // Отправка команды через WebSocket fun sendCommand(command: String) { if (_webSocketState.value is WebSocketState.Connected) { Log.d(TAG, "Sending command: $command") @@ -90,35 +103,30 @@ class WebSocket_getfriendsViewModel(context: Context) : ViewModel() { _webSocketState.value = WebSocketState.Error("WebSocket is not connected") } } + fun deletefriends_from_bd(token: String){ + friendsRepository.deleteFriendByToken(token) + } + + - // Функция для парсинга JSON в объект Friend - private fun parseFriendFromJson(jsonString: String): List? { - return try { - Log.d(TAG, "Parsing JSON string into Friend objects...") - Json.decodeFromString>(jsonString) - } catch (e: Exception) { - Log.e(TAG, "Failed to parse JSON: ${e.message}") - e.printStackTrace() - null + private fun parseAndSaveFriends(jsonString: String) { + viewModelScope.launch { + try { + val newFriends = Json.decodeFromString>(jsonString) + newFriends.forEach { friend -> + friendsRepository.insertOrUpdateFriend(friend) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to parse JSON or save friends: ${e.message}") + e.printStackTrace() + } } } - // Состояния WebSocket sealed class WebSocketState { object Connecting : WebSocketState() object Connected : WebSocketState() object Disconnected : WebSocketState() data class Error(val message: String) : WebSocketState() } -} - -class ContextViewModelFactory(private val context: Context) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(WebSocket_getfriendsViewModel::class.java)) { - @Suppress("UNCHECKED_CAST") - return WebSocket_getfriendsViewModel(context) as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} - +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt index 170d28b..47e919c 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt @@ -14,16 +14,24 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -31,28 +39,37 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.ViewModelProvider import androidx.navigation.NavController import coil.compose.AsyncImage +import coil.request.ImageRequest import com.google.api.Context +import com.ilya.meetmapkmp.R import com.ilya.meetmapkmp.SocialMap.DataModel.Friend import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider - - -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel +import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocket_getfriendsViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch @Composable -fun FriendsScreen(friendsList: List, navController: NavController, context: android.content.Context) { +fun FriendsScreen(WebSocket_getfriendsViewModel : WebSocket_getfriendsViewModel, navController: NavController, context: android.content.Context) { val backgroundColor = if (isSystemInDarkTheme()) Color.Black else colorScheme.primaryContainer + + val friends by WebSocket_getfriendsViewModel.friends.collectAsState() + + + LazyColumn( modifier = Modifier.fillMaxSize(), ) { - items(friendsList) { friend -> - FriendItem(friend, navController, context) + items(friends) { friend -> + FriendItem(friend, navController, context, WebSocket_getfriendsViewModel) Spacer( modifier = Modifier .fillMaxWidth() @@ -65,18 +82,17 @@ fun FriendsScreen(friendsList: List, navController: NavController, conte } @Composable -fun FriendItem(friend: Friend, navController: NavController, context: android.content.Context) { - - +fun FriendItem( + friend: Friend, + navController: NavController, + context: android.content.Context, + WebSocket_getfriendsViewModel: WebSocket_getfriendsViewModel +) { // Получаем текущую цветовую схему val colorScheme = MaterialTheme.colorScheme - - // Определяем цвета для текста и фона в зависимости от темы val backgroundColor = if (isSystemInDarkTheme()) colorScheme.surface else Color.White val textColor = if (isSystemInDarkTheme()) colorScheme.onSurface else Color.Black - - Card( modifier = Modifier .fillMaxWidth() @@ -85,48 +101,43 @@ fun FriendItem(friend: Friend, navController: NavController, context: android.co indication = null, interactionSource = remember { MutableInteractionSource() } ) { - Log.d("Save_token", "сохраняю токен: ${friend.token}") - val dataProvider = MyDataProvider(context) dataProvider.saveToken(friend.token) // Store the token - - // Переход к чату navController.navigate("Chat") }, - colors = CardDefaults.cardColors( - containerColor = backgroundColor - ), + colors = CardDefaults.cardColors(containerColor = backgroundColor), shape = RectangleShape ) { Row( verticalAlignment = Alignment.CenterVertically, - modifier = Modifier + modifier = Modifier.padding(horizontal = 8.dp) ) { AsyncImage( - model = friend.img, + model = ImageRequest.Builder(LocalContext.current) + .data(friend.img) + .crossfade(true) // Плавная анимация загрузки + .placeholder(R.drawable.placeholder) // Placeholder при загрузке + .error(R.drawable.photo_error_icon) // Изображение при ошибке + .build(), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier + .size(60.dp) // Фиксированный размер + .clip(CircleShape) // Круглая форма .align(Alignment.CenterVertically) - .padding(10.dp) - .clip(RoundedCornerShape(30.dp)) - .width(60.dp) - .height(60.dp) ) - Spacer(modifier = Modifier.width(8.dp)) - Column( - verticalArrangement = Arrangement.Top, - modifier = Modifier.fillMaxHeight() + verticalArrangement = Arrangement.Center, + modifier = Modifier.weight(1f) ) { Text( text = friend.name, style = MaterialTheme.typography.bodyLarge, color = textColor, maxLines = 1, - overflow = TextOverflow.Ellipsis, + overflow = TextOverflow.Ellipsis ) Text( text = friend.lastmessage, @@ -136,6 +147,19 @@ fun FriendItem(friend: Friend, navController: NavController, context: android.co overflow = TextOverflow.Ellipsis ) } + IconButton( + onClick = { + CoroutineScope(Dispatchers.IO).launch { + WebSocket_getfriendsViewModel.deletefriends_from_bd(friend.token) + } + } + ) { + Icon( + imageVector = Icons.Default.Delete, + contentDescription = "Удалить друга", + tint = Color.Red // Цвет иконки удаления + ) + } } } } diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ deleted file mode 100644 index acfaf88..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt~ +++ /dev/null @@ -1,142 +0,0 @@ -package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers - -import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MaterialTheme.colorScheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import coil.compose.AsyncImage -import com.google.api.Context -import com.ilya.meetmapkmp.SocialMap.DataModel.Friend -import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider - - -import com.ilya.meetmapkmp.SocialMap.ViewModel.FriendsViewModel -import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocket_getfriendsViewModel - - -@Composable -fun FriendsScreen(viewModel: WebSocket_getfriendsViewModel, navController: NavController,context: android.content.Context) { - val backgroundColor = if (isSystemInDarkTheme()) Color.Black else colorScheme.primaryContainer - - - LazyColumn( - modifier = Modifier.fillMaxSize(), - ) { - items(viewModel.friends.value) { friend -> - FriendItem(friend, navController, context) - Spacer( - modifier = Modifier - .fillMaxWidth() - .height(1.dp) - .padding(start = 70.dp) - .background(backgroundColor) - ) - } - } -} - -@Composable -fun FriendItem(friend: Friend, navController: NavController, context: android.content.Context) { - - - // Получаем текущую цветовую схему - val colorScheme = MaterialTheme.colorScheme - - // Определяем цвета для текста и фона в зависимости от темы - val backgroundColor = if (isSystemInDarkTheme()) colorScheme.surface else Color.White - val textColor = if (isSystemInDarkTheme()) colorScheme.onSurface else Color.Black - - - - Card( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - Log.d("Save_token", "сохраняю токен: ${friend.token}") - val dataProvider = MyDataProvider(context) - dataProvider.saveToken(friend.token) // Store the token - // Переход к чату - navController.navigate("Chat") - }, - colors = CardDefaults.cardColors( - containerColor = backgroundColor - ), - shape = RectangleShape - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - ) { - AsyncImage( - model = friend.img, - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .align(Alignment.CenterVertically) - .padding(10.dp) - .clip(RoundedCornerShape(30.dp)) - .width(60.dp) - .height(60.dp) - ) - - Spacer(modifier = Modifier.width(8.dp)) - - Column( - verticalArrangement = Arrangement.Top, - modifier = Modifier.fillMaxHeight() - ) { - Text( - text = friend.name, - style = MaterialTheme.typography.bodyLarge, - color = textColor, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - Text( - text = friend.lastmessage, - style = MaterialTheme.typography.bodyMedium, - color = textColor.copy(alpha = 0.7f), - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) - } - } - } -} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt b/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt index 0e134e3..d3a37d5 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/DriverFactory.android.kt @@ -49,11 +49,12 @@ actual class DriverFactory(private val context: Context) { } - fun createFriendsTable(tableName: String): Boolean { + fun createFriendsTable(): Boolean { val db = SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath("friends.db"), null) val createTableQuery = """ - CREATE TABLE IF NOT EXISTS $tableName ( + CREATE TABLE IF NOT EXISTS friends ( token TEXT PRIMARY KEY, + 'key' TEXT, img TEXT, lastmessage TEXT, name TEXT, diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt index 9c5ca23..94cfecb 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt @@ -1,9 +1,140 @@ package com.ilya.platform.di import android.database.sqlite.SQLiteDatabase +import android.util.Log +import com.ilya.MeetingMap.SocialMap.DataModel.FindFriends +import com.ilya.meetmapkmp.SocialMap.DataModel.Friend -class FriendsRepository(private val database: SQLiteDatabase) -{ +class FriendsRepository(private val database: SQLiteDatabase) { + companion object { + private const val TABLE_NAME = "friends" + } -} \ No newline at end of file + // Интерфейс для уведомления об изменениях + var onDatabaseChanged: (() -> Unit)? = null + + + // Получение друга по token + fun getFriendByToken(token: String): Friend? { + val cursor = database.rawQuery("SELECT * FROM $TABLE_NAME WHERE token = ?", arrayOf(token)) + cursor.use { + if (it.moveToFirst()) { + return Friend( + token = it.getString(it.getColumnIndexOrThrow("token")), + key = it.getString(it.getColumnIndexOrThrow("key")), + img = it.getString(it.getColumnIndexOrThrow("img")), + lastmessage = it.getString(it.getColumnIndexOrThrow("lastmessage")), + name = it.getString(it.getColumnIndexOrThrow("name")), + online = it.getInt(it.getColumnIndexOrThrow("online")) == 1 + ) + } + } + return null + } + + // Удаление друга по token + fun deleteFriendByToken(token: String) { + try { + database.execSQL("DELETE FROM $TABLE_NAME WHERE token = ?", arrayOf(token)) + notifyDatabaseChanged() + Log.d("FriendsRepository", "Friend with token $token deleted successfully.") + } catch (e: Exception) { + Log.e("FriendsRepository", "Error deleting friend with token $token: ${e.message}") + } + } + + // Удаление всех друзей из таблицы + fun deleteAllFriends() { + try { + database.execSQL("DELETE FROM $TABLE_NAME") + notifyDatabaseChanged() + Log.d("FriendsRepository", "All friends deleted successfully.") + } catch (e: Exception) { + Log.e("FriendsRepository", "Error deleting all friends: ${e.message}") + } + } + + // Метод для уведомления об изменениях + private fun notifyDatabaseChanged() { + onDatabaseChanged?.invoke() + } + + fun insertOrUpdateFriend(friend: Friend) { + try { + val cursor = database.rawQuery("SELECT * FROM $TABLE_NAME WHERE token = ?", arrayOf(friend.token)) + if (cursor.count > 0) { + cursor.close() + updateFriend(friend) + } else { + cursor.close() + insertFriend(friend) + } + notifyDatabaseChanged() + } catch (e: Exception) { + Log.e("FriendsRepository", "Error inserting/updating friend: ${e.message}") + } + } + + private fun insertFriend(friend: Friend) { + database.execSQL( + """ + INSERT INTO $TABLE_NAME ( + token, `key`, img, lastmessage, name, online + ) VALUES (?, ?, ?, ?, ?, ?) + """.trimIndent(), + arrayOf( + friend.token, + friend.key, + friend.img, + friend.lastmessage, + friend.name, + if (friend.online) 1 else 0 + ) + ) + } + + private fun updateFriend(friend: Friend) { + database.execSQL( + """ + UPDATE $TABLE_NAME + SET `key` = ?, img = ?, lastmessage = ?, name = ?, online = ? + WHERE token = ? + """.trimIndent(), + arrayOf( + friend.key, + friend.img, + friend.lastmessage, + friend.name, + if (friend.online) 1 else 0, + friend.token + ) + ) + } + + fun getAllFriends(): List { + val friendsList = mutableListOf() + val cursor = database.query("friends", null, null, null, null, null, null) + while (cursor.moveToNext()) { + val token = cursor.getString(cursor.getColumnIndexOrThrow("token")) + val img = cursor.getString(cursor.getColumnIndexOrThrow("img")) + val lastmessage = cursor.getString(cursor.getColumnIndexOrThrow("lastmessage")) + val name = cursor.getString(cursor.getColumnIndexOrThrow("name")) + val online = cursor.getInt(cursor.getColumnIndexOrThrow("online")) == 1 + val key = cursor.getString(cursor.getColumnIndexOrThrow("key")) // Экранируем зарезервированное слово + + friendsList.add( + Friend( + token = token, + img = img, + lastmessage = lastmessage, + name = name, + online = online, + key = key + ) + ) + } + cursor.close() + return friendsList + } +} diff --git a/composeApp/src/androidMain/res/drawable/photo_error_icon.png b/composeApp/src/androidMain/res/drawable/photo_error_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2630b7d0970b8ff9288afc439e0f6550cff3a5bd GIT binary patch literal 9360 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 VtMJX?A1F^dx9!=QW9fGB{{Tm~PI3SM literal 0 HcmV?d00001 From 2aaae31eef986ba0458968550fbea992c2bc97cc Mon Sep 17 00:00:00 2001 From: Ilya Prokofev <87380526+ilyaprofev@users.noreply.github.com> Date: Tue, 11 Feb 2025 20:56:25 +0300 Subject: [PATCH 4/5] =?UTF-8?q?=20=D1=8F=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B9=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B7=D0=BE=D0=B2=D1=8B=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BC=D0=B0=D1=80=D1=88?= =?UTF-8?q?=D1=80=D1=83=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/ilya/meetmapkmp/Map/Map_Activity.kt | 86 ++++++++++- .../REST/Post_requast_add_friends.kt | 39 +++-- .../SocialMap/DataModel/TokenResponse.kt | 8 + .../Fragment/Find_friends_fragment.kt | 47 ++++-- .../SocialMap/Interface/Post_invite.kt | 7 +- .../ViewModel/FindFriends_ViewModel.kt | 2 + .../SocialMap/ViewModel/WebSocketViewModel.kt | 1 + .../ui/UI_Layers/AlertDialogExample.kt | 51 +++++++ .../SocialMap/ui/UI_Layers/FriendsScreen.kt | 69 ++++++--- .../com/ilya/platform/di/ChatRepository.kt~ | 137 ------------------ .../com/ilya/platform/di/FriendsRepository.kt | 14 ++ .../res/drawable/directions_bike_24px.xml | 11 ++ .../res/drawable/directions_car_24px.xml | 10 ++ .../res/drawable/directions_walk_24px.xml | 11 ++ .../androidMain/res/layout/activity_map.xml | 64 +++++--- 15 files changed, 348 insertions(+), 209 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/TokenResponse.kt create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/AlertDialogExample.kt delete mode 100644 composeApp/src/androidMain/kotlin/com/ilya/platform/di/ChatRepository.kt~ create mode 100644 composeApp/src/androidMain/res/drawable/directions_bike_24px.xml create mode 100644 composeApp/src/androidMain/res/drawable/directions_car_24px.xml create mode 100644 composeApp/src/androidMain/res/drawable/directions_walk_24px.xml diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt index 633b5a4..223247b 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/Map/Map_Activity.kt @@ -24,6 +24,8 @@ import android.os.Looper import android.util.Log import android.view.LayoutInflater import android.view.View +import android.view.animation.Animation +import android.view.animation.TranslateAnimation import android.widget.ArrayAdapter import android.widget.AutoCompleteTextView import android.widget.Button @@ -169,6 +171,11 @@ class Map_Activity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPolyli private var isItemDecorationAdded = false // Флаг private val webSocketManager = WebSocketManager(client, this) val markerDataMap: MutableMap = ConcurrentHashMap() + private lateinit var button2: View + private lateinit var button3: View + private lateinit var routeButton: View + private var isExpanded = false // Флаг для отслеживания состояния + private companion object { private const val MY_PERMISSIONS_REQUEST_LOCATION = 1 @@ -189,6 +196,11 @@ class Map_Activity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPolyli setContentView(R.layout.activity_map) + button2 = findViewById(R.id.button2) + button3 = findViewById(R.id.button3) + routeButton = findViewById(R.id.routeButton) + + var PersonalizedMarkersViewModel = ViewModelProvider( this, PersonalizedMarkersViewModelFactory(applicationContext) @@ -511,7 +523,7 @@ class Map_Activity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPolyli val locationAutoCompleteTextView = findViewById(R.id.locationAutoCompleteTextView) val findButton = findViewById(R.id.findButton) - val routeButton = findViewById(R.id.routeButton) + //+val routeButton = findViewById(R.id.routeButton) val socialbutton = findViewById(R.id.social) @@ -614,22 +626,82 @@ class Map_Activity : AppCompatActivity(), OnMapReadyCallback, GoogleMap.OnPolyli // Инициализация объекта Polyline polyline = mMap.addPolyline(PolylineOptions().width(5f).color(android.graphics.Color.BLUE)) + var isRouteDrawn = false +// routeButton.setOnClickListener { +// if (isRouteDrawn) { +// currentPolyline?.remove() +// removeMarkers() +// isRouteDrawn = false +// } else { +// findLocation_route() +// isRouteDrawn = true +// } +// } + // Установка обработчика нажатия на кнопку routeButton.setOnClickListener { - if (isRouteDrawn) { - currentPolyline?.remove() - removeMarkers() - isRouteDrawn = false + if (isExpanded) { + collapseMenu() } else { - findLocation_route() - isRouteDrawn = true + expandMenu() } + isExpanded = !isExpanded } } } }, 200) // Задержка в 0.2 секунду перед выполнением кода } } + + // Метод для раскрытия меню + private fun expandMenu() { + // Показать кнопки + button2.visibility = View.VISIBLE + button3.visibility = View.VISIBLE + + // Анимация перемещения кнопок влево + val animationButton2 = TranslateAnimation(0f, -60f, 0f, 0f).apply { + duration = 400 + fillAfter = true + } + button2.startAnimation(animationButton2) + + val animationButton3 = TranslateAnimation(0f, -120f, 0f, 0f).apply { + duration = 400 + fillAfter = true + } + button3.startAnimation(animationButton3) + } + + // Метод для сворачивания меню + private fun collapseMenu() { + // Анимация перемещения кнопок вправо + val animationButton2 = TranslateAnimation(-60f, 0f, 0f, 0f).apply { + duration = 400 + setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) {} + override fun onAnimationEnd(animation: Animation?) { + button2.visibility = View.GONE + } + override fun onAnimationRepeat(animation: Animation?) {} + }) + } + button2.startAnimation(animationButton2) + + val animationButton3 = TranslateAnimation(-120f, 0f, 0f, 0f).apply { + duration = 200 + setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) {} + override fun onAnimationEnd(animation: Animation?) { + button3.visibility = View.GONE + } + override fun onAnimationRepeat(animation: Animation?) {} + }) + } + button3.startAnimation(animationButton3) + } + + override fun removeSpecificMarker(markerData: MarkerData) { Handler(Looper.getMainLooper()).post { Log.d("RemoveMarker", "Попытка удалить маркер с id=${markerData.id}") diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt index efdcbd3..fc4619a 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_requast_add_friends.kt @@ -1,42 +1,63 @@ import android.util.Log +import kotlinx.serialization.json.Json import okhttp3.ResponseBody import retrofit2.Retrofit + +import com.google.gson.GsonBuilder import retrofit2.converter.gson.GsonConverterFactory suspend fun postRequestAddFriends(uid: String, key: String, friendKey: String): String? { return try { - // Создаем Retrofit-клиент + // Логирование начала выполнения функции + Log.d("PostRequestAddFriends", "Starting postRequestAddFriends with uid=$uid, key=$key, friendKey=$friendKey") + + // Создаем экземпляр Gson + val gson = GsonBuilder().setLenient().create() + + // Создаем Retrofit-клиент с поддержкой Gson val retrofit = Retrofit.Builder() .baseUrl("https://meetmap.up.railway.app/") - .addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(GsonConverterFactory.create(gson)) // Используем Gson .build() + // Логирование URL запроса + val url = "https://meetmap.up.railway.app/friendrequest/$uid/$key/$friendKey" + Log.d("PostRequestAddFriends", "Request URL: $url") + // Создаем интерфейс для запроса val service = retrofit.create(PostInvite::class.java) + Log.d("PostRequestAddFriends", "Retrofit service created successfully") // Выполняем запрос + Log.d("PostRequestAddFriends", "Executing POST request...") val response = service.postInvite(uid, key, friendKey) // Проверяем успешность запроса if (response.isSuccessful) { - // Получаем тело ответа как JSON + Log.d("PostRequestAddFriends", "POST request successful. Response code: ${response.code()}") + // Получаем тело ответа как объект TokenResponse val responseBody = response.body() if (responseBody != null) { - // Извлекаем токен из JSON-ответа - val token = responseBody.get("token")?.toString() + Log.d("PostRequestAddFriends", "Response body received: $responseBody") + // Извлекаем токен из объекта TokenResponse + val token = responseBody.token + Log.d("PostRequestAddFriends", "Token extracted successfully: $token") return token } else { // Если тело ответа пустое - null + Log.e("PostRequestAddFriends", "Response body is null") + return null } } else { // Обработка ошибки HTTP Log.e("PostRequestAddFriends", "HTTP error: ${response.code()}") - null + Log.e("PostRequestAddFriends", "Error message: ${response.errorBody()?.string()}") + return null } } catch (e: Exception) { - // Ловим возможные ошибки (например, отсутствие сети) + // Логирование исключения + Log.e("PostRequestAddFriends", "Exception occurred: ${e.message}") e.printStackTrace() - null + return null } } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/TokenResponse.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/TokenResponse.kt new file mode 100644 index 0000000..dd09c3e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DataModel/TokenResponse.kt @@ -0,0 +1,8 @@ +package com.ilya.meetmapkmp.SocialMap.DataModel + +import kotlinx.serialization.Serializable + +@Serializable +data class TokenResponse( + val token: String +) \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt index 221464a..57abfed 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt @@ -4,6 +4,7 @@ package com.example.yourapp.ui import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -291,19 +292,41 @@ class Find_friends_fragment : Fragment() { .wrapContentHeight(), onClick = { CoroutineScope(Dispatchers.IO).launch { - - val token = postRequestAddFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) - val data = Friend( - key = friend.key, - name = friend.name, - img = friend.img, - token = token.toString(), - lastmessage = "", - online = false - ) - // friendsRepository.insertOrUpdateFriend(data) + try { + // Выполняем асинхронный запрос для получения токена + val tokenDeferred = async { + postRequestAddFriends( + uid.toString(), + key = getUserKey(requireContext()).toString(), + friendKey = friend.key + ) + } + + // Дожидаемся завершения запроса и получаем токен + val token = tokenDeferred.await() + + // Создаем объект Friend с полученным токеном + val data = Friend( + key = friend.key, + name = friend.name, + img = friend.img, + token = token.toString(), + lastmessage = "", + online = false + ) + + // Добавляем друга в базу данных WebSocketViewModel.addfriends_to_bd(data) - addFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) + + // Выполняем дополнительные действия (например, добавление в чат) + addFriends( + uid.toString(), + key = getUserKey(requireContext()).toString(), + friendKey = friend.key + ) + } catch (e: Exception) { + Log.e("FriendsRepository", "Error adding friend: ${e.message}") + } } }, colors = ButtonDefaults.buttonColors( diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt index ff1220c..7508178 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt @@ -1,13 +1,14 @@ +import com.ilya.meetmapkmp.SocialMap.DataModel.TokenResponse import kotlinx.serialization.json.JsonObject import retrofit2.Response import retrofit2.http.POST import retrofit2.http.Path interface PostInvite { - @POST("friendrequest/{uid}/{key}/{friend_key}") + @POST("friendrequest/{uid}/{key}/{friendKey}") suspend fun postInvite( @Path("uid") uid: String, @Path("key") key: String, - @Path("friend_key") friendKey: String - ): Response + @Path("friendKey") friendKey: String + ): Response } \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt index c67a451..0696d33 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt @@ -48,6 +48,7 @@ class WebSocketViewModel : ViewModel() { init { + DriverFactory(context).createFriendsTable() // Установка callback-функций для обработки сообщений и ошибок webSocketManager.setOnMessageReceivedListener { message -> @@ -61,6 +62,7 @@ class WebSocketViewModel : ViewModel() { _errors.value = error } } + } // Метод для подключения к WebSocket diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt index b77e596..a554c5f 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt @@ -103,6 +103,7 @@ class WebSocket_getfriendsViewModel : ViewModel() { _webSocketState.value = WebSocketState.Error("WebSocket is not connected") } } + fun deletefriends_from_bd(token: String){ friendsRepository.deleteFriendByToken(token) } diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/AlertDialogExample.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/AlertDialogExample.kt new file mode 100644 index 0000000..a020d6b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/AlertDialogExample.kt @@ -0,0 +1,51 @@ +package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.vector.ImageVector + +@Composable +fun AlertDialogExample( + onDismissRequest: () -> Unit, + onConfirmation: () -> Unit, + dialogTitle: String, + dialogText: String, + icon: ImageVector, +) { + AlertDialog( + icon = { + Icon(icon, contentDescription = "Example Icon") + }, + title = { + Text(text = dialogTitle) + }, + text = { + Text(text = dialogText) + }, + onDismissRequest = { + onDismissRequest() + }, + confirmButton = { + TextButton( + onClick = { + onConfirmation() + } + ) { + Text("Confirm") + } + }, + dismissButton = { + TextButton( + onClick = { + onDismissRequest() + } + ) { + Text("Dismiss") + + } + } + ) +} diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt index 47e919c..2b231a9 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/FriendsScreen.kt @@ -3,6 +3,7 @@ package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement @@ -22,6 +23,8 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Warning +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon @@ -29,15 +32,21 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme.colorScheme import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow @@ -53,6 +62,7 @@ import com.ilya.meetmapkmp.SocialMap.Interface.MyDataProvider import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocket_getfriendsViewModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -87,24 +97,33 @@ fun FriendItem( navController: NavController, context: android.content.Context, WebSocket_getfriendsViewModel: WebSocket_getfriendsViewModel + ) { // Получаем текущую цветовую схему val colorScheme = MaterialTheme.colorScheme val backgroundColor = if (isSystemInDarkTheme()) colorScheme.surface else Color.White val textColor = if (isSystemInDarkTheme()) colorScheme.onSurface else Color.Black + // Состояние для показа диалогового окна + var showDialog by remember { mutableStateOf(false) } + Card( modifier = Modifier .fillMaxWidth() .height(80.dp) - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() } - ) { - Log.d("Save_token", "сохраняю токен: ${friend.token}") - val dataProvider = MyDataProvider(context) - dataProvider.saveToken(friend.token) // Store the token - navController.navigate("Chat") + .pointerInput(Unit) { + detectTapGestures( + onTap = { + Log.d("Save_token", "сохраняю токен: ${friend.token}") + val dataProvider = MyDataProvider(context) + dataProvider.saveToken(friend.token) // Store the token + navController.navigate("Chat") + }, + onLongPress = { + // Долгое нажатие + showDialog = true // Показываем диалог + } + ) }, colors = CardDefaults.cardColors(containerColor = backgroundColor), shape = RectangleShape @@ -147,19 +166,25 @@ fun FriendItem( overflow = TextOverflow.Ellipsis ) } - IconButton( - onClick = { - CoroutineScope(Dispatchers.IO).launch { - WebSocket_getfriendsViewModel.deletefriends_from_bd(friend.token) - } - } - ) { - Icon( - imageVector = Icons.Default.Delete, - contentDescription = "Удалить друга", - tint = Color.Red // Цвет иконки удаления - ) - } } } -} + + // Показываем диалоговое окно, если showDialog == true + if (showDialog) { + AlertDialogExample( + onDismissRequest = { + showDialog = false + }, // Закрыть диалог + onConfirmation = { + CoroutineScope(Dispatchers.IO).launch { + WebSocket_getfriendsViewModel.deletefriends_from_bd(friend.token) + WebSocket_getfriendsViewModel.sendCommand("delete ${friend.token}") + } + showDialog = false // Закрыть диалог после подтверждения + }, + dialogTitle = "Подтверждение", + dialogText = "Вы уверены, что хотите удалить этого друга?", + icon = Icons.Default.Warning + ) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/ChatRepository.kt~ b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/ChatRepository.kt~ deleted file mode 100644 index 4edb4fa..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/ChatRepository.kt~ +++ /dev/null @@ -1,137 +0,0 @@ -package com.ilya.platform.di - -import android.database.sqlite.SQLiteDatabase -import com.ilya.meetmapkmp.SocialMap.DataModel.Messages_Chat - -import android.util.Log -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class ChatQueriesImpl(private val database: SQLiteDatabase) { - - // Лог-тег для удобства фильтрации - private val TAG = "ChatQueriesImpl" - - // Добавление сообщения в таблицу - fun insertMessage(chatId: String, message: Messages_Chat) { - val query = """ - INSERT INTO chat_$chatId ( - message_id, content, profilerIMG, messageTime, key, senderUsername, gifUrls, imageUrls, videoUrls, fileUrls - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """.trimIndent() - - Log.d(TAG, "Вставка сообщения в таблицу chat_$chatId: $message") - try { - database.execSQL( - query, arrayOf( - message.messageId, - message.content, - message.profilerIMG, - message.messageTime, - message.key, - message.senderUsername, - message.gifUrls.joinToString(","), - message.imageUrls.joinToString(","), - message.videoUrls.joinToString(","), - message.fileUrls.joinToString(",") - ) - ) - Log.d(TAG, "Сообщение успешно добавлено в chat_$chatId") - } catch (e: Exception) { - Log.e(TAG, "Ошибка вставки сообщения в chat_$chatId: ${e.message}") - } - } - - // Извлечение всех сообщений из таблицы чата - fun getAllMessages(chatId: String): List { - Log.d(TAG, "Извлечение всех сообщений из chat_$chatId") - val messages = mutableListOf() - val cursor = database.rawQuery("SELECT * FROM chat_$chatId", null) - - cursor.use { - while (it.moveToNext()) { - try { - val message = Messages_Chat( - messageId = it.getString(it.getColumnIndexOrThrow("message_id")), - content = it.getString(it.getColumnIndexOrThrow("content")), - profilerIMG = it.getString(it.getColumnIndexOrThrow("profilerIMG")), - messageTime = it.getLong(it.getColumnIndexOrThrow("messageTime")), - key = it.getString(it.getColumnIndexOrThrow("key")), - senderUsername = it.getString(it.getColumnIndexOrThrow("senderUsername")), - gifUrls = it.getString(it.getColumnIndexOrThrow("gifUrls")).split(",").filter { url -> url.isNotBlank() }, - imageUrls = it.getString(it.getColumnIndexOrThrow("imageUrls")).split(",").filter { url -> url.isNotBlank() }, - videoUrls = it.getString(it.getColumnIndexOrThrow("videoUrls")).split(",").filter { url -> url.isNotBlank() }, - fileUrls = it.getString(it.getColumnIndexOrThrow("fileUrls")).split(",").filter { url -> url.isNotBlank() } - ) - messages.add(message) - } catch (e: Exception) { - Log.e(TAG, "Ошибка обработки сообщения из chat_$chatId: ${e.message}") - } - } - } - Log.d(TAG, "Извлечено ${messages.size} сообщений из chat_$chatId") - return messages - } - - suspend fun deleteMessageById(chatId: String, messageId: String) { - withContext(Dispatchers.IO) { - try { - // Логируем начало операции удаления - Log.i("ChatQueries", "Начинается проверка наличия сообщения с ID $messageId в таблице chat_$chatId") - - val checkQuery = "SELECT * FROM chat_$chatId WHERE message_id = ?" - val cursor = database.rawQuery(checkQuery, arrayOf(messageId)) - val recordExists = cursor.moveToFirst() - - // Логируем результат проверки наличия записи - if (recordExists) { - Log.i("ChatQueries", "Запись с ID $messageId найдена в таблице chat_$chatId") - } else { - Log.e("ChatQueries", "Запись с ID $messageId не найдена в таблице chat_$chatId") - } - cursor.close() - - if (recordExists) { - // Логируем начало транзакции для удаления записи - Log.i("ChatQueries", "Начинается транзакция для удаления сообщения с ID $messageId из таблицы chat_$chatId") - - database.beginTransaction() - try { - val deleteQuery = "DELETE FROM chat_$chatId WHERE message_id = ?" - val deletedRows = database.delete("chat_$chatId", "message_id = ?", arrayOf(messageId)) - - if (deletedRows > 0) { - Log.i("ChatQueries", "Сообщение с ID $messageId успешно удалено из таблицы chat_$chatId") - } else { - Log.e("ChatQueries", "Не удалось удалить сообщение с ID $messageId из таблицы chat_$chatId") - } - - database.setTransactionSuccessful() - } finally { - database.endTransaction() - Log.i("ChatQueries", "Транзакция завершена для удаления сообщения с ID $messageId из таблицы chat_$chatId") - } - } - } catch (e: Exception) { - Log.e("ChatQueries", "Ошибка при удалении сообщения с ID $messageId из таблицы chat_$chatId", e) - } - - // Логируем окончание операции удаления - Log.i("ChatQueries", "Операция удаления сообщения с ID $messageId из таблицы chat_$chatId завершена") - } - } - - - // Удаление всех сообщений в чате - fun deleteAllMessages(chatId: String) { - val query = "DELETE FROM chat_$chatId" - Log.d(TAG, "Удаление всех сообщений из chat_$chatId") - try { - database.execSQL(query) - Log.d(TAG, "Все сообщения удалены из chat_$chatId") - } catch (e: Exception) { - Log.e(TAG, "Ошибка удаления всех сообщений из chat_$chatId: ${e.message}") - } - } -} - diff --git a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt index 94cfecb..7a4e6a7 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt @@ -62,6 +62,12 @@ class FriendsRepository(private val database: SQLiteDatabase) { fun insertOrUpdateFriend(friend: Friend) { try { + // Проверяем, что данные валидны + if (!isValidFriend(friend)) { + Log.e("FriendsRepository", "Invalid friend data: $friend") + return + } + val cursor = database.rawQuery("SELECT * FROM $TABLE_NAME WHERE token = ?", arrayOf(friend.token)) if (cursor.count > 0) { cursor.close() @@ -137,4 +143,12 @@ class FriendsRepository(private val database: SQLiteDatabase) { cursor.close() return friendsList } + + + private fun isValidFriend(friend: Friend): Boolean { + return !(friend.token.isNullOrEmpty() || friend.token == "null" || + friend.key.isNullOrEmpty() || friend.key == "null" || + friend.img.isNullOrEmpty() || friend.img == "null" || + friend.name.isNullOrEmpty() || friend.name == "null") + } } diff --git a/composeApp/src/androidMain/res/drawable/directions_bike_24px.xml b/composeApp/src/androidMain/res/drawable/directions_bike_24px.xml new file mode 100644 index 0000000..7c07c20 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/directions_bike_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/composeApp/src/androidMain/res/drawable/directions_car_24px.xml b/composeApp/src/androidMain/res/drawable/directions_car_24px.xml new file mode 100644 index 0000000..379343e --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/directions_car_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/composeApp/src/androidMain/res/drawable/directions_walk_24px.xml b/composeApp/src/androidMain/res/drawable/directions_walk_24px.xml new file mode 100644 index 0000000..6b764cf --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/directions_walk_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/composeApp/src/androidMain/res/layout/activity_map.xml b/composeApp/src/androidMain/res/layout/activity_map.xml index d0b939e..8ca6404 100644 --- a/composeApp/src/androidMain/res/layout/activity_map.xml +++ b/composeApp/src/androidMain/res/layout/activity_map.xml @@ -62,50 +62,76 @@ map:srcCompat="@drawable/search_24px" /> - + - + - + + + + + + + + + + + + - From 0fabe5891518d5cd5ec67458e1b23e8273d00f54 Mon Sep 17 00:00:00 2001 From: Ilya Prokofev <87380526+ilyaprofev@users.noreply.github.com> Date: Sun, 16 Feb 2025 22:17:11 +0300 Subject: [PATCH 5/5] =?UTF-8?q?=20ui=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Fragment/Chat_with_Friends_fragment.kt | 7 +- .../ViewModel/FindFriends_ViewModel.kt | 5 + .../SocialMap/ui/UI_Layers/MessageList.kt | 82 ------- .../SocialMap/ui/UI_Layers/Upbar.kt | 209 ++++++++++++++++++ 4 files changed, 219 insertions(+), 84 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Upbar.kt diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt index 6b68a1f..f64b264 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt @@ -35,6 +35,7 @@ import com.ilya.codewithfriends.presentation.profile.UID import com.ilya.codewithfriends.presentation.sign_in.GoogleAuthUiClient import com.ilya.meetmapkmp.SocialMap.DATAServices.Chat_Service.ChatWebSocketService import com.ilya.meetmapkmp.SocialMap.ViewModel.ChatViewModelFactory +import com.ilya.meetmapkmp.SocialMap.ViewModel.WebSocketViewModel import com.ilya.meetmapkmp.SocialMap.ui.UI_Layers.DeleteMessage import com.ilya.meetmapkmp.SocialMap.ui.UI_Layers.MessageList @@ -67,6 +68,7 @@ class Chat_with_Friends_fragment : Fragment() { return ComposeView(requireContext()).apply { setContent { + val WebSocketViewModel: WebSocketViewModel by viewModels() val name = UID(userData = googleAuthUiClient.getSignedInUser()) @@ -81,12 +83,13 @@ class Chat_with_Friends_fragment : Fragment() { Column( modifier = Modifier .fillMaxSize() - .padding(top = 50.dp) // Отступ сверху ) { NavHost(navController = navController, startDestination = "Friend") { composable("Friend") { - Upbar("https://imlhstamcqwacpgldxsf.supabase.co/storage/v1/object/public/avatars/9274c212-c82c-41e4-9fae-2d930c8c730f.png", "Ilya", "12:00") + // Верхний блок с данными пользователя + val userInfo = WebSocketViewModel.getFriends_by_token(token.toString()) + Upbar(Modifier, userInfo?.img.toString(), userInfo?.name.toString(), userInfo?.online.toString()) } composable("delete") { diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt index 0696d33..5ebe7e9 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt @@ -33,6 +33,8 @@ class WebSocketViewModel : ViewModel() { ) + + // StateFlow для хранения списка друзей private val _friendsList = MutableStateFlow>(emptyList()) val friendsList: StateFlow> = _friendsList.asStateFlow() @@ -79,6 +81,9 @@ class WebSocketViewModel : ViewModel() { friendsRepository.insertOrUpdateFriend(friend) } + fun getFriends_by_token(token: String) : Friend? { + return friendsRepository.getFriendByToken(token) + } // Метод для отправки команды по WebSocket fun sendCommand(command: String) { diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/MessageList.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/MessageList.kt index 93b2ec4..1f8b939 100644 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/MessageList.kt +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/MessageList.kt @@ -343,89 +343,7 @@ Row(modifier = Modifier) } -@Composable -fun Upbar( - Img_url: String, - name: String, - lasttime: String -) { - val coroutineScope = rememberCoroutineScope() - var isNavReady by remember { mutableStateOf(false) } - - - - Row( - modifier = Modifier - .fillMaxWidth() - .height(50.dp) - .background(Color(0xFF315FF3)) - ) { - Box(modifier = Modifier.weight(0.1f)) { - Icon( - painter = painterResource(id = R.drawable.arrow_back_24px), - contentDescription = "Back", - tint = Color.White, - modifier = Modifier - .padding(start = 12.dp) - .size(40.dp) - .clickable( - enabled = isNavReady, - onClick = { - - } - ) - ) - } - Spacer(modifier = Modifier.fillMaxWidth(0.1f)) - Box( - modifier = Modifier - .weight(0.3f) - .fillMaxHeight() - ) { - Image( - painter = rememberImagePainter( - data = Img_url, - builder = { - precision(Precision.EXACT) - } - ), - contentDescription = "Logo", - modifier = Modifier - .fillMaxSize() - .padding(start = 20.dp) - ) - } - Spacer(modifier = Modifier.fillMaxWidth(0.1f)) - - // имя и последнее время посещения - Column( - modifier = Modifier - .weight(0.5f) - .fillMaxHeight()){ - Text( - text = name, - textAlign = TextAlign.Start, - fontSize = 20.sp, - fontFamily = FontFamily(Font(R.font.open_sans_semi_condensed_regular)), - fontWeight = FontWeight.SemiBold, - color = Color.White, - modifier = Modifier - .padding(start = 10.dp) - ) - Text( - text = lasttime, - textAlign = TextAlign.Start, - fontSize = 14.sp, - fontFamily = FontFamily(Font(R.font.open_sans_semi_condensed_regular)), - fontWeight = FontWeight.SemiBold, - color = Color.White, - modifier = Modifier - .padding(start = 10.dp) - ) - } - } -} @Composable fun DeleteMessage(rooid: String, navController: NavController, chatViewModel: ChatViewModel) { diff --git a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Upbar.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Upbar.kt new file mode 100644 index 0000000..d879e8e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ui/UI_Layers/Upbar.kt @@ -0,0 +1,209 @@ +package com.ilya.meetmapkmp.SocialMap.ui.UI_Layers + +import android.content.ContentResolver +import android.content.Context +import android.media.Image +import android.net.Uri +import android.os.Build +import android.provider.MediaStore +import android.util.Log +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.unit.dp + +import kotlinx.coroutines.launch +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.IntrinsicSize +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.AddPhotoAlternate +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Image +import androidx.compose.material.icons.filled.Send +import androidx.compose.material.icons.filled.UploadFile +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Checkbox +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable + +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState + +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow + +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import coil.compose.AsyncImage +import coil.compose.rememberAsyncImagePainter +import coil.compose.rememberImagePainter +import coil.request.ImageRequest +import coil.size.Precision + +import com.ilya.meetmapkmp.R +import com.ilya.Supabase.androidLog +import com.ilya.Supabase.bucketManager + +import com.ilya.meetmapkmp.SocialMap.DataModel.Messages_Chat + +import com.ilya.meetmapkmp.SocialMap.ViewModel.ChatViewModel +import com.ilya.meetmapkmp.SocialMap.ViewModel.FileViewModel + +import createTempFileFromUri + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext +import java.io.File +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.UUID + +@Composable +fun Upbar( + modifier: Modifier, + Img_url: String, + name: String, + lasttime: String +) { + val coroutineScope = rememberCoroutineScope() + var isNavReady by remember { mutableStateOf(false) } + + + + Row( + modifier = modifier + .fillMaxWidth() + .height(60.dp) + .background(Color(0xFF315FF3)) + ) { + Box(modifier = modifier.weight(0.1f)) { + Icon( + painter = painterResource(id = R.drawable.arrow_back_24px), + contentDescription = "Back", + tint = Color.White, + modifier = modifier + .padding(start = 12.dp) + .size(40.dp) + .clickable( + enabled = isNavReady, + onClick = { + + } + ) + ) + } + Spacer(modifier = modifier.fillMaxWidth(0.1f)) + + Box( + modifier = modifier + .weight(0.3f) + .fillMaxHeight() + ) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(Img_url) + .crossfade(true) // Плавная анимация загрузки + .placeholder(R.drawable.placeholder) // Placeholder при загрузке + .error(R.drawable.photo_error_icon) // Изображение при ошибке + .build(), + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .size(60.dp) // Фиксированный размер + .clip(CircleShape) // Круглая форма + + ) + } + Spacer(modifier = modifier.fillMaxWidth(0.1f)) + + // имя и последнее время посещения + Column( + modifier = modifier + .weight(0.5f) + .fillMaxHeight()){ + Text( + text = name, + textAlign = TextAlign.Start, + fontSize = 20.sp, + fontFamily = FontFamily(Font(R.font.open_sans_semi_condensed_regular)), + fontWeight = FontWeight.SemiBold, + color = Color.White, + modifier = Modifier + .padding(start = 10.dp) + ) + Text( + text = lasttime, + textAlign = TextAlign.Start, + fontSize = 14.sp, + fontFamily = FontFamily(Font(R.font.open_sans_semi_condensed_regular)), + fontWeight = FontWeight.SemiBold, + color = Color.White, + modifier = Modifier + .padding(start = 10.dp) + ) + } + } +} \ No newline at end of file