Skip to content

Commit

Permalink
android: impl. donation pending approval feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Roaim committed May 31, 2020
1 parent fc1c6a2 commit 684d8a2
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 17 deletions.
3 changes: 3 additions & 0 deletions dtb-android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ dependencies {
implementation deps.misc.facebook
implementation deps.misc.glide

implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
kapt deps.kapt.values()

testImplementation deps.javaTest.junit
Expand Down
12 changes: 12 additions & 0 deletions dtb-android/app/src/main/java/app/roaim/dtbazar/api/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ interface ApiService {
@Query("size") size: Int = 50
): Response<List<Donation>>

@GET("donation/pending")
@Throws(Exception::class)
suspend fun getPendingDonations(
@Query("storeId") storeId: String,
@Query("page") page: Int = 0,
@Query("size") size: Int = 25
): Response<List<Donation>>

@PATCH("donation/{id}")
@Throws(Exception::class)
suspend fun approveDonation(@Path("id") id: String): Response<Donation>

@POST("food")
@Throws(Exception::class)
suspend fun saveFood(@Body foodPostBody: FoodPostBody): Response<Food>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ class DonationRepository @Inject constructor(
emit(result)
}

fun getPendingDonations(storeId: String): LiveData<Result<List<Donation>>> =
liveData {
emit(loading())
val result = try {
apiService.getPendingDonations(storeId).getResult()
} catch (e: Exception) {
log("getPendingDonations", e)
failed<List<Donation>>(e.message)
}
emit(result)
}

fun addDonation(donationPostBody: DonationPostBody) = liveData<Result<Donation>> {
emit(loading())
val result = try {
Expand All @@ -49,4 +61,18 @@ class DonationRepository @Inject constructor(
}
emit(result)
}

fun approveDonation(donation: Donation?) = liveData<Result<Donation>> {
if (donation == null) emit(failed<Donation>("Can't approve a null donation"))
else {
emit(loading())
val result = try {
apiService.approveDonation(donation.id).getResult()
} catch (e: Exception) {
log("ApproveDonation", e)
failed<Donation>(e.message)
}
emit(result)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package app.roaim.dtbazar.di;

import app.roaim.dtbazar.ui.home.HomeFragment
import app.roaim.dtbazar.ui.login.LoginFragment
import app.roaim.dtbazar.ui.food.FoodFragment
import app.roaim.dtbazar.ui.home.HomeFragment
import app.roaim.dtbazar.ui.login.LoginCheckFragment
import app.roaim.dtbazar.ui.login.LoginFragment
import app.roaim.dtbazar.ui.store.StoreFragment
import app.roaim.dtbazar.ui.store_details.PendingDonationFragment
import app.roaim.dtbazar.ui.store_details.StoreDetailsFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
Expand All @@ -29,4 +30,7 @@ abstract class FragmentBuildersModule {

@ContributesAndroidInjector
abstract fun contributeFoodFragment(): FoodFragment

@ContributesAndroidInjector
abstract fun contributePendingDonationFragment(): PendingDonationFragment
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package app.roaim.dtbazar.di;
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import app.roaim.dtbazar.ui.DtbViewModelFactory
import app.roaim.dtbazar.ui.food.FoodViewModel
import app.roaim.dtbazar.ui.home.HomeViewModel
import app.roaim.dtbazar.ui.login.LoginViewModel
import app.roaim.dtbazar.ui.food.FoodViewModel
import app.roaim.dtbazar.ui.store.StoreViewModel
import app.roaim.dtbazar.ui.store_details.PendingDonationViewModel
import app.roaim.dtbazar.ui.store_details.StoreDetailsViewModel
import dagger.Binds
import dagger.Module
Expand All @@ -18,12 +19,12 @@ abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(LoginViewModel::class)
abstract fun bindLoginViewModel(userViewModel: LoginViewModel): ViewModel
abstract fun bindLoginViewModel(loginViewModel: LoginViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(userViewModel: HomeViewModel): ViewModel
abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel

@Binds
@IntoMap
Expand All @@ -38,7 +39,12 @@ abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(FoodViewModel::class)
abstract fun bindFoodViewModel(repoViewModel: FoodViewModel): ViewModel
abstract fun bindFoodViewModel(foodViewModel: FoodViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(PendingDonationViewModel::class)
abstract fun bindPendingDonationViewModel(pendingDonationViewModel: PendingDonationViewModel): ViewModel

@Binds
abstract fun bindViewModelFactory(factory: DtbViewModelFactory): ViewModelProvider.Factory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class HomeViewModel @Inject constructor(
val ipInfo = infoRepository.getIpInfo()

val myDonations: LiveData<List<Donation>> = _uid.switchMap { uid ->
_myDonations.addSource(donationRepository.getMyCachedDonations(uid)) {
if (uid != null) _myDonations.addSource(donationRepository.getMyCachedDonations(uid)) {
_myDonations.value = it
}
val myRemoteDonations = donationRepository.getMyDonations()
Expand All @@ -41,7 +41,7 @@ class HomeViewModel @Inject constructor(


val myStores = _uid.switchMap { uid ->
_myStores.addSource(storeRepository.getMyCachedStores(uid)) {
if (uid != null) _myStores.addSource(storeRepository.getMyCachedStores(uid)) {
_myStores.value = it
}
val myRemoteStores = storeRepository.getMyStores()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package app.roaim.dtbazar.ui.store_details

import android.os.Bundle
import android.view.*
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.drawable.RoundedBitmapDrawable
import androidx.databinding.DataBindingComponent
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.navArgs
import app.roaim.dtbazar.R
import app.roaim.dtbazar.databinding.FragmentPendingDonationBinding
import app.roaim.dtbazar.di.Injectable
import app.roaim.dtbazar.model.Status
import app.roaim.dtbazar.ui.home.HomeDonationAdapter
import app.roaim.dtbazar.utils.*
import javax.inject.Inject

/**
* A simple [Fragment] subclass.
*/
class PendingDonationFragment : Fragment(), Injectable, Loggable {

@Inject
lateinit var glidePlaceHolder: RoundedBitmapDrawable

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

private val viewModel: PendingDonationViewModel by viewModels { viewModelFactory }

private val navArgs by navArgs<PendingDonationFragmentArgs>()
private var bindingComponent by autoCleared<DataBindingComponent>()
private var binding by autoCleared<FragmentPendingDonationBinding>()
private var adapter by autoCleared<HomeDonationAdapter>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
bindingComponent = FragmentDataBindingComponent(this, glidePlaceHolder)
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_pending_donation,
container,
false,
bindingComponent
)
adapter = HomeDonationAdapter()
binding.rvDonation.adapter = adapter
adapter.setItemClickListener { donation, _, isLongClick ->
log("ItemClick: $donation")
AlertDialog.Builder(binding.root.context)
.setMessage("Accept donation ${donation?.currency} ${donation?.amount?.formatted()} to ${donation?.foodName} from ${donation?.donorName}")
.setPositiveButton("Yes") { _, _ ->
viewModel.approve(donation)
}
.setNegativeButton("No", null)
.show()
}
viewModel.setStoreId(navArgs.storeId)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.pendingDonation.observe(viewLifecycleOwner, Observer {
log("PendingDonation: $it")
if (it.status == Status.SUCCESS) adapter.submitList(it.data)
})
viewModel.approve.observe(viewLifecycleOwner, Observer {
log("ApproveDonation: $it")
if (it.status == Status.SUCCESS) {
val list = adapter.currentList.toMutableList()
list.remove(it.data)
adapter.submitList(list)
Toast.makeText(requireContext(), "Approved", Toast.LENGTH_SHORT).show()
} else if (it.status == Status.FAILED) {
Toast.makeText(requireContext(), "Failed to Approve. ${it.msg}", Toast.LENGTH_LONG)
.show()
}
})
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val menuReload = menu.add("Reload")
menuReload.setIcon(R.drawable.ic_refresh)
menuReload.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
menuReload.setOnMenuItemClickListener {
viewModel.onRetry()
true
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package app.roaim.dtbazar.ui.store_details

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.switchMap
import app.roaim.dtbazar.data.repository.DonationRepository
import app.roaim.dtbazar.model.Donation
import app.roaim.dtbazar.model.Result
import app.roaim.dtbazar.ui.RetryCallback
import javax.inject.Inject

class PendingDonationViewModel @Inject constructor(
private val donationRepository: DonationRepository
) :
ViewModel(), RetryCallback {

private val _storeId = MutableLiveData<String>()

private val _approve = MutableLiveData<Donation>()

val pendingDonation: LiveData<Result<List<Donation>>> =
_storeId.switchMap { donationRepository.getPendingDonations(it) }

val approve: LiveData<Result<Donation>> =
_approve.switchMap { donationRepository.approveDonation(it) }

fun approve(donation: Donation?) {
_approve.value = donation
}

fun setStoreId(storeId: String) {
if (_storeId.value == storeId) return
_storeId.value = storeId
}

override fun onRetry() {
_storeId.value = _storeId.value
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.transition.TransitionInflater
import app.roaim.dtbazar.R
Expand All @@ -31,9 +32,11 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis
@Inject
lateinit var vmFactory: ViewModelProvider.Factory
val viewModel: StoreDetailsViewModel by viewModels { vmFactory }
private var binding by autoCleared<FragmentStoreDetailsBinding>()
private var _binding: FragmentStoreDetailsBinding? = null
private val binding get() = _binding!!
val navArgs by navArgs<StoreDetailsFragmentArgs>()
private var adapter by autoCleared<StoreFoodAdapter>()
private var _adapter: StoreFoodAdapter? = null
private val adapter get() = _adapter!!
var addStoreFoodDialog by autoCleared<AlertDialog>()
var addStoreFoodBinding by autoCleared<ViewAddNewStoreFoodBinding>()
var foodSuggestionAdapter by autoCleared<FoodSuggestionAdapter>()
Expand All @@ -56,7 +59,7 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentStoreDetailsBinding.inflate(inflater, container, false)
_binding = FragmentStoreDetailsBinding.inflate(inflater, container, false)
binding.args = navArgs
viewModel.store.observe(viewLifecycleOwner, Observer {
log("STORE: $it")
Expand All @@ -74,7 +77,7 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis
addDonationSellBinding.isAddStock = it &&
addDonationSellBinding.rg.checkedRadioButtonId == addDonationSellBinding.rbStock.id
})
adapter = StoreFoodAdapter()
_adapter = StoreFoodAdapter()
onStoreFoodItemClickListener = getStoreFoodItemClickListener()
adapter.setItemClickListener(onStoreFoodItemClickListener)
addDonationSellBinding =
Expand All @@ -97,6 +100,12 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis
return binding.root
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
_adapter = null
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
ViewCompat.setTransitionName(binding.viewStore, navArgs.storeId)
Expand Down Expand Up @@ -139,7 +148,16 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis
).observe(viewLifecycleOwner, Observer {
log("ADD_DONATION: $it")
addDonationSellBinding.result = it
if (it.status == Status.SUCCESS) onCancelClick()
if (it.status == Status.SUCCESS) {
if (viewModel.isOwnStore.value == false) {
AlertDialog.Builder(binding.root.context)
.setTitle("Donation Pending!")
.setMessage("Ask the store owner to accept your donation")
.setPositiveButton("Ok", null)
.show()
}
onCancelClick()
}
})
}
}
Expand Down Expand Up @@ -197,13 +215,24 @@ class StoreDetailsFragment : Fragment(), Injectable, Loggable, StoreFoodClickLis

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
val menuReload = menu.add("Reload")
menuReload.setIcon(R.drawable.ic_refresh)
val menuReload = menu.add("Reload").setIcon(R.drawable.ic_refresh)
menuReload.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)
menuReload.setOnMenuItemClickListener {
viewModel.onRetry()
true
}
val menuPendingDonation =
menu.add("Pending Donation").setIcon(R.drawable.ic_notifications)
.setOnMenuItemClickListener {
StoreDetailsFragmentDirections.actionNavigationStoreDetailsToPendingDonationFragment(
navArgs.storeId
).let {
findNavController().navigate(it)
true
}
}
menuPendingDonation.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM)

}

}
Loading

0 comments on commit 684d8a2

Please sign in to comment.