Compare commits

...

12 commits

28 changed files with 1903 additions and 273 deletions

View file

@ -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")

View file

@ -18,6 +18,18 @@
<activity
android:name=".ui.CreateProjectActivity"
android:exported="false" />
<activity
android:name=".ui.ProjectEditActivity"
android:exported="false" />
<activity
android:name=".ui.CollaboratorAddActivity"
android:exported="false" />
<activity
android:name=".ui.TaskAddActivity"
android:exported="false" />
<activity
android:name=".ui.TaskEditActivity"
android:exported="false" />
<activity
android:name=".ui.ProjectDetailActivity"
android:exported="false" />

View file

@ -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?
)

View file

@ -40,6 +40,18 @@ interface ApiService {
@GET("projects/{project_id}/users/")
suspend fun getProjectUsers(@Path("project_id") projectId: Int): Response<List<UserBase>>
@POST("projects/{project_id}/users/")
suspend fun addProjectCollaborator(
@Path("project_id") projectId: Int,
@Body email: Map<String, String>
): Response<Unit>
@DELETE("projects/{project_id}/users/{user_id}/")
suspend fun removeProjectCollaborator(
@Path("project_id") projectId: Int,
@Path("user_id") userId: Int
): Response<Unit>
@POST("projects/")
suspend fun createProject(@Body projectCreate: ProjectCreate): Response<ProjectBase>
@ -57,4 +69,17 @@ interface ApiService {
@POST("projects/{project_id}/tasks/")
suspend fun createTask(@Path("project_id") projectId: Int, @Body taskBase: TaskBase): Response<TaskBase>
@PUT("projects/{project_id}/tasks/{task_id}/")
suspend fun updateProjectTask(
@Path("project_id") projectId: Int,
@Path("task_id") taskId: Int,
@Body taskUpdate: TaskUpdate
): Response<Unit>
@DELETE("projects/{project_id}/tasks/{task_id}/")
suspend fun deleteProjectTask(
@Path("project_id") projectId: Int,
@Path("task_id") taskId: Int
): Response<Unit>
}

View file

@ -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()
}
}
}
}

View file

@ -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 {
val projectCreate : ProjectCreate = ProjectCreate(projectName, projectDescription)
try {
Log.d("CreateProjectActivity", "Creating project: $projectName")
val projectCreate = ProjectCreate(projectName, projectDescription)
val response = api.createProject(projectCreate)
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))
if (response.isSuccessful) {
Log.d("CreateProjectActivity", "Project created successfully: ${response.body()}")
Toast.makeText(
this@CreateProjectActivity,
"Project created successfully",
Toast.LENGTH_SHORT
).show()
finish()
// Volver a MainActivity para ver el nuevo proyecto
val intent = Intent(this@CreateProjectActivity, MainActivity::class.java)
startActivity(intent)
} else {
Log.e("CreateProjectActivity", "Error creating project: ${response.code()} - ${response.message()}")
Toast
.makeText(this@CreateProjectActivity, "Error creating project", Toast.LENGTH_SHORT)
.show()
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()
}
}
}
}
}

View file

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

View file

@ -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<User>
) : RecyclerView.Adapter<ProjectCollaboratorAdapter.ViewHolder>() {
fun submitList(newList: List<User>) {
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
}
}
}
}

View file

@ -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<Task> = 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<Task> = 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()
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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<User>,
private val apiService: ApiService,
private val projectId: Int,
private val onCollaboratorRemoved: () -> Unit = {}
) : RecyclerView.Adapter<ProjectCollaboratorAdapter.ViewHolder>() {
private val adapterScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
fun submitList(newList: List<User>) {
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()
}
}
}
}
}
}
}

View file

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

View file

@ -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<Task>,
private val apiService: ApiService,
private val projectId: Int,
private val onTaskUpdated: () -> Unit = {},
private val onEditTask: () -> Unit = {}
) : RecyclerView.Adapter<ProjectTaskAdapter.ViewHolder>() {
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<Task>) {
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
}
}
}
}
}

