many things done

This commit is contained in:
raphael.thieffry
2025-03-10 16:53:07 +01:00
parent d7c313c013
commit e46b7c2e26
19 changed files with 685 additions and 228 deletions

View File

@@ -1,5 +1,6 @@
package com.dreamteam.timelapse
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.util.Log
@@ -7,6 +8,8 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
class CustomSwipeRefreshLayout(context: Context, attrs: AttributeSet) : SwipeRefreshLayout(context, attrs) {
init {
this.setEnabled(true)
this.setRefreshing(true)
Log.d("CustomSwipeRefreshLayout", "CustomSwipeRefreshLayout instancié")
}
override fun canChildScrollUp(): Boolean {
@@ -15,4 +18,22 @@ class CustomSwipeRefreshLayout(context: Context, attrs: AttributeSet) : SwipeRef
//Log.i("CustomSwipeRefreshLayout", "canChildScrollUp: ${view?.canScrollVertically(-1)}")
return view != null && view.canScrollVertically(-1)
}
private var mMeasured = false
private var mPreMeasureRefreshing = false
// fun dragDown(){
// this.post {
// Log.i("SwipeRefreshLayout", "Je suis delanché par le code")
// val distanceToMove = 200 // La distance que l'on veut faire descendre l'élément, ajustable
// val valueAnimator = ValueAnimator.ofInt(0, distanceToMove)
// valueAnimator.addUpdateListener { animation ->
// val value = animation.animatedValue as Int
// // Manipule le mouvement de l'animation du rafraîchissement
// this.setProgressViewOffset(false, 0, value)
// }
// valueAnimator.duration = 500 // Durée de l'animation
// valueAnimator.start()
// }
// }
}

View File

@@ -0,0 +1,62 @@
package com.dreamteam.timelapse
import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.SimpleTarget
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import com.bumptech.glide.request.transition.Transition
class ImageAdapter(private val imageUrls: List<String>) : RecyclerView.Adapter<ImageAdapter.ImageViewHolder>() {
// ViewHolder qui contient l'ImageView
class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.findViewById(R.id.imageView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_image, parent, false)
return ImageViewHolder(view)
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val imageUrl = imageUrls[position]
//Log.e("ImageAdapter", imageUrl)
Glide.with(holder.itemView.context)
.load(imageUrl) // Charge l'image via Glide
.into(holder.imageView) // Affiche l'image dans l'ImageView
}
private fun downloadImage(url: String, context: Context) {
Glide.with(context)
.asBitmap()
.load(url)
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
// Save the image to the app's internal storage
val file = File(context.filesDir, "downloaded_image.jpg")
try {
val fileOutputStream = FileOutputStream(file)
resource.compress(Bitmap.CompressFormat.JPEG, 100, fileOutputStream)
fileOutputStream.flush()
fileOutputStream.close()
Toast.makeText(context, "Image downloaded successfully", Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
e.printStackTrace()
Toast.makeText(context, "Error downloading image", Toast.LENGTH_SHORT).show()
}
}
})
}
override fun getItemCount(): Int = imageUrls.size
}

View File

