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