feat(CON-265) : Aligner les flux de l'api d'UI avec les nouveaux dev back

This commit is contained in:
2025-02-11 17:00:53 +01:00
parent c57c9b7d05
commit af91841857
36 changed files with 144 additions and 176 deletions

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -3,13 +3,15 @@ package fr.openium.consentium_ui.data.remote
import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Path
internal interface ConsentiumUIApi {
@GET("consent-config")
@GET("applications/{application}")
suspend fun getConsentConfig(
applicationID: String,
installationID: String,
@Header("Authorization") token: String,
@Path("application") applicationId: String,
): Response<GetConsentConfigDTO>
}

View File

@ -6,8 +6,6 @@ import fr.openium.consentium_ui.data.remote.model.MainConsentTextTranslationDTO
import fr.openium.consentium_ui.data.remote.model.PurposeDTO
import fr.openium.consentium_ui.data.remote.model.PurposeStatusDTO
import fr.openium.consentium_ui.data.remote.model.PurposeTranslationDTO
import fr.openium.consentium_ui.data.remote.model.VendorDTO
import fr.openium.consentium_ui.data.remote.model.VendorTranslationDTO
import retrofit2.Response
import java.util.UUID
@ -17,9 +15,6 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
installationId = UUID.randomUUID().toString(),
appName = "Consentium",
icon = "https://amp.openium.fr/openium.png",
primaryColor = "#FF0000",
secondaryColor = "#00FF00",
textColor = "#0000FF",
consentMainTextTranslation = listOf(
MainConsentTextTranslationDTO(
id = "UUID",
@ -29,12 +24,13 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
durationText = "<p>Nous conservons votre choix pendant 12 mois. Vous pouvez changer davis à tout moment depuis les paramètres de votre compte via longlet “Notification et cookies” tetetettetettetetstts</p>\n"
)
),
consentPageUrl = "https://www.openium.fr",
purposes = listOf(
PurposeDTO(
id = "purpose-required",
order = 0,
isRequired = true,
isAccepted = PurposeStatusDTO.ACCEPTED,
choice = PurposeStatusDTO.ACCEPTED,
translations = listOf(
PurposeTranslationDTO(
id = "UUID",
@ -43,40 +39,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Nécessaire"
)
),
vendors = listOf(
VendorDTO(
id = "vendors-crashlytics",
order = 0,
isAccepted = true,
isRequired = true,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaire au fonctionnement de lapplication. Ils permettent de vérifier la stabilité technique de lapplication et de mesurer notre audience. Ces données ne sont utilisées que pour notre compte exclusif (en ne produisant que des données statistiques anonymes).</p>"
)
)
),
VendorDTO(
id = "vendors-matomo",
order = 1,
isAccepted = true,
isRequired = false,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaire au fonctionnement de lapplication. Ils permettent de vérifier la stabilité technique de lapplication et de mesurer notre audience. Ces données ne sont utilisées que pour notre compte exclusif (en ne produisant que des données statistiques anonymes).</p>"
)
)
)
)
),
PurposeDTO(
id = "purpose-advertising",
order = 1,
isRequired = false,
isAccepted = PurposeStatusDTO.REJECTED,
choice = PurposeStatusDTO.REFUSED,
translations = listOf(
PurposeTranslationDTO(
id = "UUID",
@ -85,27 +53,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Publicité"
)
),
vendors = listOf(
VendorDTO(
id = "vendors-admob",
order = 0,
isAccepted = true,
isRequired = false,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaires pour afficher des publicités susceptibles de vous intéresser. Ils permettent de mesurer lefficacité de nos campagnes publicitaires et de personnaliser les publicités affichées.</p>"
)
)
)
)
),
PurposeDTO(
id = "purpose-analytics",
order = 2,
isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED,
choice = PurposeStatusDTO.ACCEPTED,
translations = listOf(
PurposeTranslationDTO(
id = "UUID",
@ -114,27 +67,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Analyse"
)
),
vendors = listOf(
VendorDTO(
id = "vendors-firebase",
order = 0,
isAccepted = true,
isRequired = false,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaires pour mesurer lefficacité de nos contenus. Ils permettent de mesurer laudience de lapplication et de comprendre comment les utilisateurs interagissent avec lapplication.</p>"
)
)
)
)
),
PurposeDTO(
id = "purpose-personalization",
order = 3,
isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED,
choice = PurposeStatusDTO.ACCEPTED,
translations = listOf(
PurposeTranslationDTO(
id = "UUID",
@ -143,27 +81,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Personnalisation"
)
),
vendors = listOf(
VendorDTO(
id = "vendors-firebase",
order = 0,
isAccepted = true,
isRequired = false,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaires pour mesurer lefficacité de nos contenus. Ils permettent de mesurer laudience de lapplication et de comprendre comment les utilisateurs interagissent avec lapplication.</p>"
)
)
)
)
),
PurposeDTO(
id = "purpose-social",
order = 4,
isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED,
choice = PurposeStatusDTO.ACCEPTED,
translations = listOf(
PurposeTranslationDTO(
id = "UUID",
@ -172,28 +95,13 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Social"
)
),
vendors = listOf(
VendorDTO(
id = "vendors-firebase",
order = 0,
isAccepted = true,
isRequired = false,
translations = listOf(
VendorTranslationDTO(
id = "UUID",
language = "fr",
text = "<p>Ces traceurs sont nécessaires pour mesurer lefficacité de nos contenus. Ils permettent de mesurer laudience de lapplication et de comprendre comment les utilisateurs interagissent avec lapplication.</p>"
)
)
)
)
)
)
)
override suspend fun getConsentConfig(
applicationID: String,
installationID: String,
token: String,
applicationId: String,
): Response<GetConsentConfigDTO> {
return Response.success(consents)
}

