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 {
// 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 :
val BASE_URL = "http://10.0.2.2:3000/"
// val BASE_URL = "http://10.0.2.2:3000/"
// L'utilitaire conversion JSON <=> Objet
val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build();

View File

@@ -1,4 +1,4 @@
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"
) {
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.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.example.tpfilrouge.R
import com.example.tpfilrouge.ui.theme.EniButton
import com.example.tpfilrouge.ui.theme.EniPage
@@ -28,7 +29,7 @@ import com.example.tpfilrouge.ui.theme.EniTextField
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
fun ArticleForm(viewModel: ListArticleViewModel){
fun ArticleForm(viewModel: ListArticleViewModel, navController : NavController?=null){
var titleFieldState by remember { mutableStateOf("Un article") }
var descFieldState by remember { mutableStateOf("Une description") }
@@ -42,25 +43,69 @@ fun ArticleForm(viewModel: ListArticleViewModel){
val newArticle = Article(titleFieldState, descFieldState,
"https://avatar.iran.liara.run/public");
// Envoyer l'article via une méthode view model
viewModel.addArticle(newArticle)
viewModel.addArticle(newArticle, onSuccess = {
navController!!.navigate("list_article")
})
}
}
}
@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 {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(40.dp)
) {
Text(text = "Ajouter un article",
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.Center,
style = TextStyle(color = Color.White, fontSize = 28.sp)
)
ArticleForm(viewModel)
if (isEdit){
EditArticleFormPage(viewModel, navController)
}
else {
AddArticleFormPage(viewModel, navController)
}
}
}
}

View File

@@ -3,13 +3,23 @@ package com.example.demoandroid.demoapi
import com.example.tpfilrouge.api.RetrofitTools.Companion.retrofit
import com.example.tpfilrouge.api.ServiceResponseDTO
import com.example.tpfilrouge.article.Article
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface ArticleService {
@GET("articles")
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 {
val articleService : ArticleService by lazy { retrofit.create(ArticleService::class.java) }
}

View File

@@ -1,5 +1,6 @@
package com.example.tpfilrouge.article
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
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.lazy.LazyColumn
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.ElevatedCard
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@@ -32,7 +39,7 @@ import com.example.tpfilrouge.ui.theme.EniPage
import kotlinx.coroutines.flow.MutableStateFlow
@Composable
fun ArticleCard(article: Article){
fun ArticleCard(article: Article, viewModel: ListArticleViewModel, navController: NavController?=null){
ElevatedCard(
elevation = CardDefaults.cardElevation(defaultElevation = 6.dp),
modifier = Modifier.fillMaxWidth().padding(vertical = 14.dp)
@@ -48,6 +55,29 @@ fun ArticleCard(article: Article){
Text(article.title,
style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 15.sp))
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 {
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.viewModelScope
import com.example.demoandroid.demoapi.ArticleService
import com.example.tpfilrouge.helpers.AlertDialogHelpers
import com.example.tpfilrouge.helpers.AppDialogHelpers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
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());
fun addArticle(article: Article){
articles.value = articles.value + article;
fun editArticle(article: 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(){
@@ -23,20 +70,48 @@ class ListArticleViewModel : ViewModel() {
// Récupérer le metier listArticle via un API Web
val serviceResponse = ArticleService.ArticleApi.articleService.getArticles();
// Plus tard il sera possible de tester le code métier
/*
if (serviceResponse.code.equals("200")) {
// Fermer la popup de chargement aprés l'appel de API
AppDialogHelpers.get().closeDialog()
}
*/
// 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!!;
// 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 = {})
}
}
}