Some checks failed
release / build-release (push) Has been cancelled
- 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
70 lines
2.7 KiB
Kotlin
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"))
|
|
}
|
|
}
|
|
}
|