View File

@ -7,10 +7,8 @@ import kotlinx.serialization.Serializable
internal data class GetConsentConfigDTO(
@SerialName("id") val installationId: String,
@SerialName("name") val appName: String,
@SerialName("icon") val icon: String,
@SerialName("primaryColor") val primaryColor: String,
@SerialName("secondaryColor") val secondaryColor: String,
@SerialName("textColor") val textColor: String,
@SerialName("translation") val consentMainTextTranslation: List<MainConsentTextTranslationDTO>,
@SerialName("icon") val icon: String? = null,
@SerialName("consentPageUrl") val consentPageUrl: String,
@SerialName("translations") val consentMainTextTranslation: List<MainConsentTextTranslationDTO>,
@SerialName("purposes") val purposes: List<PurposeDTO>,
)

View File

@ -7,7 +7,8 @@ import kotlinx.serialization.Serializable
internal data class MainConsentTextTranslationDTO(
@SerialName("id") val id: String,
@SerialName("lang") val language: String,
@SerialName("consentPageUrl") val consentPageUrl: String,
@SerialName("mainConsentText") val mainConsentText: String,
@SerialName("durationText") val durationText: String,
@SerialName("consentPageUrl") val consentPageUrl: String,
)

View File

@ -5,10 +5,10 @@ import kotlinx.serialization.Serializable
@Serializable
internal data class PurposeDTO(
@SerialName("identifier") val id: String,
@SerialName("order") val order: Int,
@SerialName("sortOrder") val order: Int,
@SerialName("isRequired") val isRequired: Boolean,
@SerialName("isAccepted") val isAccepted: PurposeStatusDTO,
@SerialName("choice") val choice: PurposeStatusDTO,
@SerialName("translations") val translations: List<PurposeTranslationDTO>,
@SerialName("vendors") val vendors: List<VendorDTO>,
@SerialName("identifier") val id: String,
)

View File

@ -5,12 +5,12 @@ import kotlinx.serialization.Serializable
@Serializable
internal enum class PurposeStatusDTO {
@SerialName("ACCEPTED")
@SerialName("accepted")
ACCEPTED,
@SerialName("REJECTED")
REJECTED,
@SerialName("refused")
REFUSED,
@SerialName("NOT_DEFINED")
NOT_DEFINED,
@SerialName("partial")
PARTIAL,
}

View File

@ -7,9 +7,9 @@ internal fun PurposeDTO.toPurposeData() =
PurposeData(
identifier = id,
isRequired = isRequired,
isAccepted = isAccepted.toPurposeStatusData(),
choice = choice.toPurposeStatusData(),
order = order,
vendors = vendors.toVendorDataList(),
vendors = emptyList(),
translations = translations.toPurposeTranslationDataList(),
)

