Compare commits

...

15 Commits

@ -1,6 +1,7 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("plugin.serialization") version "1.5.10"
}
android {
@ -51,14 +52,35 @@ android {
dependencies {
// Retrofit
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0")
implementation ("androidx.compose.runtime:runtime-livedata:1.0.0-alpha07")
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.0")
implementation("androidx.navigation:navigation-compose:2.4.0-alpha08")
implementation ("androidx.media3:media3-session:1.3.0")
implementation("com.google.android.exoplayer:exoplayer-core:2.19.1")
implementation ("com.google.android.exoplayer:exoplayer-ui:2.19.1")
implementation ("com.squareup.okhttp3:logging-interceptor:4.9.1")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
implementation ("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("com.google.firebase:firebase-crashlytics-buildtools:2.9.9")
implementation("androidx.navigation:navigation-compose:2.8.0-alpha05")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
@ -69,4 +91,5 @@ dependencies {
implementation("io.coil-kt:coil-compose:1.4.0")
}

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
xmlns:tools="http://schemas.android.com/tools"
>
<application
android:allowBackup="true"
@ -11,7 +12,10 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.VeraxApplication"
tools:targetApi="31">
tools:targetApi="31"
android:networkSecurityConfig="@xml/network_security_config"
>
<activity
android:name=".MainActivity"
android:exported="true"

@ -1,268 +1,63 @@
package com.example.veraxapplication
import android.os.Build
import ArticlesViewModel
import IUsersDataManager
import StubUsers
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.ui.graphics.Color
import androidx.compose.material3.Text
import androidx.compose.ui.res.colorResource
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberImagePainter
import coil.size.Scale
import com.example.veraxapplication.articles.IArticlesDataManager
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.veraxapplication.modele.IArticlesDataManager
import com.example.veraxapplication.articles.StubArticles
import com.example.veraxapplication.data.Article
import com.example.veraxapplication.data.Paragraph
import com.example.veraxapplication.ui.theme.Salmon
import com.example.veraxapplication.modele.api.UsersViewModel
import com.example.veraxapplication.navigation.VeraxNavHost
import com.example.veraxapplication.ui.connexion.AfficherForm
import com.example.veraxapplication.ui.topBar.TopBarVerax
// doc navBar: https://developer.android.com/reference/kotlin/androidx/compose/material3/package-summary#TopAppBar(kotlin.Function0,androidx.compose.ui.Modifier,kotlin.Function0,kotlin.Function1,androidx.compose.foundation.layout.WindowInsets,androidx.compose.material3.TopAppBarColors,androidx.compose.material3.TopAppBarScrollBehavior)
// doc compose, pleins de trucs: https://developer.android.com/jetpack/compose/text?hl=fr
//doc couleur background pas finie: https://developer.android.com/jetpack/compose/components/scaffold
class MainActivity : ComponentActivity() {
// un truc vite fait pour avoir un visi
// var article = listOf("Thinkerview", "thinkerview.jgp", "Thinkerview est une chaîne youtube d'interview-débat")
// var articles = listOf( Article("Thinkerview", "This is a descrition", Author = "IAmAGreatAuthor", Image = "https://www.gstatic.com/webp/gallery/1.jpg", LectureTime = "12", Content = listOf(Paragraph("This is a paragraph"), Paragraph("This is another paragraph"), Paragraph("This is a third paragraph"))))
var dataManager: IArticlesDataManager = StubArticles()
var articles = dataManager.getDerniersArticles(4)
var theme = listOf("Economique", "Culture", "Politique", "Faits divers")
var color = Color(0xFF00FF00)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// articles?.forEach({ a -> println(a)})
articles?.forEach({ a ->
Log.println(Log.ASSERT, "debug articles", a.toString())
})
VeraxContent()
}
}
}
// Il faudrait mettre ca dans un fichier appart mais je connais plus les conventions ...
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBarVerax(theme: List<String>, article: List<Article>) {
var leMenu by remember {
mutableStateOf(false)
}
Row() {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
text = "Verax",
style = TextStyle(fontSize = 35.sp),
color = colorResource(R.color.red),
textAlign = TextAlign.Center,
/*backcolor = topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer),*/ //version recommandée par le prof
modifier = Modifier.fillMaxWidth()
)
},
navigationIcon = {
IconButton(onClick = { /* action() */ }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Retour",
Modifier.size(30.dp)
)
}
},
actions = {
IconButton(onClick = { leMenu = !leMenu }) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu",
Modifier.size(35.dp)
)
}
DropdownMenu(
expanded = leMenu, onDismissRequest = { leMenu = false },
modifier = Modifier
.background(Color.hsl(0.08F, 1F, 0.96F))
) {
theme.sorted().forEach {
DropdownMenuItem(
text = {
Text(
it,
style = TextStyle(fontSize = 25.sp),
modifier = Modifier
.padding(10.dp)
)
},
onClick = { /* faut un moyen d'appeler une methode diff pour chaque, ca doit etre faisable facilement */ }
)
}
}
}
@Composable
fun VeraxContent() {
)
},
bottomBar = {
// Faudrait pouvoir faire un flex sur les boutons parce que là ils sont juste côte à côte
BottomAppBar(containerColor = Color.Black, contentColor = Color.White) {
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Home,
contentDescription = "Home",
Modifier.size(35.dp)
)
}
IconButton(onClick = { /*TODO*/ }) {
Icon(
imageVector = Icons.Filled.Person,
contentDescription = "Account",
Modifier.size(35.dp)
)
}
}
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
AffichageUnArticle(article = article)
}
}
}
}
// Initialiser les données ou observer les données du ViewModel
var dataManager: IArticlesDataManager = StubArticles()
var articles = dataManager.getDerniersArticles(4)
var usersManager: IUsersDataManager = StubUsers()
var users = usersManager.getUsers();
@Composable
fun AffichageUnArticle(article: List<Article>) {
Column() {
for (e in article) {
DisplayTitle(title = e.Title)
DisplayHeader(
author = e.Author,
description = e.Description,
lectureTime = e.LectureTime
)
DisplayImage(image = e.Image)
DisplayContentArticle(content = e.Content)
}
}
}
// Observer les données du ViewModel
val articlesViewModel: ArticlesViewModel = viewModel()
@Composable
fun DisplayImage(image: String) {
Log.d("DisplayImage", "Chargement de l'image à partir de l'URL : $image")
// Observez les articles du ViewModel
val articlesApi by articlesViewModel.articles.observeAsState(initial = articles)
val painter = rememberImagePainter(
data = image,
builder = {
scale(Scale.FILL)
}
)
val usersViewModel: UsersViewModel = viewModel()
Log.d("DisplayImage", "Painter créé avec succès")
val usersApi by usersViewModel.users.observeAsState(initial = users)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
androidx.compose.foundation.Image(
painter = painter,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
)
}
}
var theme = listOf("Economique", "Culture", "Politique", "Faits divers")
@Composable
fun DisplayHeader(author: String, description: String, lectureTime: String) {
Box(
modifier = Modifier
.fillMaxWidth()
.border(width = 1.dp, color = Color.Black, shape = RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp))
.background(Salmon)
.padding(10.dp)
) {
Column() {
Text(text = author)
Text(text = description)
Text(text = "Lecture Time: " + lectureTime + " minutes")
}
}
}
@Composable
fun DisplayTitle(title: String) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = title, fontFamily = FontFamily.Serif, fontSize = 30.sp)
}
}
TopBarVerax(articles = articlesApi, theme = theme, articlesStub= articles)
@Composable
fun DisplayContentArticle(content: List<Paragraph>) {
Column {
for (e in content) {
Text(
text = e.Content,
fontSize = 15.sp,
fontFamily = FontFamily.Serif,
textAlign = TextAlign.Justify,
modifier = Modifier.padding(10.dp)
)
}
}
}
}

