Add the map icons for teller and teller cluster. Create TellerRepository to delegate it the responsability to keep the tellers info up to date into the database and serve the info to the MerchantsViewModel and MerchantsFragment immediatly. Create TellerMarketRenderer which is in charge of creating and serving the icons used for the tellers and tellers' clusters in the map.

This commit is contained in:
Severiano Jaramillo 2019-01-23 19:15:06 -06:00
parent 5331d8ebe2
commit fbbb4f9f48
8 changed files with 250 additions and 27 deletions

View file

@ -1,8 +1,10 @@
package cy.agorise.bitsybitshareswallet.database.daos package cy.agorise.bitsybitshareswallet.database.daos
import androidx.lifecycle.LiveData
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query
import cy.agorise.bitsybitshareswallet.database.entities.Teller import cy.agorise.bitsybitshareswallet.database.entities.Teller
@Dao @Dao
@ -12,4 +14,10 @@ interface TellerDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(tellers: List<Teller>) fun insertAll(tellers: List<Teller>)
@Query("SELECT * FROM tellers")
fun getAll(): LiveData<List<Teller>>
@Query("DELETE FROM tellers")
fun deleteAll()
} }

View file

@ -3,6 +3,8 @@ package cy.agorise.bitsybitshareswallet.database.entities
import androidx.room.ColumnInfo import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
@Entity(tableName = "tellers") @Entity(tableName = "tellers")
data class Teller( data class Teller(
@ -10,9 +12,22 @@ data class Teller(
@ColumnInfo(name = "id") val _id: String, @ColumnInfo(name = "id") val _id: String,
@ColumnInfo(name = "name") val gt_name: String, @ColumnInfo(name = "name") val gt_name: String,
@ColumnInfo(name = "address") val address: String?, @ColumnInfo(name = "address") val address: String?,
@ColumnInfo(name = "lat") val lat: Float, @ColumnInfo(name = "lat") val lat: Double,
@ColumnInfo(name = "lon") val lon: Float, @ColumnInfo(name = "lon") val lon: Double,
@ColumnInfo(name = "phone") val phone: String?, @ColumnInfo(name = "phone") val phone: String?,
@ColumnInfo(name = "telegram") val telegram: String?, @ColumnInfo(name = "telegram") val telegram: String?,
@ColumnInfo(name = "website") val url: String? @ColumnInfo(name = "website") val url: String?
) ) : ClusterItem {
override fun getSnippet(): String {
return address ?: ""
}
override fun getTitle(): String {
return gt_name
}
override fun getPosition(): LatLng {
return LatLng(lat, lon)
}
}

View file

@ -23,8 +23,10 @@ import com.google.android.gms.maps.model.*
import com.google.maps.android.clustering.Cluster import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.ClusterManager import com.google.maps.android.clustering.ClusterManager
import cy.agorise.bitsybitshareswallet.database.entities.Merchant import cy.agorise.bitsybitshareswallet.database.entities.Merchant
import cy.agorise.bitsybitshareswallet.database.entities.Teller
import cy.agorise.bitsybitshareswallet.utils.Constants import cy.agorise.bitsybitshareswallet.utils.Constants
import cy.agorise.bitsybitshareswallet.utils.MerchantMarkerRenderer import cy.agorise.bitsybitshareswallet.utils.MerchantMarkerRenderer
import cy.agorise.bitsybitshareswallet.utils.TellerMarkerRenderer
import cy.agorise.bitsybitshareswallet.utils.toast import cy.agorise.bitsybitshareswallet.utils.toast
import cy.agorise.bitsybitshareswallet.viewmodels.MerchantViewModel import cy.agorise.bitsybitshareswallet.viewmodels.MerchantViewModel
import java.lang.Exception import java.lang.Exception
@ -46,7 +48,8 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback,
private lateinit var mMerchantViewModel: MerchantViewModel private lateinit var mMerchantViewModel: MerchantViewModel
private var mClusterManager: ClusterManager<Merchant>? = null private var mMerchantClusterManager: ClusterManager<Merchant>? = null
private var mTellerClusterManager: ClusterManager<Teller>? = null
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
@ -107,27 +110,9 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback,
verifyLocationPermission() verifyLocationPermission()
// Setup clusters to group markers when possible initMerchantsCluster()
mClusterManager = ClusterManager(context, mMap)
val renderer = MerchantMarkerRenderer(context, mMap, mClusterManager)
mClusterManager?.renderer = renderer
// Point the map's listeners at the listeners implemented by the cluster manager. initTellersCluster()
mMap.setOnCameraIdleListener(mClusterManager)
mMap.setOnMarkerClickListener(mClusterManager)
mMap.setOnMarkerClickListener(mClusterManager)
mMap.setInfoWindowAdapter(mClusterManager?.markerManager)
mMap.setOnInfoWindowClickListener(mClusterManager)
mClusterManager?.setOnClusterClickListener(this)
mClusterManager?.setOnClusterItemClickListener(this)
mClusterManager?.setOnClusterItemInfoWindowClickListener(this)
mMerchantViewModel.getAllMerchants().observe(this, Observer<List<Merchant>> {merchants ->
mClusterManager?.clearItems()
mClusterManager?.addItems(merchants)
mClusterManager?.cluster()
})
} }
private fun applyMapTheme() { private fun applyMapTheme() {
@ -149,9 +134,44 @@ class MerchantsFragment : Fragment(), OnMapReadyCallback,
} }
} }
/** private fun initMerchantsCluster() {
* Animates the camera update to focus on an area that shows all the items from the cluster that was tapped. // Setup clusters to group markers when possible
*/ mMerchantClusterManager = ClusterManager(context, mMap)
val merchantRenderer = MerchantMarkerRenderer(context, mMap, mMerchantClusterManager)
mMerchantClusterManager?.renderer = merchantRenderer
// Point the map's listeners at the listeners implemented by the cluster manager.
mMap.setOnCameraIdleListener(mMerchantClusterManager)
mMap.setOnMarkerClickListener(mMerchantClusterManager)
mMap.setOnMarkerClickListener(mMerchantClusterManager)
mMap.setInfoWindowAdapter(mMerchantClusterManager?.markerManager)
mMap.setOnInfoWindowClickListener(mMerchantClusterManager)
mMerchantClusterManager?.setOnClusterClickListener(this)
mMerchantClusterManager?.setOnClusterItemClickListener(this)
mMerchantClusterManager?.setOnClusterItemInfoWindowClickListener(this)
mMerchantViewModel.getAllMerchants().observe(this, Observer<List<Merchant>> {merchants ->
mMerchantClusterManager?.clearItems()
mMerchantClusterManager?.addItems(merchants)
mMerchantClusterManager?.cluster()
})
}
private fun initTellersCluster() {
// Setup clusters to group markers when possible
mTellerClusterManager = ClusterManager(context, mMap)
val tellerRenderer = TellerMarkerRenderer(context, mMap, mTellerClusterManager)
mTellerClusterManager?.renderer = tellerRenderer
mMerchantViewModel.getAllTellers().observe(this, Observer<List<Teller>> {tellers ->
mTellerClusterManager?.clearItems()
mTellerClusterManager?.addItems(tellers)
mTellerClusterManager?.cluster()
})
}
/** Animates the camera update to focus on an area that shows all the items from the cluster that was tapped. */
override fun onClusterClick(cluster: Cluster<Merchant>?): Boolean { override fun onClusterClick(cluster: Cluster<Merchant>?): Boolean {
val builder = LatLngBounds.builder() val builder = LatLngBounds.builder()
val merchantMarkers = cluster?.items val merchantMarkers = cluster?.items

View file

@ -0,0 +1,75 @@
package cy.agorise.bitsybitshareswallet.repositories
import android.content.Context
import android.os.AsyncTask
import android.preference.PreferenceManager
import androidx.lifecycle.LiveData
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
import cy.agorise.bitsybitshareswallet.database.daos.TellerDao
import cy.agorise.bitsybitshareswallet.database.entities.Teller
import cy.agorise.bitsybitshareswallet.network.FeathersResponse
import cy.agorise.bitsybitshareswallet.network.MerchantsWebservice
import cy.agorise.bitsybitshareswallet.utils.Constants
import retrofit2.Call
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class TellerRepository internal constructor(val context: Context) : retrofit2.Callback<FeathersResponse<Teller>> {
private val mTellerDao: TellerDao
init {
val db = BitsyDatabase.getDatabase(context)
mTellerDao = db!!.tellerDao()
}
/** Returns a LiveData object directly from the database while the response from the WebService is obtained. */
fun getAll(): LiveData<List<Teller>> {
refreshTellers()
return mTellerDao.getAll()
}
/** Refreshes the tellers information only if the MERCHANT_UPDATE_PERIOD has passed, otherwise it does nothing */
private fun refreshTellers() {
val lastTellerUpdate = PreferenceManager.getDefaultSharedPreferences(context)
.getLong(Constants.KEY_TELLERS_LAST_UPDATE, 0)
val now = System.currentTimeMillis()
if (lastTellerUpdate + Constants.MERCHANTS_UPDATE_PERIOD < now) {
val retrofit = Retrofit.Builder()
.baseUrl(Constants.MERCHANTS_WEBSERVICE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
val ambassadorService = retrofit.create<MerchantsWebservice>(MerchantsWebservice::class.java)
val call = ambassadorService.getTellers(0)
call.enqueue(this)
}
}
override fun onResponse(call: Call<FeathersResponse<Teller>>, response: Response<FeathersResponse<Teller>>) {
if (response.isSuccessful) {
val res: FeathersResponse<Teller>? = response.body()
val tellers = res?.data ?: return
insertAllAsyncTask(mTellerDao).execute(tellers)
val now = System.currentTimeMillis()
PreferenceManager.getDefaultSharedPreferences(context).edit()
.putLong(Constants.KEY_TELLERS_LAST_UPDATE, now).apply()
}
}
override fun onFailure(call: Call<FeathersResponse<Teller>>, t: Throwable) { /* Do nothing */ }
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: TellerDao) :
AsyncTask<List<Teller>, Void, Void>() {
override fun doInBackground(vararg tellers: List<Teller>): Void? {
mAsyncTaskDao.deleteAll()
mAsyncTaskDao.insertAll(tellers[0])
return null
}
}
}

View file

@ -0,0 +1,83 @@
package cy.agorise.bitsybitshareswallet.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.SparseArray
import android.view.ViewGroup
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.MarkerOptions
import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.ClusterManager
import com.google.maps.android.clustering.view.DefaultClusterRenderer
import com.google.maps.android.ui.IconGenerator
import com.google.maps.android.ui.SquareTextView
import cy.agorise.bitsybitshareswallet.R
import cy.agorise.bitsybitshareswallet.database.entities.Teller
/**
* This class is used to create custom merchant and merchant cluster icons to show on the map.
*/
class TellerMarkerRenderer(val context: Context?, map: GoogleMap?, clusterManager: ClusterManager<Teller>?) :
DefaultClusterRenderer<Teller>(context, map, clusterManager) {
// Icons used to display merchants and merchants' clusters on the map
private var tellerIcon: BitmapDescriptor
private val mIcons = SparseArray<BitmapDescriptor>()
private val mDensity: Float
private val mIconGenerator = IconGenerator(context)
init {
tellerIcon = getMarkerIconFromDrawable(
context?.resources?.getDrawable(R.drawable.ic_teller_pin, null))
mDensity = context?.resources?.displayMetrics?.density ?: 2.0F
this.mIconGenerator.setContentView(this.makeSquareTextView(context))
this.mIconGenerator.setTextAppearance(com.google.maps.android.R.style.amu_ClusterIcon_TextAppearance)
this.mIconGenerator.setBackground(context?.resources?.getDrawable(R.drawable.ic_teller_cluster, null))
}
override fun onBeforeClusterItemRendered(item: Teller?, markerOptions: MarkerOptions?) {
markerOptions?.icon(tellerIcon)
}
override fun onBeforeClusterRendered(cluster: Cluster<Teller>?, markerOptions: MarkerOptions?) {
val bucket = getBucket(cluster)
var descriptor: BitmapDescriptor? = mIcons.get(bucket)
if (descriptor == null) {
descriptor = BitmapDescriptorFactory.fromBitmap(mIconGenerator.makeIcon(getClusterText(bucket)))
mIcons.put(bucket, descriptor)
}
markerOptions?.icon(descriptor)
}
override fun shouldRenderAsCluster(cluster: Cluster<Teller>?): Boolean {
return (cluster?.size ?: 0) > 1
}
private fun makeSquareTextView(context: Context?): SquareTextView {
val squareTextView = SquareTextView(context)
val layoutParams = ViewGroup.LayoutParams(-2, -2)
squareTextView.layoutParams = layoutParams
squareTextView.id = com.google.maps.android.R.id.amu_text
val padding = (24.0f * this.mDensity).toInt()
squareTextView.setPadding(padding, padding, padding, padding)
return squareTextView
}
private fun getMarkerIconFromDrawable(drawable: Drawable?): BitmapDescriptor {
val canvas = Canvas()
val bitmap = Bitmap.createBitmap(drawable?.intrinsicWidth ?: 24,
drawable?.intrinsicHeight ?: 24, Bitmap.Config.ARGB_8888)
canvas.setBitmap(bitmap)
drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable?.draw(canvas)
return BitmapDescriptorFactory.fromBitmap(bitmap)
}
}

View file

@ -4,12 +4,19 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import cy.agorise.bitsybitshareswallet.database.entities.Merchant import cy.agorise.bitsybitshareswallet.database.entities.Merchant
import cy.agorise.bitsybitshareswallet.database.entities.Teller
import cy.agorise.bitsybitshareswallet.repositories.MerchantRepository import cy.agorise.bitsybitshareswallet.repositories.MerchantRepository
import cy.agorise.bitsybitshareswallet.repositories.TellerRepository
class MerchantViewModel(application: Application) : AndroidViewModel(application) { class MerchantViewModel(application: Application) : AndroidViewModel(application) {
private var mMerchantRepository = MerchantRepository(application) private var mMerchantRepository = MerchantRepository(application)
private var mTellerRepository = TellerRepository(application)
internal fun getAllMerchants(): LiveData<List<Merchant>> { internal fun getAllMerchants(): LiveData<List<Merchant>> {
return mMerchantRepository.getAll() return mMerchantRepository.getAll()
} }
internal fun getAllTellers(): LiveData<List<Teller>> {
return mTellerRepository.getAll()
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,15 @@
<vector android:height="51dp" android:viewportHeight="225.302"
android:viewportWidth="154.618" android:width="35dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FBCF10"
android:pathData="M151.705,76.396C151.705,35.31 118.397,2 77.31,2C36.219,2 2.913,35.31 2.913,76.396c0,20.073 7.961,38.277 20.889,51.66l-0.09,0.969c0,0 43.451,42.428 51.781,93.092h0.81h2.014h1.297c8.332,-50.664 51.537,-93.092 51.537,-93.092l-0.352,-0.457C143.735,115.18 151.705,96.481 151.705,76.396z"
android:strokeColor="#FFFFFF" android:strokeWidth="5"/>
<path android:fillColor="#FFFFFF"
android:pathData="M14.478,77.906c0,-34.705 28.131,-62.835 62.832,-62.835c34.706,0 62.836,28.13 62.836,62.835c0,34.7 -28.13,62.837 -62.836,62.837C42.608,140.743 14.478,112.606 14.478,77.906z"
android:strokeColor="#DAB327" android:strokeWidth="2.5162"/>
<path android:fillColor="#FCCF0E" android:fillType="evenOdd" android:pathData="M119.277,118.897c-28.441,0 -56.739,0 -85.19,0c0,-0.422 0,-0.81 0,-1.199c0,-4.414 0.149,-8.834 -0.035,-13.24c-0.291,-6.93 2.809,-12.225 7.716,-16.653c1.353,-1.221 3.033,-2.139 4.683,-2.953c2.445,-1.207 4.996,-2.196 7.571,-3.31c2.418,2.654 4.822,5.291 7.224,7.93c1.129,1.241 2.247,2.493 3.387,3.723c1.406,1.518 3.069,1.355 4.13,-0.399c1.019,-1.684 2.042,-3.365 3.021,-5.071c0.332,-0.576 0.614,-0.82 1.362,-0.62c2.366,0.634 4.768,0.633 7.135,-0.016c0.629,-0.173 0.914,-0.014 1.216,0.509c0.93,1.61 2.033,3.131 2.827,4.801c0.967,2.033 3.072,2.458 4.784,0.452c3.074,-3.606 6.36,-7.032 9.519,-10.569c0.526,-0.589 0.932,-0.689 1.649,-0.362c2.418,1.104 4.937,2.002 7.298,3.213c7.225,3.706 11.312,9.607 11.664,17.782c0.21,4.951 0.037,9.92 0.039,14.882C119.277,118.121 119.277,118.443 119.277,118.897zM87.933,107.673c6.71,0 13.357,0 20.053,0c0,-2.607 0,-5.142 0,-7.72c-6.71,0 -13.363,0 -20.053,0C87.933,102.553 87.933,105.087 87.933,107.673z"/>
<path android:fillColor="#FCCF0E" android:fillType="evenOdd" android:pathData="M90.132,42.745c3.046,2.139 4.141,5.264 4.965,8.636c0.94,3.855 0.183,7.494 -0.879,11.157c-1.501,5.18 -3.436,10.173 -6.903,14.375c-1.35,1.634 -2.963,3.159 -4.727,4.324c-3.812,2.516 -7.823,2.467 -11.745,0.095c-3.246,-1.962 -5.478,-4.874 -7.331,-8.1c-3.013,-5.244 -4.753,-10.943 -5.74,-16.867c-0.193,-1.155 0.214,-2.455 0.523,-3.641c0.574,-2.212 1.124,-4.453 1.975,-6.564c0.836,-2.075 2.14,-2.672 4.313,-2.173c2.621,0.603 5.231,1.294 7.787,2.129c6.04,1.971 11.561,1.008 16.672,-2.669C89.367,43.214 89.716,43.013 90.132,42.745z"/>
<path android:fillColor="#FCCF0E" android:fillType="evenOdd" android:pathData="M54.709,48.134c-1.315,-7.199 0.415,-13.35 4.896,-18.73c1.954,-2.348 4.28,-4.264 7.462,-4.549c3.363,-0.302 6.636,0.265 9.634,1.876c1.63,0.875 3.198,1.894 4.684,2.998c0.714,0.53 1.166,0.536 1.91,0.136c1.938,-1.043 3.981,-1.906 6.243,-1.655c2.918,0.323 5.316,1.74 6.822,4.232c0.998,1.651 1.729,3.531 2.228,5.402c0.842,3.16 0.459,6.42 0.214,9.693c-0.088,-0.168 -0.196,-0.329 -0.259,-0.505c-1.218,-3.53 -3.361,-6.343 -6.456,-8.445c-1.519,-1.032 -2.251,-1.085 -3.789,-0.09c-1.038,0.674 -2.074,1.353 -3.106,2.037c-3.274,2.172 -6.75,2.666 -10.525,1.468c-2.772,-0.88 -5.569,-1.746 -8.416,-2.312c-5.565,-1.106 -8.927,0.936 -10.814,6.291C55.209,46.623 55,47.271 54.709,48.134z"/>
<path android:fillColor="#FCCF0E" android:fillType="evenOdd" android:pathData="M68.197,84.973c-0.672,1.122 -1.283,2.142 -1.986,3.313c-2.636,-2.903 -5.19,-5.717 -7.831,-8.626c0.942,-0.416 1.747,-0.822 2.591,-1.109c0.229,-0.079 0.694,0.112 0.858,0.327C63.612,81.209 65.634,83.284 68.197,84.973z"/>
<path android:fillColor="#FCCF0E" android:fillType="evenOdd" android:pathData="M87.159,88.282c-0.699,-1.156 -1.313,-2.172 -1.836,-3.034c2.296,-2.27 4.531,-4.478 6.882,-6.802c0.74,0.319 1.695,0.732 2.785,1.202C92.355,82.552 89.802,85.369 87.159,88.282z"/>
<path android:fillColor="#FFFFFF" android:fillType="evenOdd" android:pathData="M87.933,107.673c0,-2.586 0,-5.12 0,-7.72c6.689,0 13.343,0 20.053,0c0,2.578 0,5.112 0,7.72C101.29,107.673 94.643,107.673 87.933,107.673z"/>
</vector>