@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
86
app/src/main/java/com/dreamteam/timelapse/VideoAdapter.kt
Normal file
86
app/src/main/java/com/dreamteam/timelapse/VideoAdapter.kt
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>>
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
24
app/src/main/java/com/dreamteam/timelapse/data/VideoDTO.kt
Normal file
24
app/src/main/java/com/dreamteam/timelapse/data/VideoDTO.kt
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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}")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
11
app/src/main/res/layout/activity_video_player.xml
Normal file
11
app/src/main/res/layout/activity_video_player.xml
Normal 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" />-->
|
||||||
@@ -1,28 +1,28 @@
|
|||||||
<?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"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
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> -->
|
||||||
|
|||||||
47
app/src/main/res/layout/item_video.xml
Normal file
47
app/src/main/res/layout/item_video.xml
Normal 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>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
Reference in New Issue
Block a user