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"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
|
||||
@@ -38,6 +38,7 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions: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'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
@@ -69,4 +70,5 @@ dependencies {
|
||||
|
||||
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">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".AppClass"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
||||
@@ -1,14 +1,30 @@
|
||||
package com.example.h_mal.candyspace
|
||||
|
||||
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.KodeinAware
|
||||
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{
|
||||
|
||||
override val kodein = Kodein.lazy {
|
||||
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 {
|
||||
|
||||
@GET("users?")
|
||||
suspend fun getUsersFromApi(@Query("inname") inname: String): Response<User>
|
||||
suspend fun getUsersFromApi(@Query("inname") inname: String): Response<ApiResponse>
|
||||
|
||||
companion object{
|
||||
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 {
|
||||
|
||||
suspend fun<T: Any> apiRequest(call: suspend () -> Response<T>) : T{
|
||||
suspend fun<T: Any> responseUnwrap(call: suspend () -> Response<T>) : T{
|
||||
|
||||
val response = call.invoke()
|
||||
if(response.isSuccessful){
|
||||
|
||||
@@ -5,6 +5,9 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
class User {
|
||||
@SerializedName("badge_counts")
|
||||
@Expose
|
||||
var badgeCounts: BadgeCounts? = null
|
||||
@SerializedName("last_modified_date")
|
||||
@Expose
|
||||
var lastModifiedDate: Int? = null
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
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.ApiResponse
|
||||
import com.example.h_mal.candyspace.data.api.ResponseUnwrap
|
||||
import com.example.h_mal.candyspace.data.api.User
|
||||
|
||||
@@ -8,7 +10,7 @@ class Repository(
|
||||
private val api: ApiClass
|
||||
): ResponseUnwrap() {
|
||||
|
||||
suspend fun getUsers(username: String): User {
|
||||
return apiRequest { api.getUsersFromApi(username) }
|
||||
suspend fun getUsers(username: String): ApiResponse {
|
||||
return responseUnwrap { api.getUsersFromApi(username) }
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,87 @@ package com.example.h_mal.candyspace.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
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.ui.main.CompletionListener
|
||||
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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
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) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.container, MainFragment.newInstance())
|
||||
.commitNow()
|
||||
.replace(R.id.container, MainFragment())
|
||||
.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
|
||||
|
||||
import android.os.Bundle
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.AdapterView
|
||||
import androidx.databinding.DataBindingUtil
|
||||
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.data.api.User
|
||||
import com.example.h_mal.candyspace.databinding.MainFragmentBinding
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.android.kodein
|
||||
import org.kodein.di.generic.instance
|
||||
import com.example.h_mal.candyspace.ui.MainActivity.Companion.viewModel
|
||||
import com.example.h_mal.candyspace.utils.displayToast
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.ViewHolder
|
||||
|
||||
|
||||
/*
|
||||
* UI for the first screen holding the list, search box
|
||||
*/
|
||||
class MainFragment : Fragment(), KodeinAware{
|
||||
|
||||
//retrieve the viewmodel factory from the kodein dependency injection
|
||||
override val kodein by lazy { (context as KodeinAware).kodein }
|
||||
private val factory : MainViewModelFactory by instance()
|
||||
class MainFragment : Fragment(){
|
||||
|
||||
companion object {
|
||||
fun newInstance() = MainFragment()
|
||||
}
|
||||
|
||||
private lateinit var viewModel: MainViewModel
|
||||
lateinit var binding: MainFragmentBinding
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||
savedInstanceState: Bundle?): View {
|
||||
//retrieve viewmodel from viewmodel factory
|
||||
viewModel = ViewModelProvider(this, factory).get(MainViewModel::class.java)
|
||||
//Bind layout to Viewmodel
|
||||
val binding: MainFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
|
||||
//Bind layout to Viewmodel in main activity
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.main_fragment, container, false)
|
||||
binding.viewmodel = viewModel
|
||||
binding.lifecycleOwner = this
|
||||
|
||||
return binding.root
|
||||
}
|
||||
@@ -44,8 +42,37 @@ class MainFragment : Fragment(), KodeinAware{
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
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 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.squareup.picasso.Picasso
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -14,9 +23,17 @@ class MainViewModel(
|
||||
|
||||
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){
|
||||
view.let { v ->
|
||||
val imm = view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
|
||||
imm?.hideSoftInputFromWindow(v.windowToken, 0)
|
||||
}
|
||||
|
||||
completionListener?.onStarted()
|
||||
if (searchString.isNullOrEmpty()){
|
||||
completionListener?.onFailure("Search box is empty")
|
||||
@@ -25,15 +42,25 @@ class MainViewModel(
|
||||
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
try {
|
||||
val user = repository.getUsers(searchString!!)
|
||||
//todo: update live data
|
||||
val apiResponse = repository.getUsers(searchString!!)
|
||||
|
||||
apiResponse.items?.let {
|
||||
if (it.isNotEmpty()){
|
||||
//update live data
|
||||
usersLiveData.value = it
|
||||
//successfully finished
|
||||
completionListener?.onSuccess()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
completionListener?.onFailure("No Users found")
|
||||
}catch (e: IOException){
|
||||
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:layout_width="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:orientation="horizontal"
|
||||
android:id="@+id/container">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_weight="1"
|
||||
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
|
||||
android:id="@+id/submit"
|
||||
@@ -36,6 +41,7 @@
|
||||
app:layout_constraintStart_toEndOf="@id/search_bar"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:onClick="@{viewmodel::submit}"
|
||||
android:text="Submit"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">Candy Space</string>
|
||||
|
||||
<!-- TODO: Remove or change this placeholder text -->
|
||||
<string name="hello_blank_fragment">Hello blank fragment</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
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.ApiResponse
|
||||
import com.example.h_mal.candyspace.data.api.User
|
||||
import com.google.gson.Gson
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.ResponseBody
|
||||
@@ -11,7 +10,6 @@ import org.junit.Before
|
||||
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.mockito.ArgumentMatchers
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.mock
|
||||
@@ -33,8 +31,8 @@ class RepositoryTest {
|
||||
|
||||
@Test
|
||||
fun fetchUserFromApi_positiveResponse() = runBlocking {
|
||||
val mockUser = mock(User::class.java)
|
||||
val mockResponse = Response.success(mockUser)
|
||||
val mockApiResponse = mock(ApiResponse::class.java)
|
||||
val mockResponse = Response.success(mockApiResponse)
|
||||
|
||||
Mockito.`when`(api.getUsersFromApi("12345")).thenReturn(
|
||||
mockResponse
|
||||
@@ -43,12 +41,12 @@ class RepositoryTest {
|
||||
val getUser = repository.getUsers("12345")
|
||||
|
||||
assertNotNull(getUser)
|
||||
assertEquals(mockUser, getUser)
|
||||
assertEquals(mockApiResponse, getUser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchUserFromApi_negativeResponse() = runBlocking {
|
||||
val mockResponse = Response.error<User>(403,
|
||||
val mockResponse = Response.error<ApiResponse>(403,
|
||||
ResponseBody.create(
|
||||
MediaType.parse("application/json"),
|
||||
"{\"key\":[\"somestuff\"]}"
|
||||
|
||||
Reference in New Issue
Block a user