@@ -1,36 +1,46 @@
package com.dreamteam.timelapse
import android.R
import com.dreamteam.timelapse.R as Rtmp
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.EditText
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.viewpager2.widget.ViewPager2
import com.dreamteam.timelapse.databinding.ActivityMainBinding
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import com.dreamteam.timelapse.R as Rtmp
import com.dreamteam.timelapse.R
import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.ProjectRepository
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding
private lateinit var dialog: AlertDialog
private lateinit var projectRepository : ProjectRepository
override fun onCreate(savedInstanceState: Bundle?) {
// super.onCreate(savedInstanceState)
//
val retrofit = Retrofit.Builder()
.baseUrl("https://timelapse.kerboul.me/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
projectRepository = ProjectRepository(retrofit.create(ApiService::class.java))
Log.d("mainActivity", "La main activity est créée")
// setSupportActionBar(binding.toolbar)
//
super.onCreate(savedInstanceState)
//val navController = findNavController(R.id.nav_host_fragment_content_main)
//appBarConfiguration = AppBarConfiguration(navController.graph)
@@ -46,13 +56,13 @@ class MainActivity : AppCompatActivity() {
setContentView(Rtmp.layout.activity_main)
val tabLayout = findViewById<View>(Rtmp.id.tabLayout) as TabLayout
val viewPager = findViewById<View>(Rtmp.id.viewPager) as ViewPager2
createDialog(findViewById<View>(Rtmp.id.fab))
// Configurer l'adapter pour ViewPager2
var tabsAdapter = TabsAdapter(this)
viewPager.setAdapter(tabsAdapter)
// Connecter le ViewPager au TabLayout
TabLayoutMediator(
tabLayout, viewPager
@@ -63,12 +73,39 @@ class MainActivity : AppCompatActivity() {
}
}.attach()
// Bouton flottant
// binding.fab.setOnClickListener { view ->
// Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
// //.setAction("Action", null) //sert à rien
// .setAnchorView(R.id.fab).show() //au dessus du bouton mail
// }
//Bouton flottant
findViewById<View>(Rtmp.id.fab).setOnClickListener { view -> run {
this.dialog.show()
}
}
}
fun createDialog(viewfortoast: View){
//Preparing views
val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout: View = inflater.inflate(
R.layout.project_create_dialog_layout,
findViewById<LinearLayout>(R.id.layout_root)
)
val project_name = layout.findViewById<View>(Rtmp.id.project_name_dialog) as EditText
val project_desc = layout.findViewById<View>(Rtmp.id.project_desc_dialog) as EditText
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setView(layout)
builder.setPositiveButton("Save",
DialogInterface.OnClickListener { dialog, which ->
val onSuccess : () -> Unit = {dialog.dismiss() ; Snackbar.make(viewfortoast, "Nouveau projet créé", Snackbar.LENGTH_LONG).setAnchorView(Rtmp.id.fab).show() ; }
val onError : (s:String) -> Unit = { s-> Snackbar.make(viewfortoast, "Erreur lors de la création du projet. ${s}", Snackbar.LENGTH_LONG).setAnchorView(Rtmp.id.fab).show() }
projectRepository.createProject(project_name.text.toString(), project_desc.text.toString(), onSuccess, onError)
})
builder.setNegativeButton("Cancel",
DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() })
val dialog: AlertDialog = builder.create()
this.dialog = dialog
}
// override fun onSupportNavigateUp(): Boolean {

View File

@@ -1,61 +1,74 @@
package com.dreamteam.timelapse
import android.graphics.Color
import android.graphics.Rect
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.ui.AppBarConfiguration
import androidx.viewpager2.widget.ViewPager2
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.Measurement
import com.dreamteam.timelapse.data.Project
import com.dreamteam.timelapse.data.ProjectRepository
import com.dreamteam.timelapse.databinding.ProjectBinding
import com.google.android.material.tabs.TabLayout
import com.dreamteam.timelapse.R
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class ProjectActivity : AppCompatActivity() {
private lateinit var binding: ProjectBinding
private lateinit var projectRepository: ProjectRepository
private lateinit var apiService: ApiService // Déclare l'apiService
private var measures: List<Measurement> = emptyList() // Déclare une liste de projets vide
private var project: Project? = null
private var imageUrls = emptyList<String>()
private class GridSpacingItemDecoration(private val spacing: Int) : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view) // Position de l'item
if (position == RecyclerView.NO_POSITION) return
outRect.left = spacing / 2
outRect.right = spacing / 2
outRect.top = spacing
}
}
override fun onCreate(savedInstanceState: Bundle?) {
val project = intent.getParcelableExtra<Project>("PROJECT") // -1 est la valeur par défaut si l'ID n'est pas trouvé
Log.d("project", "La project activity "+project?.id+" est créée")
val retrofit = Retrofit.Builder()
.baseUrl("https://timelapse.kerboul.me/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(ApiService::class.java) // Crée une instance d'ApiService
projectRepository = ProjectRepository(apiService) // Tu initialises ton repository
this.project = intent.getParcelableExtra<Project>("PROJECT") // -1 est la valeur par défaut si l'ID n'est pas trouvé
Log.d("project", "La project activity "+this.project?.id+" est créée")
super.onCreate(savedInstanceState)
binding = ProjectBinding.inflate(layoutInflater)
this.binding = ProjectBinding.inflate(layoutInflater)
setContentView(binding.root)
// Initialisation du graphique
val temperatureHumidityChart = binding.temperatureHumidityChart
// Données fictives (température et humidité)
val temperatureEntries = mutableListOf<Entry>()
val humidityEntries = mutableListOf<Entry>()
initProjectInfo()
// Ajouter des points de données (exemples)
temperatureEntries.add(Entry(0f, 25f)) // (temps, température)
humidityEntries.add(Entry(0f, 60f)) // (temps, humidité)
fetchMeasuresAndRebuildGraph()
val swipeRefreshLayout = binding.swipeRefreshLayout
swipeRefreshLayout.setOnRefreshListener {
fetchMeasuresAndRebuildGraph()
Log.d("ProjetsFrag", "Actualisation des projets")
}
// Créer des LineDataSet pour chaque série de données
val temperatureDataSet = LineDataSet(temperatureEntries, "Température")
val humidityDataSet = LineDataSet(humidityEntries, "Hygrométrie")
// Ajouter les datasets au graphique
val lineData = LineData(temperatureDataSet, humidityDataSet)
temperatureHumidityChart.data = lineData
// Personnaliser le graphique (par exemple, couleur, légende, etc.)
temperatureDataSet.color = Color.RED
humidityDataSet.color = Color.BLUE
temperatureHumidityChart.invalidate() // Rafraîchir le graphique
//super.onCreate(savedInstanceState)
val nameview = findViewById<TextView>(R.id.project_name)
val descriptionview = findViewById<TextView>(R.id.project_name)
val beginview = findViewById<TextView>(R.id.project_start_date)
val statusview = findViewById<TextView>(R.id.project_status)
@@ -66,4 +79,92 @@ class ProjectActivity : AppCompatActivity() {
// .setAnchorView(R.id.fab).show() //au dessus du bouton mail
// }
}
fun fetchMeasuresAndRebuildGraph(){
this.project?.let{
projectRepository.fetchMeasurementsOfProject(it.id,
onSuccess = { measures ->
Log.d("ProjectActivity", "Mesures reçus : $measures")
this.measures = measures
if(this.measures.size > 0)
initGraph(this.measures)
val adapter = ImageAdapter(this.measures.map { m-> "https://timelapse.kerboul.me/api/images/${m.project_id}/${m.order_id}"}) //ImageAdapter(imageUrls)
binding.imagesList.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.HORIZONTAL)
binding.imagesList.addItemDecoration(GridSpacingItemDecoration(16)) // 16px spacing
binding.imagesList.adapter = adapter
binding.swipeRefreshLayout.isRefreshing = false
//this.projectAdapter = ProjectAdapter(this.projects, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
//this.recyclerView.adapter = this.projectAdapter
// Mettre à jour le RecyclerView ou autre traitement
},
onError = { errorMessage ->
Log.e("ProjectActivity", errorMessage)
fetchMeasuresAndRebuildGraph()
})
}
}
fun initGraph(lm : List<Measurement>){
val temperatureHumidityChart = this.binding.temperatureHumidityChart
val temperatures = lm.sortedBy { p -> p.order_id }
.map { m -> Pair(m.temperature, m.timestamp) }
val humidities = lm.sortedBy { p -> p.order_id }
.map { m -> Pair(m.humidity, m.timestamp) }
// Ajouter des points de données (exemples)
val temperatureEntries = temperatures.map { pair ->
Entry((pair.second.time).toFloat(), pair.first) // (timestamp, température)
}
val humidityEntries = humidities.map { pair ->
Entry((pair.second.time).toFloat(), pair.first) // (timestamp, humidité)
}
// Créer des LineDataSet pour chaque série de données
val temperatureDataSet = LineDataSet(temperatureEntries, "Température")
val humidityDataSet = LineDataSet(humidityEntries, "Hygrométrie")
Log.i("ProjectActivity", temperatureEntries.toString())
val minX = temperatureEntries.minOf { it.x } // La valeur minimale de x
val maxX = temperatureEntries.maxOf { it.x } // La valeur maximale de x
val xAxis = temperatureHumidityChart.xAxis
xAxis.valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
val date = Date(value.toLong())
val sdf = SimpleDateFormat("hh:mm", Locale.getDefault())
return sdf.format(date)
}
}
xAxis.axisMinimum = minX
xAxis.axisMaximum = maxX
// Ajouter les datasets au graphique
val dataSets: MutableList<ILineDataSet> = ArrayList()
dataSets.add(temperatureDataSet)
dataSets.add(humidityDataSet)
// val lineData = LineData(temperatureDataSet, humidityDataSet)
//Log.e("Graph", lineData.getDataSetByIndex(0).entryCount.toString())
temperatureHumidityChart.data = LineData(dataSets)
// Personnaliser le graphique (par exemple, couleur, légende, etc.)
temperatureDataSet.color = Color.RED
humidityDataSet.color = Color.BLUE
temperatureHumidityChart.invalidate() // Rafraîchir le graphique
}
fun initProjectInfo(){
val nameview = findViewById<TextView>(R.id.project_name)
val descriptionview = findViewById<TextView>(R.id.project_description)
val beginview = findViewById<TextView>(R.id.project_start_date)
val statusview = findViewById<TextView>(R.id.project_status)
nameview.text = project!!.name
descriptionview.text = project!!.description
beginview.text = project!!.start_date.toString()
statusview.text = project!!.getStatusText()
}
}