View File

@ -6,7 +6,7 @@ import fr.openium.consentium_ui.domain.model.PurposeStatusData
internal fun PurposeStatusDTO.toPurposeStatusData(): PurposeStatusData {
return when (this) {
PurposeStatusDTO.ACCEPTED -> PurposeStatusData.ACCEPTED
PurposeStatusDTO.REJECTED -> PurposeStatusData.REJECTED
PurposeStatusDTO.NOT_DEFINED -> PurposeStatusData.NOT_DEFINED
PurposeStatusDTO.REFUSED -> PurposeStatusData.REJECTED
PurposeStatusDTO.PARTIAL -> PurposeStatusData.PARTIAL
}
}

View File

@ -2,7 +2,7 @@ package fr.openium.consentium_ui.domain.model
internal data class ContentConfigData(
val applicationName: String,
val iconUrl : String,
val iconUrl : String?,
val mainTextTranslation: List<MainConsentTextTranslationData>,
val purposes: List<PurposeData>
)

View File

@ -4,7 +4,7 @@ internal data class PurposeData(
val identifier: String,
val order: Int,
val isRequired: Boolean,
val isAccepted: PurposeStatusData,
val choice: PurposeStatusData,
val translations: List<PurposeTranslationData>,
val vendors: List<VendorData>,
)

View File

@ -3,5 +3,5 @@ package fr.openium.consentium_ui.domain.model
internal enum class PurposeStatusData {
ACCEPTED,
REJECTED,
NOT_DEFINED,
PARTIAL,
}

View File

