Correction partiel TP 5 ++

This commit is contained in:
Chocolaterie
2025-01-30 15:02:20 +01:00
parent 586f854b9d
commit 7052a52779
7 changed files with 188 additions and 28 deletions

View File

@@ -11,10 +11,10 @@ class RetrofitTools {
companion object { companion object {
// La racine de l'api // La racine de l'api
//val BASE_URL = "http://127.0.0.1:3000/" val BASE_URL = "http://127.0.0.1:3000/"
// Pour les personnes emulateurs : // Pour les personnes emulateurs :
val BASE_URL = "http://10.0.2.2:3000/" // val BASE_URL = "http://10.0.2.2:3000/"
// L'utilitaire conversion JSON <=> Objet // L'utilitaire conversion JSON <=> Objet
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build(); val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build();

View File

@@ -1,4 +1,4 @@
package com.example.tpfilrouge.article package com.example.tpfilrouge.article
data class Article(var title : String, var desc : String, var imgPath : String) { data class Article(var title : String, var desc : String, var imgPath : String, var id : String = "") {
} }

View File

@@ -32,7 +32,7 @@ fun ArticleActivityPage() {
startDestination = "list_article" startDestination = "list_article"
) { ) {
composable("list_article") { ListArticleFragmentPage(viewModel, navController) } composable("list_article") { ListArticleFragmentPage(viewModel, navController) }
composable("article_form") { ArticleFormFragmentPage(viewModel) } composable("article_form") { ArticleFormFragmentPage(viewModel, navController) }
} }
} }

View File

@@ -21,6 +21,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.tpfilrouge.R import com.example.tpfilrouge.R
import com.example.tpfilrouge.ui.theme.EniButton import com.example.tpfilrouge.ui.theme.EniButton
import com.example.tpfilrouge.ui.theme.EniPage import com.example.tpfilrouge.ui.theme.EniPage
@@ -28,7 +29,7 @@ import com.example.tpfilrouge.ui.theme.EniTextField
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@Composable @Composable
fun ArticleForm(viewModel: ListArticleViewModel){ fun ArticleForm(viewModel: ListArticleViewModel, navController : NavController?=null){
var titleFieldState by remember { mutableStateOf("Un article") } var titleFieldState by remember { mutableStateOf("Un article") }
var descFieldState by remember { mutableStateOf("Une description") } var descFieldState by remember { mutableStateOf("Une description") }
@@ -42,25 +43,69 @@ fun ArticleForm(viewModel: ListArticleViewModel){
val newArticle = Article(titleFieldState, descFieldState, val newArticle = Article(titleFieldState, descFieldState,
"https://avatar.iran.liara.run/public"); "https://avatar.iran.liara.run/public");
// Envoyer l'article via une méthode view model // Envoyer l'article via une méthode view model
viewModel.addArticle(newArticle) viewModel.addArticle(newArticle, onSuccess = {
navController!!.navigate("list_article")
})
} }
} }
} }
@Composable @Composable
fun ArticleFormFragmentPage(viewModel: ListArticleViewModel) { fun ArticleEditForm(viewModel: ListArticleViewModel, navController : NavController?=null){
var titleFieldState by remember { mutableStateOf(viewModel.editedArticle.title) }
var descFieldState by remember { mutableStateOf(viewModel.editedArticle.desc) }
Column {
EniTextField(label = "Saisir un Email", value = titleFieldState,
onValueChange = { newValue -> titleFieldState = newValue })
EniTextField(label = "Saisir une Description", value = descFieldState,
onValueChange = { newValue -> descFieldState = newValue })
EniButton("Editer") {
// Instancer un article avec les infos de la saisie
val editedArticle = Article(titleFieldState, descFieldState,
"https://avatar.iran.liara.run/public", id = viewModel.editedArticleId);
// Envoyer l'article via une méthode view model
viewModel.editArticle(editedArticle)
}
}
}
@Composable
fun AddArticleFormPage(viewModel: ListArticleViewModel, navController : NavController?=null){
Text(text = "Ajouter un article",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = TextStyle(color = Color.White, fontSize = 28.sp)
)
ArticleForm(viewModel, navController)
}
@Composable
fun EditArticleFormPage(viewModel: ListArticleViewModel, navController : NavController?=null){
Text(text = "Modifier un article",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = TextStyle(color = Color.White, fontSize = 28.sp)
)
ArticleEditForm(viewModel, navController)
}
@Composable
fun ArticleFormFragmentPage(viewModel: ListArticleViewModel, navController : NavController?=null) {
// Savoir si je suis en mode edition ou ajout
val isEdit by viewModel.isEdit.collectAsState();
EniPage { EniPage {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(40.dp) modifier = Modifier.padding(40.dp)
) { ) {
Text(text = "Ajouter un article", if (isEdit){
modifier = Modifier.fillMaxWidth(), EditArticleFormPage(viewModel, navController)
textAlign = TextAlign.Center, }
style = TextStyle(color = Color.White, fontSize = 28.sp) else {
) AddArticleFormPage(viewModel, navController)
ArticleForm(viewModel) }
} }
} }
} }