View File

@@ -11,8 +11,12 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.dreamteam.timelapse.data.Project
class ProjectAdapter(private val projects: List<Project>, private val listener: OnEmptyStateListener) : RecyclerView.Adapter<ProjectAdapter.ProjectViewHolder>() {
init{
listener.onEmptyStateChanged(projects.isEmpty())
}
interface OnEmptyStateListener {
fun onEmptyStateChanged(isEmpty: Boolean)
@@ -30,7 +34,6 @@ class ProjectAdapter(private val projects: List<Project>, private val listener:
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_project_card, parent, false)
//projects.isEmpty()
listener.onEmptyStateChanged(projects.isEmpty())
return ProjectViewHolder(view)
}

View File

@@ -2,20 +2,17 @@ package com.dreamteam.timelapse
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.dreamteam.timelapse.data.ProjectRepository
import com.dreamteam.timelapse.databinding.FragmentProjetsBinding
import com.dreamteam.timelapse.databinding.FragmentVideosBinding
import kotlinx.coroutines.delay
import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.Project
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
@@ -50,11 +47,11 @@ class ProjetsFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.d("ProjetsFrag", "Fragment Projets créé")
_binding = FragmentProjetsBinding.inflate(inflater, container, false)
Log.d("ProjetsFrag", "Fragment Projets créé")
projectRepository = ProjectRepository(apiService) // Tu initialises ton repository
fetchProjects()
//fetchProjects()
return _binding?.root
}
@@ -68,6 +65,7 @@ class ProjetsFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
fetchProjects()
Log.d("ProjetsFrag", "Actualisation des projets")
}
fetchProjects()
recyclerView = binding.recyclerView // view.findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(context)
@@ -77,8 +75,11 @@ class ProjetsFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
private fun fetchProjects() {
// Simulez l'appel API
binding.swipeRefreshLayout.isRefreshing = true
binding.swipeRefreshLayout.setEnabled(true)
binding.swipeRefreshLayout.setRefreshing(true)
//binding.swipeRefreshLayout.
//binding.swipeRefreshLayout.dragDown()
Log.d("ProjetsFrag", "User has refreshed the projects list")
projectRepository.fetchProjects(
onSuccess = { projects ->
@@ -89,7 +90,12 @@ class ProjetsFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
// Mettre à jour le RecyclerView ou autre traitement
},
onError = { errorMessage ->
Log.e("ProjetsFrag", errorMessage)
Log.e("ProjetsFrag prout", errorMessage)
//onEmptyStateChanged(true)
this.projects = listOf<Project>()
this.projectAdapter = ProjectAdapter(this.projects, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
this.recyclerView.adapter = this.projectAdapter
}
)
binding.swipeRefreshLayout.isRefreshing = false

View File

@@ -17,4 +17,5 @@ class TabsAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
else -> ProjetsFrag() // Par défaut
}
}
}

