F2.4 Android: agregar evidencias fotográficas al Hallazgo
This commit is contained in:
+94
-2
@@ -6,15 +6,24 @@ import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import retrofit2.HttpException
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Multipart
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Part
|
||||
import retrofit2.http.Path
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
|
||||
data class FieldFindingAct(
|
||||
val id: String,
|
||||
@@ -64,6 +73,30 @@ data class FieldFindingItem(
|
||||
val correctionDueOn: String? = null,
|
||||
)
|
||||
|
||||
data class FieldFindingEvidence(
|
||||
val id: String,
|
||||
val findingId: String,
|
||||
val kind: String,
|
||||
val purpose: String,
|
||||
val originalName: String,
|
||||
val mimeType: String,
|
||||
val sizeBytes: Long,
|
||||
val sha256: String,
|
||||
val title: String? = null,
|
||||
val description: String? = null,
|
||||
val capturedAt: String? = null,
|
||||
val latitude: Double? = null,
|
||||
val longitude: Double? = null,
|
||||
val accuracyM: Double? = null,
|
||||
val deviceLabel: String? = null,
|
||||
val source: String,
|
||||
val createdAt: String,
|
||||
)
|
||||
|
||||
data class FieldFindingEvidenceListResponse(
|
||||
val data: List<FieldFindingEvidence> = emptyList(),
|
||||
)
|
||||
|
||||
data class FieldFindingOptionsResponse(
|
||||
val act: FieldFindingAct,
|
||||
val capture: CaptureStatus = CaptureStatus(),
|
||||
@@ -104,13 +137,36 @@ private interface FieldFindingsApi {
|
||||
@Body request: CreateFieldFindingRequest,
|
||||
): FieldFindingCreateResponse
|
||||
|
||||
@GET("inspection-findings/{findingId}/evidence")
|
||||
suspend fun evidence(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("findingId") findingId: String,
|
||||
): FieldFindingEvidenceListResponse
|
||||
|
||||
@Multipart
|
||||
@POST("inspection-findings/{findingId}/evidence")
|
||||
suspend fun uploadEvidence(
|
||||
@Header("Authorization") authorization: String,
|
||||
@Path("findingId") findingId: String,
|
||||
@Part file: MultipartBody.Part,
|
||||
@Part("kind") kind: RequestBody,
|
||||
@Part("purpose") purpose: RequestBody,
|
||||
@Part("title") title: RequestBody?,
|
||||
@Part("description") description: RequestBody?,
|
||||
@Part("capturedAt") capturedAt: RequestBody,
|
||||
@Part("latitude") latitude: RequestBody,
|
||||
@Part("longitude") longitude: RequestBody,
|
||||
@Part("accuracyM") accuracyM: RequestBody?,
|
||||
@Part("deviceLabel") deviceLabel: RequestBody,
|
||||
): FieldFindingEvidence
|
||||
|
||||
@POST("auth/mobile/refresh")
|
||||
suspend fun refresh(@Body request: RefreshRequest): MobileSessionResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Cliente separado para F2.3. Comparte el almacén cifrado de sesión de la APK,
|
||||
* pero mantiene el contrato de Hallazgos desacoplado del cliente F2.2.
|
||||
* Cliente de campo para Hallazgos y sus evidencias append-only.
|
||||
* Comparte el almacén cifrado de sesión y nunca persiste la contraseña.
|
||||
*/
|
||||
class FieldFindingsRepository(context: Context) {
|
||||
private val store = SecureSessionStore(context.applicationContext)
|
||||
@@ -136,6 +192,42 @@ class FieldFindingsRepository(context: Context) {
|
||||
api.create("Bearer ${session.accessToken}", visitId, assetId, request)
|
||||
}
|
||||
|
||||
suspend fun evidence(findingId: String): FieldFindingEvidenceListResponse = authorized { session ->
|
||||
api.evidence("Bearer ${session.accessToken}", findingId)
|
||||
}
|
||||
|
||||
suspend fun uploadObservationPhoto(
|
||||
findingId: String,
|
||||
file: File,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
accuracyM: Double?,
|
||||
title: String? = null,
|
||||
description: String? = null,
|
||||
capturedAt: String = Instant.now().toString(),
|
||||
): FieldFindingEvidence = authorized { session ->
|
||||
val text = "text/plain".toMediaType()
|
||||
val part = MultipartBody.Part.createFormData(
|
||||
"file",
|
||||
file.name,
|
||||
file.asRequestBody("image/jpeg".toMediaType()),
|
||||
)
|
||||
api.uploadEvidence(
|
||||
authorization = "Bearer ${session.accessToken}",
|
||||
findingId = findingId,
|
||||
file = part,
|
||||
kind = "PHOTO".toRequestBody(text),
|
||||
purpose = "OBSERVATION".toRequestBody(text),
|
||||
title = title?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text),
|
||||
description = description?.trim()?.takeIf { it.isNotBlank() }?.toRequestBody(text),
|
||||
capturedAt = capturedAt.toRequestBody(text),
|
||||
latitude = latitude.toString().toRequestBody(text),
|
||||
longitude = longitude.toString().toRequestBody(text),
|
||||
accuracyM = accuracyM?.toString()?.toRequestBody(text),
|
||||
deviceLabel = "DH Android".toRequestBody(text),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun <T> authorized(block: suspend (StoredSession) -> T): T {
|
||||
var session = store.load() ?: throw IllegalStateException("Sesión no iniciada")
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user