Untitled
kotlin
a month ago
39 kB
2
Indexable
Never
package org.transhelp.bykerr.uiRevamp.ui.fragments import android.Manifest import android.animation.AnimatorSet import android.animation.ObjectAnimator import android.animation.ValueAnimator import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.graphics.* import android.location.Location import android.os.Bundle import android.text.TextPaint import android.util.SparseArray import android.util.TypedValue import android.view.Gravity import android.view.MenuItem import android.view.View import android.view.animation.AccelerateDecelerateInterpolator import android.view.animation.AnticipateInterpolator import android.widget.LinearLayout import android.widget.TextView import androidx.core.animation.doOnEnd import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.location.component1 import androidx.core.location.component2 import androidx.core.view.* import androidx.lifecycle.lifecycleScope import com.google.android.gms.maps.CameraUpdateFactory import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.OnMapReadyCallback import com.google.android.gms.maps.UiSettings import com.google.android.gms.maps.model.* import com.google.gson.Gson import com.google.maps.android.clustering.algo.PreCachingAlgorithmDecorator import com.neovisionaries.ws.client.WebSocket import kotlinx.coroutines.* import org.json.JSONObject import org.transhelp.bykerr.R import org.transhelp.bykerr.databinding.FragmentRoutesLiveTrackBinding import org.transhelp.bykerr.databinding.RoundedCardHeaderBinding import org.transhelp.bykerr.databinding.SessionSocketTimeoutDialogBinding import org.transhelp.bykerr.uiRevamp.helpers.* import org.transhelp.bykerr.uiRevamp.helpers.component1 import org.transhelp.bykerr.uiRevamp.helpers.component2 import org.transhelp.bykerr.uiRevamp.helpers.listeners.LoadDataListener import org.transhelp.bykerr.uiRevamp.lifecycleobserver.GpsDialogLifecycleObserver import org.transhelp.bykerr.uiRevamp.models.tracking.BusStopTrack import org.transhelp.bykerr.uiRevamp.models.tracking.TrackPacket import org.transhelp.bykerr.uiRevamp.models.tracking.TrackResponseAllBus import org.transhelp.bykerr.uiRevamp.ui.activities.BookTicketActivity import org.transhelp.bykerr.uiRevamp.ui.activities.HomeActivity.Companion.addGuest import org.transhelp.bykerr.uiRevamp.ui.activities.NearbyStopsAndLiveTrackActivity import org.transhelp.bykerr.uiRevamp.ui.activities.ViewRouteTrackingActivity import java.util.concurrent.atomic.AtomicInteger /** * Fragment view to display live tracking of nearby routes */ class RoutesLiveTrackFragment : BaseFragmentBinding<FragmentRoutesLiveTrackBinding, NearbyStopsAndLiveTrackActivity>(FragmentRoutesLiveTrackBinding::inflate), OnMapReadyCallback, LoadDataListener { private lateinit var initialLatLng: LatLng private lateinit var currentLatLng: LatLng private var mIsActVisible = false private var mAnimatedCameraOnce = false private var mSocket: WebSocket? = null private var mJobResponse: Job? = null private var mJobMarkerUpdateRemoveAdd: Job? = null private val mGoogleMapLiveData = GoogleMap::class.asLiveData private val mCoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace() } // mResponseAllBusObserver - holds initial array vehicle data private val mResponseAllBusObserver = TrackResponseAllBus::class.asLiveData private val mBinding by lazy { binding } private val mMarkersSparse = SparseArray<Marker>() private val mBusImg by lazy { AppUtils.resize( AppUtils.bitmapFromVector(baseActivity, R.drawable.ic_bus_tracking_solid), 16.toDp().toPx().toInt(), 16.toDp().toPx().toInt() ) } private val dp8 by lazy { TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics) } private val dp4 by lazy { TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics) } private val bitmapPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { isDither = false } private val textPaint by lazy { TextPaint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.WHITE isDither = true textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12f, resources.displayMetrics) } } private val paddingPaint by lazy { Paint(Paint.ANTI_ALIAS_FLAG).apply { color = ResourcesCompat.getColor(resources, R.color.colorPrimary, null) style = Paint.Style.FILL } } private val textBoundRect = Rect() private val mSocketListener by CustomSocketListenerImpl(onConnected = { // mBinding.root.postDelayed(3000) { // disconnectSocket() // val vv = mResponseAllBusObserver.value!! // // mResponseAllBusObserver.postValue(vv.copy(error = "wew")) // } buildSocketModel { channel `is` "1006" command `is` SocketModel.COMMAND_INITIATE_FOR_MAP currentLatLng.apply { latLng `is` SocketModel.LatLong(latitude, longitude) } send(it, baseActivity.iPreferenceHelper, baseActivity.iprefWrapper.getUserToken()) } }, onMessage = { logit("message $it") // mJobResponse?.cancel() // IO - for running on suspending process // handler - for getting any crash recorded to stacktrace and not dismiss current coroutine // SuperVisor for not cancelling subsequent coroutine children mJobResponse = lifecycleScope.launch(Dispatchers.IO + mCoroutineExceptionHandler + SupervisorJob()) { val jsonObj = JSONObject(it ?: "") val command = jsonObj.optString(SocketModel.COMMAND) if (command.isEmpty()) return@launch onMain { baseActivity.loadViewModel.isLoaded.postValue(true) } val gson = Gson() if (command.equals(SocketModel.COMMAND_INITIATE_FOR_MAP, true)) { mResponseAllBusObserver.postValue( gson.fromJson( it, TrackResponseAllBus::class.java ) ) } else if (command.equals(SocketModel.COMMAND_RECEIVER, true)) { val packet = gson.fromJson(it, TrackPacket::class.java) val v = packet?.vehicle ?: return@launch val originalData = mResponseAllBusObserver.value ?: return@launch val items = mResponseAllBusObserver.value?.vehicle.orEmpty().toMutableList() val newItem = TrackResponseAllBus.Data( v.latitude.toString(), v.longitude.toString(), v.routeNumber, v.serialNo, v.routeId, v.header, v.title ) // find such bus whose serialNo matches with existing list // and then update it contents // if no bus found add in list items.indexOf(newItem).takeIf { it != -1 }?.also { items[it] = newItem } ?: items.add(newItem) mResponseAllBusObserver.postValue( originalData.copy( vehicle = items, error = packet.error ) ) } } }) private var updateContainerWidth = 0 private var updateContainerHeight = 0 private lateinit var mCardViewRounded: RoundedCardHeaderBinding override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) lifecycle.addObserver(baseActivity.locationLifecycleObserver) mBinding.init(savedInstanceState) AppUtils.captureCleverTapEventLiveBusTracking( baseActivity.clevertapDefaultInstance, CleverTapConstants.LIVE_TRACKING__ALL_NEARBY_LIVE_ROUTES_SCREEN_VIEWED, baseActivity.iPreferenceHelper.getSelectedCityObject()?.cityName) } override fun onViewMount() { mAnimatedCameraOnce = false mMarkersSparse.clear() if (this::mCardViewRounded.isInitialized.not() || mBinding.root.contains(mCardViewRounded.root)) setupHeaderRounded() showLottie() setupMap() } override fun onDetach() { super.onDetach() disconnectSocket() mJobResponse?.cancel() mJobMarkerUpdateRemoveAdd?.cancel() mMarkersSparse.clear() mGoogleMapLiveData.value?.clear() mResponseAllBusObserver.value = null mGoogleMapLiveData.value = null } override fun onDestroyView() { super.onDestroyView() try { baseActivity.locationLifecycleObserver.let { lifecycle.removeObserver(it) } } catch (ex: Exception) { } mBinding.root.tag = null mBinding.socketBinding.root.tag = null mBinding.mapView.onDestroy() // contentResolver?.unregisterContentObserver(locationObserver) } private fun setupHeaderRounded() { if (this::mCardViewRounded.isInitialized.not()) mCardViewRounded = RoundedCardHeaderBinding.inflate( layoutInflater, mBinding.fragmentViewMapsRoot, false ) val parent = mCardViewRounded.root parent.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,LinearLayout.LayoutParams.WRAP_CONTENT).apply { leftMargin = 8.dp.px gravity = Gravity.CENTER } parent.isInvisible = true if (!mBinding.fragmentViewMapsRoot.contains(parent)) { mCardViewRounded.roundedCardHeader.measure( View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED ) mCardViewRounded.roundedCardHeader.radius = parent.measuredHeight / 2f mCardViewRounded.refreshCard.radius = mBinding.socketBinding.rootCard.height / 2f mBinding.fragmentViewMapsRoot.addView(parent) // val c = mBinding.fragmentViewMapsRoot // // ConstraintSet().apply { // clone(c) // connect(parent.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 16.dp.px) // connect( // parent.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, // 16.dp.px // ) // // applyTo(c) // } } } override fun onAttach(context: Context) { super.onAttach(context) onActivity { GpsDialogLifecycleObserver(this) { // todo: check if other classes need below logic if (isFinishing || isDestroyed || it.not()) return@GpsDialogLifecycleObserver checkLoadData() } } } private fun hideLottie(){ if(mBinding.mapLoaderLottie.isVisible.not()) return with(mBinding.mapLoaderLottie) { cancelAnimation() isVisible = false } } private fun showLottie(){ with(mBinding.mapLoaderLottie) { setAnimation(R.raw.map_live_track_loader) progress = 0f playAnimation() isVisible = true } } private var ivRefreshAnimator: ObjectAnimator? = null private val maxRetriesForGuest = AtomicInteger(2) private fun FragmentRoutesLiveTrackBinding.init(savedInstanceState: Bundle?) { mapView.onCreate(savedInstanceState) tvSearchByRoute.setOnClickListener { startActivity(Intent(baseActivity, BookTicketActivity::class.java)) } dismissNoTrack.setOnClickListener { baseActivity.finish() } socketBinding.root.setOnClickListener { if (root.tag == true) { AppUtils.captureCleverTapEventLiveBusTracking( baseActivity.clevertapDefaultInstance, CleverTapConstants.LIVE_TRACKING__REFRESH_BUTTON_CLICKED_FOR_FETCHING_LIVE_ROUTES, baseActivity.iPreferenceHelper.getSelectedCityObject()?.cityName) ivRefreshAnimator?.cancel() ivRefreshAnimator = ObjectAnimator.ofFloat( mBinding.socketBinding.ivRefresh, View.ROTATION, 0f, 360f ).apply { duration = 1000 repeatMode = ValueAnimator.RESTART repeatCount = ValueAnimator.INFINITE interpolator = AccelerateDecelerateInterpolator() start() } mBinding.socketBinding.collapseOrHide(true) { // wait like ~~~~~~ for 2s mBinding.socketBinding.root.postDelayed(2000) { setupSocket() } } } } gpsCardView.setOnClickListener { val lastLoc = baseActivity.locationLifecycleObserver.locationLiveData.value ?: return@setOnClickListener val map = mGoogleMapLiveData.value ?: return@setOnClickListener val latLng = LatLng(lastLoc.latitude, lastLoc.longitude) // mCircleMap?.center = latLng currentLatLng = latLng map.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, MIN_ZOOM)) } root.post { logit("gawd initial ${socketBinding.root.height}, ${socketBinding.rootCard.height}") socketBinding.rootCard.radius = socketBinding.root.height / 2f updateContainerWidth = socketBinding.root.width updateContainerHeight = socketBinding.root.height mapView.getMapAsync(this@RoutesLiveTrackFragment) } } private val dp56 by lazy { resources.getDimensionPixelSize(R.dimen.dp_56) } val cornerRadiusAm by lazy { ObjectAnimator.ofFloat( mBinding.socketBinding.root, "radius", updateContainerHeight / 2f, 16f ).apply { duration = 1000 } } val cornerRadiusAm2 by lazy { ObjectAnimator.ofFloat( mBinding.socketBinding.refreshCard, "radius", updateContainerHeight / 2f, 16f ).apply { duration = 1000 } } private fun SessionSocketTimeoutDialogBinding.collapseOrHide( hide: Boolean, onComplete: () -> Unit = {}, ) { if (root.tag == hide) return root.tag = hide val scaleTo: Float val scaleFrom: Float val heightTo: Int val heightFrom: Int val widthTo: Int val widthFrom: Int if (hide) { scaleTo = 0.8f scaleFrom = 1f heightTo = dp56 heightFrom = updateContainerHeight widthTo = dp56 widthFrom = updateContainerWidth } else { scaleFrom = 0.8f scaleTo = 1f heightFrom = dp56 heightTo = updateContainerHeight widthFrom = dp56 widthTo = updateContainerWidth } val anm = AnimatorSet() // val scaleAm = ValueAnimator.ofFloat(scaleFrom, scaleTo).apply { // addUpdateListener { // val v = it.animatedValue as Float // root.scaleX = v // root.scaleY = v // root.requestLayout() // } // doOnEnd { // cornerRadiusAm.start() // } // } val widthAm = ValueAnimator.ofInt(widthFrom, widthTo).apply { addUpdateListener { val v = it.animatedValue as Int val prog = it.animatedFraction root.updateLayoutParams { width = v } // if (prog == 1f) { // cornerRadiusAm.start() // cornerRadiusAm2.start() // } // if (prog > 0.6f && scaleAm.isStarted.not()) { // scaleAm.start() // } } } val heightAm = ValueAnimator.ofInt(heightFrom, heightTo).apply { addUpdateListener { val v = it.animatedValue as Int root.updateLayoutParams { height = v } } } anm.apply { duration = 400 interpolator = AnticipateInterpolator() playTogether(widthAm, heightAm) doOnEnd { onComplete() root.requestLayout() if (widthTo == dp56) { } } start() } } private suspend fun isMarkerInBounds( newLatLng: LatLng, ) = withContext(Dispatchers.Main) { latLngBoundsScreen.contains( newLatLng ) } private suspend fun isMarkerInBounds( marker: Marker, ) = withContext(Dispatchers.Main) { latLngBoundsScreen.contains( marker.position ) } private suspend fun isMarkerInVisibleRegion( newLatLng: LatLng, googleMap: GoogleMap?, ): Boolean { // googleMap?.projection?.toScreenLocation(newLatLng) return withContext(Dispatchers.Main) { googleMap?.projection?.visibleRegion?.latLngBounds?.contains( newLatLng ) ?: false } } // private var mCircleMap: Circle? = null private val errorResponse by lazy { getString(R.string.socket_401) } @SuppressLint("PotentialBehaviorOverride") private fun setupMap() { // locationLifecycleObserver.locationLiveData.observe(this) { // if (it == null) return@observe // val (lat, lng) = it // val latLng = LatLng(lat, lng) // val (x, y) = mGoogleMapLiveData.value?.projection?.toScreenLocation(latLng) // ?: return@observe // mBinding.wew.x = x.toFloat() - (mBinding.wew.height / 2f) // // subtract extra toolbar height since map parent has toolbar and `y` here is from root parent // mBinding.wew.y = y.toFloat() - (mBinding.wew.height / 2f - mBinding.toolbarBinding.root.height) // // logit("Wew ${mBinding.wew.y}, ${mBinding.wew.x}") // if(mBinding.wew.y >= 0f && mBinding.wew.x >= 0f) // mBinding.wew.invalidate() // } mGoogleMapLiveData.observe(viewLifecycleOwner) { if(it == null) return@observe setupSocket() } onActivity { mResponseAllBusObserver.observe(viewLifecycleOwner) { responseItem -> logit("Response $responseItem") if(responseItem == null) return@observe val error = responseItem.error if (error.isNullOrEmpty().not() && mBinding.root.tag == null) { val isAuthError = error?.equals(errorResponse, true) == true // mBinding.ivQuit.performClick() if (isAuthError) { clearLoggedOutUserSession(true,::setupSocket) return@observe } mBinding.socketBinding.root.animate().alpha(1f).withEndAction { mBinding.socketBinding.collapseOrHide(false) mBinding.root.tag = true disconnectSocket() }.start() return@observe } val googleMap = mGoogleMapLiveData.value ?: return@observe if (mIsActVisible.not()) return@observe val response = when { responseItem.data.isNullOrEmpty().not() -> responseItem.data responseItem.vehicle.isNullOrEmpty().not() -> responseItem.vehicle else -> null } if(mCardViewRounded.root.isInvisible){ mCardViewRounded.root.isVisible = true } mCardViewRounded.time = AppUtils.getCurrentTimehhmmss() lifecycleScope.launch(Dispatchers.Default + mCoroutineExceptionHandler) { for (it in response.orEmpty()) { if (mIsActVisible.not()) return@launch addOrAnimateMarker(it, googleMap) // if (mAnimatedCameraOnce.not()) { // locationLiveData.value?.let { // val (latitude, longitude) = it // val current = LatLng(latitude, longitude) // // withContext(Dispatchers.Main) { // map.animateCamera(CameraUpdateFactory.newLatLng(current)) // mAnimatedCameraOnce = true // // } // } // } // todo: test garbage collector doesn't creates too much racing condition in gpu and cpu // by default hardware acceleration is enabled, performance may degrade if buffer swap is high System.gc() } } } } } private suspend fun addOrAnimateMarker( it: TrackResponseAllBus.Data, googleMap: GoogleMap, checkVisibleBounds: Boolean = false, ) { if (it.routeNumber.isEmpty()) return val serialNo = it.serialNo val imeiHash = serialNo.hashCode() val oldMkr = mMarkersSparse.get(imeiHash) val newLatLng = LatLng(it.latitude.toDouble(), it.longitude.toDouble()) // 1. Look for marker in memory // 2. If exist // a. Update if in visible region // b. Update if not in transition with clustering // 3. Else // 4. Add to sparseArray // 5. Cluster all sparseArray if (newLatLng.latitude != 0.0 || newLatLng.longitude != 0.0) { (oldMkr != null).let { has -> if (has) { onMain { val oldPos = oldMkr.position if (oldPos == newLatLng) return@onMain logit("marker $serialNo from $oldPos to $newLatLng") ValueAnimator.ofObject( LatLngEvaluator(), oldPos, newLatLng, ).apply { duration = 2.msFromSec addUpdateListener { oldMkr.position = it.animatedValue as LatLng } start() } } } else { MarkerOptions().apply { position(newLatLng) snippet( JSONObject() .put("routeId", it.routeId) .put("routeNumber", it.routeNumber) .toString() ) // icon(getBitmapMarker(it.routeNumber)) icon(getBitmapMarkerFromView(it.routeNumber.trim())) }.apply { onMain { mMarkersSparse[imeiHash] = googleMap.addMarker(this) logit("Added marker to pool $it") } } } } } } private fun disconnectSocket() { logit("Socket disc....") mSocket?.disconnect() mSocket?.removeListener(mSocketListener) mSocket = null } override fun onStop() { super.onStop() mIsActVisible = false mBinding.mapView.onStop() disconnectSocket() } override fun onStart() { super.onStart() mIsActVisible = true mBinding.mapView.onStart() if (mGoogleMapLiveData.value != null) setupSocket() // if(isGPSEnabled().not()) // showGPSEnablePopup() } private fun setupSocket() = onActivity{ hideLottie() if(isUserOutOfBounds){ logit("Gawd") return } if (isGPSEnabled().not() || this@RoutesLiveTrackFragment::currentLatLng.isInitialized.not()) return if(ConnectivityManagerHelper.isNetworkAvailable(applicationContext).not()) { loadViewModel.isLoaded.postValue(false) return } logit("Creating socket") if (mBinding.root.tag == true) { ivRefreshAnimator?.cancel() ivRefreshAnimator = null mBinding.root.tag = null mBinding.socketBinding.root.animate().alpha(0f).start() } if (mSocket != null) disconnectSocket() mSocket = setupLiveTrackSocket().apply { addListener(mSocketListener) connectAsynchronously() } } companion object { private const val MIN_ZOOM = 16f private const val MAX_DISTANCE_TO_FETCH_IN_METRE = 250 private const val INITIAL_ZOOM = 16f private const val INITIAL_CLUSTER_CLICK_ZOOM = 3f } private lateinit var latLngBoundsScreen: LatLngBounds private var isUserOutOfBounds = true override fun onMapReady(map: GoogleMap) { map.setMapStyle( MapStyleOptions.loadRawResourceStyle( baseActivity, R.raw.style_json ) ) val uiSettings: UiSettings? = map.uiSettings uiSettings?.apply { setAllGesturesEnabled(false) isCompassEnabled = false isMyLocationButtonEnabled = false isMapToolbarEnabled = false isZoomGesturesEnabled = true isScrollGesturesEnabled = true } map.setMinZoomPreference(MIN_ZOOM) val locationCurrent = baseActivity.locationLifecycleObserver.locationLiveData.value logit("Loc from int $locationCurrent") locationCurrent?.also { val (lat, lng) = it val latLng = LatLng(lat, lng) initialLatLng = latLng currentLatLng = latLng mAnimatedCameraOnce = true map.animateCamera( CameraUpdateFactory.newLatLng( latLng ) ) } // p0.moveCamera(CameraUpdateFactory.newLatLngZoom(p0.cameraPosition.target, INITIAL_ZOOM)) map.isMyLocationEnabled = ActivityCompat.checkSelfPermission( baseActivity, Manifest.permission.ACCESS_FINE_LOCATION ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( baseActivity, Manifest.permission.ACCESS_COARSE_LOCATION ) == PackageManager.PERMISSION_GRANTED baseActivity.iPreferenceHelper.getSelectedCityObject()?.cityName?.lowercase() ?.let { state -> val bounds = AppUtils.getCityLatLngBounds(state, baseActivity.getCityModelFromRemoteConfig()) ?: return@let if(this::initialLatLng.isInitialized) { isUserOutOfBounds = bounds.contains(initialLatLng).not() // todo: needs testing mBinding.unableToShowLiveTrack.text = getString(R.string.unable_to_show_live_routes_nearby_looks_like_you_are_out_of_b_s_b, state?.capitalize()) mBinding.noTrackView.isVisible = isUserOutOfBounds } map.setLatLngBoundsForCameraTarget(bounds) } map.setOnMapLoadedCallback { mBinding.socketBinding.collapseOrHide(true) { baseActivity.locationLifecycleObserver.locationLiveData.value?.also { val (lat, lng) = it val latLng = LatLng(lat, lng) if (this::initialLatLng.isInitialized.not()) initialLatLng = latLng if (this::currentLatLng.isInitialized.not()) currentLatLng = latLng latLngBoundsScreen = LatLngBounds.builder().run { val (x, y, width, height) = mBinding.layoutCircle // symmetric margins val margins = mBinding.layoutCircle.marginLeft include( map.projection.fromScreenLocation(Point(x + margins, y - dp56)).apply { }) map.addCircle(CircleOptions().apply { this.center(latLng) this.radius(AppConstants.DEFAULT_RADIUS_IN_METERS) this.fillColor( ContextCompat.getColor( baseActivity, R.color.bg_green_light ) ) this.strokeColor(Color.BLACK) this.strokeWidth(1f) }) include( map.projection.fromScreenLocation( Point( x + (width - margins), y + (height - margins) ) ) ) build() } logit("bounds $latLngBoundsScreen, ${latLngBoundsScreen.contains(latLng)}") mGoogleMapLiveData.value?.clear() setupMarkerListener(map) if(isUserOutOfBounds.not()) mGoogleMapLiveData.postValue(map) } } } } private var mDidCameraMove = false /** * default algo for clustering [PreCachingAlgorithmDecorator] */ @SuppressLint("PotentialBehaviorOverride") private fun setupMarkerListener(map: GoogleMap) { map.setOnMarkerClickListener { AppUtils.captureCleverTapEventLiveBusTracking( baseActivity.clevertapDefaultInstance, CleverTapConstants.LIVE_TRACKING__BUS_ROUTE_MAP_MARKER_CLICKED, baseActivity.iPreferenceHelper.getSelectedCityObject()?.cityName) logit("marker clicked ${it.snippet}") val snip = it.snippet ?: return@setOnMarkerClickListener true val obj = JSONObject(snip) val routeNumber = obj.optString("routeNumber") val routeId = obj.getString("routeId") if (routeNumber.isNullOrEmpty()) { return@setOnMarkerClickListener true } ViewRouteTrackingActivity.start( baseActivity, routeId, routeNumber ) true } map.setOnCameraMoveStartedListener { logit("Wew moving $it") mDidCameraMove = it == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE || it == GoogleMap.OnCameraMoveStartedListener.REASON_DEVELOPER_ANIMATION } map.setOnCameraMoveCanceledListener { mDidCameraMove = false } val maxDistanceAllowed = 0.05f // 500m map.setOnCameraMoveListener { val targetLat = map.cameraPosition.target.latitude val targetLng = map.cameraPosition.target.longitude lifecycleScope.launch(Dispatchers.Default) { // distance in metres BusStopTrack.getDistance( targetLat, targetLng, initialLatLng.latitude, initialLatLng.longitude ).toFloat().takeIf { it <= maxDistanceAllowed }?.let { distance -> onMain { mBinding.imgLocationPinUp.apply { scaleX = distance.div(maxDistanceAllowed) scaleY = distance.div(maxDistanceAllowed) } } } // logit("gawd $distance") } } map.setOnCameraIdleListener { logit("Wew Moved $mDidCameraMove") if (!mDidCameraMove) return@setOnCameraIdleListener mBinding.mapView.postDelayed(2000){ synchronized(this) { mJobMarkerUpdateRemoveAdd?.cancel() mJobMarkerUpdateRemoveAdd = lifecycleScope.launch(Dispatchers.Default) { if (isActive.not()) return@launch onMain { val targetLat = map.cameraPosition.target.latitude val targetLng = map.cameraPosition.target.longitude val movedDistanceInMet = BusStopTrack.getDistance( currentLatLng.latitude, currentLatLng.longitude, targetLat, targetLng ).times(1000) currentLatLng = map.cameraPosition.target if (movedDistanceInMet > MAX_DISTANCE_TO_FETCH_IN_METRE) { setupSocket() } } // animates marker if they are updated // val responseItem = mResponseAllBusObserver.value ?: return@launch // val response = when { // responseItem.data.isNullOrEmpty().not() -> responseItem.data // responseItem.vehicle.isNullOrEmpty().not() -> responseItem.vehicle // else -> return@launch // } // val visibleRoutes = response.orEmpty().filter { // isMarkerInVisibleRegion( // LatLng( // it.latitude.toDouble(), // it.longitude.toDouble() // ), map // ) // } // // for (data in visibleRoutes) { // if (isActive.not()) break // addOrAnimateMarker(data, map, true) // } } } } // mMarkersSparse.removeAt(mMarkersSparse.indexOfValue(it)) // lifecycleScope.launch(Dispatchers.Default) { // mMarkersSparse.valueIterator().asSequence().toList().filter { // isMarkerInVisibleRegion(it.position, mGoogleMapLiveData.value).not() // }.let { // mClusterManager.removeItems(it) // } // } } } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { baseActivity.onBackPressed() } return super.onOptionsItemSelected(item) } private val defaultClusterItemView by lazy { layoutInflater.inflate(R.layout.default_cluster_item, mBinding.root, false) } // private fun getBitmapMarkerFromView(busNo: String): BitmapDescriptor { // val v = defaultClusterItemView // v.findViewById<TextView>(R.id.busNo).text = busNo // v.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED) // // val b = Bitmap.createBitmap(v.measuredWidth, v.measuredHeight, Bitmap.Config.ARGB_8888) // val c = Canvas(b) // v.layout(0, 0, v.measuredWidth, v.measuredHeight) // v.draw(c) // return BitmapDescriptorFactory.fromBitmap(b) // } private fun getBitmapMarkerFromView(busNo: String): BitmapDescriptor = run { defaultClusterItemView.findViewById<TextView>(R.id.busNo).text = busNo defaultClusterItemView.measure( View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED ) BitmapDescriptorFactory.fromBitmap( Bitmap.createBitmap( defaultClusterItemView.measuredWidth, defaultClusterItemView.measuredHeight, Bitmap.Config.ARGB_8888 ).apply { val c = Canvas(this) defaultClusterItemView.layout( 0, 0, defaultClusterItemView.measuredWidth, defaultClusterItemView.measuredHeight ) defaultClusterItemView.draw(c) } ) } private fun getBitmapMarker(busNo: String): BitmapDescriptor { val busImg = mBusImg ?: return BitmapDescriptorFactory.defaultMarker() textPaint.getTextBounds(busNo, 0, busNo.length, textBoundRect) val (textWidth, textHeight) = textBoundRect.width() to textBoundRect.height() val (imgWidth, imgHeight) = busImg.width to busImg.height val roundedBgRect = Rect( 0, dp4.toInt(), dp8.toInt() + imgWidth + dp8.toInt() + textWidth + dp4.toInt(), imgHeight + dp4.toInt() + dp4.toInt() + dp4.toInt() ) val bitmap = Bitmap.createBitmap( roundedBgRect.width(), roundedBgRect.height(), Bitmap.Config.ARGB_8888 ) val canvas = Canvas(bitmap) canvas.drawRoundRect( RectF( 0f, 0f, roundedBgRect.width().toFloat(), roundedBgRect.height().toFloat() ), dp4, dp4, paddingPaint ) canvas.drawBitmap(busImg, dp4, dp4, bitmapPaint) canvas.drawText( busNo, dp4 + dp4.div(2) + imgWidth + dp4, textHeight.div(2f) + roundedBgRect.height().div(2), textPaint ) logit("Size alloc ${bitmap.allocationByteCount / 1024f} KB, for route $busNo, canvas ${canvas.height}, ${canvas.width}") return BitmapDescriptorFactory.fromBitmap(bitmap) } override fun checkLoadData() { logit("socket Gawd") if (mGoogleMapLiveData.value != null) setupSocket() } }