View File

@@ -3,13 +3,23 @@ package com.example.demoandroid.demoapi
import com.example.tpfilrouge.api.RetrofitTools.Companion.retrofit import com.example.tpfilrouge.api.RetrofitTools.Companion.retrofit
import com.example.tpfilrouge.api.ServiceResponseDTO import com.example.tpfilrouge.api.ServiceResponseDTO
import com.example.tpfilrouge.article.Article import com.example.tpfilrouge.article.Article
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface ArticleService { interface ArticleService {
@GET("articles") @GET("articles")
suspend fun getArticles() : ServiceResponseDTO<List<Article>> suspend fun getArticles() : ServiceResponseDTO<List<Article>>
@POST("articles/save")
suspend fun saveArticle(@Body article : Article) : ServiceResponseDTO<Article>
@DELETE("articles/{id}")
suspend fun delete(@Path("id") id :String) : ServiceResponseDTO<Article>
object ArticleApi { object ArticleApi {
val articleService : ArticleService by lazy { retrofit.create(ArticleService::class.java) } val articleService : ArticleService by lazy { retrofit.create(ArticleService::class.java) }
} }

View File

@@ -1,5 +1,6 @@
package com.example.tpfilrouge.article package com.example.tpfilrouge.article
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -7,8 +8,14 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.CardDefaults import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -32,7 +39,7 @@ import com.example.tpfilrouge.ui.theme.EniPage
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@Composable @Composable
fun ArticleCard(article: Article){ fun ArticleCard(article: Article, viewModel: ListArticleViewModel, navController: NavController?=null){
ElevatedCard( ElevatedCard(
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
modifier = Modifier.fillMaxWidth().padding(vertical = 14.dp) modifier = Modifier.fillMaxWidth().padding(vertical = 14.dp)
@@ -48,6 +55,29 @@ fun ArticleCard(article: Article){
Text(article.title, Text(article.title,
style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 15.sp)) style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 15.sp))
Text(article.desc) Text(article.desc)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
IconButton(onClick = {
}) {
Icon(imageVector = Icons.Filled.Info, contentDescription = "Voir")
}
IconButton(onClick = {
// Dire au viewmodel qu'on va être en mode édition
viewModel.isEdit.value = true;
// Dire au viewmodel quel article on va modifier
viewModel.editedArticle = article;
// Dire au viewmodel quel Id d'article en cours de proccessus
viewModel.editedArticleId = article.id;
// naviguer dans le formulaire
navController!!.navigate("article_form")
}) {
Icon(imageVector = Icons.Filled.Edit, contentDescription = "Edit")
}
IconButton(onClick = {
viewModel.callDeleteArticleApi(article.id);
}) {
Icon(imageVector = Icons.Filled.Delete, contentDescription = "Delete")
}
}
} }
} }
} }
@@ -76,7 +106,7 @@ fun ListArticleFragmentPage(viewModel: ListArticleViewModel, navController: NavC
} }
LazyColumn { LazyColumn {
items(articlesState) { article -> items(articlesState) { article ->
ArticleCard(article) ArticleCard(article, viewModel, navController)
} }
} }
} }

View File

