diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 5d7a6cb..72bf4c7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,9 +4,7 @@ plugins {
android {
namespace = "com.campusaula.edbole.kanban_clone_android"
- compileSdk {
- version = release(36)
- }
+ compileSdk = 36
defaultConfig {
applicationId = "com.campusaula.edbole.KanbanCloneAndroid"
@@ -44,7 +42,6 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
- // Networking: OkHttp + Retrofit
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9bd5106..d5f2638 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -18,6 +18,18 @@
+
+
+
+
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/kanban/Task.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/kanban/Task.kt
index aea0031..87f99c5 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/kanban/Task.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/kanban/Task.kt
@@ -3,15 +3,15 @@ package com.campusaula.edbole.kanban_clone_android.kanban
import com.google.gson.annotations.SerializedName
enum class TaskStatus {
- @SerializedName("PENDING")
+ @SerializedName("pending")
PENDING,
- @SerializedName("IN_PROGRESS")
+ @SerializedName("in_progress")
IN_PROGRESS,
- @SerializedName("COMPLETED")
+ @SerializedName("completed")
COMPLETED,
- @SerializedName("FAILED")
+ @SerializedName("failed")
FAILED,
- @SerializedName("STASHED")
+ @SerializedName("stashed")
STASHED
}
@@ -19,7 +19,7 @@ class Task {
val id: Int = 0
val title: String = ""
val description: String = ""
- val status: TaskStatus = TaskStatus.PENDING
+ var status: TaskStatus = TaskStatus.PENDING
val project: Project? = null
}
@@ -36,3 +36,14 @@ data class TaskBase(
@SerializedName("status")
val status: TaskStatus
)
+
+data class TaskUpdate(
+ @SerializedName("title")
+ val title : String?,
+
+ @SerializedName("description")
+ val description : String?,
+
+ @SerializedName("status")
+ val status: TaskStatus?
+)
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/network/ApiService.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/network/ApiService.kt
index 6d9184b..296b6a1 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/network/ApiService.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/network/ApiService.kt
@@ -40,6 +40,18 @@ interface ApiService {
@GET("projects/{project_id}/users/")
suspend fun getProjectUsers(@Path("project_id") projectId: Int): Response>
+ @POST("projects/{project_id}/users/")
+ suspend fun addProjectCollaborator(
+ @Path("project_id") projectId: Int,
+ @Body email: Map
+ ): Response
+
+ @DELETE("projects/{project_id}/users/{user_id}/")
+ suspend fun removeProjectCollaborator(
+ @Path("project_id") projectId: Int,
+ @Path("user_id") userId: Int
+ ): Response
+
@POST("projects/")
suspend fun createProject(@Body projectCreate: ProjectCreate): Response
@@ -57,4 +69,17 @@ interface ApiService {
@POST("projects/{project_id}/tasks/")
suspend fun createTask(@Path("project_id") projectId: Int, @Body taskBase: TaskBase): Response
+ @PUT("projects/{project_id}/tasks/{task_id}/")
+ suspend fun updateProjectTask(
+ @Path("project_id") projectId: Int,
+ @Path("task_id") taskId: Int,
+ @Body taskUpdate: TaskUpdate
+ ): Response
+
+ @DELETE("projects/{project_id}/tasks/{task_id}/")
+ suspend fun deleteProjectTask(
+ @Path("project_id") projectId: Int,
+ @Path("task_id") taskId: Int
+ ): Response
+
}
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CollaboratorAddActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CollaboratorAddActivity.kt
new file mode 100644
index 0000000..e68042e
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CollaboratorAddActivity.kt
@@ -0,0 +1,126 @@
+package com.campusaula.edbole.kanban_clone_android.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.util.Patterns
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlinx.coroutines.launch
+
+class CollaboratorAddActivity : AppCompatActivity() {
+
+ private lateinit var api: ApiService
+
+ private lateinit var returnActionButton: FloatingActionButton
+ private lateinit var collaboratorEmailInput: EditText
+ private lateinit var addCollaboratorButton: Button
+
+ private var projectId: Int = -1
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_collaborator_add)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ api = RetrofitInstance.getRetrofit(applicationContext).create(ApiService::class.java)
+
+ // Get project ID from intent
+ projectId = intent.getIntExtra("project_id", -1)
+
+ if (projectId == -1) {
+ Toast.makeText(this, "Error: Invalid project ID", Toast.LENGTH_SHORT).show()
+ finish()
+ return
+ }
+
+ // Initialize views
+ returnActionButton = findViewById(R.id.returnActionButton)
+ collaboratorEmailInput = findViewById(R.id.collaboratorEmailInput)
+ addCollaboratorButton = findViewById(R.id.addCollaboratorButton)
+
+ // Set up button listeners
+ returnActionButton.setOnClickListener {
+ finish()
+
+ val intent = Intent(this@CollaboratorAddActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+
+ }
+
+ addCollaboratorButton.setOnClickListener {
+ addCollaborator()
+ }
+ }
+
+ private fun addCollaborator() {
+ val user_email = collaboratorEmailInput.text.toString().trim()
+
+ // Validate email
+ if (user_email.isEmpty()) {
+ Toast.makeText(this, "Email cannot be empty", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ if (!Patterns.EMAIL_ADDRESS.matcher(user_email).matches()) {
+ Toast.makeText(this, "Please enter a valid email address", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ lifecycleScope.launch {
+ try {
+ Log.d("CollaboratorAddActivity", "Adding collaborator: $user_email")
+ val emailBody = mapOf("user_email" to user_email)
+ val response = api.addProjectCollaborator(projectId, emailBody)
+
+ if (response.isSuccessful) {
+ Log.d("CollaboratorAddActivity", "Collaborator added successfully")
+ Toast.makeText(
+ this@CollaboratorAddActivity,
+ "Collaborator added successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ setResult(RESULT_OK)
+ finish()
+
+ // Reopen ProjectDetailActivity to show the new collaborator
+ val intent = Intent(this@CollaboratorAddActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("CollaboratorAddActivity", "Error adding collaborator: $errorBody")
+ Toast.makeText(
+ this@CollaboratorAddActivity,
+ "Error adding collaborator: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("CollaboratorAddActivity", "Exception adding collaborator: ${e.message}")
+ Toast.makeText(
+ this@CollaboratorAddActivity,
+ "Failed to add collaborator: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CreateProjectActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CreateProjectActivity.kt
index a0e80e4..521add0 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CreateProjectActivity.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/CreateProjectActivity.kt
@@ -3,9 +3,8 @@ package com.campusaula.edbole.kanban_clone_android.ui
import android.content.Intent
import android.os.Bundle
import android.util.Log
-import android.widget.AutoCompleteTextView
import android.widget.Button
-import android.widget.MultiAutoCompleteTextView
+import android.widget.EditText
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@@ -25,8 +24,8 @@ class CreateProjectActivity : AppCompatActivity() {
private lateinit var api: ApiService
private lateinit var returnActionButton : FloatingActionButton
- private lateinit var newProjectName : AutoCompleteTextView
- private lateinit var newProjectDescription : AutoCompleteTextView
+ private lateinit var newProjectName : EditText
+ private lateinit var newProjectDescription : EditText
private lateinit var newProjectCreateButton : Button
override fun onCreate(savedInstanceState: Bundle?) {
@@ -42,47 +41,63 @@ class CreateProjectActivity : AppCompatActivity() {
api = RetrofitInstance.getRetrofit(applicationContext).create(ApiService::class.java)
returnActionButton = findViewById(R.id.returnActionButton)
- returnActionButton.setOnClickListener { finish() }
+ returnActionButton.setOnClickListener {
+ finish()
+
+ val intent = Intent(this@CreateProjectActivity, MainActivity::class.java)
+ startActivity(intent)
+ }
newProjectName = findViewById(R.id.newProjectName)
newProjectDescription = findViewById(R.id.newProjectDescription)
newProjectCreateButton = findViewById(R.id.newProjectCreateButton)
newProjectCreateButton.setOnClickListener {
- val projectName = newProjectName.text.toString()
- val projectDescription = newProjectDescription.text.toString()
+ val projectName = newProjectName.text.toString().trim()
+ val projectDescription = newProjectDescription.text.toString().trim()
- if (projectName.isEmpty() || projectDescription.isEmpty()) {
- Toast
- .makeText(this, "Please fill in all fields", Toast.LENGTH_SHORT)
- .show()
+ if (projectName.isEmpty()) {
+ Toast.makeText(this, "Project name cannot be empty", Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
lifecycleScope.launch {
+ try {
+ Log.d("CreateProjectActivity", "Creating project: $projectName")
+ val projectCreate = ProjectCreate(projectName, projectDescription)
+ val response = api.createProject(projectCreate)
- val projectCreate : ProjectCreate = ProjectCreate(projectName, projectDescription)
- val response = api.createProject(projectCreate)
+ if (response.isSuccessful) {
+ Log.d("CreateProjectActivity", "Project created successfully: ${response.body()}")
+ Toast.makeText(
+ this@CreateProjectActivity,
+ "Project created successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ finish()
- if (response.isSuccessful){
- Toast
- .makeText(this@CreateProjectActivity, "Project created successfully", Toast.LENGTH_SHORT)
- .show()
- Log.d("CreateProjectActivity", "Created project: ${response.body()}")
- startActivity(Intent(this@CreateProjectActivity, MainActivity::class.java))
- } else {
- Log.e("CreateProjectActivity", "Error creating project: ${response.code()} - ${response.message()}")
- Toast
- .makeText(this@CreateProjectActivity, "Error creating project", Toast.LENGTH_SHORT)
- .show()
+ // Volver a MainActivity para ver el nuevo proyecto
+ val intent = Intent(this@CreateProjectActivity, MainActivity::class.java)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("CreateProjectActivity", "Error creating project: $errorBody")
+ Toast.makeText(
+ this@CreateProjectActivity,
+ "Error creating project: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("CreateProjectActivity", "Exception creating project: ${e.message}")
+ Toast.makeText(
+ this@CreateProjectActivity,
+ "Failed to create project: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
}
-
}
-
}
-
-
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/MainActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/MainActivity.kt
index fe71980..2dcfa11 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/MainActivity.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/MainActivity.kt
@@ -15,6 +15,7 @@ import com.campusaula.edbole.kanban_clone_android.R
import com.campusaula.edbole.kanban_clone_android.kanban.Project
import com.campusaula.edbole.kanban_clone_android.network.ApiService
import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.campusaula.edbole.kanban_clone_android.ui.adapters.ProjectItemAdapter
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.launch
@@ -81,8 +82,9 @@ class MainActivity : AppCompatActivity() {
if (logoutResponse.isSuccessful) {
// Clear cookies for the API host
RetrofitInstance.clearCookiesForHost(
- "10.0.2.2:8000"
+ "10.0.2.2"
)
+
// Navigate back to the login screen
val intent =
Intent(this@MainActivity, LoginActivity::class.java)
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectCollaboratorAdapter.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectCollaboratorAdapter.kt
deleted file mode 100644
index bc3cb26..0000000
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectCollaboratorAdapter.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-package com.campusaula.edbole.kanban_clone_android.ui
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import com.campusaula.edbole.kanban_clone_android.kanban.User
-
-class ProjectCollaboratorAdapter(
- private var collaborators: List
-) : RecyclerView.Adapter() {
-
- fun submitList(newList: List) {
- collaborators = newList.toMutableList()
- notifyDataSetChanged()
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = LayoutInflater.from(parent.context).inflate(R.layout.item_collaborator, parent, false)
- return ViewHolder(view)
- }
-
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- holder.bind(collaborators[position])
- }
-
- override fun getItemCount(): Int = collaborators.size
- class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
- private val collaboratorNameText: TextView = itemView.findViewById(R.id.collaboratorNameText)
- private val removeCollaboratorButton: Button = itemView.findViewById(R.id.removeCollaboratorButton)
-
- fun bind(collaborator: User) {
- collaboratorNameText.text = collaborator.name
-
- }
- }
- }
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectDetailActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectDetailActivity.kt
index 3cb0662..4db305c 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectDetailActivity.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectDetailActivity.kt
@@ -18,6 +18,8 @@ import com.campusaula.edbole.kanban_clone_android.kanban.Task
import com.campusaula.edbole.kanban_clone_android.kanban.TaskStatus
import com.campusaula.edbole.kanban_clone_android.network.ApiService
import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.campusaula.edbole.kanban_clone_android.ui.adapters.ProjectCollaboratorAdapter
+import com.campusaula.edbole.kanban_clone_android.ui.adapters.ProjectTaskAdapter
import com.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.launch
@@ -28,9 +30,14 @@ class ProjectDetailActivity : AppCompatActivity() {
private lateinit var returnActionButton: FloatingActionButton
private lateinit var addTaskButton: Button
private lateinit var addCollaboratorButton: Button
+ private lateinit var editProjectButton: Button
+ private lateinit var deleteProjectButton: Button
+ private lateinit var taskListRecycler: RecyclerView
private lateinit var collaboratorListRecycler: RecyclerView
-// private lateinit var collaboratorListAdapter: CollaboratorListAdapter
+ private lateinit var collaboratorListAdapter: ProjectCollaboratorAdapter
+ private lateinit var taskListAdapter: ProjectTaskAdapter
+
private lateinit var projectTitleText : TextView
private lateinit var projectDescriptionText : TextView
@@ -59,23 +66,52 @@ class ProjectDetailActivity : AppCompatActivity() {
returnActionButton.setOnClickListener { finish() }
addTaskButton = findViewById(R.id.addTaskButton)
-// addTaskButton.setOnClickListener {
-// val intent: Intent = Intent(this, CreateTaskActivity::class.java)
-// intent.putExtra("project_id", projectId)
-// startActivity(intent)
-// }
+ addTaskButton.setOnClickListener {
+ val intent = Intent(this, TaskAddActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ finish()
+ }
addCollaboratorButton = findViewById(R.id.addCollaboratorButton)
-// addCollaboratorButton.setOnClickListener {
-// val intent: Intent = Intent(this, AddCollaboratorActivity::class.java)
-// intent.putExtra("project_id", projectId)
-// startActivity(intent)
-// }
+ addCollaboratorButton.setOnClickListener {
+ val intent = Intent(this, CollaboratorAddActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ finish()
+ }
+
+ taskListRecycler = findViewById(R.id.taskListRecycler)
+ taskListRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
+ taskListAdapter = ProjectTaskAdapter(emptyList(), api, projectId, {
+ updateCompletionRate()
+ }, {
+ finish()
+ })
+ taskListRecycler.adapter = taskListAdapter
collaboratorListRecycler = findViewById(R.id.collaboratorListRecycler)
-// collaboratorListAdapter =
collaboratorListRecycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
+ collaboratorListAdapter = ProjectCollaboratorAdapter(emptyList(), api, projectId) {
+ updateCollaboratorList()
+ }
+ collaboratorListRecycler.adapter = collaboratorListAdapter
+ // Danger Zone buttons
+ editProjectButton = findViewById(R.id.editProjectButton)
+ editProjectButton.setOnClickListener {
+ val intent = Intent(this, ProjectEditActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ intent.putExtra("project_name", projectTitleText.text.toString())
+ intent.putExtra("project_description", projectDescriptionText.text.toString())
+ startActivity(intent)
+ finish()
+ }
+
+ deleteProjectButton = findViewById(R.id.deleteProjectButton)
+ deleteProjectButton.setOnClickListener {
+ deleteProject(projectId)
+ }
if (projectId > 0) {
Log.d("ProjectDetailActivity", "Received project ID: $projectId")
@@ -94,16 +130,22 @@ class ProjectDetailActivity : AppCompatActivity() {
projectDescriptionText.text = project.description
var percentageFinished = 0.0;
+ val collaborators = project.users
val tasks: List = project.tasks
val totalTasks: Int = tasks.size
- val perTaskPercentage = if (totalTasks > 0) (1.0 / totalTasks)*100 else 0.0
+ var completedTasks = 0
for (task in tasks) {
if (task.status == TaskStatus.COMPLETED) {
- percentageFinished += perTaskPercentage
+ completedTasks++
}
}
- completedPercentageText.text = "Completed: ${"%.2f".format(percentageFinished * 100)}%"
+
+ percentageFinished = if (totalTasks > 0) (completedTasks.toDouble() / totalTasks.toDouble()) * 100 else 0.0
+ completedPercentageText.text = "Completed: ${"%.2f".format(percentageFinished)}%"
+
+ taskListAdapter.submitList(tasks.toMutableList())
+ collaboratorListAdapter.submitList(collaborators.toMutableList())
} else {
@@ -124,4 +166,90 @@ class ProjectDetailActivity : AppCompatActivity() {
}
+
+ private fun updateCompletionRate() {
+ lifecycleScope.launch {
+ try {
+ val projectId = intent.getIntExtra("project_id", -1)
+ val projectResponse = api.getProjectById(projectId)
+
+ if (projectResponse.isSuccessful && projectResponse.body() != null) {
+ val project = projectResponse.body()!!
+ val tasks: List = project.tasks
+ val totalTasks: Int = tasks.size
+
+ var completedTasks = 0
+ for (task in tasks) {
+ if (task.status == TaskStatus.COMPLETED) {
+ completedTasks++
+ }
+ }
+
+ val percentageFinished = if (totalTasks > 0) (completedTasks.toDouble() / totalTasks.toDouble()) * 100 else 0.0
+ completedPercentageText.text = "Completed: ${"%.2f".format(percentageFinished)}%"
+
+ // Actualizar la lista de tareas tambiƩn
+ taskListAdapter.submitList(tasks.toMutableList())
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectDetailActivity", "Error updating completion rate", e)
+ }
+ }
+ }
+
+ private fun updateCollaboratorList() {
+ lifecycleScope.launch {
+ try {
+ val projectId = intent.getIntExtra("project_id", -1)
+ val projectResponse = api.getProjectById(projectId)
+
+ if (projectResponse.isSuccessful && projectResponse.body() != null) {
+ val project = projectResponse.body()!!
+ val collaborators = project.users
+
+ collaboratorListAdapter.submitList(collaborators.toMutableList())
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectDetailActivity", "Error updating collaborator list", e)
+ }
+ }
+ }
+
+ private fun deleteProject(projectId: Int) {
+ lifecycleScope.launch {
+ try {
+ Log.d("ProjectDetailActivity", "Deleting project: $projectId")
+ val response = api.deleteProject(projectId)
+
+ if (response.isSuccessful) {
+ Log.d("ProjectDetailActivity", "Project deleted successfully")
+ android.widget.Toast.makeText(
+ this@ProjectDetailActivity,
+ "Project deleted successfully",
+ android.widget.Toast.LENGTH_SHORT
+ ).show()
+ finish()
+
+ // Volver a MainActivity
+ val intent = Intent(this@ProjectDetailActivity, MainActivity::class.java)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("ProjectDetailActivity", "Error deleting project: $errorBody")
+ android.widget.Toast.makeText(
+ this@ProjectDetailActivity,
+ "Error deleting project: ${response.code()}",
+ android.widget.Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectDetailActivity", "Exception deleting project: ${e.message}")
+ android.widget.Toast.makeText(
+ this@ProjectDetailActivity,
+ "Failed to delete project: ${e.message}",
+ android.widget.Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectEditActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectEditActivity.kt
new file mode 100644
index 0000000..0948826
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectEditActivity.kt
@@ -0,0 +1,134 @@
+package com.campusaula.edbole.kanban_clone_android.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.kanban.ProjectCreate
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlinx.coroutines.launch
+
+class ProjectEditActivity : AppCompatActivity() {
+
+ private lateinit var api: ApiService
+
+ private lateinit var returnActionButton: FloatingActionButton
+ private lateinit var projectNameInput: EditText
+ private lateinit var projectDescriptionInput: EditText
+ private lateinit var saveProjectButton: Button
+
+ private var projectId: Int = -1
+ private var currentName: String = ""
+ private var currentDescription: String = ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_project_edit)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ api = RetrofitInstance.getRetrofit(applicationContext).create(ApiService::class.java)
+
+ // Get project data from intent
+ projectId = intent.getIntExtra("project_id", -1)
+ currentName = intent.getStringExtra("project_name") ?: ""
+ currentDescription = intent.getStringExtra("project_description") ?: ""
+
+ if (projectId == -1) {
+ Toast.makeText(this, "Error: Invalid project ID", Toast.LENGTH_SHORT).show()
+ finish()
+ return
+ }
+
+ // Initialize views
+ returnActionButton = findViewById(R.id.returnActionButton)
+ projectNameInput = findViewById(R.id.projectNameInput)
+ projectDescriptionInput = findViewById(R.id.projectDescriptionInput)
+ saveProjectButton = findViewById(R.id.saveProjectButton)
+
+ // Populate fields with current project data
+ projectNameInput.setText(currentName)
+ projectDescriptionInput.setText(currentDescription)
+
+ // Set up button listeners
+ returnActionButton.setOnClickListener {
+ finish()
+
+ val intent = Intent(this@ProjectEditActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ }
+
+ saveProjectButton.setOnClickListener {
+ saveProject()
+ }
+ }
+
+ private fun saveProject() {
+ val newName = projectNameInput.text.toString().trim()
+ val newDescription = projectDescriptionInput.text.toString().trim()
+
+ if (newName.isEmpty()) {
+ Toast.makeText(this, "Project name cannot be empty", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ lifecycleScope.launch {
+ try {
+ Log.d("ProjectEditActivity", "Updating project: $projectId")
+ val projectCreate = ProjectCreate(
+ name = newName,
+ description = newDescription
+ )
+
+ val response = api.updateProject(projectId, projectCreate)
+
+ if (response.isSuccessful) {
+ Log.d("ProjectEditActivity", "Project updated successfully")
+ Toast.makeText(
+ this@ProjectEditActivity,
+ "Project updated successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ setResult(RESULT_OK)
+ finish()
+
+ // Reopen ProjectDetailActivity to show the updated project
+ val intent = Intent(this@ProjectEditActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("ProjectEditActivity", "Error updating project: $errorBody")
+ Toast.makeText(
+ this@ProjectEditActivity,
+ "Error updating project: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectEditActivity", "Exception updating project: ${e.message}")
+ Toast.makeText(
+ this@ProjectEditActivity,
+ "Failed to update project: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskAddActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskAddActivity.kt
new file mode 100644
index 0000000..37d764c
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskAddActivity.kt
@@ -0,0 +1,136 @@
+package com.campusaula.edbole.kanban_clone_android.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Spinner
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskBase
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskStatus
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlinx.coroutines.launch
+
+class TaskAddActivity : AppCompatActivity() {
+
+ private lateinit var api: ApiService
+
+ private lateinit var returnActionButton: FloatingActionButton
+ private lateinit var taskTitleInput: EditText
+ private lateinit var taskDescriptionInput: EditText
+ private lateinit var taskStatusSpinner: Spinner
+ private lateinit var createTaskButton: Button
+
+ private var projectId: Int = -1
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_task_add)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ api = RetrofitInstance.getRetrofit(applicationContext).create(ApiService::class.java)
+
+ // Get project ID from intent
+ projectId = intent.getIntExtra("project_id", -1)
+
+ if (projectId == -1) {
+ Toast.makeText(this, "Error: Invalid project ID", Toast.LENGTH_SHORT).show()
+ finish()
+ return
+ }
+
+ // Initialize views
+ returnActionButton = findViewById(R.id.returnActionButton)
+ taskTitleInput = findViewById(R.id.taskTitleInput)
+ taskDescriptionInput = findViewById(R.id.taskDescriptionInput)
+ taskStatusSpinner = findViewById(R.id.taskStatusSpinner)
+ createTaskButton = findViewById(R.id.createTaskButton)
+
+ // Set default status to PENDING (index 0)
+ taskStatusSpinner.setSelection(0)
+
+ // Set up button listeners
+ returnActionButton.setOnClickListener {
+ finish()
+
+ val intent = Intent(this@TaskAddActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ }
+
+ createTaskButton.setOnClickListener {
+ createTask()
+ }
+ }
+
+ private fun createTask() {
+ val title = taskTitleInput.text.toString().trim()
+ val description = taskDescriptionInput.text.toString().trim()
+ val status = TaskStatus.entries[taskStatusSpinner.selectedItemPosition]
+
+ if (title.isEmpty()) {
+ Toast.makeText(this, "Title cannot be empty", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ lifecycleScope.launch {
+ try {
+ Log.d("TaskAddActivity", "Creating task: $title")
+ val taskBase = TaskBase(
+ id = 0, // ID will be assigned by the server
+ title = title,
+ description = description,
+ status = status
+ )
+
+ val response = api.createTask(projectId, taskBase)
+
+ if (response.isSuccessful) {
+ Log.d("TaskAddActivity", "Task created successfully")
+ Toast.makeText(
+ this@TaskAddActivity,
+ "Task created successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ setResult(RESULT_OK)
+ finish()
+
+ // Reopen ProjectDetailActivity to show the new task
+ val intent = Intent(this@TaskAddActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("TaskAddActivity", "Error creating task: $errorBody")
+ Toast.makeText(
+ this@TaskAddActivity,
+ "Error creating task: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("TaskAddActivity", "Exception creating task: ${e.message}")
+ Toast.makeText(
+ this@TaskAddActivity,
+ "Failed to create task: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskEditActivity.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskEditActivity.kt
new file mode 100644
index 0000000..51aa741
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/TaskEditActivity.kt
@@ -0,0 +1,189 @@
+package com.campusaula.edbole.kanban_clone_android.ui
+
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Spinner
+import android.widget.Toast
+import androidx.activity.enableEdgeToEdge
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.lifecycle.lifecycleScope
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskStatus
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskUpdate
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import kotlinx.coroutines.launch
+
+class TaskEditActivity : AppCompatActivity() {
+
+ private lateinit var api: ApiService
+
+ private lateinit var returnActionButton: FloatingActionButton
+ private lateinit var taskTitleInput: EditText
+ private lateinit var taskDescriptionInput: EditText
+ private lateinit var taskStatusSpinner: Spinner
+ private lateinit var saveTaskButton: Button
+ private lateinit var deleteTaskButton: Button
+
+ private var projectId: Int = -1
+ private var taskId: Int = -1
+ private var currentTitle: String = ""
+ private var currentDescription: String = ""
+ private var currentStatus: TaskStatus = TaskStatus.PENDING
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+ setContentView(R.layout.activity_task_edit)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
+ val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
+ insets
+ }
+
+ api = RetrofitInstance.getRetrofit(applicationContext).create(ApiService::class.java)
+
+ // Get task and project IDs from intent
+ projectId = intent.getIntExtra("project_id", -1)
+ taskId = intent.getIntExtra("task_id", -1)
+ currentTitle = intent.getStringExtra("task_title") ?: ""
+ currentDescription = intent.getStringExtra("task_description") ?: ""
+ val statusString = intent.getStringExtra("task_status") ?: "PENDING"
+ currentStatus = TaskStatus.valueOf(statusString)
+
+ if (projectId == -1 || taskId == -1) {
+ Toast.makeText(this, "Error: Invalid task or project ID", Toast.LENGTH_SHORT).show()
+ finish()
+ }
+
+ // Initialize views
+ returnActionButton = findViewById(R.id.returnActionButton)
+ taskTitleInput = findViewById(R.id.taskTitleInput)
+ taskDescriptionInput = findViewById(R.id.taskDescriptionInput)
+ taskStatusSpinner = findViewById(R.id.taskStatusSpinner)
+ saveTaskButton = findViewById(R.id.saveTaskButton)
+ deleteTaskButton = findViewById(R.id.deleteTaskButton)
+
+ // Populate fields with current task data
+ taskTitleInput.setText(currentTitle)
+ taskDescriptionInput.setText(currentDescription)
+ taskStatusSpinner.setSelection(TaskStatus.entries.indexOf(currentStatus))
+
+ // Set up button listeners
+ returnActionButton.setOnClickListener {
+ finish()
+
+ val intent = Intent(this@TaskEditActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ }
+
+ saveTaskButton.setOnClickListener {
+ saveTask()
+ }
+
+ deleteTaskButton.setOnClickListener {
+ deleteTask()
+ }
+ }
+
+ private fun saveTask() {
+ val newTitle = taskTitleInput.text.toString().trim()
+ val newDescription = taskDescriptionInput.text.toString().trim()
+ val newStatus = TaskStatus.entries[taskStatusSpinner.selectedItemPosition]
+
+ if (newTitle.isEmpty()) {
+ Toast.makeText(this, "Title cannot be empty", Toast.LENGTH_SHORT).show()
+ return
+ }
+
+ lifecycleScope.launch {
+ try {
+ Log.d("TaskEditActivity", "Updating task: $taskId")
+ val taskUpdate = TaskUpdate(
+ title = if (newTitle != currentTitle) newTitle else null,
+ description = if (newDescription != currentDescription) newDescription else null,
+ status = if (newStatus != currentStatus) newStatus else null
+ )
+
+ val response = api.updateProjectTask(projectId, taskId, taskUpdate)
+
+ if (response.isSuccessful) {
+ Log.d("TaskEditActivity", "Task updated successfully")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Task updated successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ setResult(RESULT_OK)
+ finish()
+ val intent = Intent(this@TaskEditActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("TaskEditActivity", "Error updating task: $errorBody")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Error updating task: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("TaskEditActivity", "Exception updating task: ${e.message}")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Failed to update task: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+
+ private fun deleteTask() {
+ lifecycleScope.launch {
+ try {
+ Log.d("TaskEditActivity", "Deleting task: $taskId")
+ val response = api.deleteProjectTask(projectId, taskId)
+
+ if (response.isSuccessful) {
+ Log.d("TaskEditActivity", "Task deleted successfully")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Task deleted successfully",
+ Toast.LENGTH_SHORT
+ ).show()
+ setResult(RESULT_OK)
+ finish()
+
+ // Reopen ProjectDetailActivity after deleting the task
+ val intent = Intent(this@TaskEditActivity, ProjectDetailActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ startActivity(intent)
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("TaskEditActivity", "Error deleting task: $errorBody")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Error deleting task: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ } catch (e: Exception) {
+ Log.e("TaskEditActivity", "Exception deleting task: ${e.message}")
+ Toast.makeText(
+ this@TaskEditActivity,
+ "Failed to delete task: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectCollaboratorAdapter.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectCollaboratorAdapter.kt
new file mode 100644
index 0000000..96dbde5
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectCollaboratorAdapter.kt
@@ -0,0 +1,101 @@
+package com.campusaula.edbole.kanban_clone_android.ui.adapters
+
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import android.widget.Toast
+import androidx.recyclerview.widget.RecyclerView
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.kanban.User
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class ProjectCollaboratorAdapter(
+ private var collaborators: List,
+ private val apiService: ApiService,
+ private val projectId: Int,
+ private val onCollaboratorRemoved: () -> Unit = {}
+) : RecyclerView.Adapter() {
+
+ private val adapterScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+
+ fun submitList(newList: List) {
+ collaborators = newList.toMutableList()
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_collaborator, parent, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(collaborators[position])
+ }
+
+ override fun getItemCount(): Int = collaborators.size
+
+ fun onDestroy() {
+ adapterScope.cancel("Adapter destroyed")
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val collaboratorNameText: TextView = itemView.findViewById(R.id.collaboratorNameText)
+ private val collaboratorEmailText: TextView = itemView.findViewById(R.id.collaboratorEmailText)
+ private val removeCollaboratorButton: Button = itemView.findViewById(R.id.removeCollaboratorButton)
+
+ fun bind(collaborator: User) {
+ collaboratorNameText.text = collaborator.name
+ collaboratorEmailText.text = collaborator.email
+
+ removeCollaboratorButton.setOnClickListener {
+ adapterScope.launch {
+ try {
+ Log.d("ProjectCollaboratorAdapter", "Removing collaborator: ${collaborator.id}")
+ val response = apiService.removeProjectCollaborator(projectId, collaborator.id)
+
+ if (response.isSuccessful) {
+ Log.d("ProjectCollaboratorAdapter", "Collaborator removed successfully: ${collaborator.id}")
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ itemView.context,
+ "Collaborator removed: ${collaborator.name}",
+ Toast.LENGTH_SHORT
+ ).show()
+ onCollaboratorRemoved()
+ }
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("ProjectCollaboratorAdapter", "Error removing collaborator: $errorBody")
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ itemView.context,
+ "Error removing collaborator: ${response.code()}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectCollaboratorAdapter", "Exception removing collaborator: ${e.message}")
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ itemView.context,
+ "Failed to remove collaborator: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectItemAdapter.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectItemAdapter.kt
similarity index 95%
rename from app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectItemAdapter.kt
rename to app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectItemAdapter.kt
index 062b84b..b2b3219 100644
--- a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/ProjectItemAdapter.kt
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectItemAdapter.kt
@@ -1,4 +1,4 @@
-package com.campusaula.edbole.kanban_clone_android.ui
+package com.campusaula.edbole.kanban_clone_android.ui.adapters
import android.view.LayoutInflater
import android.view.View
@@ -41,4 +41,4 @@ class ProjectItemAdapter(
descriptionTextView.text = project.description
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectTaskAdapter.kt b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectTaskAdapter.kt
new file mode 100644
index 0000000..ddb16db
--- /dev/null
+++ b/app/src/main/java/com/campusaula/edbole/kanban_clone_android/ui/adapters/ProjectTaskAdapter.kt
@@ -0,0 +1,140 @@
+package com.campusaula.edbole.kanban_clone_android.ui.adapters
+
+import android.content.Intent
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.Button
+import android.widget.Spinner
+import android.widget.TextView
+import android.widget.Toast
+import androidx.recyclerview.widget.RecyclerView
+import com.campusaula.edbole.kanban_clone_android.R
+import com.campusaula.edbole.kanban_clone_android.kanban.Task
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskStatus
+import com.campusaula.edbole.kanban_clone_android.kanban.TaskUpdate
+import com.campusaula.edbole.kanban_clone_android.network.ApiService
+import com.campusaula.edbole.kanban_clone_android.ui.TaskEditActivity
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class ProjectTaskAdapter(
+ private var tasks: List,
+ private val apiService: ApiService,
+ private val projectId: Int,
+ private val onTaskUpdated: () -> Unit = {},
+ private val onEditTask: () -> Unit = {}
+) : RecyclerView.Adapter() {
+
+ private val adapterScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val view = LayoutInflater.from(parent.context).inflate(R.layout.item_task, parent, false)
+ return ViewHolder(view)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.bind(tasks[position])
+ }
+
+ override fun getItemCount(): Int = tasks.size
+
+ fun submitList(newList: List) {
+ tasks = newList
+ notifyDataSetChanged()
+ }
+
+ // Cancelar el CoroutineScope cuando el adaptador ya no sea necesario
+ fun onDestroy() {
+ adapterScope.cancel()
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ private val taskTitleText: TextView = itemView.findViewById(R.id.taskTitleText)
+ private val taskDescriptionText: TextView = itemView.findViewById(R.id.taskDescriptionText)
+ private val editTaskButton: Button = itemView.findViewById(R.id.editTaskButton)
+ private val taskStatusPicker: Spinner = itemView.findViewById(R.id.taskStatusDropdown)
+
+ fun bind(task: Task) {
+ taskTitleText.text = task.title
+ taskDescriptionText.text = task.description
+
+ editTaskButton.setOnClickListener {
+ val intent = Intent(itemView.context, TaskEditActivity::class.java)
+ intent.putExtra("project_id", projectId)
+ intent.putExtra("task_id", task.id)
+ intent.putExtra("task_title", task.title)
+ intent.putExtra("task_description", task.description)
+ intent.putExtra("task_status", task.status.name)
+ itemView.context.startActivity(intent)
+ onEditTask() // Cerrar la actividad
+ }
+
+ taskStatusPicker.setSelection(TaskStatus.entries.indexOf(task.status))
+
+ taskStatusPicker.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ id: Long
+ ) {
+ val selectedStatus: TaskStatus = TaskStatus.entries[position]
+ task.status = selectedStatus
+
+ adapterScope.launch {
+ try {
+ Log.d("ProjectTaskAdapter", "Sending PUT request for task: ${task.id}")
+ Log.d("ProjectTaskAdapter", "Project ID: $projectId")
+ Log.d("ProjectTaskAdapter", "TaskUpdate data: ${TaskUpdate(null, null, task.status)}")
+
+ val response = apiService.updateProjectTask(
+ projectId,
+ task.id,
+ TaskUpdate(null, null, task.status)
+ )
+
+ if (response.isSuccessful) {
+ Log.d("ProjectTaskAdapter", "Task updated successfully: ${task.id}")
+ withContext(Dispatchers.Main) {
+ onTaskUpdated()
+ notifyDataSetChanged()
+ }
+ } else {
+ val errorBody = response.errorBody()?.string()
+ Log.e("ProjectTaskAdapter", "Error response: $errorBody")
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ itemView.context,
+ "Error updating task: ${response.code()} - $errorBody",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ } catch (e: Exception) {
+ Log.e("ProjectTaskAdapter", "Exception updating task: ${e.message}")
+ withContext(Dispatchers.Main) {
+ Toast.makeText(
+ itemView.context,
+ "Failed to update task: ${e.message}",
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ // No action needed
+ }
+ }
+
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_collaborator_add.xml b/app/src/main/res/layout/activity_collaborator_add.xml
new file mode 100644
index 0000000..b31ddce
--- /dev/null
+++ b/app/src/main/res/layout/activity_collaborator_add.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_create_project.xml b/app/src/main/res/layout/activity_create_project.xml
index e833cd0..73e9fe1 100644
--- a/app/src/main/res/layout/activity_create_project.xml
+++ b/app/src/main/res/layout/activity_create_project.xml
@@ -16,80 +16,83 @@
app:srcCompat="@android:drawable/ic_menu_revert"
android:id="@+id/returnActionButton"
app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="16dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginBottom="@dimen/fab_margin_bottom"
+ android:layout_marginEnd="@dimen/fab_margin_end"
app:layout_constraintEnd_toEndOf="parent" />
-
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/bottom_padding_for_fab">
-
+
-
+
-
+
-
+
-
+
+
-
\ No newline at end of file
+
+
+
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index ac739f4..cd2fbff 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -55,4 +55,4 @@
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
style="@style/Widget.Material3.Button.TextButton" />
-
\ 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 53dcf01..68a2119 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -35,7 +35,7 @@
@@ -51,4 +51,4 @@
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent" />
-
\ No newline at end of file
+
diff --git a/app/src/main/res/layout/activity_project_detail.xml b/app/src/main/res/layout/activity_project_detail.xml
index c1987be..59a36f7 100644
--- a/app/src/main/res/layout/activity_project_detail.xml
+++ b/app/src/main/res/layout/activity_project_detail.xml
@@ -14,120 +14,180 @@
app:srcCompat="@android:drawable/ic_menu_revert"
android:id="@+id/returnActionButton"
app:layout_constraintBottom_toBottomOf="parent"
- android:layout_marginBottom="16dp"
- android:layout_marginEnd="16dp"
+ android:layout_marginBottom="@dimen/fab_margin_bottom"
+ android:layout_marginEnd="@dimen/fab_margin_end"
app:layout_constraintEnd_toEndOf="parent" />
-
+ android:layout_height="match_parent">
-
-
-
-
-
-
-
-
-
+ android:orientation="vertical"
+ android:gravity="start|top">
-
-
+
-
+
-
+
-
+
-
-
+
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_project_edit.xml b/app/src/main/res/layout/activity_project_edit.xml
new file mode 100644
index 0000000..7a1f7f9
--- /dev/null
+++ b/app/src/main/res/layout/activity_project_edit.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_task_add.xml b/app/src/main/res/layout/activity_task_add.xml
new file mode 100644
index 0000000..64d4c2a
--- /dev/null
+++ b/app/src/main/res/layout/activity_task_add.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_task_edit.xml b/app/src/main/res/layout/activity_task_edit.xml
new file mode 100644
index 0000000..a579092
--- /dev/null
+++ b/app/src/main/res/layout/activity_task_edit.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_collaborator.xml b/app/src/main/res/layout/item_collaborator.xml
new file mode 100644
index 0000000..d60058b
--- /dev/null
+++ b/app/src/main/res/layout/item_collaborator.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_task.xml b/app/src/main/res/layout/item_task.xml
new file mode 100644
index 0000000..9052787
--- /dev/null
+++ b/app/src/main/res/layout/item_task.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..c0dda4b
--- /dev/null
+++ b/app/src/main/res/values/arrays.xml
@@ -0,0 +1,10 @@
+
+
+
+ - Pending
+ - In progress
+ - Completed
+ - Stashed
+ - Failed
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c8524cd..4f13a53 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,4 +2,15 @@
#FF000000
#FFFFFFFF
+
+
+ #469E29
+ #FF0000
+ #FF9800
+
+
+ #666666
+
+
+ #24FF5757
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..28ce610
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,27 @@
+
+
+
+ 12dp
+ 8dp
+
+
+ 12dp
+ 16dp
+ 24dp
+ 32dp
+ 8dp
+ 4dp
+
+
+ 17dp
+ 18dp
+
+
+ 80dp
+
+
+ 24sp
+ 18sp
+ 14sp
+
+