@ -1,5 +1,5 @@
package com.example.veraxapplication.data
/*
data class Article(
var Title : String,
var Description : String,
@ -8,3 +8,4 @@ data class Article(
var Content : List<Paragraph>,
var LectureTime : String
)
*/

@ -1,5 +1,6 @@
package com.example.veraxapplication.data
/*
data class Paragraph(
var Content : String,
)
*/

@ -1,4 +1,4 @@
package com.example.veraxapplication.articles
package com.example.veraxapplication.modele
import com.example.veraxapplication.modele.articles.Article
@ -7,5 +7,5 @@ interface IArticlesDataManager {
val allArticles: List<Any?>?
fun getArticle(id: Int): Article?
fun getDerniersArticles(nbArticles: Int): List<Article?>?
fun getDerniersArticles(nbArticles: Int): List<Article>
}

@ -0,0 +1,9 @@
import com.example.veraxapplication.modele.user.User
interface IUsersDataManager {
val allUsers: List<Any?>?
fun getUser(pseudo : String): User?
fun getUsers(): List<User>
}

@ -0,0 +1,53 @@
package com.example.veraxapplication.modele.api
import com.example.veraxapplication.modele.articles.Article
import com.google.gson.annotations.SerializedName
data class ArticleDTO (
@SerializedName("id")
val id: Int,
@SerializedName("titre")
val titre: String,
@SerializedName("description")
val description: String,
@SerializedName("temps")
val temps: String,
@SerializedName("date")
val date: String,
@SerializedName("auteur")
val auteur: String,
@SerializedName("categorie")
val categorie: String,
@SerializedName("imagePrincipale")
val imagePrincipale: String,
@SerializedName("note")
val note: String,
) {
fun toModel(): Article {
return Article(
id,
titre,
description,
temps,
date,
auteur,
categorie,
imagePrincipale,
note,
)
}
}
data class ContenuDTO (
@SerializedName("id")
val id: Int,
@SerializedName("typeContenu")
val typeContenu: String,
@SerializedName("titre")
val titre: String,
@SerializedName("texte")
val texte: String,
)