@@ -3,16 +3,63 @@ package com.example.tpfilrouge.article
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.example.demoandroid.demoapi.ArticleService import com.example.demoandroid.demoapi.ArticleService
import com.example.tpfilrouge.helpers.AlertDialogHelpers
import com.example.tpfilrouge.helpers.AppDialogHelpers import com.example.tpfilrouge.helpers.AppDialogHelpers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ListArticleViewModel : ViewModel() { class ListArticleViewModel : ViewModel() {
// Indicateur pour savoir si je modifie ou pas un article
var editedArticleId : String = ""; // Quel est l'article que je modifie
var editedArticle : Article = Article("", "", ""); // Article en cours d'edition
var isEdit = MutableStateFlow(false); // Est-ce que je suis en mode edition ?
var articles = MutableStateFlow<List<Article>>(listOf()); var articles = MutableStateFlow<List<Article>>(listOf());
fun addArticle(article: Article){ fun editArticle(article: Article){
articles.value = articles.value + article; AppDialogHelpers.get().showDialog("Modification de l'article en cours...");
viewModelScope.launch {
// Appel api async
val responseService = ArticleService.ArticleApi.articleService.saveArticle(article);
// Fermer la popup de chargement aprés l'appel de API
AppDialogHelpers.get().closeDialog()
// Si ajout ok
if (responseService.code.equals("200")){
AlertDialogHelpers.get().show(responseService.message, onClose = {})
}
else{
// Erreur
AlertDialogHelpers.get().show(responseService.message, onClose = {})
}
}
}
fun addArticle(article: Article, onSuccess: () -> Unit){
AppDialogHelpers.get().showDialog("Ajout d'article en cours...");
viewModelScope.launch {
// Appel api async
val responseService = ArticleService.ArticleApi.articleService.saveArticle(article);
// Fermer la popup de chargement aprés l'appel de API
AppDialogHelpers.get().closeDialog()
// Si ajout ok
if (responseService.code.equals("200")){
AlertDialogHelpers.get().show(responseService.message, onClose = {
// Naviguer sur la page liste article
onSuccess()
})
}
else{
// Erreur
AlertDialogHelpers.get().show(responseService.message, onClose = {})
}
}
} }
fun callArticlesApi(){ fun callArticlesApi(){
@@ -23,20 +70,48 @@ class ListArticleViewModel : ViewModel() {
// Récupérer le metier listArticle via un API Web // Récupérer le metier listArticle via un API Web
val serviceResponse = ArticleService.ArticleApi.articleService.getArticles(); val serviceResponse = ArticleService.ArticleApi.articleService.getArticles();
// Plus tard il sera possible de tester le code métier // Fermer la popup de chargement aprés l'appel de API
/* AppDialogHelpers.get().closeDialog()
if (serviceResponse.code.equals("200")) {
}
*/
// La liste des articles ecoutables se met à jour par rapport à la
// la liste des articles dans le data
// Node : on remaque !! aprés data car data est nullable (!! que si nullable))
articles.value = serviceResponse.data!!; articles.value = serviceResponse.data!!;
// Fermer la popup de chargement }
AppDialogHelpers.get().closeDialog(); }
fun callDeleteArticleApi(id : String){
AppDialogHelpers.get().showDialog("Suppression de l'article en cours...");
viewModelScope.launch {
// Appel api async
val responseService = ArticleService.ArticleApi.articleService.delete(id);
// Fermer la popup de chargement aprés l'appel de API
AppDialogHelpers.get().closeDialog()
// Si suppression ok
if (responseService.code.equals("200")){
// Methode 1 : (Le flemmard) Appeler le refresh
// Avantage : Si d'autres user ont supprimés des articles entre temps, on a plus les articles
// supprimés par les autres users à l'écran
// Inconvenients : Moins performant niveau ressource du tel car on on refresh tout l'ecran et en plus on rappel une API
//callArticlesApi();
// Methode 2 : Je supprime dans le articles.value l'article je viens de supprimer
// Inconvenients : Si d'autres user ont supprimés des articles entre temps, on a encore les articles
// supprimés par les autres users à l'écran
// Avantage : Plus performant niveau ressource du tel car on fait le strict minimum pour refresh l'ecran
// EXEMPLE : Supprimer un element de la liste directement
val newArticles = articles.value.filterNot { it.id == id }
articles.value = newArticles;
AlertDialogHelpers.get().show(responseService.message, onClose = {
})
}
else{
// Erreur
AlertDialogHelpers.get().show(responseService.message, onClose = {})
}
} }
} }