From c11a26c09e3a43ec41f4868b0201031928c70788 Mon Sep 17 00:00:00 2001 From: fiatcode Date: Thu, 30 Apr 2026 11:02:09 +0700 Subject: [PATCH] feat: add TraccarHttpClient for server communication --- .../network/TraccarHttpClient.kt | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 android/app/src/main/kotlin/com/traccar/traccar_client/network/TraccarHttpClient.kt diff --git a/android/app/src/main/kotlin/com/traccar/traccar_client/network/TraccarHttpClient.kt b/android/app/src/main/kotlin/com/traccar/traccar_client/network/TraccarHttpClient.kt new file mode 100644 index 0000000..22eac76 --- /dev/null +++ b/android/app/src/main/kotlin/com/traccar/traccar_client/network/TraccarHttpClient.kt @@ -0,0 +1,80 @@ +package com.traccar.traccar_client.network + +import android.util.Base64 +import com.traccar.traccar_client.model.Location +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.OkHttpClient +import okhttp3.Request +import java.net.URLEncoder +import java.util.concurrent.TimeUnit + +data class TraccarConfig( + val serverUrl: String, + val deviceId: String, + val password: String = "" +) + +class TraccarHttpClient { + + private val client = OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build() + + suspend fun sendLocation(config: TraccarConfig, location: Location): Result = withContext(Dispatchers.IO) { + try { + val url = buildLocationUrl(config, location) + val request = buildRequest(config, url) + + val response = client.newCall(request).execute() + if (response.isSuccessful) { + Result.success(Unit) + } else { + Result.failure(Exception("HTTP ${response.code}: ${response.message}")) + } + } catch (e: Exception) { + Result.failure(e) + } + } + + suspend fun sendLocations(config: TraccarConfig, locations: List): Result = withContext(Dispatchers.IO) { + var successCount = 0 + for (location in locations) { + val result = sendLocation(config, location) + if (result.isSuccess) { + successCount++ + } + } + Result.success(successCount) + } + + private fun buildLocationUrl(config: TraccarConfig, location: Location): String { + val baseUrl = config.serverUrl.trimEnd('/') + val timestamp = location.timestamp + + return "$baseUrl/?" + + "id=${URLEncoder.encode(config.deviceId, "UTF-8")}" + + "&lat=${location.latitude}" + + "&lon=${location.longitude}" + + "×tamp=$timestamp" + + (location.speed?.let { "&speed=${it * 3.6}" } ?: "") + + (location.heading?.let { "&bearing=$it" } ?: "") + + (location.accuracy?.let { "&accuracy=$it" } ?: "") + + (location.altitude?.let { "&altitude=$it" } ?: "") + + "&is_moving=${if (location.isMoving) 1 else 0}" + } + + private fun buildRequest(config: TraccarConfig, url: String): Request { + val builder = Request.Builder().url(url).get() + + if (config.password.isNotEmpty()) { + val credentials = "${config.deviceId}:${config.password}" + val encoded = Base64.encodeToString(credentials.toByteArray(), Base64.NO_WRAP) + builder.addHeader("Authorization", "Basic $encoded") + } + + return builder.build() + } +} \ No newline at end of file