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 @@ + + + + + + + + + + + + + +