Merge pull request #4 from medici/ra-my-loans-screen

[AA-4] My Loans Screen - Add bottom navigation view with only one fragment for My Loans
This commit is contained in:
Shashidhara Gopal
2019-11-07 12:12:44 +05:30
committed by GitHub Enterprise
20 changed files with 552 additions and 9 deletions

View File

@@ -45,4 +45,5 @@ dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'com.google.android.material:material:1.0.0'
}

View File

@@ -11,7 +11,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".login.LoginActivity">
<activity android:name=".bottomNavigation.BottomNavigationActivity" />
<activity android:name=".login.LoginActivity" android:noHistory="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -2,9 +2,16 @@ package com.navi.medici.android_customer_app.api
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val gsonConverterFactory = GsonConverterFactory.create()
object RetrofitService {
fun build(baseURL: String, converterFactory: Converter.Factory): Retrofit {
fun build(
baseURL: String = BASE_URL,
converterFactory: Converter.Factory = gsonConverterFactory
): Retrofit {
val retrofitBuilder = Retrofit.Builder().baseUrl(baseURL)
return retrofitBuilder.addConverterFactory(converterFactory).build()
}

View File

@@ -0,0 +1,39 @@
package com.navi.medici.android_customer_app.bottomNavigation
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.navi.medici.android_customer_app.R
import com.navi.medici.android_customer_app.bottomNavigation.myLoans.MyLoansFragment
import com.navi.medici.android_customer_app.databinding.ActivityBottomNavigationBinding
class BottomNavigationActivity : AppCompatActivity() {
private lateinit var binding: ActivityBottomNavigationBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_bottom_navigation)
binding.bottomNavigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.my_loans -> navigateToMyLoans()
else -> false
}
}
binding.bottomNavigationView.selectedItemId = R.id.my_loans
}
private fun navigateToMyLoans(): Boolean {
val fragmentManager = supportFragmentManager.beginTransaction()
supportFragmentManager.findFragmentByTag(MyLoansFragment.TAG)?.let { fragment ->
fragmentManager.replace(R.id.content_view, fragment, MyLoansFragment.TAG)
} ?: run {
val fragment = MyLoansFragment()
fragmentManager.add(R.id.content_view, fragment, MyLoansFragment.TAG)
fragmentManager.replace(R.id.content_view, fragment, MyLoansFragment.TAG)
}
fragmentManager.commit()
return true
}
}

View File

@@ -0,0 +1,12 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
data class Loan(
var status: LoanStatus?,
var emiDue: Long?,
var amount: Long?,
var dueDate: String?,
var type: LoanType?,
var startDate: String?,
var interestRate: Float?,
var completionDate: String?
)

View File

@@ -0,0 +1,6 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
enum class LoanStatus(val value: String) {
ACTIVE("Active"),
COMPLETED("Closed")
}

View File

@@ -0,0 +1,6 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
enum class LoanType(val value: String) {
COUNTDOWN("Countdown Loan"),
PERSONAL("Personal Loan")
}

View File

@@ -0,0 +1,56 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.navi.medici.android_customer_app.R
import com.navi.medici.android_customer_app.databinding.LoanCardBinding
class MyLoansAdapter(private val context: Context) :
RecyclerView.Adapter<MyLoansAdapter.MyLoansViewHolder>() {
private val loans = mutableListOf<Loan>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyLoansViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = LoanCardBinding.inflate(layoutInflater, parent, false)
return MyLoansViewHolder(view)
}
fun setLoans(loans: List<Loan>) {
this.loans.clear()
this.loans.addAll(loans)
notifyDataSetChanged()
}
override fun getItemCount() = loans.count()
override fun onBindViewHolder(holder: MyLoansViewHolder, position: Int) {
val loan = loans[position]
holder.binding.apply {
loanAmountText.text =
context.getString(R.string.rupees_format, loan.amount)
loanTypeText.text = loan.type?.value
startDateText.text = loan.startDate
interestRateText.text =
context.getString(R.string.percentage_format, loan.interestRate)
if (loan.status == LoanStatus.ACTIVE) {
emiDueOrCompletionDateText.text =
context.getString(R.string.rupees_format, loan.emiDue)
emiDueOrCompletionDateLabelText.text =
context.getString(R.string.emi_due)
dueDateOrStatusLabelText.text = context.getString(R.string.due_date)
dueDateOrStatusText.text = loan.dueDate
} else {
emiDueOrCompletionDateText.text = loan.completionDate
emiDueOrCompletionDateLabelText.text =
context.getString(R.string.completion_date)
dueDateOrStatusLabelText.text = context.getString(R.string.status)
dueDateOrStatusText.text = loan.status?.value
}
}
}
class MyLoansViewHolder(val binding: LoanCardBinding) : RecyclerView.ViewHolder(binding.root) {
}
}

View File

