fix #5 #4 #8, en gros j'ai avancé les videos

This commit is contained in:
Raphael
2025-03-10 23:59:22 +01:00
parent 8ee061b791
commit 35b6120ebe
15 changed files with 401 additions and 44 deletions

View File

@@ -71,9 +71,10 @@ dependencies {
implementation("com.squareup.retrofit2:retrofit:2.9.0") //internet, api etc... implementation("com.squareup.retrofit2:retrofit:2.9.0") //internet, api etc...
implementation("com.squareup.retrofit2:converter-gson:2.9.0") // Si tu veux utiliser Gson pour la sérialisation implementation("com.squareup.retrofit2:converter-gson:2.9.0") // Si tu veux utiliser Gson pour la sérialisation
implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") // Pour le logging implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") // Pour le logging
implementation("com.github.bumptech.glide:glide:4.15.1") // Pour curl des images d'internet en gros implementation("com.github.bumptech.glide:glide:4.15.1")
kapt("com.github.bumptech.glide:compiler:4.15.1") kapt("com.github.bumptech.glide:compiler:4.15.1")
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0") implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
implementation("com.google.android.exoplayer:exoplayer:2.18.1")
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)

View File

@@ -21,10 +21,12 @@
android:theme="@style/Theme.Timelapse"> android:theme="@style/Theme.Timelapse">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".VideoPlayerActivity" android:exported="false" android:label="@string/app_name" android:theme="@style/Theme.Timelapse">
</activity>
<activity <activity
android:name=".ProjectActivity" android:name=".ProjectActivity"
android:exported="true" android:exported="true"

View File

