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..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 @@ -102,7 +104,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 @@ -170,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 @@ -190,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) @@ -512,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) @@ -615,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/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_invite.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_invite.kt deleted file mode 100644 index 477e79d..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/DATAServices/REST/Post_invite.kt +++ /dev/null @@ -1,13 +0,0 @@ -import okhttp3.ResponseBody -import retrofit2.Response -import retrofit2.http.POST -import retrofit2.http.Path - -interface PostInvite { - @POST("/friendrequest/{uid}/{key}/{friend_key}") - suspend fun postInvite( - @Path("uid") uid: String, - @Path("key") key: String, - @Path("friend_key") friend_key: String - ): Response -} \ 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..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,28 +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): ResponseBody? { +suspend fun postRequestAddFriends(uid: String, key: String, friendKey: String): String? { return try { + // Логирование начала выполнения функции + 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) { - response.body() // Возвращаем тело ответа + Log.d("PostRequestAddFriends", "POST request successful. Response code: ${response.code()}") + // Получаем тело ответа как объект TokenResponse + val responseBody = response.body() + if (responseBody != null) { + Log.d("PostRequestAddFriends", "Response body received: $responseBody") + // Извлекаем токен из объекта TokenResponse + val token = responseBody.token + Log.d("PostRequestAddFriends", "Token extracted successfully: $token") + return token + } else { + // Если тело ответа пустое + Log.e("PostRequestAddFriends", "Response body is null") + return null + } } else { - // Обработка ошибки - null + // Обработка ошибки HTTP + Log.e("PostRequestAddFriends", "HTTP error: ${response.code()}") + 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/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 95eaa37..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,10 +1,13 @@ package com.ilya.meetmapkmp.SocialMap.DataModel +import kotlinx.serialization.Serializable + +@Serializable data class Friend( val token: String, val img: String, val lastmessage: String, val name: String, val online: Boolean, - - ) \ No newline at end of file + val key: String +) \ 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/Chat_with_Friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Chat_with_Friends_fragment.kt index 3ede8c1..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 @@ -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 @@ -36,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 @@ -49,7 +49,6 @@ class Chat_with_Friends_fragment : Fragment() { ViewModelProvider(this, ChatViewModelFactory(requireContext())).get(ChatViewModel::class.java) } - private val friendsViewModel: FriendsViewModel by viewModels() @@ -69,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()) @@ -83,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/Fragment/Find_friends_fragment.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Fragment/Find_friends_fragment.kt index 8d83a0f..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 @@ -2,7 +2,7 @@ package com.example.yourapp.ui -import WebSocketFindFriends + import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -39,6 +39,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 +58,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 +71,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 +90,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 +116,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 +136,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 +161,6 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { textAlign = TextAlign.Start, modifier = Modifier.fillMaxWidth() ) - // Поле ввода TextField( modifier = Modifier .fillMaxWidth() @@ -194,39 +172,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,8 +292,41 @@ class Find_friends_fragment : Fragment(), WebSocketCallback_frinds { .wrapContentHeight(), onClick = { CoroutineScope(Dispatchers.IO).launch { - postRequestAddFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) - addFriends(uid.toString(), key = getUserKey(requireContext()).toString(), friendKey = friend.key) + 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 + ) + } catch (e: Exception) { + Log.e("FriendsRepository", "Error adding friend: ${e.message}") + } } }, colors = ButtonDefaults.buttonColors( @@ -329,4 +344,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/Post_invite.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt new file mode 100644 index 0000000..7508178 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/Interface/Post_invite.kt @@ -0,0 +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}/{friendKey}") + suspend fun postInvite( + @Path("uid") uid: String, + @Path("key") key: String, + @Path("friendKey") friendKey: String + ): Response +} \ No newline at end of file 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 new file mode 100644 index 0000000..5ebe7e9 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/FindFriends_ViewModel.kt @@ -0,0 +1,127 @@ +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 +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() { + + 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() + + + // StateFlow для хранения ошибок + private val _errors = MutableStateFlow("") + val errors: StateFlow = _errors.asStateFlow() + + // Инициализация WebSocketManager + private val webSocketManager = Websocket_find_friends() + + + + init { + + DriverFactory(context).createFriendsTable() + // Установка 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() + } + + fun addfriends_to_bd(friend: Friend){ + friendsRepository.insertOrUpdateFriend(friend) + } + + fun getFriends_by_token(token: String) : Friend? { + return friendsRepository.getFriendByToken(token) + } + + // Метод для отправки команды по 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/Friend_ViewItem.kt b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt deleted file mode 100644 index 2ff611a..0000000 --- a/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/Friend_ViewItem.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.ilya.meetmapkmp.SocialMap.ViewModel - - -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 kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch - - -class FriendsViewModel : ViewModel() { - val friendsList = mutableStateListOf() // Используем для хранения друзей - - - - // Функция для обновления данных друзей - 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/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..a554c5f --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/meetmapkmp/SocialMap/ViewModel/WebSocketViewModel.kt @@ -0,0 +1,133 @@ +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 +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 +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 : ViewModel() { + companion object { + private const val TAG = "WebSocketViewModel" + } + + private val context = AppContextProvider.getContext() + + // StateFlow для хранения состояния WebSocket + private val _webSocketState = MutableStateFlow(WebSocketState.Disconnected) + val webSocketState: StateFlow = _webSocketState.asStateFlow() + + // 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") + parseAndSaveFriends(jsonString) + }, + onErrorOccurred = { error -> + Log.e(TAG, "Error occurred: $error") + _webSocketState.value = WebSocketState.Error(error) + } + ) + + init { + DriverFactory(context).createFriendsTable() + + // Загрузка начальных данных + loadFriendsFromDatabase() + + // Подписка на изменения в базе данных + 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}") + } + } + } + + 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.") + } + + fun disconnect() { + Log.d(TAG, "Disconnecting WebSocket...") + webSocketService.disconnect() + _webSocketState.value = WebSocketState.Disconnected + Log.d(TAG, "WebSocket disconnected.") + } + + 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") + } + } + + fun deletefriends_from_bd(token: String){ + friendsRepository.deleteFriendByToken(token) + } + + + + 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() + } + } + } + + sealed class WebSocketState { + object Connecting : WebSocketState() + object Connected : WebSocketState() + object Disconnected : WebSocketState() + data class Error(val message: String) : WebSocketState() + } +} \ No newline at end of file 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/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 index 170d28b..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 @@ -14,45 +15,71 @@ 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.material.icons.filled.Warning +import androidx.compose.material3.AlertDialog 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.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 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.delay +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,68 +92,71 @@ 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 - + // Состояние для показа диалогового окна + 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 - ), + 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, @@ -138,4 +168,23 @@ fun FriendItem(friend: Friend, navController: NavController, context: android.co } } } -} + + // Показываем диалоговое окно, если 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/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 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 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..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,29 +49,28 @@ 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 ( - 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 friends ( + token TEXT PRIMARY KEY, + 'key' TEXT, + 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/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 new file mode 100644 index 0000000..7a4e6a7 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/com/ilya/platform/di/FriendsRepository.kt @@ -0,0 +1,154 @@ +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) { + + companion object { + private const val TABLE_NAME = "friends" + } + + // Интерфейс для уведомления об изменениях + 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 { + // Проверяем, что данные валидны + 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() + 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 + } + + + 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/drawable/photo_error_icon.png b/composeApp/src/androidMain/res/drawable/photo_error_icon.png new file mode 100644 index 0000000..2630b7d Binary files /dev/null and b/composeApp/src/androidMain/res/drawable/photo_error_icon.png differ diff --git a/composeApp/src/androidMain/res/drawable/placeholder.png b/composeApp/src/androidMain/res/drawable/placeholder.png new file mode 100644 index 0000000..f0e8d47 Binary files /dev/null and b/composeApp/src/androidMain/res/drawable/placeholder.png differ 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" /> - + - + - + + + + + + + + + + + + -