Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,21 @@ object AnalyticsConstants {
const val TRANSACTION_FILTER = "home_transaction_filter"
const val SEND_RECEIVE_BUTTON = "home_send_receive_button"
}

object ExploreDash {
const val WHERE_TO_SPEND = "portal_where_to_spend"
const val PORTAL_ATM = "portal_atm"
const val LEARN_MORE = "info_learn_more"
const val CONTINUE = "info_continue"
const val ONLINE_MERCHANTS = "online_merchants"
const val NEARBY_MERCHANTS = "nearby_merchants"
const val ALL_MERCHANTS = "all_merchants"
const val FILTER_MERCHANTS_TOP = "filter_merchants_top"
const val FILTER_MERCHANTS_BOTTOM = "filter_merchants_bottom"
const val SELECT_MERCHANT_LOCATION = "select_merchant_location"
const val SELECT_MERCHANT_MARKER = "select_merchant_marker"
const val INFO_EXPLORE_MERCHANT = "info_search"
const val PAN_MERCHANT_MAP = "pan_merchant_map"
const val ZOOM_MERCHANT_MAP = "zoom_merchant_map"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as #915, there should be a prefix, otherwise within the list of events, some explore dash events are at A and others at Z.

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,22 @@ package org.dash.wallet.features.exploredash.ui

import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import dagger.hilt.android.AndroidEntryPoint
import org.dash.wallet.common.services.analytics.AnalyticsConstants
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dash.wallet.common.ui.viewBinding
import org.dash.wallet.common.util.safeNavigate
import org.dash.wallet.features.exploredash.R
import org.dash.wallet.features.exploredash.databinding.FragmentExploreBinding
import javax.inject.Inject

@AndroidEntryPoint
class ExploreFragment : Fragment(R.layout.fragment_explore) {
private val binding by viewBinding(FragmentExploreBinding::bind)

@Inject
lateinit var analyticsService: AnalyticsService
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand All @@ -39,10 +44,12 @@ class ExploreFragment : Fragment(R.layout.fragment_explore) {
}

binding.merchantsBtn.setOnClickListener {
analyticsService.logEvent(AnalyticsConstants.ExploreDash.WHERE_TO_SPEND, bundleOf())
safeNavigate(ExploreFragmentDirections.exploreToSearch(ExploreTopic.Merchants))
}

binding.atmsBtn.setOnClickListener {
analyticsService.logEvent(AnalyticsConstants.ExploreDash.PORTAL_ATM, bundleOf())
safeNavigate(ExploreFragmentDirections.exploreToSearch(ExploreTopic.ATMs))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ class ExploreMapFragment : SupportMapFragment() {

@Inject
lateinit var userLocationState: UserLocationStateInt

private var previousZoomLevel: Float = -1.0f
private var cameraMovementReason: Int = -1
private lateinit var previousCameraCenterPosition: LatLng
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

Expand All @@ -97,6 +99,22 @@ class ExploreMapFragment : SupportMapFragment() {
googleMap?.let { map ->
map.setOnCameraIdleListener {
val bounds = map.projection.visibleRegion.latLngBounds
if (cameraMovementReason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE){
when {
viewModel.hasZoomLevelChanged(previousZoomLevel, map.cameraPosition.zoom) -> {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check if the user zoomed (in/out) the map

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This entire when can be moved to the ViewModel. You're calling a viewModel check and then calling a viewModel event if the check is satisfied. Just call a triggerEventIfSatisfied(param, param) method once.
  2. This entire check is needed to trigger an event that is then observed from another fragment that logs the event. I don't quite get why can't you either
    • Log it from this fragment
    • Log it from the viewModel

viewModel.zoomLevelChangeCallback.call()
}
viewModel.hasCameraCenterChanged(previousCameraCenterPosition,
map.cameraPosition.target
) -> {
viewModel.cameraCenterChangeCallback.call()
}
else -> {}
}
}

previousZoomLevel = map.cameraPosition.zoom
previousCameraCenterPosition = map.cameraPosition.target
viewModel.searchBounds = GeoBounds(
bounds.northeast.latitude,
bounds.northeast.longitude,
Expand All @@ -107,6 +125,10 @@ class ExploreMapFragment : SupportMapFragment() {
map.cameraPosition.zoom
)
}

map.setOnCameraMoveStartedListener { reason ->

@ClaudeHangui ClaudeHangui Mar 18, 2022 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are various reasons that can cause the camera to be in motion in a map: however our tracking only focuses on the Camera motion initiated in response to user gestures on the map including pinch to zoom and pan gestures. All other causes/reasons initiated or not by the user are to be ignored for the tracking

cameraMovementReason = reason
}
}
}

Expand Down Expand Up @@ -227,6 +249,7 @@ class ExploreMapFragment : SupportMapFragment() {
if (isGooglePlayServicesAvailable()) {
markerCollection = MarkerManager(googleMap).newCollection()
markerCollection?.setOnMarkerClickListener { marker ->
viewModel.markerClickCallback.call()
viewModel.onMapMarkerSelected(marker.tag as Int)
true
}
Expand Down Expand Up @@ -351,6 +374,8 @@ class ExploreMapFragment : SupportMapFragment() {
val radiusBounds = getRadiusBounds(mCurrentUserLocation, radius)
map.moveCamera(radiusBounds)
lastFocusedUserLocation = mCurrentUserLocation
previousZoomLevel = map.cameraPosition.zoom
previousCameraCenterPosition = map.cameraPosition.target
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import androidx.annotation.VisibleForTesting
import androidx.lifecycle.*
import androidx.paging.*
import androidx.paging.PagingData
import com.google.android.gms.maps.model.LatLng

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The viewModel should not reference LatLng since it's implementation-dependent and we're working with an interface in case of UserLocationState. If the implementation of UserLocationState changed, you'll have to change a lot of related code in the viewModel as well. Please use GeoBounds instead.

import com.google.firebase.FirebaseNetworkException
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
Expand Down Expand Up @@ -97,7 +98,9 @@ class ExploreViewModel @Inject constructor(
private var boundedFilterJob: Job? = null
private var pagingFilterJob: Job? = null
private var allMerchantLocationsJob: Job? = null

val zoomLevelChangeCallback = SingleLiveEvent<Unit>()
val cameraCenterChangeCallback = SingleLiveEvent<Unit>()
val markerClickCallback = SingleLiveEvent<Unit>()
val isMetric = Locale.getDefault().isMetric

private val _searchQuery = MutableStateFlow("")
Expand Down Expand Up @@ -703,4 +706,11 @@ class ExploreViewModel @Inject constructor(
syncStatusService.setObservedLastError()
}
}

fun hasZoomLevelChanged(previousZoomLevel: Float, currentZoomLevel: Float): Boolean =
previousZoomLevel != currentZoomLevel

fun hasCameraCenterChanged(previousCenterPosition: LatLng, currentCenterPosition: LatLng)
: Boolean = locationProvider.distanceBetween(previousCenterPosition.latitude, previousCenterPosition.longitude,
currentCenterPosition.latitude, currentCenterPosition.longitude) != 0.0
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat
import androidx.core.os.bundleOf
import androidx.core.view.*
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
Expand Down Expand Up @@ -59,6 +60,8 @@ import org.dash.wallet.features.exploredash.ui.extensions.*
import org.dash.wallet.common.Configuration
import org.dash.wallet.common.data.Resource
import org.dash.wallet.common.data.Status
import org.dash.wallet.common.services.analytics.AnalyticsConstants
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dash.wallet.features.exploredash.ui.adapters.MerchantLocationsHeaderAdapter
import org.dash.wallet.features.exploredash.ui.adapters.MerchantsLocationsAdapter
import javax.inject.Inject
Expand All @@ -73,7 +76,8 @@ class SearchFragment : Fragment(R.layout.fragment_search) {

@Inject
lateinit var configuration: Configuration

@Inject
lateinit var analyticsService: AnalyticsService
private val binding by viewBinding(FragmentSearchBinding::bind)
private val viewModel: ExploreViewModel by activityViewModels()
private val args by navArgs<SearchFragmentArgs>()
Expand Down Expand Up @@ -107,6 +111,7 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
hideKeyboard()

if (item is Merchant) {
analyticsService.logEvent(AnalyticsConstants.ExploreDash.SELECT_MERCHANT_LOCATION, bundleOf())
viewModel.openMerchantDetails(item, true)
} else if (item is Atm) {
viewModel.openAtmDetails(item)
Expand Down Expand Up @@ -150,6 +155,9 @@ class SearchFragment : Fragment(R.layout.fragment_search) {

binding.toolbar.setOnMenuItemClickListener {
if (it.itemId == R.id.menu_info) {
if (args.type == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.INFO_EXPLORE_MERCHANT, bundleOf())
}
safeNavigate(SearchFragmentDirections.exploreToInfo())
}
true
Expand Down Expand Up @@ -222,6 +230,21 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
}
}
}
viewModel.markerClickCallback.observe(viewLifecycleOwner){
if (viewModel.exploreTopic == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.SELECT_MERCHANT_MARKER, bundleOf())
}
}
viewModel.zoomLevelChangeCallback.observe(viewLifecycleOwner){
if (viewModel.exploreTopic == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.ZOOM_MERCHANT_MAP, bundleOf())
}
}
viewModel.cameraCenterChangeCallback.observe(viewLifecycleOwner){

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SearchFragment doesn't have to know anything about the map, I don't get why this is here.

if (viewModel.exploreTopic == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.PAN_MERCHANT_MAP, bundleOf())
}
}
}

private fun showError(
Expand Down Expand Up @@ -304,14 +327,27 @@ class SearchFragment : Fragment(R.layout.fragment_search) {
viewModel.setFilterMode(defaultMode)

searchHeaderAdapter.setOnFilterOptionChosen { mode ->
if (topic == ExploreTopic.Merchants){
when(mode){
FilterMode.Online -> analyticsService.logEvent(AnalyticsConstants.ExploreDash.ONLINE_MERCHANTS, bundleOf())
FilterMode.Nearby -> analyticsService.logEvent(AnalyticsConstants.ExploreDash.NEARBY_MERCHANTS, bundleOf())
else -> analyticsService.logEvent(AnalyticsConstants.ExploreDash.ALL_MERCHANTS, bundleOf())
}
}
viewModel.setFilterMode(mode)
}

searchHeaderAdapter.setOnFilterButtonClicked {
if (topic == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.FILTER_MERCHANTS_TOP, bundleOf())
}
openFilters()
}

binding.filterPanel.setOnClickListener {
if (topic == ExploreTopic.Merchants){
analyticsService.logEvent(AnalyticsConstants.ExploreDash.FILTER_MERCHANTS_BOTTOM, bundleOf())
}
openFilters()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.bundleOf
import dagger.hilt.android.AndroidEntryPoint
import org.dash.wallet.common.services.analytics.AnalyticsConstants
import org.dash.wallet.common.services.analytics.AnalyticsService
import org.dash.wallet.common.ui.dialogs.OffsetDialogFragment
import org.dash.wallet.common.ui.viewBinding
import org.dash.wallet.common.util.dialogSafeNavigate
import org.dash.wallet.features.exploredash.R
import org.dash.wallet.features.exploredash.databinding.ExploreDashMainInfoBinding

import javax.inject.Inject
@AndroidEntryPoint
class ExploreDashInfoDialog : OffsetDialogFragment<ConstraintLayout>(){

private val binding by viewBinding(ExploreDashMainInfoBinding::bind)

@Inject
lateinit var analyticsService: AnalyticsService
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand All @@ -44,9 +50,11 @@ class ExploreDashInfoDialog : OffsetDialogFragment<ConstraintLayout>(){
super.onViewCreated(view, savedInstanceState)

binding.learnMoreLabel.setOnClickListener {
analyticsService.logEvent(AnalyticsConstants.ExploreDash.LEARN_MORE, bundleOf())
dialogSafeNavigate(ExploreDashInfoDialogDirections.infoToGiftCardDetail())
}
binding.exploreDashInfoContinueBtn.setOnClickListener {
analyticsService.logEvent(AnalyticsConstants.ExploreDash.CONTINUE, bundleOf())
dismissAllowingStateLoss()
}
}
Expand Down