@@ -0,0 +1,16 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
import com.navi.medici.android_customer_app.api.RetrofitService
import retrofit2.Response
import retrofit2.http.GET
interface MyLoansApi {
@GET("posts")
suspend fun fetchMyLoans(): Response<List<Loan>>
companion object {
operator fun invoke(): MyLoansApi {
return RetrofitService.build().create(MyLoansApi::class.java)
}
}
}

View File

@@ -0,0 +1,60 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.LinearLayoutManager
import com.navi.medici.android_customer_app.R
import com.navi.medici.android_customer_app.databinding.MyLoansFragmentBinding
class MyLoansFragment : Fragment() {
private lateinit var viewModel: MyLoansViewModel
private lateinit var binding: MyLoansFragmentBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(
inflater,
R.layout.my_loans_fragment,
container,
false
)
viewModel = ViewModelProviders.of(this).get(MyLoansViewModel::class.java)
context?.let {
val myActiveLoansAdapter = MyLoansAdapter(it)
binding.activeLoansRecycler.layoutManager = LinearLayoutManager(it)
binding.activeLoansRecycler.adapter = myActiveLoansAdapter
val myCompletedLoansAdapter = MyLoansAdapter(it)
binding.completedLoansRecycler.layoutManager = LinearLayoutManager(it)
binding.completedLoansRecycler.adapter = myCompletedLoansAdapter
viewModel.myLoans.observe(this, Observer { loans ->
val myActiveLoans = loans.filter { loan ->
loan.status == LoanStatus.ACTIVE
}
myActiveLoansAdapter.setLoans(myActiveLoans)
val myCompletedLoans = loans.filter { loan ->
loan.status == LoanStatus.COMPLETED
}
myCompletedLoansAdapter.setLoans(myCompletedLoans)
})
viewModel.fetchMyLoans()
}
return binding.root
}
companion object {
const val TAG = "MY_LOANS_FRAGMENT"
}
}

View File

@@ -0,0 +1,7 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
import com.navi.medici.android_customer_app.api.SafeApiRequest
class MyLoansRepository(private val myLoansApi: MyLoansApi) : SafeApiRequest() {
suspend fun fetchMyLoans() = apiRequest { myLoansApi.fetchMyLoans() }
}

View File

@@ -0,0 +1,40 @@
package com.navi.medici.android_customer_app.bottomNavigation.myLoans
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MyLoansViewModel : ViewModel() {
private val myLoansRepository = MyLoansRepository(MyLoansApi())
private val coroutineScope = CoroutineScope(Dispatchers.Main)
val myLoans = MutableLiveData<List<Loan>>()
fun fetchMyLoans() {
coroutineScope.launch {
val response = myLoansRepository.fetchMyLoans()
response.forEachIndexed { index, loan ->
loan.apply {
if (index % 2 == 0) {
amount = 25000
dueDate = "24th December"
interestRate = 12.5f
startDate = "24th September"
emiDue = 2500
status = LoanStatus.ACTIVE
type = LoanType.COUNTDOWN
} else {
amount = 50000
interestRate = 16.7f
startDate = "24th August"
completionDate = "21st February"
status = LoanStatus.COMPLETED
type = LoanType.PERSONAL
}
}
}
myLoans.value = response.subList(0,7)
}
}
}

View File

@@ -1,11 +1,14 @@
package com.navi.medici.android_customer_app.login
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.addTextChangedListener
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.navi.medici.android_customer_app.R
import com.navi.medici.android_customer_app.bottomNavigation.BottomNavigationActivity
import com.navi.medici.android_customer_app.databinding.ActivityLoginBinding
class LoginActivity : AppCompatActivity() {
@@ -16,7 +19,12 @@ class LoginActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login)
loginViewModel.isLoggedIn.observe(this, Observer { isLoggedIn ->
if (isLoggedIn) {
val intent = Intent(this, BottomNavigationActivity::class.java)
startActivity(intent)
}
})
binding.phoneEdit.addTextChangedListener { loginViewModel.onChangePhoneNumber(it.toString()) }
}

View File

@@ -2,20 +2,15 @@ package com.navi.medici.android_customer_app.login
import com.navi.medici.android_customer_app.api.RetrofitService
import retrofit2.Response
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
private const val BASE_URL = "https://jsonplaceholder.typicode.com/"
private val gsonConverterFactory = GsonConverterFactory.create()
interface LoginApi {
@GET("posts/1")
suspend fun submitPhoneNumber(): Response<Login>
companion object {
operator fun invoke(): LoginApi {
return RetrofitService.build(BASE_URL, gsonConverterFactory)
.create(LoginApi::class.java)
return RetrofitService.build().create(LoginApi::class.java)
}
}
}

View File