View File

@@ -1,44 +1,112 @@
package com.dreamteam.timelapse
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.dreamteam.timelapse.databinding.FragmentVideosBinding
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.dreamteam.timelapse.data.ProjectRepository
import com.dreamteam.timelapse.databinding.FragmentProjetsBinding
import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.Project
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
/**
* A simple [Fragment] subclass as the second destination in the navigation.
* A simple [Fragment] subclass as the default destination in the navigation.
*/
class VideoFrag : Fragment() {
private var _binding: FragmentVideosBinding? = null
class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
// This property is only valid between onCreateView and
// onDestroyView.
private lateinit var projectRepository: ProjectRepository
//private lateinit var listView: ListView
private lateinit var recyclerView: RecyclerView
private lateinit var projectAdapter: ProjectAdapter
private var _binding: FragmentProjetsBinding? = null
private val binding get() = _binding!!
private lateinit var apiService: ApiService // Déclare l'apiService
private var projects: List<Project> = emptyList() // Déclare une liste de projets vide
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialiser apiService ici, par exemple, en utilisant Retrofit
val retrofit = Retrofit.Builder()
.baseUrl("https://timelapse.kerboul.me/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(ApiService::class.java) // Crée une instance d'ApiService
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
): View? {
_binding = FragmentProjetsBinding.inflate(inflater, container, false)
Log.d("ProjetsFrag", "Fragment Projets créé")
_binding = FragmentVideosBinding.inflate(inflater, container, false)
return binding.root
projectRepository = ProjectRepository(apiService) // Tu initialises ton repository
//fetchProjects()
return _binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonSecond.setOnClickListener {
findNavController().navigate(R.id.action_SecondFragment_to_FirstFragment)
val swipeRefreshLayout = binding.swipeRefreshLayout
swipeRefreshLayout.setOnRefreshListener {
fetchProjects()
Log.d("ProjetsFrag", "Actualisation des projets")
}
fetchProjects()
recyclerView = binding.recyclerView // view.findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(context)
}
private fun fetchProjects() {
// Simulez l'appel API
binding.swipeRefreshLayout.setEnabled(true)
binding.swipeRefreshLayout.setRefreshing(true)
//binding.swipeRefreshLayout.
//binding.swipeRefreshLayout.dragDown()
Log.d("ProjetsFrag", "User has refreshed the projects list")
projectRepository.fetchProjects(
onSuccess = { projects ->
Log.d("ProjetsFrag", "Projets reçus : $projects")
this.projects = projects
this.projectAdapter = ProjectAdapter(this.projects, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
this.recyclerView.adapter = this.projectAdapter
// Mettre à jour le RecyclerView ou autre traitement
},
onError = { errorMessage ->
Log.e("ProjetsFrag prout", errorMessage)
//onEmptyStateChanged(true)
this.projects = listOf<Project>()
this.projectAdapter = ProjectAdapter(this.projects, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
this.recyclerView.adapter = this.projectAdapter
}
)
binding.swipeRefreshLayout.isRefreshing = false
}
override fun onEmptyStateChanged(isEmpty: Boolean) {
// Afficher ou masquer le message
if (isEmpty) {
binding.noProjectsText.visibility = View.VISIBLE
} else {
binding.noProjectsText.visibility = View.GONE
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@@ -1,12 +1,19 @@
package com.dreamteam.timelapse.data
import com.dreamteam.timelapse.Project
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
interface ApiService {
@GET("projects") // Remplace par l'endpoint de ton API
@GET("projects")
fun getProjets(): Call<List<Project>>
@GET("projects/{id}") // Remplace par l'endpoint de ton API
@GET("projects/{id}")
fun getProjet(@Path("id") projectId: Int): Call<Project>
@GET("projects/{id}/measurements") // Remplace par l'endpoint de ton API
fun getMeasurements(@Path("id") projectId: Int): Call<List<Measurement>>
@POST("projects/")
fun createProject(@Body project: Project): Call<Confirmation>
}

View File

@@ -0,0 +1,8 @@
package com.dreamteam.timelapse.data
data class Confirmation (
val message: String,
val id: Int
){
}

View File

@@ -0,0 +1,14 @@
package com.dreamteam.timelapse.data
import java.util.Date
data class Measurement(
val id: Int,
val project_id: Int,
val timestamp: Date,
val path: String,
val temperature: Float,
val humidity: Float,
val order_id: Int
) {
}

View File

@@ -1,71 +1,74 @@
package com.dreamteam.timelapse
import android.os.Parcel
import android.os.Parcelable
import java.util.Date
//{
// "id": 1,
// "creation": "2024-10-24T13:46:04.513Z",
// "status": "test",
// "description": "Projet de Test",
// "titre": "Test Project"
//}
data class Project(
val id: Int,
val name: String,
val description: String,
val start_date: Date,
val status: Int,
val thumbnail_url: String?
) : Parcelable {
fun getStatusText(): String{
val statusArr = arrayOf("Brouillon", "En Cours", "Terminé", "Annulé")
return statusArr[status]
}
fun getStatusColor(): Int{
return when (status) {
0 -> R.color.brouillon
1 -> R.color.en_cours
2 -> R.color.termine
3 -> R.color.annule
else -> R.color.default_badge
}
}
// Constructor to recreate from Parcel
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString() ?: "",
parcel.readString() ?: "",
Date(parcel.readLong()),
parcel.readInt(),
parcel.readString()
)
// Write object to Parcel
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(name)
parcel.writeString(description)
parcel.writeString(start_date.toString())
parcel.writeInt(status)
}
// Describe the contents of the Parcel
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<Project> {
override fun createFromParcel(parcel: Parcel): Project {
return Project(parcel)
}
override fun newArray(size: Int): Array<Project?> {
return arrayOfNulls(size)
}
}
// fun getThumbnail(): String {
// return images[0]
// }
}
package com.dreamteam.timelapse.data
import android.os.Parcel
import android.os.Parcelable
import android.util.Log
import com.dreamteam.timelapse.R
import java.util.Date
//{
// "id": 1,
// "creation": "2024-10-24T13:46:04.513Z",
// "status": "test",
// "description": "Projet de Test",
// "titre": "Test Project"
//}
data class Project(
val id: Int,
val name: String,
val description: String,
val start_date: Date,
val status: Int,
val thumbnail_url: String?
) : Parcelable {
fun getStatusText(): String{
Log.i("Project", "Status $status being trasnlated")
val statusArr = arrayOf("Brouillon", "En Cours", "Terminé", "Annulé")
return statusArr[status]
}
fun getStatusColor(): Int{
return when (status) {
0 -> R.color.brouillon
1 -> R.color.en_cours
2 -> R.color.termine
3 -> R.color.annule
else -> R.color.default_badge
}
}
// Constructor to recreate from Parcel
constructor(parcel: Parcel) : this(
parcel.readInt(),
parcel.readString() ?: "",
parcel.readString() ?: "",
Date(parcel.readLong()),
parcel.readInt(),
parcel.readString()
)
// Write object to Parcel
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeString(name)
parcel.writeString(description)
parcel.writeLong(start_date.time)
parcel.writeInt(status)
}
// Describe the contents of the Parcel
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<Project> {
override fun createFromParcel(parcel: Parcel): Project {
return Project(parcel)
}
override fun newArray(size: Int): Array<Project?> {
return arrayOfNulls(size)
}
}
// fun getThumbnail(): String {
// return images[0]
// }
}

View File

@@ -1,9 +1,9 @@
package com.dreamteam.timelapse.data
import com.dreamteam.timelapse.Project
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.Date
class ProjectRepository(private val apiService: ApiService) {
@@ -26,4 +26,41 @@ class ProjectRepository(private val apiService: ApiService) {
}
})
}
fun fetchMeasurementsOfProject(pid: Int, onSuccess: (List<Measurement>) -> Unit, onError: (String) -> Unit) {
val call = apiService.getMeasurements(pid)
call.enqueue(object : Callback<List<Measurement>> {
override fun onResponse(call: Call<List<Measurement>>, response: Response<List<Measurement>>) {
if (response.isSuccessful) {
val measures = response.body()
measures?.let {
onSuccess(it)
} ?: onError("Aucune mesure trouvé")
} else {
onError("Erreur : ${response.code()}")
}
}
override fun onFailure(call: Call<List<Measurement>>, t: Throwable) {
onError("Échec de l'appel API : ${t.message}")
}
})
}
fun createProject(name: String, description: String, onSuccess: () -> Unit, onError: (String) -> Unit) {
val call = apiService.createProject(Project(0, name, description, Date(), 0, null))
call.enqueue(object : Callback<Confirmation> {
override fun onResponse(call: Call<Confirmation>, response: Response<Confirmation>) {
if (response.isSuccessful) {
onSuccess()
} else {
onError("Erreur : ${response.code()}")
}
}
override fun onFailure(call: Call<Confirmation>, t: Throwable) {
onError("Échec de l'appel API : ${t.message}")
}
})
}
}

