Correction partiel TP 5 ++
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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 = "") {
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user