@@ -1,11 +1,16 @@
package com.navi.medici.android_customer_app.login
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class LoginViewModel : ViewModel() {
private val _isLoggedIn = MutableLiveData<Boolean>(false)
val isLoggedIn: LiveData<Boolean>
get() = _isLoggedIn
private val login = Login()
private val loginRepository = LoginRepository(LoginApi())
private val coroutineScope = CoroutineScope(Dispatchers.Main)
@@ -19,6 +24,10 @@ class LoginViewModel : ViewModel() {
private fun submitPhoneNumber() {
coroutineScope.launch {
val response = loginRepository.submitPhoneNumber(login.phoneNumber)
if (response.id != 0) {
_isLoggedIn.value = true
}
loginRepository.submitPhoneNumber(login.phoneNumber)
}
}

View File

@@ -0,0 +1,33 @@
<?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">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".bottomNavigation.BottomNavigationActivity">
<LinearLayout
android:id="@+id/content_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="vertical"
app:layout_constraintBottom_toTopOf="@+id/bottom_navigation_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottom_navigation_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_navigation_menu">
</com.google.android.material.bottomnavigation.BottomNavigationView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@@ -0,0 +1,168 @@
<?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">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardElevation="8dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/emi_due_or_completion_date_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="@string/emi_due" />
<TextView
android:id="@+id/emi_due_or_completion_date_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/emi_due_or_completion_date_label_text"
app:layout_constraintStart_toEndOf="@+id/emi_due_or_completion_date_label_text"
app:layout_constraintTop_toTopOf="@+id/emi_due_or_completion_date_label_text"
tools:text="Rs 25000" />
<TextView
android:id="@+id/loan_amount_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/loan_amount"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="@+id/emi_due_or_completion_date_label_text"
app:layout_constraintTop_toBottomOf="@+id/emi_due_or_completion_date_label_text" />
<TextView
android:id="@+id/loan_amount_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/rupees_format"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/loan_amount_label_text"
app:layout_constraintStart_toEndOf="@+id/loan_amount_label_text"
app:layout_constraintTop_toTopOf="@+id/loan_amount_label_text" />
<TextView
android:id="@+id/due_date_or_status_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/due_date_or_status_text"
app:layout_constraintTop_toTopOf="@+id/emi_due_or_completion_date_text"
tools:text="@string/due_date" />
<TextView
android:id="@+id/due_date_or_status_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="24dp"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/due_date_or_status_label_text"
tools:text="24th August" />
<View
android:id="@+id/loan_card_divider"
android:layout_width="0dp"
android:layout_height="2dp"
android:layout_marginTop="16dp"
android:background="?android:attr/listDivider"
android:backgroundTint="@android:color/darker_gray"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loan_amount_label_text" />
<LinearLayout
android:id="@+id/loan_type_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loan_card_divider">
<TextView
android:id="@+id/loan_type_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loan_type" />
<TextView
android:id="@+id/loan_type_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
tools:text="Personal Loan" />
</LinearLayout>
<LinearLayout
android:id="@+id/start_date_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/interest_rate_layout"
app:layout_constraintStart_toEndOf="@+id/loan_type_layout"
app:layout_constraintTop_toBottomOf="@+id/loan_card_divider">
<TextView
android:id="@+id/start_date_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/start_date" />
<TextView
android:id="@+id/start_date_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
tools:text="4th March 19" />
</LinearLayout>
<LinearLayout
android:id="@+id/interest_rate_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/loan_card_divider">
<TextView
android:id="@+id/interest_rate_label_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/interest_rate" />
<TextView
android:id="@+id/interest_rate_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/percentage_format"
android:textSize="16sp"
tools:text="17 %" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</layout>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/active_loans"
android:textColor="@android:color/black"
android:textSize="20sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/active_loans_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="@string/completed_loans"
android:textColor="@android:color/black"
android:textSize="20sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/completed_loans_recycler"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</layout>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/home"
android:enabled="true"
android:title="@string/home"
app:showAsAction="withText" />
<item
android:id="@+id/my_loans"
android:enabled="true"
android:title="@string/my_loans"
app:showAsAction="withText" />
<item
android:id="@+id/profile"
android:enabled="true"
android:title="@string/profile"
app:showAsAction="withText" />
</menu>

View File

@@ -4,4 +4,19 @@
<string name="phone_number_label">Enter your mobile number to register with Navi</string>
<string name="create_new_account">Create New Account</string>
<string name="terms_and_conditions"><![CDATA[By proceeding, you agree to our Terms & Conditions & Privacy Policy. Standard operator charges may apply for sms.]]></string>
<string name="home">Home</string>
<string name="my_loans">My Loans</string>
<string name="profile">Profile</string>
<string name="active_loans">Active Loans</string>
<string name="completed_loans">Completed Loans</string>
<string name="emi_due">EMI Due :</string>
<string name="rupees_format">Rs %d</string>
<string name="loan_amount">Loan Amount :</string>
<string name="due_date">Due Date</string>
<string name="loan_type">Loan Type</string>
<string name="start_date">Start Date</string>
<string name="interest_rate">Interest Rate</string>
<string name="percentage_format">%.1f %%</string>
<string name="completion_date">Completion Date</string>
<string name="status">Status</string>
</resources>