View File

@@ -0,0 +1,12 @@
package com.dreamteam.timelapse.data
data class Video (
val id: Int,
val project_id: Int,
val measurement_ids: List<Int>,
val video_file: String?,
val resolution:String,
val duration: Int,
val status: Int, //0 pas fini, 1 fini
val name: String
){}

View File

@@ -4,7 +4,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
tools:context=".MainActivity"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@@ -23,14 +24,14 @@
<!-- useless now <include layout="@layout/content_main" />-->
<!-- <com.google.android.material.floatingactionbutton.FloatingActionButton
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" /> -->
app:srcCompat="@android:drawable/ic_input_add" />
<!-- marge pour le appBar layout -->

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:scaleType="fitCenter"
android:adjustViewBounds="true"/>

View File

@@ -31,90 +31,125 @@
</com.google.android.material.appbar.AppBarLayout>
<androidx.constraintlayout.widget.ConstraintLayout
<com.dreamteam.timelapse.CustomSwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
app:layout_constraintStart_toEndOf="@+id/appBarLayout"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
tools:context=".ProjectActivity">
<!-- Description du projet -->
<TextView
android:id="@+id/project_description"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Description du projet"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_name" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ProjectActivity">
<!-- Date de début du projet -->
<TextView
android:id="@+id/project_start_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Date de début"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_description" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@+id/appBarLayout"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
tools:context=".ProjectActivity">
<!-- Statut du projet -->
<TextView
android:id="@+id/project_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Statut: En cours"
android:textColor="@android:color/holo_green_dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_start_date" />
<!-- Liste des vidéos liées au projet -->
<TextView
android:id="@+id/video_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Vidéos associées"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_status" />
<TextView
android:id="@+id/project_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Description du projet"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/videos_list"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/video_header" />
<TextView
android:id="@+id/project_start_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Date de début"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_description" />
<!-- Graphique de température et d'hygrométrie -->
<TextView
android:id="@+id/graph_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Graphique de Température et d'Hygrométrie"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/videos_list" />
<TextView
android:id="@+id/project_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Statut: En cours"
android:textColor="@android:color/holo_green_dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_start_date" />
<TextView
android:id="@+id/graph_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Graphique de Température et d'Hygrométrie"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_status" />
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/temperature_humidity_chart"
android:layout_width="0dp"
android:layout_height="250dp"
android:layout_marginTop="16dp"
android:background="@android:color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/graph_header" />
<com.github.mikephil.charting.charts.LineChart
android:id="@+id/temperature_humidity_chart"
android:layout_width="0dp"
android:layout_height="250dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/graph_header" />
<TextView
android:id="@+id/image_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Images"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/temperature_humidity_chart" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/imagesList"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.30"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/image_header" />
<TextView
android:id="@+id/video_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Vidéos associées"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/imagesList" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/videos_list"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.30"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/video_header" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</com.dreamteam.timelapse.CustomSwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="37dp"
android:textSize="25dp"
android:text="Creation d'un projet" />
<EditText
android:id="@+id/project_name_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nom du projet"
android:inputType="text" />
<EditText
android:id="@+id/project_desc_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Description du projet"
android:inputType="text"
android:layout_marginTop="8dp"/>
</LinearLayout>