Compare commits
19 Commits
main
...
86a7020c2b
Author | SHA1 | Date | |
---|---|---|---|
86a7020c2b | |||
d406cc703f | |||
60361a5e2a | |||
ca4f4515fc | |||
ae61624eaf | |||
56aeae61de | |||
f4949c2852 | |||
f6f6a889fe | |||
3bfe96b1de | |||
62e426c38d | |||
266bb3aac7 | |||
bdbd3fcde2 | |||
57b9571924 | |||
01704d7191 | |||
2b9bfe3c5b | |||
5be4981eb2 | |||
8dfc0b6cb5 | |||
afb0d01afa | |||
61ad631f28 |
3
.gitignore
vendored
@ -109,7 +109,7 @@ Carthage/Build
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
@ -325,4 +325,5 @@ fabric.properties
|
||||
|
||||
!/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
.idea
|
||||
# End of https://www.gitignore.io/api/macos,carthage,symfony,xcode,android,androidstudio
|
||||
|
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
133
app/build.gradle.kts
Normal file
@ -0,0 +1,133 @@
|
||||
import org.gradle.language.nativeplatform.internal.BuildType
|
||||
import java.io.FileInputStream
|
||||
import java.util.Properties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.hilt)
|
||||
alias(libs.plugins.serialization)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
// Keystore
|
||||
val keystorePropertiesFile = rootProject.file("keys/keystore.properties")
|
||||
val keystoreProperties = Properties()
|
||||
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
|
||||
|
||||
android {
|
||||
namespace = "fr.openium.consentium"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "fr.openium.consentium"
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
targetSdk = libs.versions.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
named(BuildType.DEBUG.name) {
|
||||
storeFile = file(keystoreProperties["debugStoreFile"].toString())
|
||||
}
|
||||
register(BuildType.RELEASE.name) {
|
||||
storeFile = file(keystoreProperties["releaseStoreFile"].toString())
|
||||
storePassword = keystoreProperties["passwordRelease"].toString()
|
||||
keyAlias = keystoreProperties["aliasRelease"].toString()
|
||||
keyPassword = keystoreProperties["passwordRelease"].toString()
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isMinifyEnabled = false
|
||||
|
||||
versionNameSuffix = "-debug"
|
||||
applicationIdSuffix = ".debug"
|
||||
signingConfig = signingConfigs.getByName(BuildType.DEBUG.name)
|
||||
}
|
||||
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = true
|
||||
|
||||
signingConfig = signingConfigs.getByName(BuildType.RELEASE.name)
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "version"
|
||||
productFlavors {
|
||||
create("prod") {
|
||||
dimension = "version"
|
||||
}
|
||||
|
||||
create("demo") {
|
||||
dimension = "version"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Internal dependencies
|
||||
implementation(project(":consentium"))
|
||||
implementation(project(":consentium-ui"))
|
||||
|
||||
// AndroidX
|
||||
implementation(libs.bundles.androidx)
|
||||
|
||||
// Lifecycle
|
||||
implementation(libs.androidx.lifecycle.runtime)
|
||||
implementation(libs.androidx.lifecycle.viewmodel)
|
||||
|
||||
// Hilt
|
||||
implementation(libs.hilt.android)
|
||||
ksp(libs.hilt.compiler)
|
||||
implementation(libs.hilt.navigation.compose)
|
||||
|
||||
// Libs analytics
|
||||
implementation(libs.matomo)
|
||||
implementation(libs.clarity)
|
||||
implementation(libs.ga4)
|
||||
|
||||
// Compose
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.bundles.compose)
|
||||
|
||||
// Timber
|
||||
implementation(libs.timber)
|
||||
|
||||
// Compose Navigation
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.espresso)
|
||||
androidTestImplementation(libs.test.androidx.junit)
|
||||
|
||||
// Kotlin serialization
|
||||
implementation(libs.kotlin.serialization)
|
||||
|
||||
}
|
21
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,24 @@
|
||||
package fr.openium.consentium
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("fr.openium.consentium", appContext.packageName)
|
||||
}
|
||||
}
|
29
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application
|
||||
android:name=".DemoApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Consentium"
|
||||
tools:targetApi="31" >
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Consentium" >
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -0,0 +1,7 @@
|
||||
package fr.openium.consentium
|
||||
|
||||
import android.app.Application
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
@HiltAndroidApp
|
||||
class DemoApplication : Application()
|
39
app/src/main/java/fr/openium/consentium/MainActivity.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package fr.openium.consentium
|
||||
|
||||
import DemoNavGraph
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import fr.openium.consentium.ui.theme.ConsentiumTheme
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent {
|
||||
|
||||
val navHostController = rememberNavController()
|
||||
|
||||
ConsentiumTheme {
|
||||
Scaffold(modifier = Modifier.fillMaxSize()) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
DemoNavGraph(navHostController = navHostController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.toRoute
|
||||
import fr.openium.consentium.api.Consentium
|
||||
import fr.openium.consentium.ui.screens.main.MainScreen
|
||||
import fr.openium.consentium.ui.screens.splash.SplashScreen
|
||||
import fr.openium.consentium.ui.theme.Error
|
||||
import fr.openium.consentium.ui.theme.OnPrimary
|
||||
import fr.openium.consentium.ui.theme.OnSecondary
|
||||
import fr.openium.consentium.ui.theme.OnSurface
|
||||
import fr.openium.consentium.ui.theme.OnSurfaceVariant
|
||||
import fr.openium.consentium.ui.theme.Primary
|
||||
import fr.openium.consentium.ui.theme.Secondary
|
||||
import fr.openium.consentium.ui.theme.Success
|
||||
import fr.openium.consentium.ui.theme.SurfaceHigh
|
||||
import fr.openium.consentium.ui.theme.SurfaceHighest
|
||||
import fr.openium.consentium.ui.theme.SurfaceMiddle
|
||||
import fr.openium.consentium.ui.theme.Tertiary
|
||||
import fr.openium.consentium_ui.ui.components.ConsentiumComponent
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumDefaults
|
||||
import fr.openium.consentium_ui.ui.model.ConsentiumPageUI
|
||||
|
||||
private const val NAV_ANIMATION_TIME = 500
|
||||
|
||||
@Composable
|
||||
fun DemoNavGraph(navHostController: NavHostController) {
|
||||
|
||||
NavHost(
|
||||
navController = navHostController,
|
||||
startDestination = Destination.Splash,
|
||||
enterTransition = {
|
||||
slideIn(
|
||||
animationSpec = tween(NAV_ANIMATION_TIME),
|
||||
initialOffset = { fullSize -> IntOffset(x = fullSize.height, y = 0) }
|
||||
)
|
||||
},
|
||||
exitTransition = {
|
||||
slideOut(
|
||||
animationSpec = tween(NAV_ANIMATION_TIME),
|
||||
targetOffset = { fullSize -> IntOffset(x = -fullSize.height, y = 0) }
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
composable<Destination.Splash> {
|
||||
SplashScreen(
|
||||
navigateToMain = {
|
||||
navHostController.navigate(Destination.Main) {
|
||||
popUpTo(navHostController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
}
|
||||
},
|
||||
navigateToConsent = {
|
||||
navHostController.navigate(Destination.Consent(ConsentiumPageUI.GENERAL_CONSENT))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
composable<Destination.Main> {
|
||||
MainScreen(
|
||||
onGoToConsentMaster = {
|
||||
navHostController.navigate(Destination.Consent(ConsentiumPageUI.GENERAL_CONSENT))
|
||||
},
|
||||
onGoToConsentDetail = {
|
||||
navHostController.navigate(Destination.Consent(ConsentiumPageUI.DETAILS_CONSENT))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
composable<Destination.Consent> { backStackEntry ->
|
||||
val consent = backStackEntry.toRoute<Destination.Consent>()
|
||||
|
||||
val context = LocalContext.current
|
||||
ConsentiumComponent(
|
||||
defaultLandingPage = consent.landingPage,
|
||||
onQuitConsent = {
|
||||
navHostController.navigate(Destination.Main) {
|
||||
popUpTo(navHostController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
}
|
||||
},
|
||||
consentium = Consentium(
|
||||
context = context,
|
||||
applicationId = "ApplicationId",
|
||||
),
|
||||
colors = ConsentiumDefaults.colors(
|
||||
primary = Primary,
|
||||
onPrimary = OnPrimary,
|
||||
secondary = Secondary,
|
||||
onSecondary = OnSecondary,
|
||||
tertiary = Tertiary,
|
||||
onSurfaceVariant = OnSurfaceVariant,
|
||||
onSurface = OnSurface,
|
||||
error = Error,
|
||||
surfaceHighest = SurfaceHighest,
|
||||
surfaceHigh = SurfaceHigh,
|
||||
surfaceMiddle = SurfaceMiddle,
|
||||
success = Success,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import fr.openium.consentium_ui.ui.model.ConsentiumPageUI
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
||||
sealed interface Destination {
|
||||
|
||||
@Serializable
|
||||
data object Splash : Destination
|
||||
|
||||
@Serializable
|
||||
data object Main : Destination
|
||||
|
||||
@Serializable
|
||||
data class Consent(
|
||||
val landingPage: ConsentiumPageUI
|
||||
): Destination
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package fr.openium.consentium.ui.screens.main
|
||||
|
||||
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.material3.Button
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
onGoToConsentMaster: () -> Unit,
|
||||
onGoToConsentDetail: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
// View
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
Button(
|
||||
onClick = onGoToConsentMaster,
|
||||
) {
|
||||
Text(
|
||||
text = "Go to Consent Master"
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
Button(
|
||||
onClick = onGoToConsentDetail,
|
||||
) {
|
||||
Text(
|
||||
text = "Go to Consent Detail"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package fr.openium.consentium.ui.screens.splash
|
||||
|
||||
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.size
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.openium.consentium.api.Consentium
|
||||
import fr.openium.consentium.api.state.FetchConsentiumState
|
||||
|
||||
@Composable
|
||||
fun SplashScreen(
|
||||
navigateToMain: () -> Unit,
|
||||
navigateToConsent: () -> Unit,
|
||||
) {
|
||||
// Property
|
||||
val context = LocalContext.current
|
||||
val consentium = remember { Consentium(context = context, applicationId = "DemoApplicationId") }
|
||||
|
||||
// Effect
|
||||
LaunchedEffect(Unit) {
|
||||
consentium.fetchConsentState.collect { consentState ->
|
||||
when (consentState) {
|
||||
FetchConsentiumState.Idle,
|
||||
FetchConsentiumState.Loading -> {}
|
||||
|
||||
FetchConsentiumState.Error -> {
|
||||
// Handle error
|
||||
consentium.fetchConsents()
|
||||
}
|
||||
|
||||
is FetchConsentiumState.Invalid -> {
|
||||
navigateToConsent()
|
||||
}
|
||||
|
||||
is FetchConsentiumState.Valid -> {
|
||||
// The tracking services should be initialized here
|
||||
navigateToMain()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
consentium.fetchConsents()
|
||||
}
|
||||
|
||||
// View
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
Text("Splash Screen") // TODO
|
||||
|
||||
Spacer(modifier = Modifier.height(14.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
22
app/src/main/java/fr/openium/consentium/ui/theme/Color.kt
Normal file
@ -0,0 +1,22 @@
|
||||
package fr.openium.consentium.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Primary = Color(0xFF70ACDC)
|
||||
val OnPrimary = Color(0XFFFFFFFF)
|
||||
|
||||
val Secondary = Color(0xFFF29413)
|
||||
val OnSecondary = Color(0xFFFFFFFF)
|
||||
|
||||
val Tertiary = Color(0xFF3470A0)
|
||||
|
||||
val OnSurfaceVariant = Color(0xFF3470A0)
|
||||
val OnSurface = Color(0xFF163752)
|
||||
|
||||
val Error = Color(0xFF3470A0)
|
||||
|
||||
val SurfaceHighest = Color(0xFFFFFFFF)
|
||||
val SurfaceHigh = Color(0xFFF2F8FC)
|
||||
val SurfaceMiddle = Color(0xFFD6E6F5)
|
||||
|
||||
val Success = Color(0xFF479B3F)
|
36
app/src/main/java/fr/openium/consentium/ui/theme/Theme.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package fr.openium.consentium.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Primary,
|
||||
secondary = Secondary,
|
||||
tertiary = Tertiary
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Primary,
|
||||
secondary = Secondary,
|
||||
tertiary = Tertiary
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ConsentiumTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
val colorScheme = when {
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
18
app/src/main/java/fr/openium/consentium/ui/theme/Type.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package fr.openium.consentium.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
)
|
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
After Width: | Height: | Size: 7.6 KiB |
10
app/src/main/res/values/colors.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
3
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Consentium</string>
|
||||
</resources>
|
4
app/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.Consentium" parent="android:Theme.Material.Light.NoActionBar" />
|
||||
</resources>
|
13
app/src/main/res/xml/backup_rules.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
17
app/src/test/java/fr/openium/consentium/ExampleUnitTest.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package fr.openium.consentium
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
30
build.gradle.kts
Normal file
@ -0,0 +1,30 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
// Application
|
||||
alias(libs.plugins.android.application) apply false
|
||||
|
||||
// Android
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
|
||||
// Compose
|
||||
alias(libs.plugins.kotlin.compose) apply false
|
||||
|
||||
// Ksp
|
||||
alias(libs.plugins.ksp) apply false
|
||||
|
||||
// Agp
|
||||
alias(libs.plugins.android.library) apply false
|
||||
|
||||
// Hilt
|
||||
alias(libs.plugins.hilt) apply false
|
||||
|
||||
// Kotlin serialization
|
||||
alias(libs.plugins.serialization) apply false
|
||||
|
||||
// Firebase crashlytics
|
||||
alias(libs.plugins.firebaseCrashlytics) apply false
|
||||
|
||||
// Google services
|
||||
alias(libs.plugins.googleServices) apply false
|
||||
|
||||
}
|
1
consentium-ui/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
109
consentium-ui/build.gradle.kts
Normal file
@ -0,0 +1,109 @@
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.serialization)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
alias(libs.plugins.hilt)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "fr.openium.consentium_ui"
|
||||
compileSdk = libs.versions.compileSdk.get().toInt()
|
||||
|
||||
defaultConfig {
|
||||
minSdk = libs.versions.minSdk.get().toInt()
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "version"
|
||||
productFlavors {
|
||||
create("prod") {
|
||||
dimension = "version"
|
||||
}
|
||||
|
||||
create("demo") {
|
||||
dimension = "version"
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "11"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Internal dependencies
|
||||
implementation(project(":consentium"))
|
||||
|
||||
// AndroidX
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.runtime.android)
|
||||
implementation(libs.androidx.foundation.android)
|
||||
implementation(libs.androidx.ui.android)
|
||||
implementation(libs.androidx.foundation.layout.android)
|
||||
implementation(libs.androidx.ui.tooling.preview.android)
|
||||
implementation(libs.androidx.material3.android)
|
||||
implementation(libs.androidx.ui.tooling.preview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
|
||||
|
||||
//Compose
|
||||
implementation(platform(libs.compose.bom))
|
||||
implementation(libs.compose.ui.graphics)
|
||||
implementation(libs.compose.ui)
|
||||
implementation(libs.compose.ui.tooling)
|
||||
implementation(libs.compose.material3)
|
||||
|
||||
// Serialization
|
||||
implementation(libs.kotlin.serialization)
|
||||
|
||||
// Retrofit
|
||||
api(libs.retrofit)
|
||||
implementation(libs.retrofitConverter)
|
||||
implementation(libs.logging.interceptor)
|
||||
|
||||
// Hilt
|
||||
implementation(libs.hilt.android)
|
||||
implementation(libs.hilt.navigation.compose)
|
||||
ksp(libs.hilt.compiler)
|
||||
|
||||
// Timber
|
||||
implementation(libs.timber)
|
||||
|
||||
// Coil
|
||||
implementation(libs.coil)
|
||||
implementation(libs.coil.network)
|
||||
|
||||
// Tests
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.test.espresso)
|
||||
|
||||
|
||||
}
|
0
consentium-ui/consumer-rules.pro
Normal file
21
consentium-ui/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,24 @@
|
||||
package fr.openium.consentium_ui
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("fr.openium.consentium_ui.test", appContext.packageName)
|
||||
}
|
||||
}
|
4
consentium-ui/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?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>
|
@ -0,0 +1,54 @@
|
||||
package fr.openium.consentium_ui.data.di
|
||||
|
||||
import dagger.Lazy
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Reusable
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import fr.openium.consentium.data.di.ConsentiumUrl
|
||||
import fr.openium.consentium.data.di.OkHttpClientDefault
|
||||
import fr.openium.consentium_ui.BuildConfig
|
||||
import fr.openium.consentium_ui.data.remote.ConsentiumUIApi
|
||||
import fr.openium.consentium_ui.data.remote.mock.ConsentiumUIMockApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
|
||||
private val json by lazy {
|
||||
Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object NetworkModule {
|
||||
|
||||
@Reusable
|
||||
@Provides
|
||||
fun provideConsentiumUIApi(
|
||||
@ConsentiumUrl url: HttpUrl,
|
||||
@OkHttpClientDefault okHttpClient: Lazy<OkHttpClient>,
|
||||
): ConsentiumUIApi = if (BuildConfig.FLAVOR != "demo") {
|
||||
createRetrofit(
|
||||
url,
|
||||
okHttpClient
|
||||
).create(ConsentiumUIApi::class.java)
|
||||
} else {
|
||||
ConsentiumUIMockApi
|
||||
}
|
||||
|
||||
private fun createRetrofit(url: HttpUrl, okHttpClient: Lazy<OkHttpClient>): Retrofit =
|
||||
Retrofit.Builder()
|
||||
.callFactory { request ->
|
||||
okHttpClient.get().newCall(request)
|
||||
}.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
|
||||
.baseUrl(url)
|
||||
.build()
|
||||
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package fr.openium.consentium_ui.data.remote
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
|
||||
internal interface ConsentiumUIApi {
|
||||
|
||||
@GET("consent-config")
|
||||
suspend fun getConsentConfig(
|
||||
applicationID: String,
|
||||
installationID: String,
|
||||
): Response<GetConsentConfigDTO>
|
||||
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
package fr.openium.consentium_ui.data.remote.mock
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.ConsentiumUIApi
|
||||
import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO
|
||||
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
|
||||
|
||||
internal object ConsentiumUIMockApi : ConsentiumUIApi {
|
||||
|
||||
private val consents = GetConsentConfigDTO(
|
||||
installationId = UUID.randomUUID().toString(),
|
||||
appName = "Consentium",
|
||||
icon = "https://amp.openium.fr/openium.png",
|
||||
primaryColor = "#FF0000",
|
||||
secondaryColor = "#00FF00",
|
||||
textColor = "#0000FF",
|
||||
consentMainTextTranslation = listOf(
|
||||
MainConsentTextTranslationDTO(
|
||||
id = "UUID",
|
||||
language = "fr",
|
||||
consentPageUrl = "https:consentium.fr",
|
||||
mainConsentText = "<p>[Nom de l’application] utilise des cookies pour différents objectifs : faire fonctionner l’application, améliorer nos services en mesurant l’efficacité de nos contenus et afficher des publicités susceptibles de vous intéresser.<br></p>\n<p>En cliquant sur “Accepter et fermer”, vous acceptez cette utilisation sur l’application mobile. Vous pouvez également paramétrer vos choix en cliquant sur “Paramétrer mes choix” ou refuser ces cookies en cliquant sur “Continuer sans accepter”. Vous pouvez changer d’avis à tout moment depuis les paramètres de votre compte via l’onglet “Notifications et cookies”</p>\n",
|
||||
durationText = "<p>Nous conservons votre choix pendant 12 mois. Vous pouvez changer d’avis à tout moment depuis les paramètres de votre compte via l’onglet “Notification et cookies” tetetettetettetetstts</p>\n"
|
||||
)
|
||||
),
|
||||
purposes = listOf(
|
||||
PurposeDTO(
|
||||
id = "purpose-required",
|
||||
order = 0,
|
||||
isRequired = true,
|
||||
isAccepted = PurposeStatusDTO.ACCEPTED,
|
||||
translations = listOf(
|
||||
PurposeTranslationDTO(
|
||||
id = "UUID",
|
||||
language = "fr",
|
||||
text = "<p>Ces traceurs sont nécessaire au fonctionnement de l’application. Ils permettent de vérifier la stabilité technique de l’application 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>",
|
||||
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 l’application. Ils permettent de vérifier la stabilité technique de l’application 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 l’application. Ils permettent de vérifier la stabilité technique de l’application 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,
|
||||
translations = listOf(
|
||||
PurposeTranslationDTO(
|
||||
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 l’efficacité de nos campagnes publicitaires et de personnaliser les publicités affichées.</p>",
|
||||
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 l’efficacité de nos campagnes publicitaires et de personnaliser les publicités affichées.</p>"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
PurposeDTO(
|
||||
id = "purpose-analytics",
|
||||
order = 2,
|
||||
isRequired = false,
|
||||
isAccepted = PurposeStatusDTO.ACCEPTED,
|
||||
translations = listOf(
|
||||
PurposeTranslationDTO(
|
||||
id = "UUID",
|
||||
language = "fr",
|
||||
text = "<p>Ces traceurs sont nécessaires pour mesurer l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>",
|
||||
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 l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
PurposeDTO(
|
||||
id = "purpose-personalization",
|
||||
order = 3,
|
||||
isRequired = false,
|
||||
isAccepted = PurposeStatusDTO.ACCEPTED,
|
||||
translations = listOf(
|
||||
PurposeTranslationDTO(
|
||||
id = "UUID",
|
||||
language = "fr",
|
||||
text = "<p>Ces traceurs sont nécessaires pour mesurer l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>",
|
||||
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 l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
PurposeDTO(
|
||||
id = "purpose-social",
|
||||
order = 4,
|
||||
isRequired = false,
|
||||
isAccepted = PurposeStatusDTO.ACCEPTED,
|
||||
translations = listOf(
|
||||
PurposeTranslationDTO(
|
||||
id = "UUID",
|
||||
language = "fr",
|
||||
text = "<p>Ces traceurs sont nécessaires pour mesurer l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>",
|
||||
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 l’efficacité de nos contenus. Ils permettent de mesurer l’audience de l’application et de comprendre comment les utilisateurs interagissent avec l’application.</p>"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
override suspend fun getConsentConfig(
|
||||
applicationID: String,
|
||||
installationID: String,
|
||||
): Response<GetConsentConfigDTO> {
|
||||
return Response.success(consents)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@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("purposes") val purposes: List<PurposeDTO>,
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@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,
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PurposeDTO(
|
||||
@SerialName("identifier") val id: String,
|
||||
@SerialName("order") val order: Int,
|
||||
@SerialName("isRequired") val isRequired: Boolean,
|
||||
@SerialName("isAccepted") val isAccepted: PurposeStatusDTO,
|
||||
@SerialName("translations") val translations: List<PurposeTranslationDTO>,
|
||||
@SerialName("vendors") val vendors: List<VendorDTO>,
|
||||
)
|
@ -0,0 +1,16 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal enum class PurposeStatusDTO {
|
||||
@SerialName("ACCEPTED")
|
||||
ACCEPTED,
|
||||
|
||||
@SerialName("REJECTED")
|
||||
REJECTED,
|
||||
|
||||
@SerialName("NOT_DEFINED")
|
||||
NOT_DEFINED,
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PurposeTranslationDTO(
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("lang") val language: String,
|
||||
@SerialName("text") val text: String,
|
||||
@SerialName("name") val name: String,
|
||||
)
|
@ -0,0 +1,13 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class VendorDTO(
|
||||
@SerialName("identifier") val id: String,
|
||||
@SerialName("order") val order: Int,
|
||||
@SerialName("isRequired") val isRequired: Boolean,
|
||||
@SerialName("isAccepted") val isAccepted: Boolean,
|
||||
@SerialName("translations") val translations: List<VendorTranslationDTO>,
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package fr.openium.consentium_ui.data.remote.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class VendorTranslationDTO(
|
||||
@SerialName("id") val id: String,
|
||||
@SerialName("lang") val language: String,
|
||||
@SerialName("text") val text: String,
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.GetConsentConfigDTO
|
||||
import fr.openium.consentium_ui.domain.model.ContentConfigData
|
||||
|
||||
internal fun GetConsentConfigDTO.toConsentConfigData() =
|
||||
ContentConfigData(
|
||||
applicationName = appName,
|
||||
iconUrl = icon,
|
||||
mainTextTranslation = consentMainTextTranslation.toMainConsentTextTranslationDataList(),
|
||||
purposes = purposes.toPurposeDataList()
|
||||
)
|
@ -0,0 +1,15 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.MainConsentTextTranslationDTO
|
||||
import fr.openium.consentium_ui.domain.model.MainConsentTextTranslationData
|
||||
|
||||
internal fun MainConsentTextTranslationDTO.toMainConsentTextTranslationData() =
|
||||
MainConsentTextTranslationData(
|
||||
id = id,
|
||||
language = language,
|
||||
consentPageUrl = consentPageUrl,
|
||||
mainConsentText = mainConsentText,
|
||||
durationText = durationText,
|
||||
)
|
||||
|
||||
internal fun List<MainConsentTextTranslationDTO>.toMainConsentTextTranslationDataList() = map { it.toMainConsentTextTranslationData() }
|
@ -0,0 +1,16 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.PurposeDTO
|
||||
import fr.openium.consentium_ui.domain.model.PurposeData
|
||||
|
||||
internal fun PurposeDTO.toPurposeData() =
|
||||
PurposeData(
|
||||
identifier = id,
|
||||
isRequired = isRequired,
|
||||
isAccepted = isAccepted.toPurposeStatusData(),
|
||||
order = order,
|
||||
vendors = vendors.toVendorDataList(),
|
||||
translations = translations.toPurposeTranslationDataList(),
|
||||
)
|
||||
|
||||
internal fun List<PurposeDTO>.toPurposeDataList() = map { it.toPurposeData() }
|
@ -0,0 +1,12 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.PurposeStatusDTO
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.PurposeTranslationDTO
|
||||
import fr.openium.consentium_ui.domain.model.PurposeTranslationData
|
||||
|
||||
internal fun PurposeTranslationDTO.toPurposeTranslationData() =
|
||||
PurposeTranslationData(
|
||||
id = id,
|
||||
language = language,
|
||||
text = text,
|
||||
name = name,
|
||||
)
|
||||
|
||||
internal fun List<PurposeTranslationDTO>.toPurposeTranslationDataList() = map { it.toPurposeTranslationData() }
|
@ -0,0 +1,15 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.VendorDTO
|
||||
import fr.openium.consentium_ui.domain.model.VendorData
|
||||
|
||||
internal fun VendorDTO.toVendorData() =
|
||||
VendorData(
|
||||
identifier = id,
|
||||
order = order,
|
||||
isRequired = isRequired,
|
||||
isAccepted = isAccepted,
|
||||
translations = translations.toVendorTranslationDataList(),
|
||||
)
|
||||
|
||||
internal fun List<VendorDTO>.toVendorDataList() = map { it.toVendorData() }
|
@ -0,0 +1,13 @@
|
||||
package fr.openium.consentium_ui.domain.adapter
|
||||
|
||||
import fr.openium.consentium_ui.data.remote.model.VendorTranslationDTO
|
||||
import fr.openium.consentium_ui.domain.model.VendorTranslationData
|
||||
|
||||
internal fun VendorTranslationDTO.toVendorTranslationData() =
|
||||
VendorTranslationData(
|
||||
id = id,
|
||||
language = language,
|
||||
text = text,
|
||||
)
|
||||
|
||||
internal fun List<VendorTranslationDTO>.toVendorTranslationDataList() = map { it.toVendorTranslationData() }
|
@ -0,0 +1,26 @@
|
||||
package fr.openium.consentium_ui.domain.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCase
|
||||
import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCaseImpl
|
||||
import fr.openium.consentium_ui.domain.usecase.GetConfigTextForLanguageUseCase
|
||||
import fr.openium.consentium_ui.domain.usecase.GetConfigTextForLanguageUseCaseImpl
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface ConsentiumUseCaseModule {
|
||||
|
||||
@Binds
|
||||
fun bindsGetConfigTextForLanguageUseCase(
|
||||
getConfigTextForLanguageUseCaseImpl: GetConfigTextForLanguageUseCaseImpl,
|
||||
): GetConfigTextForLanguageUseCase
|
||||
|
||||
@Binds
|
||||
fun bindGetApplicationLangageUseCase(
|
||||
getApplicationLangageUseCaseImpl: GetApplicationLanguageUseCaseImpl,
|
||||
): GetApplicationLanguageUseCase
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class ContentConfigData(
|
||||
val applicationName: String,
|
||||
val iconUrl : String,
|
||||
val mainTextTranslation: List<MainConsentTextTranslationData>,
|
||||
val purposes: List<PurposeData>
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class MainConsentTextTranslationData(
|
||||
val id: String,
|
||||
val language: String,
|
||||
val consentPageUrl: String,
|
||||
val mainConsentText: String,
|
||||
val durationText: String,
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class PurposeData(
|
||||
val identifier: String,
|
||||
val order: Int,
|
||||
val isRequired: Boolean,
|
||||
val isAccepted: PurposeStatusData,
|
||||
val translations: List<PurposeTranslationData>,
|
||||
val vendors: List<VendorData>,
|
||||
)
|
@ -0,0 +1,7 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal enum class PurposeStatusData {
|
||||
ACCEPTED,
|
||||
REJECTED,
|
||||
NOT_DEFINED,
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class PurposeTranslationData(
|
||||
val id: String,
|
||||
val language: String,
|
||||
val text: String,
|
||||
val name: String,
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class VendorData(
|
||||
val identifier: String,
|
||||
val order: Int,
|
||||
val isRequired: Boolean,
|
||||
val isAccepted: Boolean,
|
||||
val translations: List<VendorTranslationData>,
|
||||
)
|
@ -0,0 +1,7 @@
|
||||
package fr.openium.consentium_ui.domain.model
|
||||
|
||||
internal data class VendorTranslationData(
|
||||
val id: String,
|
||||
val language: String,
|
||||
val text: String,
|
||||
)
|
@ -0,0 +1,40 @@
|
||||
package fr.openium.consentium_ui.domain.repository
|
||||
|
||||
import fr.openium.consentium.domain.useCase.GetConsentiumUniqueInstallationIdUseCase
|
||||
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 javax.inject.Inject
|
||||
|
||||
internal class ConsentiumRepository @Inject constructor(
|
||||
private val getConsentiumUniqueInstallationIdUseCase: GetConsentiumUniqueInstallationIdUseCase,
|
||||
private val consentiumUIApi: ConsentiumUIApi,
|
||||
) {
|
||||
suspend fun getConsentiumConfig(
|
||||
applicationId: String,
|
||||
): ConsentiumUIRepositoryResponse {
|
||||
val installationId = getConsentiumUniqueInstallationIdUseCase.invoke()
|
||||
val consentsResponse = consentiumUIApi.getConsentConfig(applicationId, installationId)
|
||||
|
||||
return try {
|
||||
val consentsBody = if (consentsResponse.isSuccessful) {
|
||||
consentsResponse.body() ?: throw Exception()
|
||||
} else {
|
||||
throw Exception()
|
||||
}
|
||||
|
||||
ConsentiumUIRepositoryResponse.Success(consentsBody.toConsentConfigData())
|
||||
} catch (e: Exception) {
|
||||
ConsentiumUIRepositoryResponse.Error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal interface ConsentiumUIRepositoryResponse {
|
||||
|
||||
data object Error : ConsentiumUIRepositoryResponse
|
||||
|
||||
data class Success(val contentConfigData: ContentConfigData) : ConsentiumUIRepositoryResponse
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package fr.openium.consentium_ui.domain.usecase
|
||||
|
||||
import android.content.Context
|
||||
import javax.inject.Inject
|
||||
|
||||
internal interface GetApplicationLanguageUseCase {
|
||||
suspend operator fun invoke(context: Context): String
|
||||
}
|
||||
|
||||
internal class GetApplicationLanguageUseCaseImpl @Inject constructor() : GetApplicationLanguageUseCase {
|
||||
override suspend fun invoke(context: Context): String {
|
||||
return context.resources.configuration.locales[0].language
|
||||
}
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
package fr.openium.consentium_ui.domain.usecase
|
||||
|
||||
import fr.openium.consentium_ui.domain.model.ContentConfigData
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val FALLBACK_LANGUAGE = "en"
|
||||
|
||||
internal interface GetConfigTextForLanguageUseCase {
|
||||
suspend operator fun invoke(
|
||||
language: String = FALLBACK_LANGUAGE,
|
||||
configData: ContentConfigData,
|
||||
): GetConfigTextForLanguageUseCaseResponse
|
||||
}
|
||||
|
||||
internal class GetConfigTextForLanguageUseCaseImpl @Inject constructor() :
|
||||
GetConfigTextForLanguageUseCase {
|
||||
|
||||
override suspend fun invoke(
|
||||
language: String,
|
||||
configData: ContentConfigData,
|
||||
): GetConfigTextForLanguageUseCaseResponse {
|
||||
|
||||
return try {
|
||||
|
||||
val canIUseTheTranslation =
|
||||
configData.mainTextTranslation.any { it.language == language } &&
|
||||
configData.purposes.all { purposeData ->
|
||||
purposeData.translations.any { it.language == language } &&
|
||||
purposeData.vendors.all { vendorData ->
|
||||
vendorData.translations.any { it.language == language }
|
||||
}
|
||||
}
|
||||
|
||||
val languageToUse = if (canIUseTheTranslation) {
|
||||
language
|
||||
} else {
|
||||
val isThereAGoodFallbackLanguage =
|
||||
configData.mainTextTranslation.any { it.language == FALLBACK_LANGUAGE } &&
|
||||
configData.purposes.all { purposeData ->
|
||||
purposeData.translations.any { it.language == FALLBACK_LANGUAGE } &&
|
||||
purposeData.vendors.all { vendorData ->
|
||||
vendorData.translations.any { it.language == FALLBACK_LANGUAGE }
|
||||
}
|
||||
}
|
||||
if (isThereAGoodFallbackLanguage) {
|
||||
FALLBACK_LANGUAGE
|
||||
} else {
|
||||
throw Exception()
|
||||
}
|
||||
}
|
||||
|
||||
val filteredConfigData = configData.copy(
|
||||
mainTextTranslation = configData.mainTextTranslation.filter { it.language == languageToUse },
|
||||
purposes = configData.purposes.map { purposeData ->
|
||||
purposeData.copy(
|
||||
translations = purposeData.translations.filter { it.language == languageToUse },
|
||||
vendors = purposeData.vendors.map { vendorData ->
|
||||
vendorData.copy(
|
||||
translations = vendorData.translations.filter { it.language == languageToUse }
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
if (languageToUse == FALLBACK_LANGUAGE) {
|
||||
GetConfigTextForLanguageUseCaseResponse.DefaultLanguage(filteredConfigData)
|
||||
} else {
|
||||
GetConfigTextForLanguageUseCaseResponse.Success(filteredConfigData)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GetConfigTextForLanguageUseCaseResponse.Error
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal interface GetConfigTextForLanguageUseCaseResponse {
|
||||
|
||||
data object Error : GetConfigTextForLanguageUseCaseResponse
|
||||
|
||||
data class Success(val configData: ContentConfigData) : GetConfigTextForLanguageUseCaseResponse
|
||||
|
||||
data class DefaultLanguage(val configData: ContentConfigData) :
|
||||
GetConfigTextForLanguageUseCaseResponse
|
||||
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
package fr.openium.consentium_ui.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.slideIn
|
||||
import androidx.compose.animation.slideOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import fr.openium.consentium_ui.ui.components.ConsentiumUIDetailConsentComponent
|
||||
import fr.openium.consentium_ui.ui.components.ConsentiumUIErrorComponent
|
||||
import fr.openium.consentium_ui.ui.components.ConsentiumUIGeneralConsentComponent
|
||||
import fr.openium.consentium_ui.ui.components.ConsentiumUILoadingComponent
|
||||
import fr.openium.consentium_ui.ui.model.ConsentiumPageUI
|
||||
import fr.openium.consentium_ui.ui.model.DetailConsentUI
|
||||
import fr.openium.consentium_ui.ui.model.LoadingElement
|
||||
import fr.openium.consentium_ui.ui.state.ConsentiumUIState
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumScreen(
|
||||
page: ConsentiumPageUI,
|
||||
state: ConsentiumUIState,
|
||||
loadingElement: LoadingElement,
|
||||
onNavigateBack: () -> Unit,
|
||||
onAcceptAndClose: (consents: DetailConsentUI) -> Unit,
|
||||
onDenyAndClose: (consents: DetailConsentUI) -> Unit,
|
||||
onSaveAndCloseDetails: (consents: DetailConsentUI) -> Unit,
|
||||
onNavigateToDetails: () -> Unit,
|
||||
onClickCookiesPolicies: () -> Unit,
|
||||
) {
|
||||
when (state) {
|
||||
is ConsentiumUIState.Loading -> {
|
||||
ConsentiumUILoadingComponent()
|
||||
}
|
||||
|
||||
is ConsentiumUIState.Error -> {
|
||||
ConsentiumUIErrorComponent(
|
||||
errorMessage = state.message,
|
||||
)
|
||||
}
|
||||
|
||||
is ConsentiumUIState.Loaded -> {
|
||||
AnimatedContent(
|
||||
targetState = page,
|
||||
transitionSpec = {
|
||||
when (page) {
|
||||
ConsentiumPageUI.GENERAL_CONSENT -> {
|
||||
slideIn(
|
||||
initialOffset = { fullSize -> IntOffset(-fullSize.width, 0) }
|
||||
).togetherWith(
|
||||
slideOut(
|
||||
targetOffset = { fullSize -> IntOffset(fullSize.width, 0) }
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ConsentiumPageUI.DETAILS_CONSENT -> {
|
||||
slideIn(
|
||||
initialOffset = { fullSize -> IntOffset(fullSize.width, 0) }
|
||||
).togetherWith(
|
||||
slideOut(
|
||||
targetOffset = { fullSize -> IntOffset(-fullSize.width, 0) }
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) { currentPage ->
|
||||
|
||||
when (currentPage) {
|
||||
ConsentiumPageUI.GENERAL_CONSENT -> {
|
||||
ConsentiumUIGeneralConsentComponent(
|
||||
generalConsentUI = state.generalConsentUI,
|
||||
onNavigateToDetails = onNavigateToDetails,
|
||||
onAcceptAndClose = {
|
||||
onAcceptAndClose(state.detailConsentUI)
|
||||
},
|
||||
onDenyAndClose = {
|
||||
onDenyAndClose(state.detailConsentUI)
|
||||
},
|
||||
onClickCookiesPolicies = onClickCookiesPolicies,
|
||||
loadingElement = loadingElement
|
||||
)
|
||||
}
|
||||
|
||||
ConsentiumPageUI.DETAILS_CONSENT -> {
|
||||
ConsentiumUIDetailConsentComponent(
|
||||
detailConsentUI = state.detailConsentUI,
|
||||
onNavigateBack = onNavigateBack,
|
||||
onSave = {
|
||||
onSaveAndCloseDetails(state.detailConsentUI)
|
||||
},
|
||||
loadingElement = loadingElement
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,74 @@
|
||||
package fr.openium.consentium_ui.ui
|
||||
|
||||
import android.content.Context
|
||||
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.domain.repository.ConsentiumRepository
|
||||
import fr.openium.consentium_ui.domain.repository.ConsentiumUIRepositoryResponse
|
||||
import fr.openium.consentium_ui.domain.usecase.GetApplicationLanguageUseCase
|
||||
import fr.openium.consentium_ui.domain.usecase.GetConfigTextForLanguageUseCase
|
||||
import fr.openium.consentium_ui.domain.usecase.GetConfigTextForLanguageUseCaseResponse
|
||||
import fr.openium.consentium_ui.ui.adapter.toDetailConsentUI
|
||||
import fr.openium.consentium_ui.ui.adapter.toGeneralConsentUI
|
||||
import fr.openium.consentium_ui.ui.state.ConsentiumUIState
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class ConsentiumUIViewModel @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val consentiumUIRepository: ConsentiumRepository,
|
||||
private val configTextLanguageUseCase: GetConfigTextForLanguageUseCase,
|
||||
private val getApplicationLanguageUseCase: GetApplicationLanguageUseCase,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _state = MutableStateFlow<ConsentiumUIState>(ConsentiumUIState.Loading)
|
||||
val state: StateFlow<ConsentiumUIState> by lazy { _state.asStateFlow() }
|
||||
|
||||
fun init(appId: String) {
|
||||
viewModelScope.launch {
|
||||
|
||||
_state.value = ConsentiumUIState.Loading
|
||||
|
||||
when (val consentiumUIFetchResponse = consentiumUIRepository.getConsentiumConfig(appId)) {
|
||||
|
||||
is ConsentiumUIRepositoryResponse.Success -> {
|
||||
val consentUIResponse =
|
||||
configTextLanguageUseCase(getApplicationLanguageUseCase(context), consentiumUIFetchResponse.contentConfigData)
|
||||
|
||||
when (val consentUI = consentUIResponse) {
|
||||
|
||||
is GetConfigTextForLanguageUseCaseResponse.Success -> {
|
||||
_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(),
|
||||
))
|
||||
}
|
||||
|
||||
is GetConfigTextForLanguageUseCaseResponse.Error -> {
|
||||
_state.emit(ConsentiumUIState.Error("Failed to load data"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
is ConsentiumUIRepositoryResponse.Error -> {
|
||||
_state.value = ConsentiumUIState.Error("Failed to load data")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package fr.openium.consentium_ui.ui.adapter
|
||||
|
||||
import fr.openium.consentium.api.model.PurposeChoice
|
||||
import fr.openium.consentium_ui.domain.model.ContentConfigData
|
||||
import fr.openium.consentium_ui.ui.model.DetailConsentUI
|
||||
import fr.openium.consentium_ui.ui.model.PurposeStatusUI
|
||||
import fr.openium.consentium_ui.ui.model.PurposeUI
|
||||
|
||||
internal fun ContentConfigData.toDetailConsentUI(): DetailConsentUI = DetailConsentUI(
|
||||
conservationDurationText = mainTextTranslation.first().durationText,
|
||||
purposes = purposes.sortedBy { it.order }.map { it.toPurposeUI() },
|
||||
)
|
||||
|
||||
internal fun DetailConsentUI.toPurposeChoices(): List<PurposeChoice> = purposes.map {
|
||||
it.toPurposeChoice()
|
||||
}
|
||||
|
||||
internal fun DetailConsentUI.toDeniedPurposeChoices(): List<PurposeChoice> = purposes.map {
|
||||
it.toPurposeChoice().copy(isAccepted = false)
|
||||
}
|
||||
|
||||
internal fun DetailConsentUI.toAcceptedPurposeChoices(): List<PurposeChoice> = purposes.map {
|
||||
it.toPurposeChoice().copy(isAccepted = true)
|
||||
}
|
||||
|
||||
internal fun PurposeUI.toPurposeChoice(): PurposeChoice =
|
||||
PurposeChoice(
|
||||
purposeIdentifier = id,
|
||||
isAccepted = isAccepted == PurposeStatusUI.ACCEPTED,
|
||||
vendors = emptyList(), // Not in v1
|
||||
)
|
@ -0,0 +1,12 @@
|
||||
package fr.openium.consentium_ui.ui.adapter
|
||||
|
||||
import fr.openium.consentium_ui.domain.model.ContentConfigData
|
||||
import fr.openium.consentium_ui.ui.model.GeneralConsentUI
|
||||
|
||||
internal fun ContentConfigData.toGeneralConsentUI(): GeneralConsentUI =
|
||||
GeneralConsentUI(
|
||||
applicationName = applicationName,
|
||||
iconUrl = iconUrl,
|
||||
mainConsentText = mainTextTranslation.first().mainConsentText,
|
||||
consentPageUrl = mainTextTranslation.first().consentPageUrl,
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package fr.openium.consentium_ui.ui.adapter
|
||||
|
||||
import fr.openium.consentium_ui.domain.model.PurposeStatusData
|
||||
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
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package fr.openium.consentium_ui.ui.adapter
|
||||
|
||||
import fr.openium.consentium_ui.domain.model.PurposeData
|
||||
import fr.openium.consentium_ui.ui.model.PurposeUI
|
||||
|
||||
internal fun PurposeData.toPurposeUI(): PurposeUI = PurposeUI(
|
||||
id = identifier,
|
||||
isRequired = isRequired,
|
||||
isAccepted = isAccepted.toPurposeStatusUI(),
|
||||
title = translations.first().name,
|
||||
description = translations.first().text,
|
||||
vendors = vendors.toVendorUIList(),
|
||||
)
|
@ -0,0 +1,11 @@
|
||||
package fr.openium.consentium_ui.ui.adapter
|
||||
|
||||
import fr.openium.consentium_ui.domain.model.VendorData
|
||||
import fr.openium.consentium_ui.ui.model.VendorUI
|
||||
|
||||
internal fun VendorData.toVendorUI() = VendorUI(
|
||||
id = identifier,
|
||||
isAccepted = isAccepted,
|
||||
)
|
||||
|
||||
internal fun List<VendorData>.toVendorUIList() = map { it.toVendorUI() }
|
@ -0,0 +1,119 @@
|
||||
package fr.openium.consentium_ui.ui.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import fr.openium.consentium.api.Consentium
|
||||
import fr.openium.consentium.api.state.SetConsentiumState
|
||||
import fr.openium.consentium_ui.R
|
||||
import fr.openium.consentium_ui.ui.ConsentiumScreen
|
||||
import fr.openium.consentium_ui.ui.ConsentiumUIViewModel
|
||||
import fr.openium.consentium_ui.ui.adapter.toAcceptedPurposeChoices
|
||||
import fr.openium.consentium_ui.ui.adapter.toDeniedPurposeChoices
|
||||
import fr.openium.consentium_ui.ui.adapter.toPurposeChoices
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumColors
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumDefaults
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumTypography
|
||||
import fr.openium.consentium_ui.ui.components.style.LocalColors
|
||||
import fr.openium.consentium_ui.ui.components.style.LocalTypography
|
||||
import fr.openium.consentium_ui.ui.model.ConsentiumPageUI
|
||||
import fr.openium.consentium_ui.ui.model.LoadingElement
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ConsentiumComponent(
|
||||
consentium: Consentium,
|
||||
onQuitConsent: () -> (Unit),
|
||||
colors: ConsentiumColors = ConsentiumDefaults.colors(),
|
||||
typography: ConsentiumTypography = ConsentiumDefaults.typography(),
|
||||
defaultLandingPage: ConsentiumPageUI = ConsentiumPageUI.GENERAL_CONSENT,
|
||||
) {
|
||||
// Property
|
||||
val viewModel: ConsentiumUIViewModel = hiltViewModel()
|
||||
val loadingState by viewModel.state.collectAsStateWithLifecycle()
|
||||
var currentPage by remember(defaultLandingPage) { mutableStateOf(defaultLandingPage) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val errorMessage = stringResource(id = R.string.save_consents_error_message)
|
||||
val consentiumState by consentium.saveConsentState.collectAsStateWithLifecycle()
|
||||
var loadingElement by remember { mutableStateOf(LoadingElement.NONE) }
|
||||
|
||||
// Effect
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init(consentium.applicationId)
|
||||
}
|
||||
|
||||
LaunchedEffect(consentiumState) {
|
||||
when (consentiumState) {
|
||||
SetConsentiumState.Idle,
|
||||
SetConsentiumState.Loading,
|
||||
-> {
|
||||
}
|
||||
|
||||
SetConsentiumState.Error -> {
|
||||
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
SetConsentiumState.Success -> {
|
||||
onQuitConsent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// View
|
||||
CompositionLocalProvider(
|
||||
LocalColors provides colors,
|
||||
LocalTypography provides typography,
|
||||
) {
|
||||
ConsentiumScreen(
|
||||
state = loadingState,
|
||||
page = currentPage,
|
||||
loadingElement = if (consentiumState is SetConsentiumState.Loading) loadingElement else LoadingElement.NONE,
|
||||
onNavigateBack = {
|
||||
when {
|
||||
defaultLandingPage == ConsentiumPageUI.GENERAL_CONSENT && currentPage == ConsentiumPageUI.DETAILS_CONSENT -> {
|
||||
currentPage = ConsentiumPageUI.GENERAL_CONSENT
|
||||
}
|
||||
|
||||
else -> {
|
||||
onQuitConsent()
|
||||
}
|
||||
}
|
||||
},
|
||||
onNavigateToDetails = {
|
||||
currentPage = ConsentiumPageUI.DETAILS_CONSENT
|
||||
},
|
||||
onAcceptAndClose = { consent ->
|
||||
scope.launch {
|
||||
loadingElement = LoadingElement.BUTTON
|
||||
consentium.saveConsents(consent.toAcceptedPurposeChoices())
|
||||
}
|
||||
},
|
||||
onDenyAndClose = { consent ->
|
||||
scope.launch {
|
||||
loadingElement = LoadingElement.LINK
|
||||
consentium.saveConsents(consent.toDeniedPurposeChoices())
|
||||
}
|
||||
},
|
||||
onSaveAndCloseDetails = { consent ->
|
||||
scope.launch {
|
||||
loadingElement = LoadingElement.BUTTON
|
||||
consentium.saveConsents(consent.toPurposeChoices())
|
||||
}
|
||||
},
|
||||
onClickCookiesPolicies = {
|
||||
// TODO Open cookies policies
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package fr.openium.consentium_ui.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
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.unit.dp
|
||||
import fr.openium.consentium_ui.R
|
||||
import fr.openium.consentium_ui.ui.components.core.PurposeComponent
|
||||
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.model.DetailConsentUI
|
||||
import fr.openium.consentium_ui.ui.model.LoadingElement
|
||||
import fr.openium.consentium_ui.ui.utils.htmlToAnnotatedString
|
||||
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUIDetailConsentComponent(
|
||||
detailConsentUI: DetailConsentUI,
|
||||
loadingElement: LoadingElement,
|
||||
onNavigateBack: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
IconButton(onClick = { onNavigateBack() }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = "Back",
|
||||
tint = ConsentiumUITheme.colors.onSurface,
|
||||
modifier = Modifier.size(26.dp),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.parameters),
|
||||
style = ConsentiumUITheme.typography.h2,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(25.dp))
|
||||
|
||||
Text(
|
||||
text = htmlToAnnotatedString(detailConsentUI.conservationDurationText),
|
||||
style = ConsentiumUITheme.typography.p3,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
items(detailConsentUI.purposes) { purposeUI ->
|
||||
PurposeComponent(
|
||||
purposeUI = purposeUI,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ConsentButton(
|
||||
text = stringResource(R.string.save),
|
||||
buttonStyle = ConsentiumUIButtonStyle.PRIMARY,
|
||||
onclick = onSave,
|
||||
isLoading = loadingElement == LoadingElement.BUTTON,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package fr.openium.consentium_ui.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUIErrorComponent(
|
||||
errorMessage: String,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = errorMessage,
|
||||
color = ConsentiumUITheme.colors.error,
|
||||
style = ConsentiumUITheme.typography.b2,
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package fr.openium.consentium_ui.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil3.compose.AsyncImage
|
||||
import fr.openium.consentium_ui.R
|
||||
import fr.openium.consentium_ui.ui.components.core.TextLink
|
||||
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.model.GeneralConsentUI
|
||||
import fr.openium.consentium_ui.ui.model.LoadingElement
|
||||
import fr.openium.consentium_ui.ui.utils.htmlToAnnotatedString
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUIGeneralConsentComponent(
|
||||
generalConsentUI: GeneralConsentUI,
|
||||
loadingElement: LoadingElement,
|
||||
onClickCookiesPolicies: () -> Unit,
|
||||
onDenyAndClose: () -> Unit,
|
||||
onAcceptAndClose: () -> Unit,
|
||||
onNavigateToDetails: () -> Unit,
|
||||
) {
|
||||
// View
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 24.dp),
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
AsyncImage(
|
||||
modifier = Modifier.heightIn(min = 48.dp),
|
||||
model = generalConsentUI.iconUrl,
|
||||
contentDescription = "Image",
|
||||
contentScale = ContentScale.FillBounds,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Text(
|
||||
text = htmlToAnnotatedString(generalConsentUI.mainConsentText),
|
||||
style = ConsentiumUITheme.typography.p3,
|
||||
textAlign = TextAlign.Start,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
TextLink(
|
||||
text = stringResource(R.string.cookies),
|
||||
onclick = onClickCookiesPolicies,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
TextLink(
|
||||
text = stringResource(R.string.refuse),
|
||||
onclick = onDenyAndClose,
|
||||
isLoading = loadingElement == LoadingElement.LINK,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ConsentButton(
|
||||
text = stringResource(R.string.accept),
|
||||
buttonStyle = ConsentiumUIButtonStyle.PRIMARY,
|
||||
onclick = onAcceptAndClose,
|
||||
isLoading = loadingElement == LoadingElement.BUTTON,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ConsentButton(
|
||||
text = stringResource(R.string.parameters),
|
||||
buttonStyle = ConsentiumUIButtonStyle.SECONDARY,
|
||||
onclick = onNavigateToDetails,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showSystemUi = true)
|
||||
@Composable
|
||||
private fun ConsentiumUIGeneralConsentComponentPreview() {
|
||||
ConsentiumUIGeneralConsentComponent(
|
||||
generalConsentUI = GeneralConsentUI(
|
||||
iconUrl = "https://amp.openium.fr/openium.png",
|
||||
mainConsentText = "[Nom de l’application] utilise des cookies pour différents objectifs : faire fonctionner l’application, améliorer nos services en mesurant l’efficacité de nos contenus et afficher des publicités susceptibles de vous intéresser. \n\n En cliquant sur “Accepter et fermer”, vous acceptez cette utilisation sur l’application mobile. Vous pouvez également paramétrer vos choix en cliquant sur “Paramétrer mes choix” ou refuser ces cookies en cliquant sur “Continuer sans accepter”. Vous pouvez changer d’avis à tout moment depuis les paramètres de votre compte via l’onglet “Notifications et cookies”.",
|
||||
applicationName = "Application name",
|
||||
consentPageUrl = "https://www.google.com"
|
||||
),
|
||||
onClickCookiesPolicies = {},
|
||||
onDenyAndClose = {},
|
||||
onAcceptAndClose = {},
|
||||
onNavigateToDetails = {},
|
||||
loadingElement = LoadingElement.LINK,
|
||||
)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package fr.openium.consentium_ui.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUILoadingComponent() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CircularProgressIndicator(
|
||||
color = ConsentiumUITheme.colors.primary
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
package fr.openium.consentium_ui.ui.components.core
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.openium.consentium_ui.R
|
||||
import fr.openium.consentium_ui.ui.components.core.toggle.ConsentiumUISwitch
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
import fr.openium.consentium_ui.ui.model.PurposeStatusUI
|
||||
import fr.openium.consentium_ui.ui.model.PurposeUI
|
||||
import fr.openium.consentium_ui.ui.utils.htmlToAnnotatedString
|
||||
|
||||
@Composable
|
||||
internal fun PurposeComponent(
|
||||
purposeUI: PurposeUI,
|
||||
) {
|
||||
// Properties
|
||||
var isChecked by remember { mutableStateOf(purposeUI.isAccepted != PurposeStatusUI.REJECTED) }
|
||||
|
||||
// View
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = purposeUI.title,
|
||||
style = ConsentiumUITheme.typography.b3,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (purposeUI.isRequired) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.require),
|
||||
style = ConsentiumUITheme.typography.b3,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
} else {
|
||||
ConsentiumUISwitch(
|
||||
checked = isChecked,
|
||||
onCheckedChange = {
|
||||
isChecked = !isChecked
|
||||
purposeUI.isAccepted = if (isChecked) PurposeStatusUI.ACCEPTED else PurposeStatusUI.REJECTED
|
||||
purposeUI.vendors.forEach { vendorUI ->
|
||||
vendorUI.isAccepted = isChecked
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/** To enable in v2
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
Icon(
|
||||
modifier = Modifier.size(24.dp),
|
||||
imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
|
||||
contentDescription = null,
|
||||
tint = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
**/
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(10.dp))
|
||||
|
||||
Text(
|
||||
text = htmlToAnnotatedString(purposeUI.description),
|
||||
style = ConsentiumUITheme.typography.p3,
|
||||
color = ConsentiumUITheme.colors.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PurposeComponentPreview() {
|
||||
PurposeComponent(
|
||||
purposeUI = PurposeUI(
|
||||
id = "1",
|
||||
isRequired = true,
|
||||
isAccepted = PurposeStatusUI.ACCEPTED,
|
||||
title = "Title",
|
||||
description = "Description",
|
||||
vendors = emptyList(),
|
||||
)
|
||||
)
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package fr.openium.consentium_ui.ui.components.core
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
@Composable
|
||||
internal fun TextLink(
|
||||
text: String,
|
||||
onclick: () -> Unit,
|
||||
color: Color = ConsentiumUITheme.colors.primary,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
interactionSource = interactionSource,
|
||||
indication = null,
|
||||
onClick = onclick,
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
style = ConsentiumUITheme.typography.b3,
|
||||
textDecoration = TextDecoration.Underline
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
color = color,
|
||||
modifier = Modifier.size(14.dp),
|
||||
strokeWidth = 3.dp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TextLinkPreview() {
|
||||
TextLink(
|
||||
text = "Continuer sans accepter",
|
||||
onclick = {},
|
||||
isLoading = true,
|
||||
)
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package fr.openium.consentium_ui.ui.components.core.button
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
@Composable
|
||||
internal fun ConsentButton(
|
||||
text: String,
|
||||
buttonStyle: ConsentiumUIButtonStyle,
|
||||
onclick: () -> Unit,
|
||||
isLoading: Boolean = false,
|
||||
) {
|
||||
Button(
|
||||
onClick = onclick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(40.dp),
|
||||
shape = RoundedCornerShape(6.dp),
|
||||
colors = ButtonDefaults.buttonColors(buttonStyle.backgroundColor())
|
||||
) {
|
||||
if (isLoading) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = ConsentiumUITheme.colors.onPrimary,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = text,
|
||||
color = ConsentiumUITheme.colors.onPrimary,
|
||||
style = ConsentiumUITheme.typography.button,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ConsentButtonPreview() {
|
||||
ConsentButton(
|
||||
text = "Accept and close",
|
||||
buttonStyle = ConsentiumUIButtonStyle.PRIMARY,
|
||||
onclick = {}
|
||||
)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package fr.openium.consentium_ui.ui.components.core.button
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
internal enum class ConsentiumUIButtonStyle {
|
||||
PRIMARY,
|
||||
SECONDARY,
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUIButtonStyle.backgroundColor(): Color {
|
||||
return when (this) {
|
||||
ConsentiumUIButtonStyle.PRIMARY -> ConsentiumUITheme.colors.primary
|
||||
ConsentiumUIButtonStyle.SECONDARY -> ConsentiumUITheme.colors.secondary
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package fr.openium.consentium_ui.ui.components.core.toggle
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import fr.openium.consentium_ui.ui.components.style.ConsentiumUITheme
|
||||
|
||||
@Composable
|
||||
internal fun ConsentiumUISwitch(
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
) {
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = onCheckedChange,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedTrackColor = ConsentiumUITheme.colors.secondary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ConsentiumUISwitchPreview() {
|
||||
Column {
|
||||
ConsentiumUISwitch(
|
||||
checked = true,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
ConsentiumUISwitch(
|
||||
checked = false,
|
||||
onCheckedChange = {},
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
package fr.openium.consentium_ui.ui.components.style
|
||||
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.ColorUtils
|
||||
|
||||
|
||||
object ConsentiumDefaults {
|
||||
private const val OUTLINE_COLOR_BLEND_RATIO = 0.1f
|
||||
|
||||
private object Typography {
|
||||
private val DEFAULT_LINE_HEIGHT = 24.sp
|
||||
private val LINE_HEIGHT_18 = 18.sp
|
||||
private val LINE_HEIGHT_22 = 22.sp
|
||||
private val DEFAULT_LETTER_SPACING = 0.5.sp
|
||||
|
||||
val h1 = TextStyle(
|
||||
fontSize = 32.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val h2 = TextStyle(
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val b1 = TextStyle(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = DEFAULT_LINE_HEIGHT,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val p1 = TextStyle(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = DEFAULT_LINE_HEIGHT,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val b2 = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = LINE_HEIGHT_22,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val p2 = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = LINE_HEIGHT_22,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val b3 = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = LINE_HEIGHT_18,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val p3 = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = LINE_HEIGHT_18,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val button = TextStyle(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
|
||||
val sm2 = TextStyle(
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
lineHeight = LINE_HEIGHT_22,
|
||||
letterSpacing = DEFAULT_LETTER_SPACING
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun colors(
|
||||
primary: Color = MaterialTheme.colorScheme.primary,
|
||||
onPrimary: Color = MaterialTheme.colorScheme.onPrimary,
|
||||
secondary: Color = MaterialTheme.colorScheme.background,
|
||||
onSecondary: Color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
tertiary: Color = MaterialTheme.colorScheme.onBackground,
|
||||
onSurfaceVariant: Color = MaterialTheme.colorScheme.onBackground,
|
||||
onSurface: Color = MaterialTheme.colorScheme.onBackground,
|
||||
outline: Color = Color(
|
||||
ColorUtils.blendARGB(
|
||||
onPrimary.toArgb(),
|
||||
primary.toArgb(),
|
||||
OUTLINE_COLOR_BLEND_RATIO,
|
||||
)
|
||||
),
|
||||
error: Color = MaterialTheme.colorScheme.error,
|
||||
surfaceHighest: Color = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||
surfaceHigh: Color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
surfaceMiddle: Color = MaterialTheme.colorScheme.surfaceContainer,
|
||||
success: Color = Color(0xFF479B3F),
|
||||
): ConsentiumColors = ConsentiumColors(
|
||||
primary = primary,
|
||||
onPrimary = onPrimary,
|
||||
secondary = secondary,
|
||||
onSecondary = onSecondary,
|
||||
tertiary = tertiary,
|
||||
onSurfaceVariant = onSurfaceVariant,
|
||||
onSurface = onSurface,
|
||||
outline = outline,
|
||||
error = error,
|
||||
surfaceHighest = surfaceHighest,
|
||||
surfaceHigh = surfaceHigh,
|
||||
surfaceMiddle = surfaceMiddle,
|
||||
success = success,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun typography(
|
||||
h1: TextStyle = LocalTextStyle.current.merge(Typography.h1),
|
||||
h2: TextStyle = LocalTextStyle.current.merge(Typography.h2),
|
||||
b1: TextStyle = LocalTextStyle.current.merge(Typography.b1),
|
||||
p1: TextStyle = LocalTextStyle.current.merge(Typography.p1),
|
||||
b2: TextStyle = LocalTextStyle.current.merge(Typography.b2),
|
||||
p2: TextStyle = LocalTextStyle.current.merge(Typography.p2),
|
||||
b3: TextStyle = LocalTextStyle.current.merge(Typography.b3),
|
||||
p3: TextStyle = LocalTextStyle.current.merge(Typography.p3),
|
||||
button: TextStyle = LocalTextStyle.current.merge(Typography.button),
|
||||
sm2: TextStyle = LocalTextStyle.current.merge(Typography.sm2),
|
||||
): ConsentiumTypography = ConsentiumTypography(
|
||||
h1 = h1,
|
||||
h2 = h2,
|
||||
b1 = b1,
|
||||
p1 = p1,
|
||||
b2 = b2,
|
||||
p2 = p2,
|
||||
b3 = b3,
|
||||
p3 = p3,
|
||||
button = button,
|
||||
sm2 = sm2,
|
||||
)
|
||||
}
|
||||
|
||||
data class ConsentiumColors internal constructor(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val secondary: Color,
|
||||
val onSecondary: Color,
|
||||
val tertiary: Color,
|
||||
val onSurfaceVariant: Color,
|
||||
val onSurface: Color,
|
||||
val outline: Color,
|
||||
val error: Color,
|
||||
val surfaceHighest: Color,
|
||||
val surfaceHigh: Color,
|
||||
val surfaceMiddle: Color,
|
||||
val success: Color,
|
||||
)
|
||||
|
||||
data class ConsentiumTypography internal constructor(
|
||||
val h1: TextStyle,
|
||||
val h2: TextStyle,
|
||||
val b1: TextStyle,
|
||||
val p1: TextStyle,
|
||||
val b2: TextStyle,
|
||||
val p2: TextStyle,
|
||||
val b3: TextStyle,
|
||||
val p3: TextStyle,
|
||||
val button: TextStyle,
|
||||
val sm2: TextStyle,
|
||||
)
|
@ -0,0 +1,52 @@
|
||||
package fr.openium.consentium_ui.ui.components.style
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
||||
internal val LocalColors = staticCompositionLocalOf {
|
||||
ConsentiumColors(
|
||||
primary = Color.Unspecified,
|
||||
onPrimary = Color.Unspecified,
|
||||
secondary = Color.Unspecified,
|
||||
onSecondary = Color.Unspecified,
|
||||
tertiary = Color.Unspecified,
|
||||
onSurfaceVariant = Color.Unspecified,
|
||||
onSurface = Color.Unspecified,
|
||||
outline = Color.Unspecified,
|
||||
error = Color.Unspecified,
|
||||
surfaceHighest = Color.Unspecified,
|
||||
surfaceHigh = Color.Unspecified,
|
||||
surfaceMiddle = Color.Unspecified,
|
||||
success = Color.Unspecified,
|
||||
)
|
||||
}
|
||||
|
||||
internal val LocalTypography = staticCompositionLocalOf {
|
||||
ConsentiumTypography(
|
||||
h1 = TextStyle.Default,
|
||||
h2 = TextStyle.Default,
|
||||
b1 = TextStyle.Default,
|
||||
p1 = TextStyle.Default,
|
||||
b2 = TextStyle.Default,
|
||||
p2 = TextStyle.Default,
|
||||
b3 = TextStyle.Default,
|
||||
p3 = TextStyle.Default,
|
||||
button = TextStyle.Default,
|
||||
sm2 = TextStyle.Default,
|
||||
)
|
||||
}
|
||||
|
||||
internal object ConsentiumUITheme {
|
||||
val colors: ConsentiumColors
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalColors.current
|
||||
|
||||
val typography: ConsentiumTypography
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
get() = LocalTypography.current
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
enum class ConsentiumPageUI {
|
||||
GENERAL_CONSENT,
|
||||
DETAILS_CONSENT
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal data class DetailConsentUI(
|
||||
val conservationDurationText: String,
|
||||
val purposes: List<PurposeUI>,
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal data class GeneralConsentUI(
|
||||
val applicationName: String,
|
||||
val iconUrl: String,
|
||||
val mainConsentText: String,
|
||||
val consentPageUrl: String,
|
||||
)
|
@ -0,0 +1,7 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal enum class LoadingElement {
|
||||
BUTTON,
|
||||
LINK,
|
||||
NONE,
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal enum class PurposeStatusUI {
|
||||
ACCEPTED,
|
||||
REJECTED,
|
||||
NOT_DEFINED,
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal data class PurposeUI(
|
||||
val id: String,
|
||||
val isRequired: Boolean,
|
||||
var isAccepted: PurposeStatusUI,
|
||||
val title: String,
|
||||
val description: String,
|
||||
val vendors: List<VendorUI>,
|
||||
)
|
@ -0,0 +1,6 @@
|
||||
package fr.openium.consentium_ui.ui.model
|
||||
|
||||
internal data class VendorUI(
|
||||
val id: String,
|
||||
var isAccepted: Boolean,
|
||||
)
|
@ -0,0 +1,17 @@
|
||||
package fr.openium.consentium_ui.ui.state
|
||||
|
||||
import fr.openium.consentium_ui.ui.model.DetailConsentUI
|
||||
import fr.openium.consentium_ui.ui.model.GeneralConsentUI
|
||||
|
||||
internal sealed interface ConsentiumUIState {
|
||||
|
||||
data object Loading : ConsentiumUIState
|
||||
|
||||
data class Loaded(
|
||||
val generalConsentUI: GeneralConsentUI,
|
||||
val detailConsentUI: DetailConsentUI,
|
||||
) : ConsentiumUIState
|
||||
|
||||
data class Error(val message: String) : ConsentiumUIState
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package fr.openium.consentium_ui.ui.utils
|
||||
|
||||
import android.text.ParcelableSpan
|
||||
import android.text.Spanned
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.core.text.HtmlCompat
|
||||
|
||||
fun htmlToAnnotatedString(html: String): AnnotatedString {
|
||||
val spanned: Spanned = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
return buildAnnotatedString {
|
||||
val text = spanned.toString()
|
||||
var start = 0
|
||||
spanned.getSpans(0, spanned.length, Any::class.java).map { it as ParcelableSpan }.forEach { span ->
|
||||
val startSpan = spanned.getSpanStart(span)
|
||||
val endSpan = spanned.getSpanEnd(span)
|
||||
|
||||
// Ajoutez le texte précédent sans style
|
||||
if (start < startSpan) {
|
||||
append(text.substring(start, startSpan))
|
||||
}
|
||||
|
||||
// Ajoutez le texte stylé
|
||||
withStyle(style = span.toSpanStyle()) {
|
||||
append(text.substring(startSpan, endSpan))
|
||||
}
|
||||
start = endSpan
|
||||
}
|
||||
|
||||
// Ajoutez le texte restant sans style
|
||||
if (start < text.length) {
|
||||
append(text.substring(start, text.length).trimEnd())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ParcelableSpan.toSpanStyle(): SpanStyle {
|
||||
return when (this) {
|
||||
is android.text.style.StyleSpan -> {
|
||||
when (style) {
|
||||
android.graphics.Typeface.BOLD -> SpanStyle(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
|
||||
android.graphics.Typeface.ITALIC -> SpanStyle(fontStyle = androidx.compose.ui.text.font.FontStyle.Italic)
|
||||
else -> SpanStyle()
|
||||
}
|
||||
}
|
||||
|
||||
is android.text.style.UnderlineSpan -> SpanStyle(textDecoration = TextDecoration.Underline)
|
||||
is android.text.style.StrikethroughSpan -> SpanStyle(textDecoration = TextDecoration.LineThrough)
|
||||
else -> SpanStyle()
|
||||
}
|
||||
}
|
10
consentium-ui/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="cookies">Politique de cookies</string>
|
||||
<string name="accept">Accepter et fermer</string>
|
||||
<string name="refuse">Continuer sans accepter</string>
|
||||
<string name="parameters">Paramétrer mes choix</string>
|
||||
<string name="save">Enregistrer et fermer</string>
|
||||
<string name="require">Requis</string>
|
||||
<string name="save_consents_error_message">Une erreur est survenue l\'or de la ssauvegarde de vos consentements.</string>
|
||||
</resources>
|
@ -0,0 +1,17 @@
|
||||
package fr.openium.consentium_ui
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|