View file

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.CollaboratorAddActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/returnActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/fab_margin_end"
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:contentDescription="Return"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@android:drawable/ic_menu_revert" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/bottom_padding_for_fab">
<TextView
android:id="@+id/titleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Collaborator"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:padding="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_tiny" />
<TextView
android:id="@+id/collaboratorEmailLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Email Address:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/collaboratorEmailInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter collaborator email"
android:inputType="textEmailAddress"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<Button
android:id="@+id/addCollaboratorButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add Collaborator"
android:backgroundTint="@color/primary_green"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -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" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/bottom_padding_for_fab">
<TextView
android:text="Create a new project..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activityTitle"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
android:padding="12dp"
android:textSize="24dp"
android:layout_marginTop="4dp" />
<MultiAutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="500dp"
android:id="@+id/newProjectDescription"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/newProjectDescriptionLabel"
android:hint="This is my new super-special project that will change the world!!!"
android:textAlignment="textStart"
android:gravity="start|top"
android:padding="12dp" />
<AutoCompleteTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectName"
app:layout_constraintTop_toBottomOf="@+id/newProjectNameLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
android:hint="My new project!"
android:padding="12dp" />
android:padding="@dimen/padding_standard"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_tiny" />
<TextView
android:text="Project name:"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectNameLabel"
tools:layout_editor_absoluteX="0dp"
android:labelFor="@id/activityTitle"
app:layout_constraintTop_toBottomOf="@+id/activityTitle"
android:layout_marginTop="16dp" />
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectName"
android:hint="My new project!"
android:inputType="text"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<TextView
android:text="Project description:"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectDescriptionLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/newProjectName"
android:layout_marginTop="20dp" />
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectDescription"
android:hint="This is my new super-special project that will change the world!!!"
android:inputType="textMultiLine"
android:minLines="3"
android:maxLines="5"
android:gravity="start|top"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<Button
android:text="Create new project"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/newProjectCreateButton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="15dp"
app:layout_constraintTop_toBottomOf="@+id/newProjectDescription"
android:padding="20dp"
android:layout_margin="12dp" />
android:backgroundTint="@color/primary_green"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -35,7 +35,7 @@
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteX="0dp"
app:layout_constraintTop_toBottomOf="@+id/logoutButton"
android:layout_marginTop="8dp"
android:id="@+id/projectsRecyclerView" />

View file

@ -14,71 +14,64 @@
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" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="start|top">
<TextView
android:text="Project Name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/projectTitleText"
android:padding="12dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
android:padding="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_tiny"
android:textSize="24sp" />
<TextView
android:text="Completed: 100%"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/completedPercentageText"
app:layout_constraintTop_toBottomOf="@+id/projectTitleText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
android:paddingRight="12dp"
android:paddingLeft="12dp"
android:layout_marginTop="16dp"
android:textSize="18dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/projectDescriptionText"
app:layout_constraintTop_toBottomOf="@+id/projectDescriptionLabel"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
android:text="Project description"
android:padding="12dp"
android:gravity="start|top" />
<TextView
android:text="Project description:"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/projectDescriptionLabel"
tools:layout_editor_absoluteX="0dp"
app:layout_constraintTop_toBottomOf="@+id/completedPercentageText"
android:layout_marginTop="16dp"
android:paddingLeft="12dp"
android:paddingRight="12dp"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard"
android:labelFor="@id/projectDescriptionText"
android:textSize="18dp" />
android:textSize="@dimen/text_size_title"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/projectDescriptionText"
android:text="Project description"
android:padding="@dimen/padding_standard"
android:gravity="start|top" />
<TextView
android:text="Completed: 100%"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/completedPercentageText"
android:paddingRight="@dimen/padding_standard"
android:paddingLeft="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_medium"
android:textSize="@dimen/text_size_subtitle" />
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/projectDescriptionText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp"
android:layout_marginTop="@dimen/margin_large"
android:id="@+id/taskLinearLayout">
<TextView
@ -86,48 +79,115 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/taskListTitle"
android:paddingLeft="12dp"
android:textSize="18dp" />
android:paddingLeft="@dimen/padding_standard"
android:textSize="@dimen/text_size_title"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/taskListRecycler"
android:padding="8dp"
android:scrollbars="none"
android:isScrollContainer="false" />
<Button
android:text="Add a task..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addTaskButton"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp" />
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:backgroundTint="@color/primary_green" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="0dp"
android:id="@+id/collaboratorsLinearLayout"
android:gravity="top|center_vertical"
app:layout_constraintTop_toBottomOf="@+id/taskLinearLayout"
android:layout_marginTop="24dp">
android:layout_marginTop="@dimen/margin_large">
<TextView
android:text="Collaborators:"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/collaboratorListTitle"
android:paddingLeft="12dp"
android:textSize="18dp" />
android:paddingLeft="@dimen/padding_standard"
android:textSize="@dimen/text_size_title"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/collaboratorListRecycler" />
android:id="@+id/collaboratorListRecycler"
android:padding="8dp"
android:scrollbars="none"
android:isScrollContainer="false" />
<Button
android:text="Add a new collaborator..."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addCollaboratorButton"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp" />
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:backgroundTint="@color/primary_green" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/dangerZoneLinearLayout"
android:layout_marginTop="@dimen/margin_xlarge"
android:paddingBottom="16dp">
<TextView
android:text="Danger Zone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/dangerZoneTitle"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:textColor="@color/danger_red" />
<TextView
android:text="These actions cannot be undone. Proceed with caution."
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/dangerZoneWarning"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_small"
android:textSize="@dimen/text_size_body"
android:textColor="@color/text_secondary" />
<Button
android:text="Edit Project"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editProjectButton"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_medium"
android:backgroundTint="@color/warning_orange" />
<Button
android:text="Delete Project"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/deleteProjectButton"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small"
android:backgroundTint="@color/danger_red" />
</LinearLayout>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.ProjectEditActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/returnActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/fab_margin_end"
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:contentDescription="Return"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@android:drawable/ic_menu_revert" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/bottom_padding_for_fab">
<TextView
android:id="@+id/titleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Edit Project"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:padding="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_tiny" />
<TextView
android:id="@+id/projectNameLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Project Name:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/projectNameInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter project name"
android:inputType="text"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<TextView
android:id="@+id/projectDescriptionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/projectDescriptionInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter project description"
android:inputType="textMultiLine"
android:minLines="3"
android:maxLines="5"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small"
android:gravity="start|top" />
<Button
android:id="@+id/saveProjectButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Changes"
android:backgroundTint="@color/primary_green"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.TaskAddActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/returnActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/fab_margin_end"
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:contentDescription="Return"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@android:drawable/ic_menu_revert" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/bottom_padding_for_fab">
<TextView
android:id="@+id/titleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add New Task"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:padding="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_tiny" />
<TextView
android:id="@+id/taskTitleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/taskTitleInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter task title"
android:inputType="text"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<TextView
android:id="@+id/taskDescriptionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/taskDescriptionInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter task description"
android:inputType="textMultiLine"
android:minLines="3"
android:maxLines="5"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small"
android:gravity="start|top" />
<TextView
android:id="@+id/taskStatusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Status:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<Spinner
android:id="@+id/taskStatusSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/task_status_options"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<Button
android:id="@+id/createTaskButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create Task"
android:backgroundTint="@color/primary_green"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.TaskEditActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/returnActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/fab_margin_end"
android:layout_marginBottom="@dimen/fab_margin_bottom"
android:contentDescription="Return"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:srcCompat="@android:drawable/ic_menu_revert" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/bottom_padding_for_fab">
<TextView
android:id="@+id/titleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Edit Task"
android:textSize="@dimen/text_size_title"
android:textStyle="bold"
android:padding="@dimen/padding_standard"
android:layout_marginTop="@dimen/margin_tiny" />
<TextView
android:id="@+id/taskTitleLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Title:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/taskTitleInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter task title"
android:inputType="text"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<TextView
android:id="@+id/taskDescriptionLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Description:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<EditText
android:id="@+id/taskDescriptionInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter task description"
android:inputType="textMultiLine"
android:minLines="3"
android:maxLines="5"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small"
android:gravity="start|top" />
<TextView
android:id="@+id/taskStatusLabel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Status:"
android:textSize="@dimen/text_size_subtitle"
android:textStyle="bold"
android:layout_marginTop="@dimen/margin_medium"
android:paddingLeft="@dimen/padding_standard"
android:paddingRight="@dimen/padding_standard" />
<Spinner
android:id="@+id/taskStatusSpinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:entries="@array/task_status_options"
android:padding="@dimen/padding_standard"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard"
android:layout_marginTop="@dimen/margin_small" />
<Button
android:id="@+id/saveTaskButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Save Changes"
android:backgroundTint="@color/primary_green"
android:layout_marginTop="@dimen/margin_large"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
<Button
android:id="@+id/deleteTaskButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete Task"
android:backgroundTint="@color/danger_red"
android:layout_marginTop="@dimen/margin_medium"
android:layout_marginLeft="@dimen/margin_standard"
android:layout_marginRight="@dimen/margin_standard" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_horizontal|end"
android:layout_margin="8dp"
android:id="@+id/linearLayout">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="4dp">
<TextView
android:id="@+id/collaboratorNameText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Collaborator Name"
android:textSize="18dp"
android:textStyle="bold" />
<TextView
android:text="example@email.com"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/collaboratorEmailText"
android:textStyle="italic" />
</LinearLayout>
<Button
android:id="@+id/removeCollaboratorButton"
android:layout_width="50dp"
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.Icon"
app:icon="@android:drawable/ic_delete"
android:backgroundTint="@color/transparent_red"
app:iconTint="@color/danger_red"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.333" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start|top"
android:layout_margin="4dp"
android:id="@+id/linearLayout2">
<LinearLayout
android:orientation="vertical"
android:layout_width="331dp"
android:layout_height="50dp"
android:id="@+id/linearLayout3"
tools:layout_editor_absoluteY="4dp"
tools:layout_editor_absoluteX="4dp">
<TextView
android:id="@+id/taskTitleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Task Title"
android:textSize="20dp"
android:textStyle="bold"
android:paddingBottom="4dp" />
<TextView
android:id="@+id/taskDescriptionText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="Task Description"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/taskStatusLinearLayout"
app:layout_constraintTop_toBottomOf="@+id/linearLayout3"
app:layout_constraintStart_toStartOf="parent"
android:layout_margin="4dp">
<TextView
android:text="STATUS:"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/taskStatusLabel"
android:textStyle="bold"
android:textAlignment="textStart"
android:gravity="start|center_vertical" />
<Spinner
android:id="@+id/taskStatusDropdown"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:entries="@array/task_status_options"
android:layout_marginStart="16dp"
android:spinnerMode="dropdown"
android:dropDownWidth="wrap_content" />
</LinearLayout>
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:id="@+id/editTaskButton"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.5"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:icon="@android:drawable/ic_menu_edit"
style="@style/Widget.Material3.Button.IconButton"
app:iconTint="#0C0808" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="task_status_options">
<item>Pending</item>
<item>In progress</item>
<item>Completed</item>
<item>Stashed</item>
<item>Failed</item>
</string-array>
</resources>

View file

@ -2,4 +2,15 @@
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- Primary Colors -->
<color name="primary_green">#469E29</color>
<color name="danger_red">#FF0000</color>
<color name="warning_orange">#FF9800</color>
<!-- Text Colors -->
<color name="text_secondary">#666666</color>
<!-- Background Colors -->
<color name="transparent_red">#24FF5757</color>
</resources>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Paddings -->
<dimen name="padding_standard">12dp</dimen>
<dimen name="padding_small">8dp</dimen>
<!-- Margins -->
<dimen name="margin_standard">12dp</dimen>
<dimen name="margin_medium">16dp</dimen>
<dimen name="margin_large">24dp</dimen>
<dimen name="margin_xlarge">32dp</dimen>
<dimen name="margin_small">8dp</dimen>
<dimen name="margin_tiny">4dp</dimen>
<!-- FAB Margins -->
<dimen name="fab_margin_bottom">17dp</dimen>
<dimen name="fab_margin_end">18dp</dimen>
<!-- Bottom Padding for FAB -->
<dimen name="bottom_padding_for_fab">80dp</dimen>
<!-- Text Sizes -->
<dimen name="text_size_title">24sp</dimen>
<dimen name="text_size_subtitle">18sp</dimen>
<dimen name="text_size_body">14sp</dimen>
</resources>