@ -0,0 +1,54 @@
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.veraxapplication.modele.api.IArticleService
import com.example.veraxapplication.modele.articles.Article
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object ArticleApiClient {
private const val BASE_URL = "http://181.214.189.133:9092/"
private val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val httpClient = OkHttpClient.Builder().apply {
addInterceptor(logging)
}.build()
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(httpClient)
.build()
}
class ArticlesViewModel : ViewModel() {
private val _articles = MutableLiveData<List<Article>>()
val articles: LiveData<List<Article>> = _articles
private val service = ArticleApiClient.retrofit.create(IArticleService::class.java)
init {
loadArticles()
}
fun loadArticles() {
viewModelScope.launch {
try {
val articlesDto = service.getArticles() // Pas besoin d'appeler .execute()
// Convertissez les DTO en modèles de données si nécessaire
_articles.value = articlesDto.map { it.toModel() }
} catch (e: Exception) {
Log.e("ArticlesViewModel", "Erreur lors du chargement des articles", e)
}
}
}
}

@ -0,0 +1,8 @@
package com.example.veraxapplication.modele.api
import retrofit2.http.GET
interface IArticleService {
@GET("articles")
suspend fun getArticles(): List<ArticleDTO>
}

@ -0,0 +1,11 @@
package com.example.veraxapplication.modele.api
import com.example.veraxapplication.modele.user.User
import retrofit2.Call
import retrofit2.http.GET
interface IUserService {
@GET("users")
suspend fun getUsers(): List<UserDTO>
}

@ -0,0 +1,32 @@
package com.example.veraxapplication.modele.api
import com.example.veraxapplication.modele.user.User
import com.google.gson.annotations.SerializedName
data class UserDTO (
@SerializedName("pseudo")
val pseudo: String,
@SerializedName("mdp")
val mdp: String,
@SerializedName("mail")
val mail: String,
@SerializedName("nom")
val nom: String,
@SerializedName("prenom")
val prenom: String,
@SerializedName("role")
val role: String,
) {
fun toModel(): User {
return User(
pseudo,
mdp,
mail,
nom,
prenom,
role,
)
}
}

@ -0,0 +1,63 @@
package com.example.veraxapplication.modele.api
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.modele.user.User
import kotlinx.coroutines.launch
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
object RetrofitClientUser {
private const val BASE_URL = "https://codefirst.iut.uca.fr/containers/Verax-verax-api"
private val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val httpClient = OkHttpClient.Builder().apply {
addInterceptor(logging)
}.build()
val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
// interface UserApiService {
// @GET("users")
// suspend fun getUsers() : List<User>
// }
}
class UsersViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> = _users
private val service = ArticleApiClient.retrofit.create(IUserService::class.java)
init {
loadUsers()
}
fun loadUsers() {
viewModelScope.launch {
try {
val usersDto = service.getUsers() // Pas besoin d'appeler .execute()
// Convertissez les DTO en modèles de données si nécessaire
_users.value = usersDto.map { it.toModel() }
} catch (e: Exception) {
Log.e("UsersViewModel", "Erreur lors du chargement des users", e)
}
}
}
}

