Skip to content

Commit

Permalink
Refactor PlacesSearchResultsAdapter
Browse files Browse the repository at this point in the history
  • Loading branch information
bubelov committed Feb 17, 2018
1 parent 0395233 commit 7e750c9
Show file tree
Hide file tree
Showing 7 changed files with 154 additions and 69 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Expand Up @@ -58,6 +58,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
implementation "org.jetbrains.anko:anko-commons:$anko_version"
implementation 'androidx.core:core-ktx:0.1'

// Android support libraries
implementation "com.android.support:support-v13:$support_version"
Expand Down
@@ -1,3 +1,30 @@
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <https://unlicense.org>
*/

package com.bubelov.coins.repository.placeicon

import android.content.Context
Expand All @@ -13,10 +40,7 @@ import android.support.annotation.DrawableRes
import android.support.graphics.drawable.VectorDrawableCompat
import android.graphics.drawable.VectorDrawable
import android.support.v4.content.ContextCompat

/**
* @author Igor Bubelov
*/
import androidx.graphics.drawable.toBitmap

@Singleton
class PlaceIconsRepository @Inject constructor(
Expand All @@ -35,7 +59,12 @@ class PlaceIconsRepository @Inject constructor(
return marker
}

fun getPlaceCategoryIconResId(category: String): Int? {
fun getPlaceIcon(category: String): Bitmap {
val iconId = getPlaceCategoryIconResId(category) ?: R.drawable.ic_place
return ContextCompat.getDrawable(context, iconId)!!.toBitmap()
}

private fun getPlaceCategoryIconResId(category: String): Int? {
return when (category.toLowerCase()) {
"atm" -> R.drawable.ic_atm
"restaurant" -> R.drawable.ic_restaurant
Expand Down
Expand Up @@ -33,14 +33,13 @@ import android.view.View
import android.view.ViewGroup

import com.bubelov.coins.R
import com.bubelov.coins.ui.model.PlacesSearchResult
import com.bubelov.coins.ui.model.PlacesSearchRow

import kotlinx.android.synthetic.main.row_places_search_result.view.*
import java.text.NumberFormat

class PlacesSearchResultsAdapter(
private val items: List<PlacesSearchResult>,
private val itemClick: (PlacesSearchResult) -> Unit
private val items: List<PlacesSearchRow>,
private val itemClick: (PlacesSearchRow) -> Unit
) : RecyclerView.Adapter<PlacesSearchResultsAdapter.ViewHolder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
Expand All @@ -54,34 +53,20 @@ class PlacesSearchResultsAdapter(
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]

holder.itemView.apply {
icon.setImageResource(item.iconResId)
name.text = item.placeName

if (item.distance != null) {
distance.visibility = android.view.View.VISIBLE

distance.text = resources.getString(
R.string.distance_format,
DISTANCE_FORMAT.format(item.distance),
item.distanceUnits
)
} else {
distance.visibility = android.view.View.GONE
}

setOnClickListener { itemClick(item) }
}
holder.bind(items[position], itemClick)
}

override fun getItemCount() = items.size

class ViewHolder(view: View) : RecyclerView.ViewHolder(view)

companion object {
private val DISTANCE_FORMAT =
NumberFormat.getNumberInstance().apply { maximumFractionDigits = 1 }
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: PlacesSearchRow, itemClick: (PlacesSearchRow) -> Unit) {
itemView.apply {
icon.setImageBitmap(item.icon)
name.text = item.name
distance.visibility = if (item.distance.isNotEmpty()) View.VISIBLE else View.GONE
distance.text = item.distance
setOnClickListener { itemClick(item) }
}
}
}
}
31 changes: 27 additions & 4 deletions app/src/main/java/com/bubelov/coins/ui/model/PlaceMarker.kt
@@ -1,13 +1,36 @@
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <https://unlicense.org>
*/

package com.bubelov.coins.ui.model

import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem

/**
* @author Igor Bubelov
*/

data class PlaceMarker internal constructor(val placeId: Long, val icon: BitmapDescriptor, val latitude: Double, val longitude: Double) : ClusterItem {
override fun getPosition(): LatLng {
return LatLng(latitude, longitude)
Expand Down
13 changes: 0 additions & 13 deletions app/src/main/java/com/bubelov/coins/ui/model/PlacesSearchResult.kt

This file was deleted.

37 changes: 37 additions & 0 deletions app/src/main/java/com/bubelov/coins/ui/model/PlacesSearchRow.kt
@@ -0,0 +1,37 @@
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <https://unlicense.org>
*/

package com.bubelov.coins.ui.model

import android.graphics.Bitmap

data class PlacesSearchRow(
val placeId: Long,
val icon: Bitmap,
val name: String,
val distance: String
)
Expand Up @@ -34,15 +34,16 @@ import android.preference.PreferenceManager

import com.bubelov.coins.App
import com.bubelov.coins.R
import com.bubelov.coins.model.Place
import com.bubelov.coins.repository.place.PlacesRepository
import com.bubelov.coins.repository.placeicon.PlaceIconsRepository
import com.bubelov.coins.ui.model.PlacesSearchResult
import com.bubelov.coins.ui.model.PlacesSearchRow
import com.bubelov.coins.util.*
import java.text.NumberFormat
import javax.inject.Inject

class PlacesSearchViewModel(app: Application) : AndroidViewModel(app) {
@Inject lateinit var placesRepository: PlacesRepository

@Inject lateinit var placeIconsRepository: PlaceIconsRepository

private var userLocation: Location? = null
Expand All @@ -51,23 +52,19 @@ class PlacesSearchViewModel(app: Application) : AndroidViewModel(app) {

val searchQuery = MutableLiveData<String>()

val searchResults: LiveData<List<PlacesSearchResult>> = Transformations.switchMap(searchQuery) { query ->
if (query.length < MIN_QUERY_LENGTH) {
MutableLiveData<List<PlacesSearchResult>>().apply { value = emptyList() }
} else {
Transformations.map(placesRepository.getPlaces(query)) {
it.filter { it.currencies.contains(currency) }.map { place ->
PlacesSearchResult(
placeId = place.id,
placeName = place.name,
distance = userLocation?.distanceTo(place.latitude, place.longitude, getDistanceUnits()),
distanceUnits = getDistanceUnits().getShortName(),
iconResId = placeIconsRepository.getPlaceCategoryIconResId(place.category) ?: R.drawable.ic_place
)
}.sortedBy { it.distance }
val searchResults: LiveData<List<PlacesSearchRow>> =
Transformations.switchMap(searchQuery) { query ->
if (query.length < MIN_QUERY_LENGTH) {
MutableLiveData<List<PlacesSearchRow>>().apply { value = emptyList() }
} else {
Transformations.map(placesRepository.getPlaces(query)) { places ->
places
.filter { it.currencies.contains(currency) }
.map { it.toRow() }
.sortedBy { it.distance }
}
}
}
}

init {
appComponent().inject(this)
Expand All @@ -78,9 +75,32 @@ class PlacesSearchViewModel(app: Application) : AndroidViewModel(app) {
this.currency = currency
}

private fun Place.toRow(): PlacesSearchRow {
val userLocation = userLocation

val distanceString = if (userLocation != null) DISTANCE_FORMAT.format(
userLocation.distanceTo(
latitude,
longitude,
getDistanceUnits()
)
) + " ${getDistanceUnits().getShortName()}" else ""

return PlacesSearchRow(
placeId = id,
name = name,
distance = distanceString,
icon = placeIconsRepository.getPlaceIcon(category)
)
}

private fun getDistanceUnits(): DistanceUnits {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplication<App>())
val distanceUnitsString = sharedPreferences.getString(getApplication<App>().getString(R.string.pref_distance_units_key), getApplication<App>().getString(R.string.pref_distance_units_automatic))

val distanceUnitsString = sharedPreferences.getString(
getApplication<App>().getString(R.string.pref_distance_units_key),
getApplication<App>().getString(R.string.pref_distance_units_automatic)
)

return if (distanceUnitsString == getApplication<App>().getString(R.string.pref_distance_units_automatic)) {
DistanceUnits.default
Expand All @@ -98,5 +118,8 @@ class PlacesSearchViewModel(app: Application) : AndroidViewModel(app) {

companion object {
private const val MIN_QUERY_LENGTH = 2

private val DISTANCE_FORMAT =
NumberFormat.getNumberInstance().apply { maximumFractionDigits = 1 }
}
}

0 comments on commit 7e750c9

Please sign in to comment.