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

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

View File

@ -3,12 +3,14 @@ import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut import androidx.compose.animation.slideOut
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.toRoute import androidx.navigation.toRoute
import fr.openium.consentium.R
import fr.openium.consentium.api.Consentium import fr.openium.consentium.api.Consentium
import fr.openium.consentium.ui.screens.main.MainScreen import fr.openium.consentium.ui.screens.main.MainScreen
import fr.openium.consentium.ui.screens.splash.SplashScreen import fr.openium.consentium.ui.screens.splash.SplashScreen
@ -79,6 +81,9 @@ fun DemoNavGraph(navHostController: NavHostController) {
composable<Destination.Consent> { backStackEntry -> composable<Destination.Consent> { backStackEntry ->
val consent = backStackEntry.toRoute<Destination.Consent>() val consent = backStackEntry.toRoute<Destination.Consent>()
val appId = stringResource(R.string.app_id)
val apiKey = stringResource(R.string.api_key)
val context = LocalContext.current val context = LocalContext.current
ConsentiumComponent( ConsentiumComponent(
defaultLandingPage = consent.landingPage, defaultLandingPage = consent.landingPage,
@ -91,7 +96,8 @@ fun DemoNavGraph(navHostController: NavHostController) {
}, },
consentium = Consentium( consentium = Consentium(
context = context, context = context,
applicationId = "ApplicationId", apiKey = apiKey,
appId = appId,
), ),
colors = ConsentiumDefaults.colors( colors = ConsentiumDefaults.colors(
primary = Primary, primary = Primary,

View File

@ -27,8 +27,9 @@ fun SplashScreen(
) { ) {
// Property // Property
val context = LocalContext.current val context = LocalContext.current
val consentiumKey = stringResource(R.string.consentium_api_key) val apiKey = stringResource(R.string.api_key)
val consentium = remember { Consentium(context = context, applicationId = consentiumKey) } val appId = stringResource(R.string.app_id)
val consentium = remember { Consentium(context = context, apiKey = apiKey, appId = appId) }
// Effect // Effect
LaunchedEffect(Unit) { LaunchedEffect(Unit) {

View File

@ -1,4 +1,5 @@
<resources> <resources>
<string name="app_name">Consentium</string> <string name="app_name">Consentium</string>
<string translatable="false" name="consentium_api_key">01938ce4-331a-7592-9e90-f09201ff4f36</string> <string name="app_id" translatable="false">01938ce4-331a-7592-9e90-f09201ff4f36</string>
<string name="api_key" translatable="false">c452a27f-2e90-427d-be82-2f631c31dd09</string>
</resources> </resources>

View File

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

View File

@ -3,13 +3,15 @@ package fr.openium.consentium_ui.data.remote
import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO
import retrofit2.Response import retrofit2.Response
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Path
internal interface ConsentiumUIApi { internal interface ConsentiumUIApi {
@GET("consent-config") @GET("applications/{application}")
suspend fun getConsentConfig( suspend fun getConsentConfig(
applicationID: String, @Header("Authorization") token: String,
installationID: String, @Path("application") applicationId: String,
): Response<GetConsentConfigDTO> ): 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.PurposeDTO
import fr.openium.consentium_ui.data.remote.model.PurposeStatusDTO 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.PurposeTranslationDTO
import fr.openium.consentium_ui.data.remote.model.VendorDTO
import fr.openium.consentium_ui.data.remote.model.VendorTranslationDTO
import retrofit2.Response import retrofit2.Response
import java.util.UUID import java.util.UUID
@ -17,9 +15,6 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
installationId = UUID.randomUUID().toString(), installationId = UUID.randomUUID().toString(),
appName = "Consentium", appName = "Consentium",
icon = "https://amp.openium.fr/openium.png", icon = "https://amp.openium.fr/openium.png",
primaryColor = "#FF0000",
secondaryColor = "#00FF00",
textColor = "#0000FF",
consentMainTextTranslation = listOf( consentMainTextTranslation = listOf(
MainConsentTextTranslationDTO( MainConsentTextTranslationDTO(
id = "UUID", 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" 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( purposes = listOf(
PurposeDTO( PurposeDTO(
id = "purpose-required", id = "purpose-required",
order = 0, order = 0,
isRequired = true, isRequired = true,
isAccepted = PurposeStatusDTO.ACCEPTED, choice = PurposeStatusDTO.ACCEPTED,
translations = listOf( translations = listOf(
PurposeTranslationDTO( PurposeTranslationDTO(
id = "UUID", id = "UUID",
@ -43,40 +39,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Nécessaire" 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( PurposeDTO(
id = "purpose-advertising", id = "purpose-advertising",
order = 1, order = 1,
isRequired = false, isRequired = false,
isAccepted = PurposeStatusDTO.REJECTED, choice = PurposeStatusDTO.REFUSED,
translations = listOf( translations = listOf(
PurposeTranslationDTO( PurposeTranslationDTO(
id = "UUID", id = "UUID",
@ -85,27 +53,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Publicité" 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( PurposeDTO(
id = "purpose-analytics", id = "purpose-analytics",
order = 2, order = 2,
isRequired = false, isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED, choice = PurposeStatusDTO.ACCEPTED,
translations = listOf( translations = listOf(
PurposeTranslationDTO( PurposeTranslationDTO(
id = "UUID", id = "UUID",
@ -114,27 +67,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Analyse" 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( PurposeDTO(
id = "purpose-personalization", id = "purpose-personalization",
order = 3, order = 3,
isRequired = false, isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED, choice = PurposeStatusDTO.ACCEPTED,
translations = listOf( translations = listOf(
PurposeTranslationDTO( PurposeTranslationDTO(
id = "UUID", id = "UUID",
@ -143,27 +81,12 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Personnalisation" 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( PurposeDTO(
id = "purpose-social", id = "purpose-social",
order = 4, order = 4,
isRequired = false, isRequired = false,
isAccepted = PurposeStatusDTO.ACCEPTED, choice = PurposeStatusDTO.ACCEPTED,
translations = listOf( translations = listOf(
PurposeTranslationDTO( PurposeTranslationDTO(
id = "UUID", id = "UUID",
@ -172,28 +95,13 @@ internal object ConsentiumUIMockApi : ConsentiumUIApi {
name = "Social" 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( override suspend fun getConsentConfig(
applicationID: String, token: String,
installationID: String, applicationId: String,
): Response<GetConsentConfigDTO> { ): Response<GetConsentConfigDTO> {
return Response.success(consents) return Response.success(consents)
} }

View File

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

View File

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

View File

@ -5,10 +5,10 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
internal data class PurposeDTO( internal data class PurposeDTO(
@SerialName("identifier") val id: String, @SerialName("sortOrder") val order: Int,
@SerialName("order") val order: Int,
@SerialName("isRequired") val isRequired: Boolean, @SerialName("isRequired") val isRequired: Boolean,
@SerialName("isAccepted") val isAccepted: PurposeStatusDTO, @SerialName("choice") val choice: PurposeStatusDTO,
@SerialName("translations") val translations: List<PurposeTranslationDTO>, @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 @Serializable
internal enum class PurposeStatusDTO { internal enum class PurposeStatusDTO {
@SerialName("ACCEPTED") @SerialName("accepted")
ACCEPTED, ACCEPTED,
@SerialName("REJECTED") @SerialName("refused")
REJECTED, REFUSED,
@SerialName("NOT_DEFINED") @SerialName("partial")
NOT_DEFINED, PARTIAL,
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext 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.ConsentiumRepository
import fr.openium.consentium_ui.domain.repository.ConsentiumUIRepositoryResponse import fr.openium.consentium_ui.domain.repository.ConsentiumUIRepositoryResponse
import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCase import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCase
@ -30,12 +31,12 @@ internal class ConsentiumUIViewModel @Inject constructor(
private val _state = MutableStateFlow<ConsentiumUIState>(ConsentiumUIState.Loading) private val _state = MutableStateFlow<ConsentiumUIState>(ConsentiumUIState.Loading)
val state: StateFlow<ConsentiumUIState> by lazy { _state.asStateFlow() } val state: StateFlow<ConsentiumUIState> by lazy { _state.asStateFlow() }
fun init(appId: String) { fun init(appId: String, apiKey: String) {
viewModelScope.launch { viewModelScope.launch {
_state.value = ConsentiumUIState.Loading _state.value = ConsentiumUIState.Loading
when (val consentiumUIFetchResponse = consentiumUIRepository.getConsentiumConfig(appId)) { when (val consentiumUIFetchResponse = consentiumUIRepository.getConsentiumConfig(appId, apiKey)) {
is ConsentiumUIRepositoryResponse.Success -> { is ConsentiumUIRepositoryResponse.Success -> {
val consentUIResponse = val consentUIResponse =
@ -44,28 +45,32 @@ internal class ConsentiumUIViewModel @Inject constructor(
when (val consentUI = consentUIResponse) { when (val consentUI = consentUIResponse) {
is GetConfigTextForLanguageUseCaseResponse.Success -> { is GetConfigTextForLanguageUseCaseResponse.Success -> {
_state.emit(ConsentiumUIState.Loaded( _state.emit(
ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(), generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(), detailConsentUI = consentUI.configData.toDetailConsentUI(),
)) )
)
} }
is GetConfigTextForLanguageUseCaseResponse.DefaultLanguage -> { is GetConfigTextForLanguageUseCaseResponse.DefaultLanguage -> {
_state.emit(ConsentiumUIState.Loaded( _state.emit(
ConsentiumUIState.Loaded(
generalConsentUI = consentUI.configData.toGeneralConsentUI(), generalConsentUI = consentUI.configData.toGeneralConsentUI(),
detailConsentUI = consentUI.configData.toDetailConsentUI(), detailConsentUI = consentUI.configData.toDetailConsentUI(),
)) )
)
} }
is GetConfigTextForLanguageUseCaseResponse.Error -> { 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 -> { 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 package fr.openium.consentium_ui.ui.adapter
import fr.openium.consentium.api.model.PurposeChoice 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.domain.model.ContentConfigData
import fr.openium.consentium_ui.ui.model.DetailConsentUI import fr.openium.consentium_ui.ui.model.DetailConsentUI
import fr.openium.consentium_ui.ui.model.PurposeStatusUI 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 { 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 { internal fun DetailConsentUI.toAcceptedPurposeChoices(): List<PurposeChoice> = purposes.map {
it.toPurposeChoice().copy(isAccepted = true) it.toPurposeChoice().copy(choice = PurposeStatus.ACCEPTED)
} }
internal fun PurposeUI.toPurposeChoice(): PurposeChoice = internal fun PurposeUI.toPurposeChoice(): PurposeChoice =
PurposeChoice( PurposeChoice(
purposeIdentifier = id, purposeIdentifier = id,
isAccepted = isAccepted == PurposeStatusUI.ACCEPTED, choice = choice.toPurposeStatus(),
vendors = emptyList(), // Not in v1 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) { internal fun PurposeStatusData.toPurposeStatusUI(): PurposeStatusUI = when(this) {
PurposeStatusData.ACCEPTED -> PurposeStatusUI.ACCEPTED PurposeStatusData.ACCEPTED -> PurposeStatusUI.ACCEPTED
PurposeStatusData.REJECTED -> PurposeStatusUI.REJECTED 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( internal fun PurposeData.toPurposeUI(): PurposeUI = PurposeUI(
id = identifier, id = identifier,
isRequired = isRequired, isRequired = isRequired,
isAccepted = isAccepted.toPurposeStatusUI(), choice = choice.toPurposeStatusUI(),
title = translations.first().name, title = translations.first().name,
description = translations.first().text, description = translations.first().text,
vendors = vendors.toVendorUIList(), vendors = vendors.toVendorUIList(),

View File

@ -51,7 +51,7 @@ fun ConsentiumComponent(
// Effect // Effect
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.init(consentium.applicationId) viewModel.init(apiKey = consentium.apiKey, appId = consentium.appId)
} }
LaunchedEffect(consentiumState) { LaunchedEffect(consentiumState) {
@ -131,6 +131,9 @@ fun ConsentiumComponent(
onClickCookiesPolicies = { onClickCookiesPolicies = {
currentPage = ConsentiumPageUI.COOKIES_POLICY 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.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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 import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
@Composable @Composable
internal fun ConsentiumUIErrorComponent( internal fun ConsentiumUIErrorComponent(
errorMessage: String, errorMessage: String,
onRetry: () -> Unit,
) { ) {
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxSize()
.padding(horizontal = 24.dp),
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
@ -22,6 +34,15 @@ internal fun ConsentiumUIErrorComponent(
text = errorMessage, text = errorMessage,
color = ConsentiumUITheme.colors.error, color = ConsentiumUITheme.colors.error,
style = ConsentiumUITheme.typography.b2, 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, purposeUI: PurposeUI,
) { ) {
// Properties // Properties
var isChecked by remember { mutableStateOf(purposeUI.isAccepted != PurposeStatusUI.REJECTED) } var isChecked by remember { mutableStateOf(purposeUI.choice != PurposeStatusUI.REJECTED) }
// View // View
Column( Column(
@ -58,7 +58,7 @@ internal fun PurposeComponent(
checked = isChecked, checked = isChecked,
onCheckedChange = { onCheckedChange = {
isChecked = !isChecked isChecked = !isChecked
purposeUI.isAccepted = if (isChecked) PurposeStatusUI.ACCEPTED else PurposeStatusUI.REJECTED purposeUI.choice = if (isChecked) PurposeStatusUI.ACCEPTED else PurposeStatusUI.REJECTED
purposeUI.vendors.forEach { vendorUI -> purposeUI.vendors.forEach { vendorUI ->
vendorUI.isAccepted = isChecked vendorUI.isAccepted = isChecked
} }
@ -95,7 +95,7 @@ private fun PurposeComponentPreview() {
purposeUI = PurposeUI( purposeUI = PurposeUI(
id = "1", id = "1",
isRequired = true, isRequired = true,
isAccepted = PurposeStatusUI.ACCEPTED, choice = PurposeStatusUI.ACCEPTED,
title = "Title", title = "Title",
description = "Description", description = "Description",
vendors = emptyList(), vendors = emptyList(),

View File

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

View File

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

View File

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

View File

@ -6,5 +6,7 @@
<string name="parameters">Paramétrer mes choix</string> <string name="parameters">Paramétrer mes choix</string>
<string name="save">Enregistrer et fermer</string> <string name="save">Enregistrer et fermer</string>
<string name="require">Requis</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="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> </resources>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources>
<string name="backend_url" translatable="false" tools:ignore="UnusedResources">https://consentium-api-dev.openium.fr/api/v1/app</string> <string name="backend_url" translatable="false">https://consentium-api-dev.openium.fr/api/v1/app/</string>
</resources> </resources>

View File

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

View File

@ -15,7 +15,8 @@ import kotlinx.coroutines.flow.asStateFlow
class Consentium( class Consentium(
context: Context, context: Context,
val applicationId: String, val apiKey: String,
val appId: String,
) { ) {
private val _fetchConsentState = MutableStateFlow<FetchConsentiumState>(FetchConsentiumState.Idle) private val _fetchConsentState = MutableStateFlow<FetchConsentiumState>(FetchConsentiumState.Idle)
val fetchConsentState by lazy { _fetchConsentState.asStateFlow() } val fetchConsentState by lazy { _fetchConsentState.asStateFlow() }
@ -32,7 +33,7 @@ class Consentium(
suspend fun fetchConsents() { suspend fun fetchConsents() {
_fetchConsentState.value = FetchConsentiumState.Loading _fetchConsentState.value = FetchConsentiumState.Loading
try { try {
when (val consentResponse = consentiumRepository.getConsents(applicationId)) { when (val consentResponse = consentiumRepository.getConsents(apiKey)) {
ConsentiumRepositoryGetResponse.Error -> _fetchConsentState.value = FetchConsentiumState.Error ConsentiumRepositoryGetResponse.Error -> _fetchConsentState.value = FetchConsentiumState.Error
is ConsentiumRepositoryGetResponse.GetConsentsSuccess -> { is ConsentiumRepositoryGetResponse.GetConsentsSuccess -> {
@ -54,7 +55,7 @@ class Consentium(
) { ) {
_saveConsentState.emit(SetConsentiumState.Loading) _saveConsentState.emit(SetConsentiumState.Loading)
try { try {
when (consentiumRepository.setConsents(applicationId, consent)) { when (consentiumRepository.setConsents(apiKey, consent)) {
ConsentiumRepositorySetResponse.Error -> { ConsentiumRepositorySetResponse.Error -> {
_saveConsentState.emit(SetConsentiumState.Error) _saveConsentState.emit(SetConsentiumState.Error)
} }

View File

@ -11,12 +11,12 @@ import retrofit2.http.POST
internal interface ConsentiumApi { internal interface ConsentiumApi {
@GET("/consents") @GET("consents")
suspend fun getConsents( suspend fun getConsents(
@Header("Authorization") token: String, @Header("Authorization") token: String,
): Response<GetConsent.GetConsentPayloadDTO> ): Response<GetConsent.GetConsentPayloadDTO>
@POST("/consents") @POST("consents")
suspend fun setConsents( suspend fun setConsents(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body patchConsent: PatchConsent.PatchConsentPayloadDTO, @Body patchConsent: PatchConsent.PatchConsentPayloadDTO,

View File

@ -7,7 +7,7 @@ internal sealed interface GetConsent {
@Serializable @Serializable
data class GetConsentPayloadDTO( data class GetConsentPayloadDTO(
@SerialName("id") val id: String, @SerialName("id") val id: String? = null,
@SerialName("installationId") val installationId: String, @SerialName("installationId") val installationId: String,
@SerialName("state") val state: ConsentStateDTO? = null, @SerialName("state") val state: ConsentStateDTO? = null,
@SerialName("acceptedDate") val acceptedData: Long? = null, @SerialName("acceptedDate") val acceptedData: Long? = null,

View File

@ -16,10 +16,11 @@ internal class ConsentiumRepository @Inject constructor(
private val getAuthTokenUseCase: GetAuthTokenUseCase, private val getAuthTokenUseCase: GetAuthTokenUseCase,
) { ) {
suspend fun getConsents(applicationId: String): ConsentiumRepositoryGetResponse { suspend fun getConsents(apiKey: String): ConsentiumRepositoryGetResponse {
val authToken = getAuthTokenUseCase(applicationId)
val consentsResponse = consentiumApi.getConsents(authToken)
return try { return try {
val authToken = getAuthTokenUseCase(apiKey)
val consentsResponse = consentiumApi.getConsents(authToken)
val consentsBody = if (consentsResponse.isSuccessful) { val consentsBody = if (consentsResponse.isSuccessful) {
consentsResponse.body() ?: throw Exception() consentsResponse.body() ?: throw Exception()
} else { } else {

View File

@ -5,7 +5,7 @@ import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.encoding.ExperimentalEncodingApi
interface GetAuthTokenUseCase { interface GetAuthTokenUseCase {
suspend operator fun invoke(applicationId: String): String suspend operator fun invoke(apiKey: String): String
} }
class GetAuthTokenUseCaseImpl @Inject internal constructor( class GetAuthTokenUseCaseImpl @Inject internal constructor(
@ -13,11 +13,11 @@ class GetAuthTokenUseCaseImpl @Inject internal constructor(
) : GetAuthTokenUseCase { ) : GetAuthTokenUseCase {
@OptIn(ExperimentalEncodingApi::class) @OptIn(ExperimentalEncodingApi::class)
override suspend operator fun invoke(applicationId: String): String { override suspend operator fun invoke(apiKey: String): String {
val uniqueInstallationId = getConsentiumUniqueInstallationIdUseCase() val uniqueInstallationId = getConsentiumUniqueInstallationIdUseCase()
val clearToken = "$applicationId.$uniqueInstallationId" val clearToken = "$apiKey.$uniqueInstallationId"
val cipheredToken = Base64.Default.encode(clearToken.toByteArray()) val cipheredToken = Base64.Default.encode(clearToken.toByteArray())
return "Bearer $cipheredToken" return "Basic $cipheredToken"
} }
} }

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="backend_url" translatable="false" tools:ignore="UnusedResources">https://consentium-api.openium.fr/api/v1/app/</string>
</resources>