mirror of
https://github.com/hmalik144/Candy_Space_tech_test.git
synced 2025-12-10 03:05:27 +00:00
View binding completed for both fragments and item view layout.
Data populated in recycler view completed. Back button navigation completed. Title for fragments completed. Displaying of profile images with Picasso added.
This commit is contained in:
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ dependencies {
|
|||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0'
|
||||||
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||||
@@ -69,4 +70,5 @@ dependencies {
|
|||||||
|
|
||||||
implementation "androidx.preference:preference-ktx:1.1.0"
|
implementation "androidx.preference:preference-ktx:1.1.0"
|
||||||
|
|
||||||
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
package="com.example.h_mal.candyspace">
|
package="com.example.h_mal.candyspace">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".AppClass"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
@@ -1,14 +1,30 @@
|
|||||||
package com.example.h_mal.candyspace
|
package com.example.h_mal.candyspace
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import com.example.h_mal.candyspace.data.api.ApiClass
|
||||||
|
import com.example.h_mal.candyspace.data.api.NetworkConnectionInterceptor
|
||||||
|
import com.example.h_mal.candyspace.data.api.QueryParamsInterceptor
|
||||||
|
import com.example.h_mal.candyspace.data.repositories.Repository
|
||||||
|
import com.example.h_mal.candyspace.ui.main.MainViewModelFactory
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
|
import org.kodein.di.generic.bind
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
import org.kodein.di.generic.provider
|
||||||
|
import org.kodein.di.generic.singleton
|
||||||
|
|
||||||
class AppClass : Application(), KodeinAware{
|
class AppClass : Application(), KodeinAware{
|
||||||
|
|
||||||
override val kodein = Kodein.lazy {
|
override val kodein = Kodein.lazy {
|
||||||
import(androidXModule(this@AppClass))
|
import(androidXModule(this@AppClass))
|
||||||
|
|
||||||
|
bind() from singleton { NetworkConnectionInterceptor(instance()) }
|
||||||
|
bind() from singleton { QueryParamsInterceptor() }
|
||||||
|
bind() from singleton { ApiClass(instance(), instance()) }
|
||||||
|
// bind() from singleton { PreferenceProvider(instance()) }
|
||||||
|
bind() from singleton { Repository(instance()) }
|
||||||
|
bind() from provider { MainViewModelFactory(instance()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ import retrofit2.http.Query
|
|||||||
interface ApiClass {
|
interface ApiClass {
|
||||||
|
|
||||||
@GET("users?")
|
@GET("users?")
|
||||||
suspend fun getUsersFromApi(@Query("inname") inname: String): Response<User>
|
suspend fun getUsersFromApi(@Query("inname") inname: String): Response<ApiResponse>
|
||||||
|
|
||||||
companion object{
|
companion object{
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.example.h_mal.candyspace.data.api
|
||||||
|
|
||||||
|
data class ApiResponse(
|
||||||
|
val items : List<User>?,
|
||||||
|
val has_more : Boolean?,
|
||||||
|
val quota_max : Int?,
|
||||||
|
val quota_Remaining: Int?
|
||||||
|
)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.example.h_mal.candyspace.data.api
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
class BadgeCounts {
|
||||||
|
@SerializedName("bronze")
|
||||||
|
@Expose
|
||||||
|
var bronze: Int? = null
|
||||||
|
@SerializedName("silver")
|
||||||
|
@Expose
|
||||||
|
var silver: Int? = null
|
||||||
|
@SerializedName("gold")
|
||||||
|
@Expose
|
||||||
|
var gold: Int? = null
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import java.io.IOException
|
|||||||
|
|
||||||
abstract class ResponseUnwrap {
|
abstract class ResponseUnwrap {
|
||||||
|
|
||||||
suspend fun<T: Any> apiRequest(call: suspend () -> Response<T>) : T{
|
suspend fun<T: Any> responseUnwrap(call: suspend () -> Response<T>) : T{
|
||||||
|
|
||||||
val response = call.invoke()
|
val response = call.invoke()
|
||||||
if(response.isSuccessful){
|
if(response.isSuccessful){
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
|
|
||||||
class User {
|
class User {
|
||||||
|
@SerializedName("badge_counts")
|
||||||
|
@Expose
|
||||||
|
var badgeCounts: BadgeCounts? = null
|
||||||
@SerializedName("last_modified_date")
|
@SerializedName("last_modified_date")
|
||||||
@Expose
|
@Expose
|
||||||
var lastModifiedDate: Int? = null
|
var lastModifiedDate: Int? = null
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.example.h_mal.candyspace.data.repositories
|
package com.example.h_mal.candyspace.data.repositories
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
import com.example.h_mal.candyspace.data.api.ApiClass
|
import com.example.h_mal.candyspace.data.api.ApiClass
|
||||||
|
import com.example.h_mal.candyspace.data.api.ApiResponse
|
||||||
import com.example.h_mal.candyspace.data.api.ResponseUnwrap
|
import com.example.h_mal.candyspace.data.api.ResponseUnwrap
|
||||||
import com.example.h_mal.candyspace.data.api.User
|
import com.example.h_mal.candyspace.data.api.User
|
||||||
|
|
||||||
@@ -8,7 +10,7 @@ class Repository(
|
|||||||
private val api: ApiClass
|
private val api: ApiClass
|
||||||
): ResponseUnwrap() {
|
): ResponseUnwrap() {
|
||||||
|
|
||||||
suspend fun getUsers(username: String): User {
|
suspend fun getUsers(username: String): ApiResponse {
|
||||||
return apiRequest { api.getUsersFromApi(username) }
|
return responseUnwrap { api.getUsersFromApi(username) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,87 @@ package com.example.h_mal.candyspace.ui
|
|||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.example.h_mal.candyspace.R
|
import com.example.h_mal.candyspace.R
|
||||||
|
import com.example.h_mal.candyspace.ui.main.CompletionListener
|
||||||
import com.example.h_mal.candyspace.ui.main.MainFragment
|
import com.example.h_mal.candyspace.ui.main.MainFragment
|
||||||
|
import com.example.h_mal.candyspace.ui.main.MainViewModel
|
||||||
|
import com.example.h_mal.candyspace.ui.main.MainViewModelFactory
|
||||||
|
import com.example.h_mal.candyspace.utils.displayToast
|
||||||
|
import com.example.h_mal.candyspace.utils.hide
|
||||||
|
import com.example.h_mal.candyspace.utils.show
|
||||||
|
import kotlinx.android.synthetic.main.main_activity.*
|
||||||
|
import org.kodein.di.Kodein
|
||||||
|
import org.kodein.di.KodeinAware
|
||||||
|
import org.kodein.di.android.kodein
|
||||||
|
import org.kodein.di.generic.instance
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity(), KodeinAware, CompletionListener {
|
||||||
|
|
||||||
|
//retrieve the viewmodel factory from the kodein dependency injection
|
||||||
|
override val kodein by kodein()
|
||||||
|
private val factory : MainViewModelFactory by instance()
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
//View model to be used by the fragments hosted by MainActivity
|
||||||
|
lateinit var viewModel: MainViewModel
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.main_activity)
|
setContentView(R.layout.main_activity)
|
||||||
|
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true);
|
||||||
|
supportActionBar?.setDisplayShowHomeEnabled(true);
|
||||||
|
|
||||||
|
//retrieve viewmodel from viewmodel factory
|
||||||
|
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
|
||||||
|
viewModel.completionListener = this
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
supportFragmentManager.beginTransaction()
|
supportFragmentManager.beginTransaction()
|
||||||
.replace(R.id.container, MainFragment.newInstance())
|
.replace(R.id.container, MainFragment())
|
||||||
.commitNow()
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFragmentManager.addOnBackStackChangedListener {
|
||||||
|
val name = when (supportFragmentManager.fragments[0]::class.java.simpleName) {
|
||||||
|
"UserProfileFragment" -> "User"
|
||||||
|
else -> "Candy space"
|
||||||
|
}
|
||||||
|
title = name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if(supportFragmentManager.backStackEntryCount > 0){
|
||||||
|
supportFragmentManager.popBackStack()
|
||||||
|
}else{
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
when (item.itemId) {
|
||||||
|
android.R.id.home -> onBackPressed()
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStarted() {
|
||||||
|
progress_circular.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess() {
|
||||||
|
progress_circular.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(message: String) {
|
||||||
|
progress_circular.hide()
|
||||||
|
displayToast(message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.example.h_mal.candyspace.ui.main
|
||||||
|
|
||||||
|
import com.example.h_mal.candyspace.R
|
||||||
|
import com.example.h_mal.candyspace.data.api.User
|
||||||
|
import com.example.h_mal.candyspace.databinding.ListItemLayoutBinding
|
||||||
|
import com.xwray.groupie.databinding.BindableItem
|
||||||
|
|
||||||
|
class ListItemViewModel (
|
||||||
|
val user: User
|
||||||
|
): BindableItem<ListItemLayoutBinding>(){
|
||||||
|
override fun getLayout(): Int = R.layout.list_item_layout
|
||||||
|
|
||||||
|
override fun bind(viewBinding: ListItemLayoutBinding, position: Int) {
|
||||||
|
viewBinding.user = user
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,42 +1,40 @@
|
|||||||
package com.example.h_mal.candyspace.ui.main
|
package com.example.h_mal.candyspace.ui.main
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.AdapterView
|
||||||
import androidx.databinding.DataBindingUtil
|
import androidx.databinding.DataBindingUtil
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.example.h_mal.candyspace.R
|
import com.example.h_mal.candyspace.R
|
||||||
|
import com.example.h_mal.candyspace.data.api.User
|
||||||
import com.example.h_mal.candyspace.databinding.MainFragmentBinding
|
import com.example.h_mal.candyspace.databinding.MainFragmentBinding
|
||||||
import org.kodein.di.Kodein
|
import com.example.h_mal.candyspace.ui.MainActivity.Companion.viewModel
|
||||||
import org.kodein.di.KodeinAware
|
import com.example.h_mal.candyspace.utils.displayToast
|
||||||
import org.kodein.di.android.kodein
|
import com.xwray.groupie.GroupAdapter
|
||||||
import org.kodein.di.generic.instance
|
import com.xwray.groupie.ViewHolder
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UI for the first screen holding the list, search box
|
* UI for the first screen holding the list, search box
|
||||||
*/
|
*/
|
||||||
class MainFragment : Fragment(), KodeinAware{
|
class MainFragment : Fragment(){
|
||||||
|
|
||||||
//retrieve the viewmodel factory from the kodein dependency injection
|
|
||||||
override val kodein by lazy { (context as KodeinAware).kodein }
|
|
||||||
private val factory : MainViewModelFactory by instance()
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = MainFragment()
|
fun newInstance() = MainFragment()
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var viewModel: MainViewModel
|
lateinit var binding: MainFragmentBinding
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View {
|
savedInstanceState: Bundle?): View {
|
||||||
//retrieve viewmodel from viewmodel factory
|
//Bind layout to Viewmodel in main activity
|
||||||
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
|
binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
|
||||||
//Bind layout to Viewmodel
|
|
||||||
val binding: MainFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
|
|
||||||
binding.viewmodel = viewModel
|
binding.viewmodel = viewModel
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
@@ -44,8 +42,37 @@ class MainFragment : Fragment(), KodeinAware{
|
|||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
super.onActivityCreated(savedInstanceState)
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
|
||||||
|
viewModel.usersLiveData.observe(viewLifecycleOwner, Observer {
|
||||||
|
val mAdapter = GroupAdapter<ViewHolder>().apply {
|
||||||
|
addAll(it.toUserViewModels())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.recyclerView.apply {
|
||||||
|
layoutManager = LinearLayoutManager(context)
|
||||||
|
setHasFixedSize(true)
|
||||||
|
adapter = mAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
mAdapter.setOnItemClickListener { item, _ ->
|
||||||
|
val i = mAdapter.getAdapterPosition(item)
|
||||||
|
|
||||||
|
|
||||||
|
viewModel.setCurrentUser(it[i])
|
||||||
|
|
||||||
|
activity!!.supportFragmentManager.beginTransaction()
|
||||||
|
.replace(R.id.container, UserProfileFragment())
|
||||||
|
.addToBackStack("user")
|
||||||
|
.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun List<User>.toUserViewModels() : List<ListItemViewModel>{
|
||||||
|
return this.map {
|
||||||
|
ListItemViewModel(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,17 @@
|
|||||||
package com.example.h_mal.candyspace.ui.main
|
package com.example.h_mal.candyspace.ui.main
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.core.content.ContextCompat.getSystemService
|
||||||
|
import androidx.databinding.BindingAdapter
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.example.h_mal.candyspace.R
|
||||||
|
import com.example.h_mal.candyspace.data.api.User
|
||||||
import com.example.h_mal.candyspace.data.repositories.Repository
|
import com.example.h_mal.candyspace.data.repositories.Repository
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -14,9 +23,17 @@ class MainViewModel(
|
|||||||
|
|
||||||
var searchString: String? = null
|
var searchString: String? = null
|
||||||
|
|
||||||
val completionListener: CompletionListener? = null
|
var completionListener: CompletionListener? = null
|
||||||
|
|
||||||
|
val usersLiveData = MutableLiveData<List<User>>()
|
||||||
|
var currentUserLiveData: User? = null
|
||||||
|
|
||||||
fun submit(view: View){
|
fun submit(view: View){
|
||||||
|
view.let { v ->
|
||||||
|
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||||
|
imm?.hideSoftInputFromWindow(v.windowToken, 0)
|
||||||
|
}
|
||||||
|
|
||||||
completionListener?.onStarted()
|
completionListener?.onStarted()
|
||||||
if (searchString.isNullOrEmpty()){
|
if (searchString.isNullOrEmpty()){
|
||||||
completionListener?.onFailure("Search box is empty")
|
completionListener?.onFailure("Search box is empty")
|
||||||
@@ -25,15 +42,25 @@ class MainViewModel(
|
|||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
try {
|
try {
|
||||||
val user = repository.getUsers(searchString!!)
|
val apiResponse = repository.getUsers(searchString!!)
|
||||||
//todo: update live data
|
|
||||||
|
|
||||||
//successfully finished
|
apiResponse.items?.let {
|
||||||
completionListener?.onSuccess()
|
if (it.isNotEmpty()){
|
||||||
|
//update live data
|
||||||
|
usersLiveData.value = it
|
||||||
|
//successfully finished
|
||||||
|
completionListener?.onSuccess()
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
completionListener?.onFailure("No Users found")
|
||||||
}catch (e: IOException){
|
}catch (e: IOException){
|
||||||
completionListener?.onFailure(e.message!!)
|
completionListener?.onFailure(e.message!!)
|
||||||
}
|
}
|
||||||
completionListener?.onFailure("Failed to retrieve Users")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setCurrentUser(user: User) {
|
||||||
|
currentUserLiveData = user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.example.h_mal.candyspace.ui.main
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.*
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.databinding.DataBindingUtil
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
import com.example.h_mal.candyspace.R
|
||||||
|
import com.example.h_mal.candyspace.databinding.FragmentUserProfileBinding
|
||||||
|
import com.example.h_mal.candyspace.ui.MainActivity
|
||||||
|
import com.example.h_mal.candyspace.ui.MainActivity.Companion.viewModel
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple [Fragment] subclass.
|
||||||
|
* Use the [UserProfileFragment.newInstance] factory method to
|
||||||
|
* create an instance of this fragment.
|
||||||
|
*/
|
||||||
|
class UserProfileFragment : Fragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = UserProfileFragment()
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var binding: FragmentUserProfileBinding
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
activity?.actionBar?.setHomeButtonEnabled(true)
|
||||||
|
// Inflate the layout for this fragment
|
||||||
|
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_user_profile, container, false)
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
viewModel.currentUserLiveData?.let {
|
||||||
|
binding.user = it
|
||||||
|
binding.lifecycleOwner = this
|
||||||
|
|
||||||
|
Picasso.get().load(it.profileImage).placeholder(R.mipmap.ic_launcher).into(binding.imageView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.h_mal.candyspace.utils
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
object ConverterUtil {
|
||||||
|
@JvmStatic fun epochToData(number: Int): String {
|
||||||
|
return try {
|
||||||
|
val sdf = SimpleDateFormat("dd/MM/yyyy")
|
||||||
|
val netDate = Date(number.toLong() * 1000)
|
||||||
|
sdf.format(netDate)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
"Unspecified"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.example.h_mal.candyspace.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Toast
|
||||||
|
|
||||||
|
fun View.show(){
|
||||||
|
this.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.hide(){
|
||||||
|
this.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.displayToast(message: String){
|
||||||
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||||
|
}
|
||||||
162
app/src/main/res/layout/fragment_user_profile.xml
Normal file
162
app/src/main/res/layout/fragment_user_profile.xml
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout 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"
|
||||||
|
tools:context=".ui.main.UserProfileFragment">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<import type="com.example.h_mal.candyspace.R"/>
|
||||||
|
<import type="com.example.h_mal.candyspace.utils.ConverterUtil"/>
|
||||||
|
<variable
|
||||||
|
name="user"
|
||||||
|
type="com.example.h_mal.candyspace.data.api.User" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imageView"
|
||||||
|
android:layout_width="108dp"
|
||||||
|
android:layout_height="108dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
android:src="@mipmap/ic_launcher"
|
||||||
|
tools:src="@drawable/ic_launcher_foreground"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/imageView"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/divider"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Username:" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
tools:text="Username1234"
|
||||||
|
android:text="@{user.displayName}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/imageView" />
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reputation_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/username_label"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/divider"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Reputation:" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/reputation"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
tools:text="1548252"
|
||||||
|
android:text="@{user.reputation.toString()}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/username" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/badges_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/reputation_label"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/divider"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Bagdes:" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/badges_gold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Gold - "
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/reputation" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/gold_score"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="3554"
|
||||||
|
android:text="@{user.badgeCounts.gold.toString()}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/badges_gold"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/badges_gold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/badges_silver"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="Silver - "
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/badges_gold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/silver_score"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="3554"
|
||||||
|
android:text="@{user.badgeCounts.silver.toString()}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/badges_silver"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/badges_silver"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/badges_bronze"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="Bronze - "
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/badges_silver" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bronze_score"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:text="3554"
|
||||||
|
android:text="@{user.badgeCounts.bronze.toString()}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/badges_bronze"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/badges_bronze"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date_joined_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/badges_bronze"
|
||||||
|
app:layout_constraintRight_toLeftOf="@id/divider"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="Date Joined:" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date_joined"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
tools:text="31/01/2019"
|
||||||
|
android:text="@{ConverterUtil.epochToData(user.creationDate)}"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/divider"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/badges_bronze" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:id="@+id/divider"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/imageView" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
39
app/src/main/res/layout/list_item_layout.xml
Normal file
39
app/src/main/res/layout/list_item_layout.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout 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">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
<variable
|
||||||
|
name="user"
|
||||||
|
type="com.example.h_mal.candyspace.data.api.User" />
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
tools:text="12345"
|
||||||
|
android:text="@{user.userId.toString()}"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
tools:text="Username"
|
||||||
|
android:text="@{user.displayName}"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toRightOf="@id/textView"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
@@ -4,4 +4,13 @@
|
|||||||
android:id="@+id/container"
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".ui.MainActivity" />
|
tools:context=".ui.MainActivity" >
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_circular"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
</FrameLayout>
|
||||||
|
|||||||
@@ -23,11 +23,16 @@
|
|||||||
android:layout_margin="4dp"
|
android:layout_margin="4dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:id="@+id/container">
|
android:id="@+id/container">
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/search_bar"
|
android:id="@+id/search_bar"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content" />
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:inputType="text"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@={viewmodel.searchString}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/submit"
|
android:id="@+id/submit"
|
||||||
@@ -36,6 +41,7 @@
|
|||||||
app:layout_constraintStart_toEndOf="@id/search_bar"
|
app:layout_constraintStart_toEndOf="@id/search_bar"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
android:onClick="@{viewmodel::submit}"
|
||||||
android:text="Submit"/>
|
android:text="Submit"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Candy Space</string>
|
<string name="app_name">Candy Space</string>
|
||||||
|
|
||||||
|
<!-- TODO: Remove or change this placeholder text -->
|
||||||
|
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.example.h_mal.candyspace.data.repositories
|
package com.example.h_mal.candyspace.data.repositories
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.example.h_mal.candyspace.data.api.ApiClass
|
import com.example.h_mal.candyspace.data.api.ApiClass
|
||||||
|
import com.example.h_mal.candyspace.data.api.ApiResponse
|
||||||
import com.example.h_mal.candyspace.data.api.User
|
import com.example.h_mal.candyspace.data.api.User
|
||||||
import com.google.gson.Gson
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
@@ -11,7 +10,6 @@ import org.junit.Before
|
|||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.mock
|
import org.mockito.Mockito.mock
|
||||||
@@ -33,8 +31,8 @@ class RepositoryTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun fetchUserFromApi_positiveResponse() = runBlocking {
|
fun fetchUserFromApi_positiveResponse() = runBlocking {
|
||||||
val mockUser = mock(User::class.java)
|
val mockApiResponse = mock(ApiResponse::class.java)
|
||||||
val mockResponse = Response.success(mockUser)
|
val mockResponse = Response.success(mockApiResponse)
|
||||||
|
|
||||||
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
|
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
|
||||||
mockResponse
|
mockResponse
|
||||||
@@ -43,12 +41,12 @@ class RepositoryTest {
|
|||||||
val getUser = repository.getUsers("12345")
|
val getUser = repository.getUsers("12345")
|
||||||
|
|
||||||
assertNotNull(getUser)
|
assertNotNull(getUser)
|
||||||
assertEquals(mockUser, getUser)
|
assertEquals(mockApiResponse, getUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun fetchUserFromApi_negativeResponse() = runBlocking {
|
fun fetchUserFromApi_negativeResponse() = runBlocking {
|
||||||
val mockResponse = Response.error<User>(403,
|
val mockResponse = Response.error<ApiResponse>(403,
|
||||||
ResponseBody.create(
|
ResponseBody.create(
|
||||||
MediaType.parse("application/json"),
|
MediaType.parse("application/json"),
|
||||||
"{\"key\":[\"somestuff\"]}"
|
"{\"key\":[\"somestuff\"]}"
|
||||||
|
|||||||
Reference in New Issue
Block a user