@@ -6,12 +6,14 @@ import android.util.Log
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.dreamteam.timelapse.data.ApiService import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.Measurement import com.dreamteam.timelapse.data.Measurement
import com.dreamteam.timelapse.data.Project import com.dreamteam.timelapse.data.Project
import com.dreamteam.timelapse.data.ProjectRepository import com.dreamteam.timelapse.data.ProjectRepository
import com.dreamteam.timelapse.data.VideoRepository
import com.dreamteam.timelapse.databinding.ProjectBinding import com.dreamteam.timelapse.databinding.ProjectBinding
import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.data.LineData
@@ -29,6 +31,7 @@ class ProjectActivity : AppCompatActivity() {
private lateinit var binding: ProjectBinding private lateinit var binding: ProjectBinding
private lateinit var projectRepository: ProjectRepository private lateinit var projectRepository: ProjectRepository
private lateinit var videoRepository: VideoRepository
private lateinit var apiService: ApiService // Déclare l'apiService private lateinit var apiService: ApiService // Déclare l'apiService
private var measures: List<Measurement> = emptyList() // Déclare une liste de projets vide private var measures: List<Measurement> = emptyList() // Déclare une liste de projets vide
private var project: Project? = null private var project: Project? = null
@@ -50,7 +53,8 @@ class ProjectActivity : AppCompatActivity() {
.addConverterFactory(GsonConverterFactory.create()) .addConverterFactory(GsonConverterFactory.create())
.build() .build()
apiService = retrofit.create(ApiService::class.java) // Crée une instance d'ApiService apiService = retrofit.create(ApiService::class.java) // Crée une instance d'ApiService
projectRepository = ProjectRepository(apiService) // Tu initialises ton repository projectRepository = ProjectRepository(apiService)
videoRepository = VideoRepository(apiService)
this.project = intent.getParcelableExtra<Project>("PROJECT") // -1 est la valeur par défaut si l'ID n'est pas trouvé 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") Log.d("project", "La project activity "+this.project?.id+" est créée")
@@ -67,6 +71,7 @@ class ProjectActivity : AppCompatActivity() {
fetchMeasuresAndRebuildGraph() fetchMeasuresAndRebuildGraph()
Log.d("ProjetsFrag", "Actualisation des projets") Log.d("ProjetsFrag", "Actualisation des projets")
} }
fetchVideos()
@@ -80,7 +85,19 @@ class ProjectActivity : AppCompatActivity() {
// } // }
} }
fun fetchVideos(){
this.project?.let{
videoRepository.fetchVideosOfProject(it.id, onSuccess = { videos ->
val adapter = VideoAdapter(false, videos, null) //ImageAdapter(imageUrls)
binding.videosList.layoutManager = GridLayoutManager(this.baseContext, 2, GridLayoutManager.HORIZONTAL, false)
binding.videosList.addItemDecoration(GridSpacingItemDecoration(16)) // 16px spacing
binding.videosList.adapter = adapter
}, onError = {errorMessage ->
Log.e("ProjectActivity", errorMessage)
fetchVideos()
})
}
}
fun fetchMeasuresAndRebuildGraph(){ fun fetchMeasuresAndRebuildGraph(){
this.project?.let{ this.project?.let{
projectRepository.fetchMeasurementsOfProject(it.id, projectRepository.fetchMeasurementsOfProject(it.id,
@@ -91,7 +108,7 @@ class ProjectActivity : AppCompatActivity() {
initGraph(this.measures) initGraph(this.measures)
val adapter = ImageAdapter(this.measures.map { m-> "https://timelapse.kerboul.me/api/images/${m.project_id}/${m.order_id}"}) //ImageAdapter(imageUrls) 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.layoutManager = GridLayoutManager(this.baseContext, 2, GridLayoutManager.HORIZONTAL, false)
binding.imagesList.addItemDecoration(GridSpacingItemDecoration(16)) // 16px spacing binding.imagesList.addItemDecoration(GridSpacingItemDecoration(16)) // 16px spacing
binding.imagesList.adapter = adapter binding.imagesList.adapter = adapter

View File

@@ -0,0 +1,86 @@
package com.dreamteam.timelapse
import android.annotation.SuppressLint
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
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.ApiService
import com.dreamteam.timelapse.data.ProjectRepository
import com.dreamteam.timelapse.data.Video
import com.dreamteam.timelapse.data.VideoRepository
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class VideoAdapter(
public val withProjectNames : Boolean,
private val videos: List<Video>,
private val listener: OnEmptyStateListener?
) : RecyclerView.Adapter<VideoAdapter.VideoViewHolder>() {
private var projectRepository: ProjectRepository;
init {
val retrofit = Retrofit.Builder()
.baseUrl("https://timelapse.kerboul.me/api/")
.addConverterFactory(GsonConverterFactory.create())
.build()
projectRepository = ProjectRepository(retrofit.create(ApiService::class.java))
listener?.onEmptyStateChanged(videos.isEmpty())
}
interface OnEmptyStateListener {
fun onEmptyStateChanged(isEmpty: Boolean)
}
class VideoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val videoTitle: TextView = itemView.findViewById(R.id.videoTitle)
val videoThumbnail: ImageView = itemView.findViewById(R.id.videoThumbnail)
val playButton: ImageView = itemView.findViewById(R.id.playButton)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VideoViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_video, parent, false)
return VideoViewHolder(view)
}
override fun onBindViewHolder(holder: VideoViewHolder, position: Int) {
val video = videos[position]
val context = holder.itemView.context
holder.videoTitle.text = video.name
if(withProjectNames){
projectRepository.fetchProject(video.project_id, { p->
holder.videoTitle.text = video.name + " (" + p.name + ")"
}, { s->
Log.e("VideoAdapter", s)
})
}
// Load video thumbnail
val url = "https://timelapse.kerboul.me/api/videos/file/${video.id}"
Glide.with(context)
.asBitmap()
.load(url) // Load the video URL
.frame(0)
.placeholder(R.drawable.not_found) // Optional placeholder
.into(holder.videoThumbnail)
// Click listener to open video player
holder.itemView.setOnClickListener {
val intent = Intent(context, VideoPlayerActivity::class.java)
intent.putExtra("VIDEO_URL", url) // Pass video URL
intent.setFlags(FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
}
}
override fun getItemCount(): Int {
return videos.size
}
}

View File

@@ -9,10 +9,11 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.dreamteam.timelapse.data.ProjectRepository import com.dreamteam.timelapse.data.VideoRepository
import com.dreamteam.timelapse.databinding.FragmentProjetsBinding import com.dreamteam.timelapse.databinding.FragmentProjetsBinding
import com.dreamteam.timelapse.data.ApiService import com.dreamteam.timelapse.data.ApiService
import com.dreamteam.timelapse.data.Project import com.dreamteam.timelapse.data.Video
import com.dreamteam.timelapse.databinding.FragmentVideosBinding
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
@@ -20,17 +21,17 @@ import retrofit2.converter.gson.GsonConverterFactory
* A simple [Fragment] subclass as the default destination in the navigation. * A simple [Fragment] subclass as the default destination in the navigation.
*/ */
class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener { class VideoFrag : Fragment(), VideoAdapter.OnEmptyStateListener {
private lateinit var projectRepository: ProjectRepository private lateinit var videoRepository: VideoRepository
//private lateinit var listView: ListView //private lateinit var listView: ListView
private lateinit var recyclerView: RecyclerView private lateinit var recyclerView: RecyclerView
private lateinit var projectAdapter: ProjectAdapter private lateinit var videoAdapter: VideoAdapter
private var _binding: FragmentProjetsBinding? = null private var _binding: FragmentVideosBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var apiService: ApiService // Déclare l'apiService private lateinit var apiService: ApiService // Déclare l'apiService
private var projects: List<Project> = emptyList() // Déclare une liste de projets vide private var videos: List<Video> = emptyList() // Déclare une liste de projets vide
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -47,11 +48,11 @@ class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ): View? {
_binding = FragmentProjetsBinding.inflate(inflater, container, false) _binding = FragmentVideosBinding.inflate(inflater, container, false)
Log.d("ProjetsFrag", "Fragment Projets créé") Log.d("ProjetsFrag", "Fragment Projets créé")
projectRepository = ProjectRepository(apiService) // Tu initialises ton repository videoRepository = VideoRepository(apiService) // Tu initialises ton repository
//fetchProjects() //fetchVideos()
return _binding?.root return _binding?.root
} }
@@ -62,10 +63,10 @@ class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
val swipeRefreshLayout = binding.swipeRefreshLayout val swipeRefreshLayout = binding.swipeRefreshLayout
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener {
fetchProjects() fetchVideos()
Log.d("ProjetsFrag", "Actualisation des projets") Log.d("ProjetsFrag", "Actualisation des projets")
} }
fetchProjects() fetchVideos()
recyclerView = binding.recyclerView // view.findViewById(R.id.recyclerView) recyclerView = binding.recyclerView // view.findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.layoutManager = LinearLayoutManager(context)
@@ -73,28 +74,28 @@ class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
} }
private fun fetchProjects() { private fun fetchVideos() {
// Simulez l'appel API // Simulez l'appel API
binding.swipeRefreshLayout.setEnabled(true) binding.swipeRefreshLayout.setEnabled(true)
binding.swipeRefreshLayout.setRefreshing(true) binding.swipeRefreshLayout.setRefreshing(true)
//binding.swipeRefreshLayout. //binding.swipeRefreshLayout.
//binding.swipeRefreshLayout.dragDown() //binding.swipeRefreshLayout.dragDown()
Log.d("ProjetsFrag", "User has refreshed the projects list") Log.d("ProjetsFrag", "User has refreshed the videos list")
projectRepository.fetchProjects( videoRepository.fetchVideos(
onSuccess = { projects -> onSuccess = { videos ->
Log.d("ProjetsFrag", "Projets reçus : $projects") Log.d("ProjetsFrag", "Projets reçus : $videos")
this.projects = projects this.videos = videos
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.videoAdapter = VideoAdapter(true, this.videos, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
this.recyclerView.adapter = this.projectAdapter this.recyclerView.adapter = this.videoAdapter
// Mettre à jour le RecyclerView ou autre traitement // Mettre à jour le RecyclerView ou autre traitement
}, },
onError = { errorMessage -> onError = { errorMessage ->
Log.e("ProjetsFrag prout", errorMessage) Log.e("ProjetsFrag prout", errorMessage)
//onEmptyStateChanged(true) //onEmptyStateChanged(true)
this.projects = listOf<Project>() this.videos = listOf<Video>()
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.videoAdapter = VideoAdapter(true, this.videos, this) //TODO : Potentiel problème pour les refresh ici (update de l'adapter ou un truc du genre à voir)
this.recyclerView.adapter = this.projectAdapter this.recyclerView.adapter = this.videoAdapter
} }
) )
@@ -103,9 +104,9 @@ class VideoFrag : Fragment(), ProjectAdapter.OnEmptyStateListener {
override fun onEmptyStateChanged(isEmpty: Boolean) { override fun onEmptyStateChanged(isEmpty: Boolean) {
// Afficher ou masquer le message // Afficher ou masquer le message
if (isEmpty) { if (isEmpty) {
binding.noProjectsText.visibility = View.VISIBLE binding.noVideosText.visibility = View.VISIBLE
} else { } else {
binding.noProjectsText.visibility = View.GONE binding.noVideosText.visibility = View.GONE
} }
} }

View File

@@ -0,0 +1,55 @@
package com.dreamteam.timelapse
import android.media.MediaPlayer
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.ui.PlayerView
class VideoPlayerActivity : AppCompatActivity() {
private lateinit var player: ExoPlayer
private lateinit var playerView: PlayerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_player)
// Get reference to the PlayerView
playerView = findViewById(R.id.playerView)
// Initialize the player
player = ExoPlayer.Builder(this).build()
// Get video URL from intent
val videoUrl = intent.getStringExtra("VIDEO_URL")
if (videoUrl.isNullOrEmpty()) {
Toast.makeText(this, "Invalid video URL", Toast.LENGTH_SHORT).show()
return
}
// Create a MediaItem from the URL
val mediaItem = MediaItem.fromUri(videoUrl)
// Set the media item to the player
player.setMediaItem(mediaItem)
// Prepare and start playback
player.prepare()
player.play()
// Connect the player to the PlayerView
playerView.player = player
}
override fun onPause() {
super.onPause()
player.pause() // Pause the player when the activity is paused
}
override fun onStop() {
super.onStop()
player.release() // Release the player when the activity is stopped
}
}

View File

@@ -14,6 +14,10 @@ interface ApiService {
fun getProjet(@Path("id") projectId: Int): Call<Project> fun getProjet(@Path("id") projectId: Int): Call<Project>
@GET("projects/{id}/measurements") // Remplace par l'endpoint de ton API @GET("projects/{id}/measurements") // Remplace par l'endpoint de ton API
fun getMeasurements(@Path("id") projectId: Int): Call<List<Measurement>> fun getMeasurements(@Path("id") projectId: Int): Call<List<Measurement>>
@GET("videos")
fun getVideos(): Call<List<VideoDTO>>
@POST("projects/") @POST("projects/")
fun createProject(@Body project: Project): Call<Confirmation> fun createProject(@Body project: Project): Call<Confirmation>
@GET("projects/{id}/videos")
fun getVideosOfProject(@Path("id") projectId:Int): Call<List<VideoDTO>>
} }

View File

@@ -26,6 +26,25 @@ class ProjectRepository(private val apiService: ApiService) {
} }
}) })
} }
fun fetchProject(id:Int, onSuccess: (Project) -> Unit, onError: (String) -> Unit) {
val call = apiService.getProjet(id)
call.enqueue(object : Callback<Project> {
override fun onResponse(call: Call<Project>, response: Response<Project>) {
if (response.isSuccessful) {
val project = response.body()
project?.let {
onSuccess(it)
} ?: onError("Le projet ${id} n'existe pas")
} else {
onError("Erreur : ${response.code()}")
}
}
override fun onFailure(call: Call<Project>, t: Throwable) {
onError("Échec de l'appel API : ${t.message}")
}
})
}
fun fetchMeasurementsOfProject(pid: Int, onSuccess: (List<Measurement>) -> Unit, onError: (String) -> Unit) { fun fetchMeasurementsOfProject(pid: Int, onSuccess: (List<Measurement>) -> Unit, onError: (String) -> Unit) {
val call = apiService.getMeasurements(pid) val call = apiService.getMeasurements(pid)

View File

@@ -0,0 +1,24 @@
package com.dreamteam.timelapse.data
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
data class VideoDTO (
val id: Int,
val project_id: Int,
val measurement_ids: String,
val video_file: String?,
val resolution:String,
val duration: Int,
val status: Int, //0 pas fini, 1 fini
val name: String
){
fun toVideo(): Video {
fun parseIntArray(jsonString: String): List<Int> {
val type = object : TypeToken<List<Int>>() {}.type
return Gson().fromJson<List<Int>>(jsonString, type)
}
return Video(id, project_id, parseIntArray(measurement_ids), video_file, resolution, duration, status, name)
}
}

View File

@@ -0,0 +1,43 @@
package com.dreamteam.timelapse.data
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.util.Date
class VideoRepository(private val apiService: ApiService) {
fun fetchVideos(onSuccess: (List<Video>) -> Unit, onError: (String) -> Unit) {
apiService.getVideos().enqueue(object : Callback<List<VideoDTO>> {
override fun onResponse(call: Call<List<VideoDTO>>, response: Response<List<VideoDTO>>) {
if (response.isSuccessful) {
val videoDtos = response.body() ?: emptyList()
val videos = videoDtos.map { it.toVideo() } // Convert ugly data to clean Video objects
onSuccess(videos)
} else {
onError("Erreur serveur : ${response.code()}")
}
}
override fun onFailure(call: Call<List<VideoDTO>>, t: Throwable) {
onError("Erreur réseau : ${t.message}")
}
})
}
fun fetchVideosOfProject(id:Int, onSuccess: (List<Video>) -> Unit, onError: (String) -> Unit){
apiService.getVideosOfProject(id).enqueue(object : Callback<List<VideoDTO>> {
override fun onResponse(call: Call<List<VideoDTO>>, response: Response<List<VideoDTO>>) {
if (response.isSuccessful) {
val videoDtos = response.body() ?: emptyList()
val videos = videoDtos.map { it.toVideo() } // Convert ugly data to clean Video objects
onSuccess(videos)
} else {
onError("Erreur serveur : ${response.code()}")
}
}
override fun onFailure(call: Call<List<VideoDTO>>, t: Throwable) {
onError("Erreur réseau : ${t.message}")
}
})
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.exoplayer2.ui.PlayerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/playerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<!--<SurfaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />-->

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" <!--<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".VideoFrag"> tools:context=".ProjetsFrag">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -12,17 +12,17 @@
android:padding="16dp"> android:padding="16dp">
<Button <Button
android:id="@+id/button_second" android:id="@+id/button_first"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/previous" android:text="@string/next"
app:layout_constraintBottom_toTopOf="@id/textview_second" app:layout_constraintBottom_toTopOf="@id/textview_first"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/textview_second" android:id="@+id/textview_first"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
@@ -30,6 +30,45 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_second" /> app:layout_constraintTop_toBottomOf="@id/button_first" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> -->
<!--<androidx.swiperefreshlayout.widget.SwipeRefreshLayout-->
<com.dreamteam.timelapse.CustomSwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".VideoFrag"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- RecyclerView -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/noVideosText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Aucune video disponible"
android:visibility="gone"
android:gravity="center"
android:textSize="18sp"
android:layout_gravity="center" />
</FrameLayout>
<!--<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />-->
</com.dreamteam.timelapse.CustomSwipeRefreshLayout>
<!--</androidx.core.widget.NestedScrollView> -->

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- Video Thumbnail -->
<ImageView
android:id="@+id/videoThumbnail"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/videoTitle"
/>
<!-- Play Button (Centered on Thumbnail) -->
<ImageView
android:id="@+id/playButton"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@android:drawable/ic_media_play"
app:layout_constraintBottom_toBottomOf="@id/videoThumbnail"
app:layout_constraintEnd_toEndOf="@id/videoThumbnail"
app:layout_constraintStart_toStartOf="@id/videoThumbnail"
app:layout_constraintTop_toTopOf="@id/videoThumbnail" />
<!-- Video Title -->
<TextView
android:id="@+id/videoTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="..."
android:textAppearance="?android:attr/textAppearanceMedium"
android:maxLines="2"
android:ellipsize="end"
app:layout_constraintTop_toBottomOf="@id/videoThumbnail"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="4dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -15,7 +15,9 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
tools:ignore="MissingConstraints"> tools:ignore="MissingConstraints"
app:layout_constraintEnd_toEndOf="parent"
>
<TextView <TextView
android:id="@+id/project_name" android:id="@+id/project_name"
@@ -35,13 +37,17 @@
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/appBarLayout" app:layout_constraintStart_toEndOf="@+id/appBarLayout"
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" app:layout_constraintTop_toBottomOf="@+id/appBarLayout"
tools:context=".ProjectActivity"> tools:context=".ProjectActivity">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
tools:context=".ProjectActivity"> tools:context=".ProjectActivity">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
@@ -82,6 +88,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_start_date" /> app:layout_constraintTop_toBottomOf="@id/project_start_date" />
<TextView <TextView
android:id="@+id/graph_header" android:id="@+id/graph_header"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -93,6 +100,7 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/project_status" /> app:layout_constraintTop_toBottomOf="@id/project_status" />
<com.github.mikephil.charting.charts.LineChart <com.github.mikephil.charting.charts.LineChart
android:id="@+id/temperature_humidity_chart" android:id="@+id/temperature_humidity_chart"
android:layout_width="0dp" android:layout_width="0dp"
@@ -147,8 +155,6 @@
app:layout_constraintTop_toBottomOf="@id/video_header" /> app:layout_constraintTop_toBottomOf="@id/video_header" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</com.dreamteam.timelapse.CustomSwipeRefreshLayout> </com.dreamteam.timelapse.CustomSwipeRefreshLayout>

View File

@@ -13,6 +13,7 @@ appcompat = "1.6.1"
constraintlayout = "2.1.4" constraintlayout = "2.1.4"
navigationFragmentKtx = "2.6.0" navigationFragmentKtx = "2.6.0"
navigationUiKtx = "2.6.0" navigationUiKtx = "2.6.0"
media3Exoplayer = "1.5.1"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -34,6 +35,7 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" }
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3Exoplayer" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }