diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f16c8c5..e73a3ec 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,6 +14,9 @@
android:supportsRtl="true"
android:theme="@style/Theme.GithubUser"
tools:targetApi="31">
+
diff --git a/app/src/main/java/com/aurelioklv/githubuser/data/response/UserResponse.kt b/app/src/main/java/com/aurelioklv/githubuser/data/response/UserResponse.kt
index 40f955f..2b2b014 100644
--- a/app/src/main/java/com/aurelioklv/githubuser/data/response/UserResponse.kt
+++ b/app/src/main/java/com/aurelioklv/githubuser/data/response/UserResponse.kt
@@ -4,99 +4,99 @@ import com.google.gson.annotations.SerializedName
data class UserResponse(
- @field:SerializedName("gists_url")
- val gistsUrl: String,
+ @field:SerializedName("gists_url")
+ val gistsUrl: String,
- @field:SerializedName("repos_url")
- val reposUrl: String,
+ @field:SerializedName("repos_url")
+ val reposUrl: String,
- @field:SerializedName("following_url")
- val followingUrl: String,
+ @field:SerializedName("following_url")
+ val followingUrl: String,
- @field:SerializedName("twitter_username")
- val twitterUsername: String,
+ @field:SerializedName("twitter_username")
+ val twitterUsername: String,
- @field:SerializedName("bio")
- val bio: Any,
+ @field:SerializedName("bio")
+ val bio: String?,
- @field:SerializedName("created_at")
- val createdAt: String,
+ @field:SerializedName("created_at")
+ val createdAt: String,
- @field:SerializedName("login")
- val login: String,
+ @field:SerializedName("login")
+ val login: String,
- @field:SerializedName("type")
- val type: String,
+ @field:SerializedName("type")
+ val type: String,
- @field:SerializedName("blog")
- val blog: String,
+ @field:SerializedName("blog")
+ val blog: String,
- @field:SerializedName("subscriptions_url")
- val subscriptionsUrl: String,
+ @field:SerializedName("subscriptions_url")
+ val subscriptionsUrl: String,
- @field:SerializedName("updated_at")
- val updatedAt: String,
+ @field:SerializedName("updated_at")
+ val updatedAt: String,
- @field:SerializedName("site_admin")
- val siteAdmin: Boolean,
+ @field:SerializedName("site_admin")
+ val siteAdmin: Boolean,
- @field:SerializedName("company")
- val company: Any,
+ @field:SerializedName("company")
+ val company: String,
- @field:SerializedName("id")
- val id: Int,
+ @field:SerializedName("id")
+ val id: Int,
- @field:SerializedName("public_repos")
- val publicRepos: Int,
+ @field:SerializedName("public_repos")
+ val publicRepos: Int,
- @field:SerializedName("gravatar_id")
- val gravatarId: String,
+ @field:SerializedName("gravatar_id")
+ val gravatarId: String,
- @field:SerializedName("email")
- val email: Any,
+ @field:SerializedName("email")
+ val email: String,
- @field:SerializedName("organizations_url")
- val organizationsUrl: String,
+ @field:SerializedName("organizations_url")
+ val organizationsUrl: String,
- @field:SerializedName("hireable")
- val hireable: Any,
+ @field:SerializedName("hireable")
+ val hireable: Any,
- @field:SerializedName("starred_url")
- val starredUrl: String,
+ @field:SerializedName("starred_url")
+ val starredUrl: String,
- @field:SerializedName("followers_url")
- val followersUrl: String,
+ @field:SerializedName("followers_url")
+ val followersUrl: String,
- @field:SerializedName("public_gists")
- val publicGists: Int,
+ @field:SerializedName("public_gists")
+ val publicGists: Int,
- @field:SerializedName("url")
- val url: String,
+ @field:SerializedName("url")
+ val url: String,
- @field:SerializedName("received_events_url")
- val receivedEventsUrl: String,
+ @field:SerializedName("received_events_url")
+ val receivedEventsUrl: String,
- @field:SerializedName("followers")
- val followers: Int,
+ @field:SerializedName("followers")
+ val followers: Int,
- @field:SerializedName("avatar_url")
- val avatarUrl: String,
+ @field:SerializedName("avatar_url")
+ val avatarUrl: String,
- @field:SerializedName("events_url")
- val eventsUrl: String,
+ @field:SerializedName("events_url")
+ val eventsUrl: String,
- @field:SerializedName("html_url")
- val htmlUrl: String,
+ @field:SerializedName("html_url")
+ val htmlUrl: String,
- @field:SerializedName("following")
- val following: Int,
+ @field:SerializedName("following")
+ val following: Int,
- @field:SerializedName("name")
- val name: String,
+ @field:SerializedName("name")
+ val name: String? = null,
- @field:SerializedName("location")
- val location: Any,
+ @field:SerializedName("location")
+ val location: String,
- @field:SerializedName("node_id")
- val nodeId: String
+ @field:SerializedName("node_id")
+ val nodeId: String,
)
diff --git a/app/src/main/java/com/aurelioklv/githubuser/ui/Util.kt b/app/src/main/java/com/aurelioklv/githubuser/ui/Util.kt
new file mode 100644
index 0000000..0358446
--- /dev/null
+++ b/app/src/main/java/com/aurelioklv/githubuser/ui/Util.kt
@@ -0,0 +1,10 @@
+package com.aurelioklv.githubuser.ui
+
+fun formatCount(count: Long): String {
+ return when {
+ count < 1000L -> count.toString()
+ count < 100000L -> String.format("%.1fk", count / 1000.0)
+ count < 1000000L -> String.format("%dk", count / 1000L)
+ else -> String.format("%.1fM", count / 1000000L)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsActivity.kt b/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsActivity.kt
new file mode 100644
index 0000000..2777970
--- /dev/null
+++ b/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsActivity.kt
@@ -0,0 +1,97 @@
+package com.aurelioklv.githubuser.ui.details
+
+import android.os.Bundle
+import android.util.Log
+import android.view.View
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import com.aurelioklv.githubuser.R
+import com.aurelioklv.githubuser.data.response.UserResponse
+import com.aurelioklv.githubuser.databinding.ActivityDetailsBinding
+import com.aurelioklv.githubuser.ui.formatCount
+import com.bumptech.glide.Glide
+
+class DetailsActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityDetailsBinding
+ private val viewModel: DetailsViewModel by viewModels()
+
+ private var username: String? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ enableEdgeToEdge()
+
+ binding = ActivityDetailsBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+ 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
+ }
+
+ if (intent != null && intent.hasExtra(EXTRA_USERNAME)) {
+ username = intent.getStringExtra(EXTRA_USERNAME)
+ viewModel.getUserDetails(username!!)
+ }
+
+ viewModel.user.observe(this) {
+ setUserDetails(it)
+ }
+
+ viewModel.isLoading.observe(this) {
+ showLoading(it)
+ }
+ }
+
+ private fun setUserDetails(userResponse: UserResponse) {
+ with(userResponse) {
+ Glide.with(this@DetailsActivity)
+ .load(avatarUrl)
+ .into(binding.ivUserAvatar)
+ Log.i(TAG, "name: $name\nusername: $login")
+ if (name.isNullOrEmpty() || name.isBlank()) {
+ binding.tvName.visibility = View.INVISIBLE
+
+ val usernameLayoutParams =
+ binding.tvUsername.layoutParams as ConstraintLayout.LayoutParams
+ usernameLayoutParams.topToTop = binding.ivUserAvatar.id
+ usernameLayoutParams.bottomToBottom = binding.ivUserAvatar.id
+ } else {
+ binding.tvName.text = name
+ }
+ binding.tvUsername.text = userResponse.login
+
+ if (bio.isNullOrEmpty() || bio.isBlank()) {
+ binding.tvBio.visibility = View.INVISIBLE
+
+ val followersLayoutParams =
+ binding.tvFollowers.layoutParams as ConstraintLayout.LayoutParams
+ followersLayoutParams.topToBottom = binding.ivUserAvatar.id
+ } else {
+ binding.tvBio.text = bio
+ }
+
+ Log.i(TAG, "followers: $followers\nfollowing: $following")
+ binding.tvFollowers.text =
+ resources.getQuantityString(
+ R.plurals.followers,
+ followers,
+ formatCount(followers.toLong())
+ )
+ binding.tvFollowing.text = getString(R.string.following, following)
+ }
+ }
+
+ private fun showLoading(isLoading: Boolean) {
+ binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
+ }
+
+ companion object {
+ const val TAG = "DetailsActivity"
+ const val EXTRA_USERNAME = "username"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsViewModel.kt b/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsViewModel.kt
new file mode 100644
index 0000000..0c0035d
--- /dev/null
+++ b/app/src/main/java/com/aurelioklv/githubuser/ui/details/DetailsViewModel.kt
@@ -0,0 +1,45 @@
+package com.aurelioklv.githubuser.ui.details
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.aurelioklv.githubuser.data.api.ApiConfig
+import com.aurelioklv.githubuser.data.response.UserResponse
+import retrofit2.Call
+import retrofit2.Callback
+import retrofit2.Response
+
+class DetailsViewModel : ViewModel() {
+ private val _user = MutableLiveData()
+ val user: LiveData = _user
+
+ private val _isLoading = MutableLiveData()
+ val isLoading: LiveData = _isLoading
+
+ fun getUserDetails(username: String) {
+ _isLoading.value = true
+ val client = ApiConfig.getApiService().getUserDetails(username)
+ client.enqueue(object : Callback {
+ override fun onResponse(call: Call, response: Response) {
+ _isLoading.value = false
+ if (response.isSuccessful) {
+ val responseBody = response.body()
+ if (responseBody != null) {
+ _user.value = responseBody
+ }
+ } else {
+ Log.e(TAG, "onResponse !isSuccessFul: $response")
+ }
+ }
+
+ override fun onFailure(call: Call, t: Throwable) {
+ Log.e(TAG, "onFailure: ${t.message}")
+ }
+ })
+ }
+
+ companion object {
+ const val TAG = "DetailsViewModel"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/aurelioklv/githubuser/ui/main/MainActivity.kt b/app/src/main/java/com/aurelioklv/githubuser/ui/main/MainActivity.kt
index 2cb5a72..ff046d9 100644
--- a/app/src/main/java/com/aurelioklv/githubuser/ui/main/MainActivity.kt
+++ b/app/src/main/java/com/aurelioklv/githubuser/ui/main/MainActivity.kt
@@ -41,6 +41,16 @@ class MainActivity : AppCompatActivity() {
viewModel.isLoading.observe(this) {
showLoading(it)
}
+
+ with(binding) {
+ searchView.setupWithSearchBar(searchBar)
+ searchView.editText.setOnEditorActionListener { v, actionId, event ->
+ searchBar.setText(searchView.text)
+ searchView.hide()
+ viewModel.searchUser(searchView.text.toString())
+ false
+ }
+ }
}
private fun showLoading(isLoading: Boolean) {
diff --git a/app/src/main/java/com/aurelioklv/githubuser/ui/main/UserSearchAdapter.kt b/app/src/main/java/com/aurelioklv/githubuser/ui/main/UserSearchAdapter.kt
index 9f6d5a6..f3f8d15 100644
--- a/app/src/main/java/com/aurelioklv/githubuser/ui/main/UserSearchAdapter.kt
+++ b/app/src/main/java/com/aurelioklv/githubuser/ui/main/UserSearchAdapter.kt
@@ -1,5 +1,6 @@
package com.aurelioklv.githubuser.ui.main
+import android.content.Intent
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
@@ -7,6 +8,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.aurelioklv.githubuser.data.response.UserSearchItem
import com.aurelioklv.githubuser.databinding.UserSearchItemBinding
+import com.aurelioklv.githubuser.ui.details.DetailsActivity
import com.bumptech.glide.Glide
class UserSearchAdapter :
@@ -30,6 +32,12 @@ class UserSearchAdapter :
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val userSearchItem = getItem(position)
holder.bind(userSearchItem)
+
+ holder.itemView.setOnClickListener {
+ val intent = Intent(holder.itemView.context, DetailsActivity::class.java)
+ intent.putExtra(DetailsActivity.EXTRA_USERNAME, userSearchItem.login)
+ holder.itemView.context.startActivity(intent)
+ }
}
companion object {
diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml
new file mode 100644
index 0000000..b17c8b9
--- /dev/null
+++ b/app/src/main/res/layout/activity_details.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b133ba5..05898e3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,4 +2,15 @@
GithubUser
Search user
username
+ Followers
+ Following
+ full name
+
+ - %s follower
+ - %s follower
+ - %s followers
+
+ %d following
+ follower
+ bio
\ No newline at end of file