Skip to content

Commit

Permalink
Complete place category list view
Browse files Browse the repository at this point in the history
  • Loading branch information
gvoltr committed Feb 7, 2020
1 parent 5b47b1e commit c38d6cb
Show file tree
Hide file tree
Showing 15 changed files with 283 additions and 113 deletions.
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
android:supportsRtl="true"
android:name=".presentation.App"
android:theme="@style/AppTheme">
<activity android:name=".presentation.MainActivity">
<activity android:name=".presentation.main.view.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class FusedLocationDataSource(

private val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
private var locationRequest: LocationRequest = LocationRequest().apply {
interval = 30000 //30s
fastestInterval = 5000 //5s
interval = 10000 //10s
fastestInterval = 2000 //2s
priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.gvoltr.placeshere.data.entity.category.PlaceCategory
import com.gvoltr.placeshere.data.location.LocationDataSource
import com.gvoltr.placeshere.data.restapi.places.PlacesDataSource
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers

class PlaceCategoriesInteractor(
private val placesDataSource: PlacesDataSource,
Expand All @@ -15,6 +16,7 @@ class PlaceCategoriesInteractor(
?: return Single.error(IllegalStateException("Location required for categories request"))

return placesDataSource.getCategories(location)
.subscribeOn(Schedulers.io())
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.gvoltr.placeshere.presentation.di

import com.gvoltr.placeshere.presentation.address.viewmodel.AddressViewModel
import com.gvoltr.placeshere.presentation.main.viewmodel.MainViewModel
import com.gvoltr.placeshere.presentation.places.viewmodel.PlacesViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

private val viewModelsModule = module {
viewModel { PlacesViewModel(get(), get(), get(), get()) }
viewModel { PlacesViewModel(get(), get(), get()) }
viewModel { AddressViewModel(get()) }
viewModel { MainViewModel(get(), get()) }
}

val presentationModule = viewModelsModule
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.gvoltr.placeshere.presentation.main.view

import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.lifecycle.Observer
import com.gvoltr.placeshere.R
import com.gvoltr.placeshere.presentation.main.viewmodel.InteractionEvent
import com.gvoltr.placeshere.presentation.main.viewmodel.MainViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel

class MainActivity : AppCompatActivity() {
private val LOCATION_PERMISSION_REQUEST_CODE = 1

private val mainViewModel: MainViewModel by viewModel()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
subscribeToVM()
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults.first() == PackageManager.PERMISSION_GRANTED) {
mainViewModel.locationAllowed()
} else {
mainViewModel.locationDisallowed()
}
}
}

private fun subscribeToVM() {
mainViewModel.getInteractionEventsLiveData()
.observe(this, Observer { event ->
event.getContentIfNotHandled()?.let { handleInteractionEvent(it) }
})
}

private fun handleInteractionEvent(event: InteractionEvent) {
when (event) {
is InteractionEvent.RequestLocation -> askToEnableLocationPermission()
}
}

private fun askToEnableLocationPermission() {
val shouldShowRationale = ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
if (shouldShowRationale) {
AlertDialog.Builder(this)
.setTitle(R.string.label_permission_required)
.setMessage(R.string.location_permission_required_message)
.setPositiveButton(R.string.ok) { _, _ ->
requestLocationPermission()
}
.create()
.show()
} else {
requestLocationPermission()
}
}

private fun requestLocationPermission() {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.gvoltr.placeshere.presentation.places.viewmodel
package com.gvoltr.placeshere.presentation.main.viewmodel

sealed class InteractionEvent {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.gvoltr.placeshere.presentation.main.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gvoltr.placeshere.domain.LocationInteractor
import com.gvoltr.placeshere.domain.PermissionInteractor
import com.gvoltr.placeshere.presentation.utils.Event

class MainViewModel(
private val locationInteractor: LocationInteractor,
private val permissionInteractor: PermissionInteractor
) : ViewModel() {


//Used to pass events to the view that require user interaction e.g. location request
private val interactionLiveData = MutableLiveData<Event<InteractionEvent>>()

init {
initLocationUpdates()
}

override fun onCleared() {
super.onCleared()
locationInteractor.stopLocationUpdates()
}

fun getInteractionEventsLiveData(): LiveData<Event<InteractionEvent>> = interactionLiveData

fun locationAllowed() {
initLocationUpdates()
}

fun locationDisallowed() {
requestLocation()
}

private fun initLocationUpdates() {
if (permissionInteractor.locationPermissionGranted()) {
locationInteractor.startLocationUpdates()
} else {
requestLocation()
}
}

private fun requestLocation() {
interactionLiveData.value = Event(InteractionEvent.RequestLocation)
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.gvoltr.placeshere.presentation.places.view


import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gvoltr.placeshere.R
import com.gvoltr.placeshere.presentation.places.viewmodel.PlaceCategoryItem
import kotlinx.android.synthetic.main.list_item_place_category.view.*

class PlaceCategoriesAdapter(
private val itemSelected: (PlaceCategoryItem) -> Unit,
private val itemUnselected: (PlaceCategoryItem) -> Unit
) : ListAdapter<PlaceCategoryItem, PlaceCategoriesAdapter.PlaceCategoryViewHolder>(DiffCallback()) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PlaceCategoryViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = inflater.inflate(R.layout.list_item_place_category, parent, false)
return PlaceCategoryViewHolder(view, itemSelected, itemUnselected)
}

override fun onBindViewHolder(holder: PlaceCategoryViewHolder, position: Int) {
holder.bindData(getItem(position))
}

class PlaceCategoryViewHolder(
itemView: View,
itemSelected: (PlaceCategoryItem) -> Unit,
itemUnselected: (PlaceCategoryItem) -> Unit
) : RecyclerView.ViewHolder(itemView) {

lateinit var placeCategoryItem: PlaceCategoryItem

init {
itemView.checkbox.setOnClickListener {
if ((it as CheckBox).isChecked) {
itemSelected.invoke(placeCategoryItem)
} else {
itemUnselected.invoke(placeCategoryItem)
}
}
}

fun bindData(placeCategory: PlaceCategoryItem) {
this.placeCategoryItem = placeCategory
itemView.checkbox.text = placeCategory.placeCategory.title
itemView.checkbox.isChecked = placeCategory.checked
}
}

private class DiffCallback : DiffUtil.ItemCallback<PlaceCategoryItem>() {
override fun areItemsTheSame(
oldItem: PlaceCategoryItem,
newItem: PlaceCategoryItem
): Boolean {
return oldItem === newItem
}

override fun areContentsTheSame(
oldItem: PlaceCategoryItem,
newItem: PlaceCategoryItem
): Boolean {
return oldItem == newItem
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
package com.gvoltr.placeshere.presentation.places.view

import android.Manifest
import android.app.Activity
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.gvoltr.placeshere.R
import com.gvoltr.placeshere.presentation.places.viewmodel.InteractionEvent
import com.gvoltr.placeshere.presentation.places.viewmodel.PlacesViewModel
import kotlinx.android.synthetic.main.fragment_places.*
import org.koin.androidx.viewmodel.ext.android.viewModel


class PlacesFragment : Fragment() {

private val LOCATION_PERMISSION_REQUEST_CODE = 1
private val placesViewModel: PlacesViewModel by viewModel()
private lateinit var categoriesAdapter: PlaceCategoriesAdapter

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -30,62 +26,31 @@ class PlacesFragment : Fragment() {
return inflater.inflate(R.layout.fragment_places, container, false)
}

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.isNotEmpty() && grantResults.first() == PackageManager.PERMISSION_GRANTED) {
placesViewModel.locationAllowed()
} else {
placesViewModel.locationDisallowed()
}
}
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
subscribeToVMChanges()
setupView()
subscribeToVM()
}

private fun subscribeToVMChanges() {
placesViewModel.getInteractionEventsLiveData()
.observe(viewLifecycleOwner, Observer { event ->
event.getContentIfNotHandled()?.let { handleInteractionEvent(it) }
})
private fun setupView() {
setupCategoriesList()
}

private fun handleInteractionEvent(event: InteractionEvent) {
when (event) {
is InteractionEvent.RequestLocation -> requestLocationPermission()
}
private fun setupCategoriesList() {
categoriesList.layoutManager =
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
categoriesAdapter = PlaceCategoriesAdapter(itemSelected = {
placesViewModel.categorySelected(it)
}, itemUnselected = {
placesViewModel.categoryUnselected(it)
})
categoriesList.adapter = categoriesAdapter
}

private fun requestLocationPermission() {
fun requestPermission() {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
}

if (ActivityCompat.shouldShowRequestPermissionRationale(
activity as Activity,
Manifest.permission.ACCESS_FINE_LOCATION
)
) {
AlertDialog.Builder(context!!)
.setTitle(R.string.label_permission_required)
.setMessage(R.string.location_permission_required_message)
.setPositiveButton(R.string.ok) { _, _ ->
requestPermission()
}
.create()
.show()
} else {
requestPermission()
}
private fun subscribeToVM() {
placesViewModel.getPlaceCategoriesLiveData().observe(viewLifecycleOwner, Observer {
categoriesAdapter.submitList(it)
})
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gvoltr.placeshere.presentation.places.viewmodel

import com.gvoltr.placeshere.data.entity.category.PlaceCategory

data class PlaceCategoryItem (
val placeCategory: PlaceCategory,
var checked: Boolean = false
)
Loading

0 comments on commit c38d6cb

Please sign in to comment.