@ -1,22 +1,23 @@
package fr.openium.consentium_ui.domain.repository
import fr.openium.consentium.domain.useCase.GetConsentiumUniqueInstallationIdUseCase
import fr.openium.consentium.domain.useCase.GetAuthTokenUseCase
import fr.openium.consentium_ui.data.remote.ConsentiumUIApi
import fr.openium.consentium_ui.domain.adapter.toConsentConfigData
import fr.openium.consentium_ui.domain.model.ContentConfigData
import timber.log.Timber
import javax.inject.Inject
internal class ConsentiumRepository @Inject constructor(
private val getConsentiumUniqueInstallationIdUseCase: GetConsentiumUniqueInstallationIdUseCase,
private val consentiumUIApi: ConsentiumUIApi,
private val getAuthTokenUseCase: GetAuthTokenUseCase,
) {
suspend fun getConsentiumConfig(
applicationId: String,
apiKey: String,
): ConsentiumUIRepositoryResponse {
val installationId = getConsentiumUniqueInstallationIdUseCase.invoke()
val consentsResponse = consentiumUIApi.getConsentConfig(applicationId, installationId)
return try {
val authToken = getAuthTokenUseCase(apiKey)
val consentsResponse = consentiumUIApi.getConsentConfig(authToken, applicationId)
val consentsBody = if (consentsResponse.isSuccessful) {
consentsResponse.body() ?: throw Exception()
} else {
@ -25,10 +26,10 @@ internal class ConsentiumRepository @Inject constructor(
ConsentiumUIRepositoryResponse.Success(consentsBody.toConsentConfigData())
} catch (e: Exception) {
Timber.d("$e")
ConsentiumUIRepositoryResponse.Error
}
}
}
internal interface ConsentiumUIRepositoryResponse {

View File

@ -27,6 +27,7 @@ internal fun ConsentiumScreen(
onSaveAndCloseDetails: (consents: DetailConsentUI) -> Unit,
onNavigateToDetails: () -> Unit,
onClickCookiesPolicies: () -> Unit,
onClickRetry: () -> Unit,
) {
when (state) {
is ConsentiumUIState.Loading -> {
@ -36,6 +37,7 @@ internal fun ConsentiumScreen(
is ConsentiumUIState.Error -> {
ConsentiumUIErrorComponent(
errorMessage = state.message,
onRetry = onClickRetry
)
}

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import fr.openium.consentium_ui.R
import fr.openium.consentium_ui.domain.repository.ConsentiumRepository
import fr.openium.consentium_ui.domain.repository.ConsentiumUIRepositoryResponse
import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCase
@ -30,12 +31,12 @@ internal class ConsentiumUIViewModel @Inject constructor(
private val _state = MutableStateFlow<ConsentiumUIState>(ConsentiumUIState.Loading)
val state: StateFlow<ConsentiumUIState> by lazy { _state.asStateFlow() }
fun init(appId: String) {
fun init(appId: String, apiKey: String) {
viewModelScope.launch {
_state.value = ConsentiumUIState.Loading
when (val consentiumUIFetchResponse = consentiumUIRepository.getConsentiumConfig(appId)) {
when (val consentiumUIFetchResponse = consentiumUIRepository.getConsentiumConfig(appId, apiKey)) {
is ConsentiumUIRepositoryResponse.Success -> {
val consentUIResponse =
@ -44,28 +45,32 @@ internal class ConsentiumUIViewModel @Inject constructor(
when (val consentUI = consentUIResponse) {
is GetConfigTextForLanguageUseCaseResponse.Success -> {
_state.emit(ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(),
))
_state.emit(
ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(),
)
)
}
is GetConfigTextForLanguageUseCaseResponse.DefaultLanguage -> {
_state.emit(ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(),
))
_state.emit(
ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(),
)
)
}
is GetConfigTextForLanguageUseCaseResponse.Error -> {
_state.emit(ConsentiumUIState.Error("Failed to load data"))
_state.emit(ConsentiumUIState.Error(context.getString(R.string.consents_error_loading_consents)))
}
}
}
is ConsentiumUIRepositoryResponse.Error -> {
_state.value = ConsentiumUIState.Error("Failed to load data")
_state.value = ConsentiumUIState.Error(context.getString(R.string.consents_error_loading_consents))
}
}

View File

@ -1,6 +1,7 @@
package fr.openium.consentium_ui.ui.adapter
import fr.openium.consentium.api.model.PurposeChoice
import fr.openium.consentium.api.model.PurposeStatus
import fr.openium.consentium_ui.domain.model.ContentConfigData
import fr.openium.consentium_ui.ui.model.DetailConsentUI
import fr.openium.consentium_ui.ui.model.PurposeStatusUI
@ -17,16 +18,23 @@ internal fun DetailConsentUI.toPurposeChoices(): List<PurposeChoice> = purposes.
}
internal fun DetailConsentUI.toDeniedPurposeChoices(): List<PurposeChoice> = purposes.map {
it.toPurposeChoice().copy(isAccepted = false)
it.toPurposeChoice().copy(choice = PurposeStatus.REJECTED)
}
internal fun DetailConsentUI.toAcceptedPurposeChoices(): List<PurposeChoice> = purposes.map {
it.toPurposeChoice().copy(isAccepted = true)
it.toPurposeChoice().copy(choice = PurposeStatus.ACCEPTED)
}
internal fun PurposeUI.toPurposeChoice(): PurposeChoice =
PurposeChoice(
purposeIdentifier = id,
isAccepted = isAccepted == PurposeStatusUI.ACCEPTED,
choice = choice.toPurposeStatus(),
vendors = emptyList(), // Not in v1
)
)
internal fun PurposeStatusUI.toPurposeStatus(): PurposeStatus =
when (this) {
PurposeStatusUI.ACCEPTED -> PurposeStatus.ACCEPTED
PurposeStatusUI.REJECTED -> PurposeStatus.REJECTED
PurposeStatusUI.PARTIAL -> PurposeStatus.PARTIAL
}

View File

@ -6,5 +6,5 @@ import fr.openium.consentium_ui.ui.model.PurposeStatusUI
internal fun PurposeStatusData.toPurposeStatusUI(): PurposeStatusUI = when(this) {
PurposeStatusData.ACCEPTED -> PurposeStatusUI.ACCEPTED
PurposeStatusData.REJECTED -> PurposeStatusUI.REJECTED
PurposeStatusData.NOT_DEFINED -> PurposeStatusUI.NOT_DEFINED
PurposeStatusData.PARTIAL -> PurposeStatusUI.PARTIAL
}

View File

@ -6,7 +6,7 @@ import fr.openium.consentium_ui.ui.model.PurposeUI
internal fun PurposeData.toPurposeUI(): PurposeUI = PurposeUI(
id = identifier,
isRequired = isRequired,
isAccepted = isAccepted.toPurposeStatusUI(),
choice = choice.toPurposeStatusUI(),
title = translations.first().name,
description = translations.first().text,
vendors = vendors.toVendorUIList(),

View File

@ -51,7 +51,7 @@ fun ConsentiumComponent(
// Effect
LaunchedEffect(Unit) {
viewModel.init(consentium.applicationId)
viewModel.init(apiKey = consentium.apiKey, appId = consentium.appId)
}
LaunchedEffect(consentiumState) {
@ -131,6 +131,9 @@ fun ConsentiumComponent(
onClickCookiesPolicies = {
currentPage = ConsentiumPageUI.COOKIES_POLICY
},
onClickRetry = {
viewModel.init(apiKey = consentium.apiKey, appId = consentium.appId)
}
)
}
}

View File

@ -2,19 +2,31 @@ package fr.openium.consentium_ui.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import fr.openium.consentium_ui.R
import fr.openium.consentium_ui.ui.components.core.button.ConsentButton
import fr.openium.consentium_ui.ui.components.core.button.ConsentiumUIButtonStyle
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
@Composable
internal fun ConsentiumUIErrorComponent(
errorMessage: String,
onRetry: () -> Unit,
) {
Column(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
@ -22,6 +34,15 @@ internal fun ConsentiumUIErrorComponent(
text = errorMessage,
color = ConsentiumUITheme.colors.error,
style = ConsentiumUITheme.typography.b2,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(24.dp))
ConsentButton(
text = stringResource(R.string.retry),
buttonStyle = ConsentiumUIButtonStyle.PRIMARY,
onclick = onRetry,
)
}
}

View File

@ -28,7 +28,7 @@ internal fun PurposeComponent(
purposeUI: PurposeUI,
) {
// Properties
var isChecked by remember { mutableStateOf(purposeUI.isAccepted != PurposeStatusUI.REJECTED) }
var isChecked by remember { mutableStateOf(purposeUI.choice != PurposeStatusUI.REJECTED) }
// View
Column(
@ -58,7 +58,7 @@ internal fun PurposeComponent(
checked = isChecked,
onCheckedChange = {
isChecked = !isChecked
purposeUI.isAccepted = if (isChecked) PurposeStatusUI.ACCEPTED else PurposeStatusUI.REJECTED
purposeUI.choice = if (isChecked) PurposeStatusUI.ACCEPTED else PurposeStatusUI.REJECTED
purposeUI.vendors.forEach { vendorUI ->
vendorUI.isAccepted = isChecked
}
@ -95,7 +95,7 @@ private fun PurposeComponentPreview() {
purposeUI = PurposeUI(
id = "1",
isRequired = true,
isAccepted = PurposeStatusUI.ACCEPTED,
choice = PurposeStatusUI.ACCEPTED,
title = "Title",
description = "Description",
vendors = emptyList(),

View File

@ -2,7 +2,7 @@ package fr.openium.consentium_ui.ui.model
internal data class GeneralConsentUI(
val applicationName: String,
val iconUrl: String,
val iconUrl: String?,
val mainConsentText: String,
val consentPageUrl: String,
)

View File

@ -3,5 +3,5 @@ package fr.openium.consentium_ui.ui.model
internal enum class PurposeStatusUI {
ACCEPTED,
REJECTED,
NOT_DEFINED,
PARTIAL,
}

View File

@ -3,7 +3,7 @@ package fr.openium.consentium_ui.ui.model
internal data class PurposeUI(
val id: String,
val isRequired: Boolean,
var isAccepted: PurposeStatusUI,
var choice: PurposeStatusUI,
val title: String,
val description: String,
val vendors: List<VendorUI>,

View File

@ -6,5 +6,7 @@
<string name="parameters">Paramétrer mes choix</string>
<string name="save">Enregistrer et fermer</string>
<string name="require">Requis</string>
<string name="retry">Réessayer</string>
<string name="save_consents_error_message">Une erreur est survenue l\'or de la ssauvegarde de vos consentements.</string>
<string name="consents_error_loading_consents">Il y a eu une erreur lors du chargement de vos consentements</string>
</resources>