Untitled
unknown
kotlin
2 years ago
39 kB
10
Indexable
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()
}
}Editor is loading...