@ -9,10 +9,11 @@ class Article(
val temps: String,
val date: String,
val auteur: String,
val imagePrincipale: String
val categorie: String,
val imagePrincipale: String,
val note: String
) {
val note = 1.0
private val lContenus: MutableList<Contenu> = java.util.ArrayList()
public val lContenus: MutableList<Contenu> = java.util.ArrayList()
init {
// Initialisation des contenus si nécessaire
@ -30,4 +31,6 @@ class Article(
override fun toString(): String {
return "Article(id=$id, titre='$titre', description='$description', temps='$temps', date='$date', auteur='$auteur', imagePrincipale='$imagePrincipale', note=$note, contenus=${lContenus.size} contenus)"
}
}

@ -1,13 +1,10 @@
package com.example.veraxapplication.articles
import android.os.Build
import androidx.annotation.RequiresApi
import com.example.veraxapplication.modele.IArticlesDataManager
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.modele.articles.contenus.Contenu
import com.example.veraxapplication.modele.articles.contenus.ContenuMedia
import com.example.veraxapplication.modele.articles.contenus.ContenuParagraphe
import java.time.LocalDate
import kotlin.math.min
class StubArticles() : IArticlesDataManager {
@ -28,9 +25,11 @@ class StubArticles() : IArticlesDataManager {
"Thinkerview",
"Thinkerview est une chaîne passionnante chaîne youtube d'interview-débat.",
"3",
"date a revoir",
"02/09/2024",
"Siwa",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219554341388816437/stub1.webp?ex=660bb97d&is=65f9447d&hm=3e1e8d3372ae897fa4e2aa1ec730d976d74b35fce96cb8d78d6f9239e2836564&"
"Politique",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219554341388816437/stub1.webp?ex=660bb97d&is=65f9447d&hm=3e1e8d3372ae897fa4e2aa1ec730d976d74b35fce96cb8d78d6f9239e2836564&",
"12"
)
article1.remplirArticle(contenuMap["article1"])
lArticles!!.add(article1)
@ -41,9 +40,11 @@ class StubArticles() : IArticlesDataManager {
"Le réchauffement climatique : un mythe ?",
"Revenons sur les différentes controverses à ce sujet.",
"7",
"date a revoir",
"02/09/2024",
"Siwa",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219555874339815454/stub2.webp?ex=660bbaea&is=65f945ea&hm=80aef945e8410b18395c716fdd19265608f7b1263731192d5c69f807fce9e944&"
"Politique",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219555874339815454/stub2.webp?ex=660bbaea&is=65f945ea&hm=80aef945e8410b18395c716fdd19265608f7b1263731192d5c69f807fce9e944&",
"12"
)
article2.remplirArticle(contenuMap["article5"])
lArticles!!.add(article2)
@ -54,9 +55,11 @@ class StubArticles() : IArticlesDataManager {
"La terre plate : vraiment ?",
"Pour réfuter la fausse croyance que la Terre est plate, il est essentiel de s'appuyer sur des preuves scientifiques et des observations historiques. ",
"5",
"date a revoir",
"02/09/2024",
"Siwa",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219547864196317225/stub1.webp?ex=660bb374&is=65f93e74&hm=a9e5dd48faa3ae68c358309af8949c46dfd4dea9c4d6e3d845d707784e5341cf&"
"Politique",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219547864196317225/stub1.webp?ex=660bb374&is=65f93e74&hm=a9e5dd48faa3ae68c358309af8949c46dfd4dea9c4d6e3d845d707784e5341cf&",
"12"
)
article3.remplirArticle(contenuMap["article4"])
lArticles!!.add(article3)
@ -67,9 +70,11 @@ class StubArticles() : IArticlesDataManager {
"L'ia & humanité : quel avenir ? ",
"Explorons les progrès remarquables dans le domaine de l'IA, les secteurs qu'elle révolutionne, et les implications éthiques majeures qu'elle soulève.",
"9",
"date a revoir",
"02/09/2024",
"Luthen",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219560686254817290/stub1.webp?ex=660bbf65&is=65f94a65&hm=021bd8c90c89347f31373468cc7a03ae15f1d3f9988a5b4325149c6df938d7bb&"
"Politique",
"https://cdn.discordapp.com/attachments/1150826798549049554/1219560686254817290/stub1.webp?ex=660bbf65&is=65f94a65&hm=021bd8c90c89347f31373468cc7a03ae15f1d3f9988a5b4325149c6df938d7bb&",
"12"
)
article4.remplirArticle(contenuMap["article6"])
lArticles!!.add(article4)
@ -88,11 +93,11 @@ class StubArticles() : IArticlesDataManager {
article1Contenus.add(
ContenuParagraphe(
1, "Le mystérieux cygne noir.",
"Sous les traits dun mystérieux cygne noir, un objet vidéo non identifié plane dans la galaxie \n" +
" médiatique. Ambiance Star Wars. Après une musique lancinante, sur fond noir et fumée grisâtre,\n" +
" un invité apparaît, et la voix dun intervieweur-mystère situé hors champ linvite à se\n" +
" présenter « succinctement ». Cest ainsi quon entre dans le monde de ThinkerView, \n" +
" chaîne qui a commencé à diffuser en janvier 2013 sur YouTube. Ici, pas de publicité,\n" +
"Sous les traits dun mystérieux cygne noir, un objet vidéo non identifié plane dans la galaxie " +
" médiatique. Ambiance Star Wars. Après une musique lancinante, sur fond noir et fumée grisâtre," +
" un invité apparaît, et la voix dun intervieweur-mystère situé hors champ linvite à se" +
" présenter « succinctement ». Cest ainsi quon entre dans le monde de ThinkerView," +
" chaîne qui a commencé à diffuser en janvier 2013 sur YouTube. Ici, pas de publicité," +
" pas de montage, pas deffets de lumière. Le calme, peut-être pour annoncer la tempête."
)
)
@ -106,13 +111,13 @@ class StubArticles() : IArticlesDataManager {
article1Contenus.add(
ContenuParagraphe(
2, "Penser, réfléchir et s'exprimer librement.",
("Dernier carton en date : un entretien de deux heures avec Juan Branco, lavocat du gilet \n" +
" jaune Maxime Nicolle et « conseiller juridique » de Wikileaks. Quelques jours avant lui,\n" +
" cétait au tour de François Boulo, autre avocat inscrit au barreau de Rouen et lun des\n" +
" porte-parole des « gilets jaunes ». « Ici, les gens ont vraiment le temps de développer \n" +
" leurs idées, confie Boulo. Il faut pouvoir écouter une pensée complète, sans être interrompu.\n" +
" » Aux yeux de ce fils dune famille de droite populaire (paysans et commerçants), ThinkerView \n" +
" a réalisé ce dont Pierre Bourdieu avait rêvé. Sil sabreuve à cette source depuis « un an ou \n" +
("Dernier carton en date : un entretien de deux heures avec Juan Branco, lavocat du gilet" +
" jaune Maxime Nicolle et « conseiller juridique » de Wikileaks. Quelques jours avant lui," +
" cétait au tour de François Boulo, autre avocat inscrit au barreau de Rouen et lun des" +
" porte-parole des « gilets jaunes ». « Ici, les gens ont vraiment le temps de développer" +
" leurs idées, confie Boulo. Il faut pouvoir écouter une pensée complète, sans être interrompu." +
" » Aux yeux de ce fils dune famille de droite populaire (paysans et commerçants), ThinkerView" +
" a réalisé ce dont Pierre Bourdieu avait rêvé. Sil sabreuve à cette source depuis « un an ou" +
" deux », en réalité, ce nest pas lui qui la trouvée, mais linverse. Magie des algorithmes.")
)
)
@ -126,18 +131,18 @@ class StubArticles() : IArticlesDataManager {
article1Contenus.add(
ContenuParagraphe(
3, "Une alternative dans un monde aux informations formatées",
("\\\"Nous faisons des interviews aux perspectives alternatives dans un monde aux informations formatées\\\",\n" +
" explique le site Thinkerview. La marque a adopté un cygne noir comme logo, un clin d'œil à la théorie \n" +
" du cygne noir (expliquée dans cet article de Challenges), soit un événement qui a peu de \n" +
" chances de se produire mais qui, s'il se produit, a des conséquences considérables.\n" +
("\\\"Nous faisons des interviews aux perspectives alternatives dans un monde aux informations formatées\\\"," +
" explique le site Thinkerview. La marque a adopté un cygne noir comme logo, un clin d'œil à la théorie" +
" du cygne noir (expliquée dans cet article de Challenges), soit un événement qui a peu de" +
" chances de se produire mais qui, s'il se produit, a des conséquences considérables." +
"\n" +
" Les invités viennent d'horizons divers avec une petite préférence pour les \n" +
" intellectuels iconoclastes et les contestataires de tous bords, de l'ancien \n" +
" ministre grec Yanis Varoufakis à l'historien et essayiste Emmanuel Todd, en\n" +
" passant par les journalistes Natacha Polony et Laurent Obertone ou encore \n" +
" la coqueluche des \\\"gilets jaunes\\\" Etienne Chouard. \\\"On est au milieu de \n" +
" toutes les communautés qui s'écharpent sur internet, de l'extrême droite à\n" +
" l'extrême gauche, explique Sky. On cherche à créer un terrain neutre pour \n" +
" Les invités viennent d'horizons divers avec une petite préférence pour les" +
" intellectuels iconoclastes et les contestataires de tous bords, de l'ancien" +
" ministre grec Yanis Varoufakis à l'historien et essayiste Emmanuel Todd, en" +
" passant par les journalistes Natacha Polony et Laurent Obertone ou encore" +
" la coqueluche des \\\"gilets jaunes\\\" Etienne Chouard. \\\"On est au milieu de" +
" toutes les communautés qui s'écharpent sur internet, de l'extrême droite à" +
" l'extrême gauche, explique Sky. On cherche à créer un terrain neutre pour" +
" que tout le monde puisse échanger.")
)
)
@ -430,10 +435,8 @@ class StubArticles() : IArticlesDataManager {
return null // Retourne null si lArticles est null
}
override fun getDerniersArticles(nbArticles: Int): List<Article>? {
return lArticles?.let {
it.take(min(nbArticles, it.size))
}
override fun getDerniersArticles(nbArticles: Int): List<Article> {
return lArticles?.takeIf { it.isNotEmpty() }?.take(nbArticles) ?: emptyList()
}
}

@ -25,6 +25,6 @@ class ContenuMedia(id: Int, var titre: String, var lien: String) : Contenu(id) {
}
override fun toString(): String {
return "ContenuMedia(id=$id, typeContenu='$typeContenu', titre='$titre', lien='$lien')"
return "$lien\n\n"
}
}

@ -15,6 +15,6 @@ class ContenuParagraphe(id: Int, var titre: String, var texte: String) : Contenu
}
override fun toString(): String {
return "ContenuParagraphe(id=$id, typeContenu='$typeContenu', titre='$titre', texte='$texte')"
return "$texte \n\n"
}
}

@ -0,0 +1,78 @@
import com.example.veraxapplication.modele.IArticlesDataManager
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.modele.articles.contenus.Contenu
import com.example.veraxapplication.modele.articles.contenus.ContenuMedia
import com.example.veraxapplication.modele.articles.contenus.ContenuParagraphe
import com.example.veraxapplication.modele.user.User
class StubUsers() : IUsersDataManager {
private var lUsers: MutableList<User>? = null
init {
chargerUsers()
}
private fun chargerUsers() {
lUsers = java.util.ArrayList<User>()
val user1 = User (
"NoaSil",
"1234",
"",
"Sillard",
"Noa",
"Admin"
)
lUsers!!.add(user1)
val user2 = User (
"Sha",
"1234",
"",
"Cascarra",
"Shana",
"Admin"
)
lUsers!!.add(user2)
val user3 = User (
"TonyF",
"1234",
"tony@gmail.com",
"Fages",
"Tony",
"Admin"
)
lUsers!!.add(user3)
val user4 = User (
"JeanSwaggLaPuissance63",
"1234",
"jean.lapuissance@gmail.com",
"Marcillac",
"Jean",
"Admin"
)
lUsers!!.add(user4)
}
override val allUsers: List<User>?
get() = lUsers
override fun getUser(pseudo : String): User? {
println("Passage dans getUser avec comme pseudo : $pseudo")
lUsers?.let {
println("Nombre d'utilisateurs disponibles : ${it.size}")
val userARenvoyer: User? = it.find { user -> user.pseudo == pseudo }
return userARenvoyer
}
return null
}
override fun getUsers(): List<User>
{
return lUsers?.takeIf { it.isNotEmpty() }?.take(lUsers!!.size) ?: emptyList()
}
}

@ -0,0 +1,3 @@
package com.example.veraxapplication.modele.user
data class User(val pseudo : String, val mdp : String, val mail : String, val nom : String, val prenom : String, val role : String)

@ -0,0 +1,46 @@
package com.example.veraxapplication.navigation
import androidx.compose.runtime.Composable
import androidx.navigation.NavHostController
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.ui.article.AffichageLesArticles
import com.example.veraxapplication.ui.article.AfficherArticle
@Composable
fun VeraxNavHost(articles : List<Article>, navController: NavHostController, articlesStub: List<Article>) {
NavHost(
navController = navController,
startDestination = "accueil"
){
composable(route="accueil"){
AffichageLesArticles(
articles = articles,
goToArticle = {
navController.navigate("article/${it.id}")
}
)
}
composable(
route="article/{articleid}",
arguments= listOf(navArgument("articleid"){ type= NavType.IntType})
){
it.arguments?.getInt("articleid")?.let { articleid ->
articlesStub.find { it.id == articleid }?.let {
AfficherArticle(
e = it
)
}
}
}
}
}

@ -0,0 +1,189 @@
package com.example.veraxapplication.ui.article
import VideoPlayer
import android.util.Log
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.rememberImagePainter
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.modele.articles.contenus.Contenu
import com.example.veraxapplication.modele.articles.contenus.ContenuMedia
import com.example.veraxapplication.modele.articles.contenus.ContenuParagraphe
import com.example.veraxapplication.ui.theme.Salmon
@Composable
fun AffichageLesArticles(articles : List<Article>, goToArticle: (Article) -> Unit){
Column(modifier = Modifier.verticalScroll(rememberScrollState())){
for(article in articles){
Box {
AffichageUnArticleInfo(e = article, goToArticle)
}
}
}
}
@Composable
fun AffichageUnArticleInfo(e : Article, goToArticle: (Article) -> Unit){
Column(modifier = Modifier
.padding(7.dp)
.border(width = 1.dp, color = Color.Black, shape = RoundedCornerShape(10.dp))
.padding(5.dp)) {
Text(text = e.titre, fontFamily = FontFamily.Serif, fontSize = 30.sp)
Box(modifier = Modifier
.padding(15.dp)
.border(width = 1.dp, color = Color.Black, shape = RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp))
.background(Salmon)
) {
Column (modifier = Modifier.padding(15.dp)) {
Text(text = "Auteur : "+e.auteur, fontSize = 17.sp)
Text(text = "Description : "+e.description, fontSize = 17.sp)
Text(text = "Temps de lecture : "+e.temps+" minutes", fontSize = 17.sp)
}
}
Image(
painter = rememberImagePainter(
data = e.imagePrincipale
),
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier
.size(350.dp)
.align(Alignment.CenterHorizontally)
.padding(5.dp, 35.dp)
)
Button(onClick = { goToArticle(e)},
colors = ButtonDefaults.buttonColors(
containerColor = Salmon,
contentColor = Color.Black
),
border = BorderStroke(1.dp, Color.Black),
modifier = Modifier
.align(Alignment.CenterHorizontally)
) {
Text(text = "Voir plus")
}
}
}
@Composable
fun AfficherArticle(e : Article){
Column(modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(7.dp)
.padding(5.dp)) {
Text(text = e.titre, fontFamily = FontFamily.Serif, fontSize = 30.sp)
// Affichage des informations de l'article
DisplayHeader(author = e.auteur, description = e.description, lectureTime = e.temps)
Image(
painter = rememberImagePainter(
data = e.imagePrincipale
),
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier
.size(350.dp)
.align(Alignment.CenterHorizontally)
.padding(5.dp, 35.dp)
)
// Affichage contenus
DisplayContenu(e.lContenus)
}
}
@Composable
fun DisplayHeader(author: String, description: String, lectureTime: String) {
Box(
modifier = Modifier
.padding(15.dp)
.border(width = 1.dp, color = Color.Black, shape = RoundedCornerShape(10.dp))
.clip(RoundedCornerShape(10.dp))
.background(Salmon)
) {
Column(modifier = Modifier.padding(15.dp)) {
Text(text = "Auteur : "+ author, fontSize = 17.sp)
Text(text = "Description : "+ description, fontSize = 17.sp)
Text(text = "Temps de lecture : " + lectureTime + " minutes", fontSize = 17.sp)
}
}
}
@Composable
fun DisplayContenu(contenus: List<Contenu>) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
for (contenu in contenus) {
when (contenu) {
is ContenuMedia -> {
Log.d("Img", contenu.titre + " " + contenu.lien)
Text(text = contenu.titre, fontSize = 20.sp, fontWeight = FontWeight.Bold)
when (contenu.typeContenu) {
"image" -> {
Image(
painter = rememberImagePainter(data = contenu.lien),
contentScale = ContentScale.FillHeight,
contentDescription = null,
modifier = Modifier
.size(350.dp)
.padding(5.dp, 35.dp)
.fillMaxWidth()
.wrapContentWidth(Alignment.CenterHorizontally) // Centrer l'image
)
}
"video" -> {
Text(
text = "Ici une video ..." + contenu.toString(),
fontSize = 15.sp,
textAlign = TextAlign.Start
)
VideoPlayer(videoUrl = contenu.lien)
}
}
}
is ContenuParagraphe -> {
Text(text = contenu.titre, fontSize = 20.sp, fontWeight = FontWeight.Bold)
Text(text = contenu.texte, fontSize = 16.sp, textAlign = TextAlign.Start)
}
}
}
}
}

@ -0,0 +1,41 @@
import android.view.ViewGroup
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ui.PlayerView
@Composable
fun VideoPlayer(videoUrl : String){
val context = LocalContext.current
val player = ExoPlayer.Builder(context).build()
val playerView = PlayerView(context).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
val mediaItem = MediaItem.Builder()
.setUri(videoUrl)
.build()
player.setMediaItem(mediaItem)
player.prepare()
player.play()
AndroidView(
factory = { context -> playerView },
modifier = Modifier.fillMaxSize(),
update = { view ->
view.player = player
}
)
}

@ -0,0 +1,23 @@
package com.example.veraxapplication.ui.connexion
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.example.veraxapplication.modele.user.User
@Composable
fun AfficherForm(users : List<User>) {
var pseudo = "DEFAULT"
var mdp = "DEFAULT"
Column {
TextField(value = "Pseudo", onValueChange = { value -> pseudo = value }, modifier = Modifier.padding(5.dp))
TextField(value = "Mot de passe", onValueChange = { value -> mdp = value }, modifier = Modifier.padding(5.dp))
for (u in users) {
Text(text = u.pseudo)
}
}
}

@ -8,5 +8,5 @@ val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val Orange = Color(0xFFFADCD1)
val Salmon = Color(0xFFE9967A)

@ -22,9 +22,9 @@ private val DarkColorScheme = darkColorScheme(
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
primary = Salmon,
secondary = PurpleGrey40,
tertiary = Pink40
tertiary = Salmon
/* Other default colors to override
background = Color(0xFFFFFBFE),

@ -0,0 +1,125 @@
package com.example.veraxapplication.ui.topBar
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.veraxapplication.R
import com.example.veraxapplication.modele.articles.Article
import com.example.veraxapplication.navigation.VeraxNavHost
import com.example.veraxapplication.ui.theme.Orange
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TopBarVerax(theme: List<String>, articles: List<Article>, articlesStub : List<Article>) {
/*var leMenu by remember { mutableStateOf(false) }*/
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
Row( modifier = Modifier.background(Color.Blue).fillMaxSize()) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
//text = R.string.app_name,
text= "Verax",
style = TextStyle(fontSize = 35.sp),
color = colorResource(R.color.red),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
},
navigationIcon = { if (navBackStackEntry?.destination?.route != "accueil"){
IconButton(onClick = { navController.popBackStack() }) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = "Retour",
Modifier.size(30.dp)
)
}
}
}/*,
actions = {
IconButton(onClick = { leMenu = !leMenu }) {
Icon(
imageVector = Icons.Filled.Menu,
contentDescription = "Menu",
Modifier.size(35.dp)
)
}
DropdownMenu(
expanded = leMenu, onDismissRequest = { leMenu = false },
modifier = Modifier
.border(
width = 1.dp,
color = Color.Black,
shape = RoundedCornerShape(10.dp)
)
.background(Orange)
) {
theme.sorted().forEach {
DropdownMenuItem(
text = {
Text(
it,
style = TextStyle(fontSize = 25.sp),
modifier = Modifier
.padding(10.dp)
)
},
onClick = { }
)
}
}
}*/
)
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
VeraxNavHost(articles = articles, navController, articlesStub= articlesStub)
}
}
}
}

@ -1,3 +1,3 @@
<resources>
<string name="app_name">VeraxApplication</string>
<string name="app_name">Verax</string>
</resources>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">181.214.189.133</domain>
<domain includeSubdomains="true">codefirst.iut.uca.fr/containers/Verax-verax-api</domain>
</domain-config>
</network-security-config>
Loading…
Cancel
Save