Endurain: Update server-side workout type after uploading

This commit is contained in:
Arjan Schrijver
2026-06-19 23:31:23 +02:00
parent 8cd90f5a66
commit 67ee952634
2 changed files with 105 additions and 8 deletions
@@ -19,7 +19,10 @@ package nodomain.freeyourgadget.gadgetbridge.activities.endurain
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import com.google.gson.Gson import com.google.gson.Gson
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind
import nodomain.freeyourgadget.gadgetbridge.util.InternetUtils import nodomain.freeyourgadget.gadgetbridge.util.InternetUtils
import org.json.JSONArray
import org.json.JSONObject
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@@ -258,7 +261,7 @@ class EndurainApiClient(
/** /**
* Upload activity file (GPX) * Upload activity file (GPX)
*/ */
fun uploadActivity(file: File, callback: (Boolean) -> Unit) { fun uploadActivity(file: File, callback: (Int?) -> Unit) {
Thread { Thread {
try { try {
val uri = "$baseUrl/api/v1/activities/create/upload".toUri() val uri = "$baseUrl/api/v1/activities/create/upload".toUri()
@@ -270,19 +273,55 @@ class EndurainApiClient(
requestHeaders = headers requestHeaders = headers
) { success, responseText -> ) { success, responseText ->
if (success && responseText != null) { if (success && responseText != null) {
callback(true) val jsonArray = JSONArray(responseText)
val firstObject = jsonArray.getJSONObject(0)
val id = firstObject.getInt("id")
callback(id)
} else { } else {
LOG.error("Activity upload failed") LOG.error("Activity upload failed")
callback(false) callback(null)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
LOG.error("Activity upload error", e) LOG.error("Activity upload error", e)
callback(false) callback(null)
} }
}.start() }.start()
} }
/**
* Edit uploaded activity
*/
fun editActivity(id: Int, activityKind: ActivityKind, name: String): Boolean {
try {
val uri = "$baseUrl/api/v1/activities/edit".toUri()
val headers = buildHeaders(AuthType.AUTH_TOKEN)
var activityType = 10 // Generic workout
if (activityLookup.containsKey(activityKind.ordinal)) {
activityType = activityLookup[activityKind.ordinal]!!
}
val bodyJson = JSONObject().apply {
put("id", id)
put("activity_type", activityType)
put("name", name)
}
val result = InternetUtils.doStringRequest(
method = "PUT",
uri = uri,
requestHeaders = headers,
body = bodyJson.toString()
)
LOG.info("editActivity result: {}", result)
return true
} catch (e: Exception) {
LOG.error("Activity edit error", e)
return false
}
}
/** /**
* Fetch server version string * Fetch server version string
*/ */
@@ -302,4 +341,54 @@ class EndurainApiClient(
return null return null
} }
} }
}
/**
* Lookup map to convert ActivityKind to the integer Endurain expects,
* based on https://docs.endurain.com/developer-guide/supported-types/
*/
val activityLookup = mapOf(
ActivityKind.RUNNING.ordinal to 1,
ActivityKind.TRAIL_RUN.ordinal to 2,
ActivityKind.VIRTUAL_RUN.ordinal to 3,
ActivityKind.CYCLING.ordinal to 4,
ActivityKind.ROAD_BIKE.ordinal to 4,
ActivityKind.GRAVEL_BIKE.ordinal to 5,
ActivityKind.MOUNTAIN_BIKE.ordinal to 6,
ActivityKind.POOL_SWIM.ordinal to 8,
ActivityKind.SWIMMING_OPENWATER.ordinal to 9,
ActivityKind.TRAINING.ordinal to 10,
ActivityKind.WALKING.ordinal to 11,
ActivityKind.HIKING.ordinal to 12,
ActivityKind.ROWING.ordinal to 13,
ActivityKind.YOGA.ordinal to 14,
ActivityKind.SKIING.ordinal to 15,
ActivityKind.SNOWBOARDING.ordinal to 17,
ActivityKind.TRANSITION.ordinal to 18,
ActivityKind.STRENGTH_TRAINING.ordinal to 19,
ActivityKind.CROSSFIT.ordinal to 20,
ActivityKind.TENNIS.ordinal to 21,
ActivityKind.TABLE_TENNIS.ordinal to 22,
ActivityKind.BADMINTON.ordinal to 23,
ActivityKind.SQUASH.ordinal to 24,
ActivityKind.RACQUETBALL.ordinal to 25,
ActivityKind.PICKLEBALL.ordinal to 26,
ActivityKind.BIKE_COMMUTE.ordinal to 27,
ActivityKind.INDOOR_CYCLING.ordinal to 28,
ActivityKind.WINDSURFING.ordinal to 30,
ActivityKind.INDOOR_WALKING.ordinal to 31,
ActivityKind.STAND_UP_PADDLEBOARDING.ordinal to 32,
ActivityKind.SURFING.ordinal to 33,
ActivityKind.TRACK_RUN.ordinal to 34,
ActivityKind.E_BIKE.ordinal to 35,
ActivityKind.E_MOUNTAIN_BIKE.ordinal to 36,
ActivityKind.ICE_SKATING.ordinal to 37,
ActivityKind.SOCCER.ordinal to 38,
ActivityKind.PADEL.ordinal to 39,
ActivityKind.TREADMILL.ordinal to 40,
ActivityKind.CARDIO.ordinal to 41,
ActivityKind.KAYAKING.ordinal to 42,
ActivityKind.SAILING.ordinal to 43,
ActivityKind.INLINE_SKATING.ordinal to 45,
ActivityKind.HIIT.ordinal to 46
)
}
@@ -746,7 +746,10 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider {
private fun uploadToEndurain() { private fun uploadToEndurain() {
val workout = currentWorkout ?: return val workout = currentWorkout ?: return
val gpxFile = ActivitySummaryUtils.getGpxFile(workout.summary) val workoutName = currentWorkout!!.summary.name
val activityKind = ActivityKind.fromCode(currentWorkout!!.summary.activityKind)
val activityTrackProvider = gbDevice.deviceCoordinator.getActivityTrackProvider(gbDevice, requireContext())
val gpxFile = ActivitySummaryUtils.getShareableGpxFile(activityTrackProvider, workout.summary)
if (gpxFile == null) { if (gpxFile == null) {
GB.toast("No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO) GB.toast("No GPX track in this activity", Toast.LENGTH_LONG, GB.INFO)
@@ -758,9 +761,14 @@ class WorkoutDetailsFragment : Fragment(), MenuProvider {
val serverUrl = GBApplication.getPrefs().preferences.getString("endurain_server", null) val serverUrl = GBApplication.getPrefs().preferences.getString("endurain_server", null)
val apiClient = EndurainApiClient(serverUrl!!, endurainVm.tokenManager) val apiClient = EndurainApiClient(serverUrl!!, endurainVm.tokenManager)
endurainVm.tokenManager.performTokenRefresh(serverUrl) { endurainVm.tokenManager.performTokenRefresh(serverUrl) {
apiClient.uploadActivity(gpxFile) { success -> LOG.info("Uploading workout '{}' (type {}) to Endurain", workoutName, activityKind)
apiClient.uploadActivity(gpxFile) { newId ->
if (newId != null) {
// Update activity type on the server
apiClient.editActivity(newId, activityKind, workoutName)
}
activity?.runOnUiThread { activity?.runOnUiThread {
if (success) if (newId != null)
GB.toast( GB.toast(
"Successfully uploaded to Endurain", "Successfully uploaded to Endurain",
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,