From bbb278d7b2f31bb0fd31157ac38236fcc9d3d547 Mon Sep 17 00:00:00 2001 From: Chocolaterie <110991127+Chocolaterie@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:42:14 +0100 Subject: [PATCH] correcction tp 5 01 --- app/build.gradle.kts | 11 +++ .../example/tpfilrouge/api/RetrofitTools.kt | 24 +++++++ .../tpfilrouge/article/ArticleService.kt | 15 ++++ .../tpfilrouge/article/ListArticleActivity.kt | 54 +++++++++----- .../article/ListArticleViewModel.kt | 23 ++++-- .../tpfilrouge/helpers/AppDialogHelpers.kt | 70 +++++++++++++++++++ .../tpfilrouge/helpers/DialogModelData.kt | 4 ++ .../example/tpfilrouge/ui/theme/AppTheme.kt | 2 + 8 files changed, 179 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/example/tpfilrouge/api/RetrofitTools.kt create mode 100644 app/src/main/java/com/example/tpfilrouge/article/ArticleService.kt create mode 100644 app/src/main/java/com/example/tpfilrouge/helpers/AppDialogHelpers.kt create mode 100644 app/src/main/java/com/example/tpfilrouge/helpers/DialogModelData.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 550bf80..d2c8782 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -58,4 +58,15 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) implementation("io.coil-kt:coil-compose:2.7.0") + + // Moshi + implementation("com.squareup.moshi:moshi:1.14.0") + implementation("com.squareup.moshi:moshi-kotlin:1.14.0") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.retrofit2:converter-moshi:2.9.0") + + // Ok HTTP + implementation("com.squareup.okhttp3:okhttp:4.9.0") } \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/api/RetrofitTools.kt b/app/src/main/java/com/example/tpfilrouge/api/RetrofitTools.kt new file mode 100644 index 0000000..e585ba1 --- /dev/null +++ b/app/src/main/java/com/example/tpfilrouge/api/RetrofitTools.kt @@ -0,0 +1,24 @@ +package com.example.tpfilrouge.api + +import com.squareup.moshi.Moshi +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +class RetrofitTools { + + // Kotlin : companion object = tout ce qui est dedans est statics + companion object { + + // La racine de l'api + val BASE_URL = "https://raw.githubusercontent.com/Chocolaterie/EniWebService/refs/heads/main/api/" + + // L'utilitaire conversion JSON <=> Objet + val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build(); + + // Retrofit + val retrofit = Retrofit.Builder() + .addConverterFactory(MoshiConverterFactory.create(moshi)) + .baseUrl(BASE_URL).build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/article/ArticleService.kt b/app/src/main/java/com/example/tpfilrouge/article/ArticleService.kt new file mode 100644 index 0000000..5386648 --- /dev/null +++ b/app/src/main/java/com/example/tpfilrouge/article/ArticleService.kt @@ -0,0 +1,15 @@ +package com.example.demoandroid.demoapi + +import com.example.tpfilrouge.api.RetrofitTools.Companion.retrofit +import com.example.tpfilrouge.article.Article +import retrofit2.http.GET + +interface ArticleService { + + @GET("android-articles.json") + suspend fun getArticles() : List
+ + object ArticleApi { + val articleService : ArticleService by lazy { retrofit.create(ArticleService::class.java) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/article/ListArticleActivity.kt b/app/src/main/java/com/example/tpfilrouge/article/ListArticleActivity.kt index d224f73..f2d4f1e 100644 --- a/app/src/main/java/com/example/tpfilrouge/article/ListArticleActivity.kt +++ b/app/src/main/java/com/example/tpfilrouge/article/ListArticleActivity.kt @@ -38,6 +38,7 @@ import com.example.tpfilrouge.R import com.example.tpfilrouge.ui.theme.EniButton import com.example.tpfilrouge.ui.theme.EniPage import com.example.tpfilrouge.ui.theme.EniTextField +import kotlinx.coroutines.flow.MutableStateFlow class ListArticleActivity : ComponentActivity() { @@ -73,6 +74,28 @@ fun ArticleForm(viewModel: ListArticleViewModel){ } } +@Composable +fun ArticleCard(article: Article){ + ElevatedCard( + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), + modifier = Modifier.fillMaxWidth().padding(vertical = 14.dp) + ) { + Row(modifier = Modifier.padding(10.dp)) { + AsyncImage( + model = article.imgPath, + contentDescription = "", + modifier = Modifier.width(82.dp).padding(horizontal = 5.dp), + placeholder = painterResource(R.drawable.reset_password_ic), + ) + Column(modifier = Modifier.padding(start = 5.dp)) { + Text(article.title, + style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 15.sp)) + Text(article.desc) + } + } + } +} + @Composable fun ListArticleActivityPage(viewModel: ListArticleViewModel) { // Ecouter les changements de la liste d'article dans le ViewModel @@ -88,26 +111,13 @@ fun ListArticleActivityPage(viewModel: ListArticleViewModel) { textAlign = TextAlign.Center, style = TextStyle(color = Color.White, fontSize = 28.sp)) ArticleForm(viewModel) + EniButton("Rafraichir") { + // Appeler l'api du viewmodel + viewModel.callArticlesApi() + } LazyColumn { items(articlesState) { article -> - ElevatedCard( - elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), - modifier = Modifier.fillMaxWidth().padding(vertical = 14.dp) - ) { - Row(modifier = Modifier.padding(10.dp)) { - AsyncImage( - model = article.imgPath, - contentDescription = "", - modifier = Modifier.width(82.dp).padding(horizontal = 5.dp), - placeholder = painterResource(R.drawable.reset_password_ic), - ) - Column(modifier = Modifier.padding(start = 5.dp)) { - Text(article.title, - style = TextStyle(fontWeight = FontWeight.Bold, fontSize = 15.sp)) - Text(article.desc) - } - } - } + ArticleCard(article) } } } @@ -119,5 +129,11 @@ fun ListArticleActivityPage(viewModel: ListArticleViewModel) { ) @Composable fun ListArticleActivityPreview() { - ListArticleActivityPage(ListArticleViewModel()) + var viewModel = ListArticleViewModel(); + viewModel.articles = MutableStateFlow(listOf( + Article("Teletubies", "Meilleur série du monde", "https://avatar.iran.liara.run/public"), + Article("Velocipastor", "Meilleur film du monde, gros budget", "https://avatar.iran.liara.run/public"), + Article("Photo mouton béret paille ?", "Pourquoi", "https://avatar.iran.liara.run/public") + )); + ListArticleActivityPage(viewModel) } \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/article/ListArticleViewModel.kt b/app/src/main/java/com/example/tpfilrouge/article/ListArticleViewModel.kt index b17ff92..cd18707 100644 --- a/app/src/main/java/com/example/tpfilrouge/article/ListArticleViewModel.kt +++ b/app/src/main/java/com/example/tpfilrouge/article/ListArticleViewModel.kt @@ -1,18 +1,31 @@ package com.example.tpfilrouge.article import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.demoandroid.demoapi.ArticleService +import com.example.tpfilrouge.helpers.AppDialogHelpers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch class ListArticleViewModel : ViewModel() { - var articles = MutableStateFlow(listOf( - Article("Teletubies", "Meilleur série du monde", "https://avatar.iran.liara.run/public"), - Article("Velocipastor", "Meilleur film du monde, gros budget", "https://avatar.iran.liara.run/public"), - Article("Photo mouton béret paille ?", "Pourquoi", "https://avatar.iran.liara.run/public") - )); + var articles = MutableStateFlow>(listOf()); fun addArticle(article: Article){ articles.value = articles.value + article; } + fun callArticlesApi(){ + // Afficher la popup de chargement + AppDialogHelpers.get().showDialog("Chargement des articles en cours") + + viewModelScope.launch { + // Récupérer les articles via un API Web + articles.value = ArticleService.ArticleApi.articleService.getArticles(); + + // Fermer la popup de chargement + AppDialogHelpers.get().closeDialog(); + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/helpers/AppDialogHelpers.kt b/app/src/main/java/com/example/tpfilrouge/helpers/AppDialogHelpers.kt new file mode 100644 index 0000000..293f14e --- /dev/null +++ b/app/src/main/java/com/example/tpfilrouge/helpers/AppDialogHelpers.kt @@ -0,0 +1,70 @@ +package com.example.tpfilrouge.helpers + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import kotlinx.coroutines.flow.MutableStateFlow + +class AppDialogHelpers { + + companion object { + val instance : AppDialogHelpers by lazy { AppDialogHelpers() } + + fun get() : AppDialogHelpers { + return instance; + } + } + + // Sert à savoir en temps réel si il faut afficher ou pas la dialog + var dialogModelData = MutableStateFlow(DialogModelData()); + + /** + * Afficher la popup + */ + fun showDialog(message: String){ + // Forcer le rafraichissement de l'etat + dialogModelData.value = dialogModelData.value.copy(isShow = true, message = message); + } + + /** + * Fermer la popup + */ + fun closeDialog() { + // Forcer le rafraichissement de l'etat + dialogModelData.value = dialogModelData.value.copy(isShow = false); + } +} + +@Composable +fun ProgressDialog(){ + // Je vais ecouter quand la dialog est true ou false + // Donc quand je dois afficher ou pas + val dialogModelDataState by AppDialogHelpers.get().dialogModelData.collectAsState(); + + if (dialogModelDataState.isShow) { + Dialog(onDismissRequest = {}){ + Box(modifier = Modifier.background( + color = Color(0xFFFFFFFF), + shape = RoundedCornerShape(30.dp) + ) + .padding(20.dp)) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + CircularProgressIndicator() + Text(text = dialogModelDataState.message) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/helpers/DialogModelData.kt b/app/src/main/java/com/example/tpfilrouge/helpers/DialogModelData.kt new file mode 100644 index 0000000..94e9915 --- /dev/null +++ b/app/src/main/java/com/example/tpfilrouge/helpers/DialogModelData.kt @@ -0,0 +1,4 @@ +package com.example.tpfilrouge.helpers + +data class DialogModelData(var isShow : Boolean = false, var message : String = "") { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/tpfilrouge/ui/theme/AppTheme.kt b/app/src/main/java/com/example/tpfilrouge/ui/theme/AppTheme.kt index 94c89e1..46fe82e 100644 --- a/app/src/main/java/com/example/tpfilrouge/ui/theme/AppTheme.kt +++ b/app/src/main/java/com/example/tpfilrouge/ui/theme/AppTheme.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import com.example.tpfilrouge.R +import com.example.tpfilrouge.helpers.ProgressDialog class AppTheme { } @@ -38,6 +39,7 @@ fun EniPage(content: @Composable () -> Unit){ // Inserer un composant dynamiquement content() } + ProgressDialog() } } }