Files
FitnessDroid/app/src/main/java/eu/brassepc/fitnessdroid/data/GraphQlClient.kt
claude 984e2374f8
Some checks failed
release / build-release (push) Has been cancelled
M2: passläge, offline-först med synk-kö, vilotimer med inställningar
- Room-cache: övningar/muskelgrupper/favoritkort + lokal-först-pass
- SyncEngine: FIFO-op-kö mot API:t, retry med backoff, synk-status per set
  (moln-ikon) + global räknare; nätfel loggar inte ut användaren
- Passläge: en övning i taget (svep), ±steppers, RPE, förifyllda värden
  från förra passet, logga set med ett tryck
- Vilotimer: helskärm eller banner (inställning), larm/vibration/visuell/
  inget vid utgång, ±15s, per-övnings vilotid via API:ts defaultRestSeconds
- Övningsväljare: sök + muskelgrupp/muskel-chips med ikoner (API:ts iconKey
  med namnheuristik som fallback), favoriter först, KG/REPS/TID/M-brickor
- Navigation: bottenrad + start-FAB + minispelare, hemskärm med favoritpass
- Inställningsskärm under Profil; historik-lista; version 0.2.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Kf4MAbZ3c3B4Xy5XfuGsCP
2026-07-24 13:29:17 +02:00

70 lines
2.7 KiB
Kotlin

package eu.brassepc.fitnessdroid.data
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.concurrent.TimeUnit
class GraphQlException(val messages: List<String>) : Exception(messages.joinToString("; "))
/**
* Minimal GraphQL-klient över OkHttp. Samma request-form som webbklienten
* (gymTrackerApi.js): POST {query, variables} + "Authorization: Bearer <jwt>".
*/
class GraphQlClient(private val urlProvider: suspend () -> String) {
private val http = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val json = Json { ignoreUnknownKeys = true }
suspend fun execute(
query: String,
variables: JsonObject = buildJsonObject {},
token: String? = null,
): JsonObject = withContext(Dispatchers.IO) {
val body = buildJsonObject {
put("query", query)
put("variables", variables)
}
val request = Request.Builder()
.url(urlProvider())
.post(body.toString().toRequestBody("application/json".toMediaType()))
.apply { if (token != null) header("Authorization", "Bearer $token") }
.build()
http.newCall(request).execute().use { response ->
val text = response.body?.string().orEmpty()
// 5xx = servern/proxyn nere — behandla som nätfel (retry) och inte
// som ett logiskt API-fel (som t.ex. loggar ut användaren).
if (response.code >= 500) {
throw java.io.IOException("HTTP ${response.code} från servern")
}
val root = runCatching { json.parseToJsonElement(text).jsonObject }.getOrNull()
?: throw GraphQlException(listOf("HTTP ${response.code}: oväntat svar från servern"))
root["errors"]?.jsonArray?.takeIf { it.isNotEmpty() }?.let { errors ->
throw GraphQlException(
errors.map {
it.jsonObject["message"]?.jsonPrimitive?.content ?: "Okänt fel"
}
)
}
root["data"]?.jsonObject
?: throw GraphQlException(listOf("HTTP ${response.code}: tomt svar från servern"))
}
}
}