diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 329860c..8b909e6 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -13,6 +13,9 @@ + + \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/CustomSwipeRefreshLayout.kt b/app/src/main/java/com/dreamteam/timelapse/CustomSwipeRefreshLayout.kt index 6842144..4527b10 100644 --- a/app/src/main/java/com/dreamteam/timelapse/CustomSwipeRefreshLayout.kt +++ b/app/src/main/java/com/dreamteam/timelapse/CustomSwipeRefreshLayout.kt @@ -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() +// } +// } } \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/ImageAdapter.kt b/app/src/main/java/com/dreamteam/timelapse/ImageAdapter.kt new file mode 100644 index 0000000..bcf452a --- /dev/null +++ b/app/src/main/java/com/dreamteam/timelapse/ImageAdapter.kt @@ -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) : RecyclerView.Adapter() { + + // 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() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + // 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 +} diff --git a/app/src/main/java/com/dreamteam/timelapse/MainActivity.kt b/app/src/main/java/com/dreamteam/timelapse/MainActivity.kt index 7895cfb..9c4c350 100644 --- a/app/src/main/java/com/dreamteam/timelapse/MainActivity.kt +++ b/app/src/main/java/com/dreamteam/timelapse/MainActivity.kt @@ -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(Rtmp.id.tabLayout) as TabLayout val viewPager = findViewById(Rtmp.id.viewPager) as ViewPager2 + createDialog(findViewById(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(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(R.id.layout_root) + ) + + val project_name = layout.findViewById(Rtmp.id.project_name_dialog) as EditText + val project_desc = layout.findViewById(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 { diff --git a/app/src/main/java/com/dreamteam/timelapse/ProjectActivity.kt b/app/src/main/java/com/dreamteam/timelapse/ProjectActivity.kt index 687e7c3..0b69640 100644 --- a/app/src/main/java/com/dreamteam/timelapse/ProjectActivity.kt +++ b/app/src/main/java/com/dreamteam/timelapse/ProjectActivity.kt @@ -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 = emptyList() // Déclare une liste de projets vide + private var project: Project? = null + private var imageUrls = emptyList() + 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") // -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") // -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() - val humidityEntries = mutableListOf() + 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(R.id.project_name) - val descriptionview = findViewById(R.id.project_name) - val beginview = findViewById(R.id.project_start_date) - val statusview = findViewById(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){ + 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 = 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(R.id.project_name) + val descriptionview = findViewById(R.id.project_description) + val beginview = findViewById(R.id.project_start_date) + val statusview = findViewById(R.id.project_status) + + nameview.text = project!!.name + descriptionview.text = project!!.description + beginview.text = project!!.start_date.toString() + statusview.text = project!!.getStatusText() + } } diff --git a/app/src/main/java/com/dreamteam/timelapse/ProjectAdapter.kt b/app/src/main/java/com/dreamteam/timelapse/ProjectAdapter.kt index 3bbde6b..67bbcdb 100644 --- a/app/src/main/java/com/dreamteam/timelapse/ProjectAdapter.kt +++ b/app/src/main/java/com/dreamteam/timelapse/ProjectAdapter.kt @@ -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, private val listener: OnEmptyStateListener) : RecyclerView.Adapter() { + init{ + listener.onEmptyStateChanged(projects.isEmpty()) + } interface OnEmptyStateListener { fun onEmptyStateChanged(isEmpty: Boolean) @@ -30,7 +34,6 @@ class ProjectAdapter(private val projects: List, 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) } diff --git a/app/src/main/java/com/dreamteam/timelapse/ProjetsFrag.kt b/app/src/main/java/com/dreamteam/timelapse/ProjetsFrag.kt index f73f739..cf4bdd1 100644 --- a/app/src/main/java/com/dreamteam/timelapse/ProjetsFrag.kt +++ b/app/src/main/java/com/dreamteam/timelapse/ProjetsFrag.kt @@ -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() + 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 diff --git a/app/src/main/java/com/dreamteam/timelapse/TabsAdapter.kt b/app/src/main/java/com/dreamteam/timelapse/TabsAdapter.kt index f425858..599a8a7 100644 --- a/app/src/main/java/com/dreamteam/timelapse/TabsAdapter.kt +++ b/app/src/main/java/com/dreamteam/timelapse/TabsAdapter.kt @@ -17,4 +17,5 @@ class TabsAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) { else -> ProjetsFrag() // Par défaut } } + } \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/VideoFrag.kt b/app/src/main/java/com/dreamteam/timelapse/VideoFrag.kt index 8824bc9..4204ac2 100644 --- a/app/src/main/java/com/dreamteam/timelapse/VideoFrag.kt +++ b/app/src/main/java/com/dreamteam/timelapse/VideoFrag.kt @@ -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 = 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() + 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 - } } \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/data/ApiService.kt b/app/src/main/java/com/dreamteam/timelapse/data/ApiService.kt index 5c04648..32a33f5 100644 --- a/app/src/main/java/com/dreamteam/timelapse/data/ApiService.kt +++ b/app/src/main/java/com/dreamteam/timelapse/data/ApiService.kt @@ -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> - @GET("projects/{id}") // Remplace par l'endpoint de ton API + @GET("projects/{id}") fun getProjet(@Path("id") projectId: Int): Call + @GET("projects/{id}/measurements") // Remplace par l'endpoint de ton API + fun getMeasurements(@Path("id") projectId: Int): Call> + @POST("projects/") + fun createProject(@Body project: Project): Call } \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/data/Confirmation.kt b/app/src/main/java/com/dreamteam/timelapse/data/Confirmation.kt new file mode 100644 index 0000000..03efc2d --- /dev/null +++ b/app/src/main/java/com/dreamteam/timelapse/data/Confirmation.kt @@ -0,0 +1,8 @@ +package com.dreamteam.timelapse.data + +data class Confirmation ( + val message: String, + val id: Int +){ + +} \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/data/Measurement.kt b/app/src/main/java/com/dreamteam/timelapse/data/Measurement.kt new file mode 100644 index 0000000..7a88cef --- /dev/null +++ b/app/src/main/java/com/dreamteam/timelapse/data/Measurement.kt @@ -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 +) { +} \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/Project.kt b/app/src/main/java/com/dreamteam/timelapse/data/Project.kt similarity index 90% rename from app/src/main/java/com/dreamteam/timelapse/Project.kt rename to app/src/main/java/com/dreamteam/timelapse/data/Project.kt index c620a8a..5a4370f 100644 --- a/app/src/main/java/com/dreamteam/timelapse/Project.kt +++ b/app/src/main/java/com/dreamteam/timelapse/data/Project.kt @@ -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 { - override fun createFromParcel(parcel: Parcel): Project { - return Project(parcel) - } - - override fun newArray(size: Int): Array { - 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 { + override fun createFromParcel(parcel: Parcel): Project { + return Project(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +// fun getThumbnail(): String { +// return images[0] +// } +} diff --git a/app/src/main/java/com/dreamteam/timelapse/data/ProjectRepository.kt b/app/src/main/java/com/dreamteam/timelapse/data/ProjectRepository.kt index 705ac57..eccad4b 100644 --- a/app/src/main/java/com/dreamteam/timelapse/data/ProjectRepository.kt +++ b/app/src/main/java/com/dreamteam/timelapse/data/ProjectRepository.kt @@ -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) -> Unit, onError: (String) -> Unit) { + val call = apiService.getMeasurements(pid) + call.enqueue(object : Callback> { + override fun onResponse(call: Call>, response: Response>) { + 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>, 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 { + override fun onResponse(call: Call, response: Response) { + if (response.isSuccessful) { + onSuccess() + } else { + onError("Erreur : ${response.code()}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + onError("Échec de l'appel API : ${t.message}") + } + }) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/dreamteam/timelapse/data/Video.kt b/app/src/main/java/com/dreamteam/timelapse/data/Video.kt new file mode 100644 index 0000000..8e65ed9 --- /dev/null +++ b/app/src/main/java/com/dreamteam/timelapse/data/Video.kt @@ -0,0 +1,12 @@ +package com.dreamteam.timelapse.data + +data class Video ( + val id: Int, + val project_id: Int, + val measurement_ids: List, + val video_file: String?, + val resolution:String, + val duration: Int, + val status: Int, //0 pas fini, 1 fini + val name: String +){} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 307b3c5..f0a2ad9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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"> --> - + app:srcCompat="@android:drawable/ic_input_add" /> diff --git a/app/src/main/res/layout/item_image.xml b/app/src/main/res/layout/item_image.xml new file mode 100644 index 0000000..d996c76 --- /dev/null +++ b/app/src/main/res/layout/item_image.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/project.xml b/app/src/main/res/layout/project.xml index bfeca0a..da97408 100644 --- a/app/src/main/res/layout/project.xml +++ b/app/src/main/res/layout/project.xml @@ -31,90 +31,125 @@ - - - - + - - + - - - - + - + - - + + + - + - + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/project_create_dialog_layout.xml b/app/src/main/res/layout/project_create_dialog_layout.xml new file mode 100644 index 0000000..30d0ab0 --- /dev/null +++ b/app/src/main/res/layout/project_create_dialog_layout.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file