diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0e3ab1c..6c286ef 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -73,6 +73,7 @@ dependencies { implementation(libs.retrofit.adapters.arrow) implementation(libs.arrow.core) + implementation(libs.androidx.navigation.compose) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) diff --git a/app/src/main/java/com/iqball/app/MainActivity.kt b/app/src/main/java/com/iqball/app/MainActivity.kt index 964daee..5e247fd 100644 --- a/app/src/main/java/com/iqball/app/MainActivity.kt +++ b/app/src/main/java/com/iqball/app/MainActivity.kt @@ -7,21 +7,29 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController import com.google.gson.Gson -import com.google.gson.GsonBuilder import com.iqball.app.api.EitherBodyConverter import com.iqball.app.api.EitherCallAdapterFactory import com.iqball.app.api.service.IQBallService +import com.iqball.app.page.HomePage +import com.iqball.app.page.LoginPage import com.iqball.app.page.RegisterPage +import com.iqball.app.session.DataSession +import com.iqball.app.session.Session import com.iqball.app.ui.theme.IQBallTheme import okhttp3.OkHttpClient -import okhttp3.ResponseBody -import retrofit2.Converter import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import retrofit2.create -import java.lang.reflect.Type class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -46,8 +54,12 @@ class MainActivity : ComponentActivity() { setContent { IQBallTheme { // A surface container using the 'background' color from the theme - Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) { - App(service) + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val sessionState = remember { mutableStateOf(DataSession()) } + App(service, sessionState) } } } @@ -55,6 +67,36 @@ class MainActivity : ComponentActivity() { } @Composable -fun App(service: IQBallService) { - RegisterPage(service) +fun App(service: IQBallService, sessionState: MutableState) { + val navController = rememberNavController() + + NavHost(navController = navController, startDestination = "login") { + composable("login") { + LoginPage( + service = service, + onLoginSuccess = { auth -> + sessionState.value = DataSession(auth) + navController.navigate("home") + }, + onNavigateToRegister = { + navController.navigate("register") + } + ) + } + composable("register") { + RegisterPage( + service = service, + onRegisterSuccess = { auth -> + sessionState.value = DataSession(auth) + navController.navigate("home") + }, + onNavigateToLogin = { + navController.navigate("login") + } + ) + } + composable("home") { + HomePage(service = service, session = sessionState.value) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/api/service/AuthService.kt b/app/src/main/java/com/iqball/app/api/service/AuthService.kt index 5b88a09..bb444fe 100644 --- a/app/src/main/java/com/iqball/app/api/service/AuthService.kt +++ b/app/src/main/java/com/iqball/app/api/service/AuthService.kt @@ -11,7 +11,7 @@ import retrofit2.http.POST interface AuthService { @Serializable - data class AuthResponse(val token: String, val expirationDate: String) + data class AuthResponse(val token: String, val expirationDate: Long) @Serializable data class RegisterRequest(val username: String, val email: String, val password: String) diff --git a/app/src/main/java/com/iqball/app/page/HomePage.kt b/app/src/main/java/com/iqball/app/page/HomePage.kt new file mode 100644 index 0000000..8bcb3bf --- /dev/null +++ b/app/src/main/java/com/iqball/app/page/HomePage.kt @@ -0,0 +1,12 @@ +package com.iqball.app.page + +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import com.iqball.app.api.service.IQBallService +import com.iqball.app.session.Session + +@Composable +fun HomePage(service: IQBallService, session: Session) { + + Text(text = "HELLO WELCOME") +} \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/page/LoginPage.kt b/app/src/main/java/com/iqball/app/page/LoginPage.kt index f6f8748..9c30099 100644 --- a/app/src/main/java/com/iqball/app/page/LoginPage.kt +++ b/app/src/main/java/com/iqball/app/page/LoginPage.kt @@ -2,8 +2,106 @@ package com.iqball.app.page import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import arrow.core.Either +import com.iqball.app.api.service.AuthService +import com.iqball.app.session.Authentication +import kotlinx.coroutines.runBlocking @Composable -fun LoginPage() { - Text(text = "Login Page") +fun LoginPage( + service: AuthService, + onLoginSuccess: (Authentication) -> Unit, + onNavigateToRegister: () -> Unit +) { + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var errors by remember { mutableStateOf("") } + + Surface( + color = Color.White, + modifier = Modifier.fillMaxSize() + ) { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "S'identifier", + fontSize = 28.sp, + color = Color.Black + ) + + Text( + text = errors, + color = Color.Red, + fontSize = 14.sp, + modifier = Modifier.padding(vertical = 8.dp) + ) + + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = email, + onValueChange = { email = it }, + label = { Text("Email") }, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text("Password") }, + visualTransformation = PasswordVisualTransformation(), + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + + Button(onClick = { + runBlocking { + when (val response = service.login(AuthService.LoginRequest(email, password))) { + is Either.Left -> { + errors = response.value.toList() + .flatMap { entry -> entry.second.map { "${entry.first} : ${it}" } } + .joinToString("\n") + } + + is Either.Right -> onLoginSuccess( + Authentication( + response.value.token, + response.value.expirationDate.toLong() + ) + ) + } + } + }) { + Text(text = "Se connecter") + } + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { onNavigateToRegister() }) { + Text(text = "Vous n'avez pas de compte ?") + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/page/RegisterPage.kt b/app/src/main/java/com/iqball/app/page/RegisterPage.kt index 6bb14bc..898ca43 100644 --- a/app/src/main/java/com/iqball/app/page/RegisterPage.kt +++ b/app/src/main/java/com/iqball/app/page/RegisterPage.kt @@ -1,54 +1,130 @@ package com.iqball.app.page -import android.util.Log +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Surface 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.graphics.Color +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import arrow.core.Either import com.iqball.app.api.service.AuthService -import com.iqball.app.api.service.AuthService.RegisterRequest -import com.iqball.app.api.service.IQBallService import com.iqball.app.session.Authentication -import com.iqball.app.session.MutableSession -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @Composable -fun RegisterPage(service: IQBallService) { +fun RegisterPage( + service: AuthService, + onRegisterSuccess: (Authentication) -> Unit, + onNavigateToLogin: () -> Unit +) { + var username by remember { mutableStateOf("") } + var email by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + var errors by remember { mutableStateOf("") } + Surface( + color = Color.White, + modifier = Modifier.fillMaxSize() - var text by remember { mutableStateOf("No message !") } + ) { + Column( + modifier = Modifier + .padding(16.dp) + .fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "S'enregistrer", + fontSize = 28.sp, + color = Color.Black + ) + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = errors, + color = Color.Red, + fontSize = 14.sp, + modifier = Modifier.padding(vertical = 8.dp) + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = username, + onValueChange = { username = it }, + label = { Text("Nom d'utilisateur") }, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black + ) + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = password, + onValueChange = { password = it }, + label = { Text("Mot de passe") }, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black + ), + visualTransformation = PasswordVisualTransformation() + ) + Spacer(modifier = Modifier.height(16.dp)) + OutlinedTextField( + value = email, + onValueChange = { email = it }, + label = { Text("Email") }, + modifier = Modifier.fillMaxWidth(), + colors = OutlinedTextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black + ) + ) + Button(onClick = { + runBlocking { + when (val response = + service.register(AuthService.RegisterRequest(username, email, password))) { + is Either.Left -> { + errors = response.value.toList() + .flatMap { entry -> entry.second.map { "${entry.first} : ${it}" } } + .joinToString("\n") + } - runBlocking { - val result = service.login(AuthService.LoginRequest("maxime@mail.com", "123456")) - - when (result) { - is Either.Left -> { - println("Error : " + result.value) - text = result.toString() - } - is Either.Right -> { - val token = result.value.token - val userDataResponse = service.getUserData(token) - - when (userDataResponse) { - is Either.Left -> println("Error User Data : " + userDataResponse.value) - is Either.Right -> println("Success User Data : " + userDataResponse.value) + is Either.Right -> { + onRegisterSuccess( + Authentication( + response.value.token, + response.value.expirationDate + ) + ) + } + } } - - text = userDataResponse.toString() + }) { + Text(text = "Créer votre compte") + } + Spacer(modifier = Modifier.height(16.dp)) + Button(onClick = { onNavigateToLogin() }) { + Text(text = "Vous avez déjà un compte ?") } } - - println(result) - Log.i("%", result.toString()) } - - Text(text = text) } + diff --git a/app/src/main/java/com/iqball/app/session/Authentication.kt b/app/src/main/java/com/iqball/app/session/Authentication.kt index 1f9aed4..b5dbeaf 100644 --- a/app/src/main/java/com/iqball/app/session/Authentication.kt +++ b/app/src/main/java/com/iqball/app/session/Authentication.kt @@ -1,5 +1,3 @@ package com.iqball.app.session -import kotlinx.datetime.LocalDateTime - -data class Authentication(val token: String, val expirationDate: LocalDateTime) +data class Authentication(val token: String, val expirationDate: Long) diff --git a/app/src/main/java/com/iqball/app/session/DataSession.kt b/app/src/main/java/com/iqball/app/session/DataSession.kt new file mode 100644 index 0000000..686adc1 --- /dev/null +++ b/app/src/main/java/com/iqball/app/session/DataSession.kt @@ -0,0 +1,3 @@ +package com.iqball.app.session + +data class DataSession(override val auth: Authentication? = null) : Session \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/session/MutableSession.kt b/app/src/main/java/com/iqball/app/session/MutableSession.kt deleted file mode 100644 index 37219bf..0000000 --- a/app/src/main/java/com/iqball/app/session/MutableSession.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.iqball.app.session - -interface MutableSession : Session { - override var auth: Authentication -} \ No newline at end of file diff --git a/app/src/main/java/com/iqball/app/session/Session.kt b/app/src/main/java/com/iqball/app/session/Session.kt index bb34b40..e0c1975 100644 --- a/app/src/main/java/com/iqball/app/session/Session.kt +++ b/app/src/main/java/com/iqball/app/session/Session.kt @@ -1,5 +1,5 @@ package com.iqball.app.session interface Session { - val auth: Authentication + val auth: Authentication? } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9c53489..395069b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -agp = "8.3.0" +agp = "8.2.2" arrowCore = "1.2.1" converterGson = "2.9.0" kotlin = "1.9.0" @@ -15,6 +15,7 @@ composeBom = "2023.08.00" retrofit = "2.9.0" retrofit2KotlinxSerializationConverter = "1.0.0" retrofitAdaptersArrow = "1.0.9" +navigationCompose = "2.7.7" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -38,6 +39,7 @@ kotlinx-serialization-json-jvm = { module = "org.jetbrains.kotlinx:kotlinx-seria retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } retrofit-adapters-arrow = { module = "com.github.skydoves:retrofit-adapters-arrow", version.ref = "retrofitAdaptersArrow" } retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }