Compare commits

..

5 commits

15 changed files with 431 additions and 34 deletions

View file

@ -51,6 +51,7 @@ dependencies {
implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.auth0.android:jwtdecode:2.0.1") implementation("com.auth0.android:jwtdecode:2.0.1")
implementation(libs.androidx.recyclerview)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)

View file

@ -16,10 +16,16 @@
android:theme="@style/Theme.KanbanCloneAndroid" android:theme="@style/Theme.KanbanCloneAndroid"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:name="com.campusaula.edbole.kanban_clone_android.LoginActivity" android:name=".ui.CreateProjectActivity"
android:exported="false" /> android:exported="false" />
<activity <activity
android:name="com.campusaula.edbole.kanban_clone_android.MainActivity" android:name=".ui.ProjectDetailActivity"
android:exported="false" />
<activity
android:name=".ui.LoginActivity"
android:exported="false" />
<activity
android:name=".ui.MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View file

@ -8,6 +8,11 @@ class Project{
val description: String = "" val description: String = ""
val users: List<User> = emptyList() val users: List<User> = emptyList()
val tasks: List<Task> = emptyList() val tasks: List<Task> = emptyList()
override fun toString(): String {
return "Project(id=$id, name='$name', description='$description', users=$users, tasks=$tasks)"
}
} }
data class ProjectBase( data class ProjectBase(

View file

@ -11,7 +11,7 @@ interface ApiService {
@POST("auth/login/") @POST("auth/login/")
suspend fun login(@Body userLogin: UserLogin): Response<LoginResponse> suspend fun login(@Body userLogin: UserLogin): Response<LoginResponse>
@POST("me/logout/") @GET("me/logout/")
suspend fun logout(): Response<Unit> suspend fun logout(): Response<Unit>
@DELETE("me/delete-me/") @DELETE("me/delete-me/")

View file

@ -1,16 +1,17 @@
package com.campusaula.edbole.kanban_clone_android package com.campusaula.edbole.kanban_clone_android.ui
import android.os.Bundle import android.os.Bundle
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.campusaula.edbole.kanban_clone_android.R
class MainActivity : AppCompatActivity() { class CreateProjectActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
setContentView(R.layout.activity_main) setContentView(R.layout.activity_create_project)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)

View file

@ -1,25 +1,31 @@
package com.campusaula.edbole.kanban_clone_android package com.campusaula.edbole.kanban_clone_android.ui
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatButton
import androidx.appcompat.widget.AppCompatEditText
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.lifecycleScope 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.ApiService
import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance import com.campusaula.edbole.kanban_clone_android.network.RetrofitInstance
import com.campusaula.edbole.kanban_clone_android.kanban.ErrorResponse import com.campusaula.edbole.kanban_clone_android.kanban.ErrorResponse
import com.campusaula.edbole.kanban_clone_android.kanban.UserLogin
import com.google.gson.Gson import com.google.gson.Gson
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import retrofit2.Retrofit import retrofit2.Retrofit
class LoginActivity : AppCompatActivity() { class LoginActivity : AppCompatActivity() {
private lateinit var emailInput : androidx.appcompat.widget.AppCompatEditText private lateinit var emailInput : AppCompatEditText
private lateinit var passwordInput : androidx.appcompat.widget.AppCompatEditText private lateinit var passwordInput : AppCompatEditText
private lateinit var loginButton : androidx.appcompat.widget.AppCompatButton private lateinit var loginButton : AppCompatButton
private lateinit var logonButton : androidx.appcompat.widget.AppCompatButton private lateinit var logonButton : AppCompatButton
private lateinit var retrofit : Retrofit private lateinit var retrofit : Retrofit
private lateinit var api : ApiService private lateinit var api : ApiService
@ -47,14 +53,14 @@ class LoginActivity : AppCompatActivity() {
val password = passwordInput.text.toString() val password = passwordInput.text.toString()
if (email.isEmpty() && password.isEmpty()) { if (email.isEmpty() && password.isEmpty()) {
android.widget.Toast.makeText(this, "Please enter email and password", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this, "Please enter email and password", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
} }
lifecycleScope.launch{ lifecycleScope.launch{
try { try {
val loginResponse = api.login( val loginResponse = api.login(
com.campusaula.edbole.kanban_clone_android.kanban.UserLogin( UserLogin(
email = email, email = email,
password = password password = password
) )
@ -67,10 +73,14 @@ class LoginActivity : AppCompatActivity() {
// Después del login exitoso OkHttp/CookieJar habrá guardado las cookies. // Después del login exitoso OkHttp/CookieJar habrá guardado las cookies.
val authValue = RetrofitInstance.getAuthCookieForUrl(baseUrl) val authValue = RetrofitInstance.getAuthCookieForUrl(baseUrl)
if (authValue != null) { if (authValue != null) {
android.widget.Toast.makeText(this@LoginActivity, "Auth cookie guardada", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this@LoginActivity, "Auth cookie guardada", Toast.LENGTH_SHORT).show()
} else { } else {
android.widget.Toast.makeText(this@LoginActivity, "Login OK pero no se encontró cookie de auth", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this@LoginActivity, "Login OK pero no se encontró cookie de auth", Toast.LENGTH_SHORT).show()
} }
// Navegar a MainActivity
val intent = Intent(this@LoginActivity, MainActivity::class.java)
startActivity(intent)
finish()
} else { } else {
if (loginResponse.code() == 401) { if (loginResponse.code() == 401) {
// parse error body if possible // parse error body if possible
@ -85,14 +95,14 @@ class LoginActivity : AppCompatActivity() {
// clear stored cookies for base host // clear stored cookies for base host
RetrofitInstance.clearCookiesForHost(baseHost) RetrofitInstance.clearCookiesForHost(baseHost)
android.widget.Toast.makeText(this@LoginActivity, "Login failed (401): $errMsg", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this@LoginActivity, "Login failed (401): $errMsg", Toast.LENGTH_SHORT).show()
} else { } else {
android.widget.Toast.makeText(this@LoginActivity, "Login failed: ${loginResponse.code()}", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this@LoginActivity, "Login failed: ${loginResponse.code()}", Toast.LENGTH_SHORT).show()
} }
} }
} catch (ex: Exception){ } catch (ex: Exception){
android.widget.Toast.makeText(this@LoginActivity, "Login failed: ${ex.message}", android.widget.Toast.LENGTH_SHORT).show() Toast.makeText(this@LoginActivity, "Login failed: ${ex.message}", Toast.LENGTH_SHORT).show()
} }
} }

View file

@ -0,0 +1,101 @@
package com.campusaula.edbole.kanban_clone_android.ui
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
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 androidx.recyclerview.widget.RecyclerView
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.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var api: ApiService
private lateinit var projectList : List<Project>
private lateinit var loggedInAs: TextView
private lateinit var logoutButton: Button
private lateinit var addProjectActionButton: FloatingActionButton
private lateinit var projectsRecyclerView: RecyclerView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
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)
projectList = emptyList()
/* Activity components */
loggedInAs = findViewById(R.id.loggedInAs)
logoutButton = findViewById(R.id.logoutButton)
addProjectActionButton = findViewById(R.id.addProjectActionButton)
projectsRecyclerView = findViewById(R.id.projectsRecyclerView)
projectsRecyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this)
val adapter = ProjectItemAdapter(projectList) { project ->
val intent = Intent(this, ProjectDetailActivity::class.java)
intent.putExtra("project_id", project.id)
startActivity(intent)
}
projectsRecyclerView.adapter = adapter
addProjectActionButton.setOnClickListener {
val intent = Intent(this, CreateProjectActivity::class.java)
startActivity(intent)
}
/* Getting the logged-in user info */
lifecycleScope.launch{
val getMe = api.getMe()
if (getMe.isSuccessful){
val user = getMe.body()
loggedInAs.text = "Logged in as: ${user?.name}"
projectList = api.getAllProjects().body()!!
adapter.submitList(projectList)
} else {
val intent = Intent(this@MainActivity, LoginActivity::class.java)
startActivity(intent)
}
}
logoutButton.setOnClickListener {
lifecycleScope.launch {
val logoutResponse = api.logout()
if (logoutResponse.isSuccessful) {
// Clear cookies for the API host
RetrofitInstance.clearCookiesForHost(
"10.0.2.2:8000"
)
// Navigate back to the login screen
val intent =
Intent(this@MainActivity, LoginActivity::class.java)
startActivity(intent)
finish() // Optional: close the MainActivity so it's removed from the back stack
} else {
Toast.makeText(
this@MainActivity,
"Logout failed",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}

View file

@ -0,0 +1,92 @@
package com.campusaula.edbole.kanban_clone_android.ui
import android.health.connect.datatypes.units.Percentage
import android.os.Bundle
import android.util.Log
import android.widget.TextView
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.Project
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.google.android.material.floatingactionbutton.FloatingActionButton
import kotlinx.coroutines.launch
class ProjectDetailActivity : AppCompatActivity() {
private lateinit var api: ApiService
private lateinit var projectTitleText : TextView
private lateinit var projectDescriptionText : TextView
private lateinit var completedPercentageText: TextView
private lateinit var returnActionButton: FloatingActionButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_project_detail)
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)
projectTitleText = findViewById(R.id.projectTitleText)
projectDescriptionText = findViewById(R.id.projectDescriptionText)
completedPercentageText = findViewById(R.id.completedPercentageText)
returnActionButton = findViewById(R.id.returnActionButton)
returnActionButton.setOnClickListener { finish() }
val projectId : Int = intent.getIntExtra("project_id", -1)
if (projectId > 0) {
Log.d("ProjectDetailActivity", "Received project ID: $projectId")
lifecycleScope.launch {
try {
val projectResponse = api.getProjectById(projectId)
if (projectResponse.isSuccessful && projectResponse.body() != null) {
Log.d("ProjectDetailActivity", "Fetched project: ${projectResponse.body()!!.name}")
val project = projectResponse.body()!!
Log.d("ProjectDetailActivity", "Displaying project details for: $project")
projectTitleText.text = project.name
projectDescriptionText.text = project.description
var percentageFinished = 0.0;
val tasks: List<Task> = project.tasks
val totalTasks: Int = tasks.size
val perTaskPercentage = if (totalTasks > 0) (1.0 / totalTasks)*100 else 0.0
for (task in tasks) {
if (task.status == TaskStatus.COMPLETED) {
percentageFinished += perTaskPercentage
}
}
completedPercentageText.text = "Completed: ${"%.2f".format(percentageFinished * 100)}%"
} else {
Log.e("ProjectDetailActivity", "Failed to fetch project: ${projectResponse.code()} - ${projectResponse.message()}")
finish()
}
} catch (e: Exception) {
Log.e("ProjectDetailActivity", "Error fetching project", e)
finish()
}
}
} else {
Log.e("ProjectDetailActivity", "No project ID found in intent")
finish()
}
}
}

View file

@ -0,0 +1,44 @@
package com.campusaula.edbole.kanban_clone_android.ui
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.campusaula.edbole.kanban_clone_android.R
import com.campusaula.edbole.kanban_clone_android.kanban.Project
class ProjectItemAdapter(
private var items: List<Project>,
private val onItemClick: ((Project) -> Unit)? = null
) : RecyclerView.Adapter<ProjectItemAdapter.ViewHolder>() {
fun submitList(newList: List<Project>) {
items = newList
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_project, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val project = items[position]
holder.bind(project)
holder.itemView.setOnClickListener { onItemClick?.invoke(project) }
}
override fun getItemCount(): Int = items.size
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val nameTv: TextView = itemView.findViewById(R.id.projectName)
private val descTv: TextView = itemView.findViewById(R.id.projectDescription)
fun bind(project: Project) {
nameTv.text = project.id.toString() + " " + project.name
descTv.text = project.description
}
}
}

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.CreateProjectActivity">
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -5,7 +5,7 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.campusaula.edbole.kanban_clone_android.LoginActivity"> tools:context="com.campusaula.edbole.kanban_clone_android.ui.LoginActivity">
<EditText <EditText
android:layout_width="350dp" android:layout_width="350dp"

View file

@ -5,15 +5,50 @@
android:id="@+id/main" android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context="com.campusaula.edbole.kanban_clone_android.MainActivity"> tools:context="com.campusaula.edbole.kanban_clone_android.ui.MainActivity">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Hello World!" android:text="Logged in as: "
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
android:id="@+id/loggedInAs"
android:textAlignment="textStart"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp" />
<Button
android:text="Not you? Log out here..."
android:layout_width="wrap_content"
android:layout_height="33dp"
android:id="@+id/logoutButton"
app:layout_constraintTop_toBottomOf="@+id/loggedInAs"
app:layout_constraintStart_toStartOf="@+id/loggedInAs"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:textSize="8sp" />
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:layout_editor_absoluteX="1dp"
app:layout_constraintTop_toBottomOf="@+id/logoutButton"
android:layout_marginTop="8dp"
android:id="@+id/projectsRecyclerView" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
app:srcCompat="@android:drawable/ic_input_add"
android:id="@+id/addProjectActionButton"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.ProjectDetailActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
app:srcCompat="@android:drawable/ic_menu_revert"
android:id="@+id/returnActionButton"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintEnd_toEndOf="parent" />
<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: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.5"
app:layout_constraintEnd_toEndOf="parent"
android:paddingRight="12dp"
android:paddingLeft="12dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/projectDescriptionText"
app:layout_constraintTop_toBottomOf="@+id/completedPercentageText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintEnd_toEndOf="parent"
android:text="Project description"
android:padding="12dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,34 @@
<?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:padding="8dp"
android:layout_margin="8dp">
<TextView
android:id="@+id/projectName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Project name"
android:textStyle="bold"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/projectDescription"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Project description"
android:textSize="14sp"
android:ellipsize="end"
android:maxLines="2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/projectName"
android:layout_marginTop="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -10,6 +10,7 @@ activity = "1.12.2"
constraintlayout = "2.2.1" constraintlayout = "2.2.1"
okhttp = "4.11.0" okhttp = "4.11.0"
retrofit = "2.9.0" retrofit = "2.9.0"
recyclerview = "1.4.0"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -24,6 +25,7 @@ okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhtt
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" } okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
androidx-recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }