From 557735ba664dfbcaed85954f69903af9c55eb922 Mon Sep 17 00:00:00 2001 From: Michael Angelo Reyes Date: Wed, 8 Jan 2025 01:42:54 +0800 Subject: [PATCH 1/2] [23029] - [TripGov5] update BaseApplication to initialize DeviceInfo singleton on app start - [TripKitUI] update createStopMarkerOptions and add an extension for generating marker options with remote checking and fetching - [TripKitUI] add DeviceInfo to set density dpi name once initialized from app start - [TripKitUI] add MarkerOptionsTarget for picasso target specifically for MarkerOptions loading - [TripKitUI] add RemoteMarkerIconFetcher to handle loading of marker icon using picasso and setting it on the MarkerOptions asynchronously - [TripKitUI] update StopMarkerUtils and generating of marker icon url from modeInfo using static endpoint - [TripKitUI] update StopPOILocation and call createStopMarkerOptions and pass picasso instance --- .../tripkit/ui/map/MarkerOptionsTarget.kt | 41 ++++++ .../tripkit/ui/map/RemoteMarkerIconFetcher.kt | 122 ++++++++++++++++++ .../skedgo/tripkit/ui/map/StopPOILocation.kt | 3 +- .../tripkit/ui/map/createStopMarkerOptions.kt | 50 +++++-- .../tripkit/ui/map/home/TripKitMapFragment.kt | 5 - .../com/skedgo/tripkit/ui/utils/DeviceInfo.kt | 16 +++ .../tripkit/ui/utils/StopMarkerUtils.kt | 15 +++ 7 files changed, 233 insertions(+), 19 deletions(-) create mode 100644 TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/MarkerOptionsTarget.kt create mode 100644 TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt create mode 100644 TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/DeviceInfo.kt diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/MarkerOptionsTarget.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/MarkerOptionsTarget.kt new file mode 100644 index 00000000..90dd7717 --- /dev/null +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/MarkerOptionsTarget.kt @@ -0,0 +1,41 @@ +package com.skedgo.tripkit.ui.map + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.MarkerOptions +import com.squareup.picasso.Picasso.LoadedFrom +import com.squareup.picasso.Target +import java.lang.ref.WeakReference + +class MarkerOptionsTarget( + /** + * If a marker was removed from a map, mutating + * its icon is unnecessary, and + * we should let it be GC-ed quickly as possible. + * That's why a weak reference is used. + */ + private val markerOptionsWeakReference: WeakReference +) : Target { + + override fun onBitmapLoaded(bitmap: Bitmap?, from: LoadedFrom?) { + bitmap?.let { + val actualMarkerOptions = markerOptionsWeakReference.get() + val icon = BitmapDescriptorFactory.fromBitmap(it) + actualMarkerOptions?.icon(icon) + } ?: run { + println("bitmap is null") + } + } + + override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { + val actualMarkerOptions = markerOptionsWeakReference.get() + val fallbackIcon = + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) + actualMarkerOptions?.icon(fallbackIcon) + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable?) { + // Placeholder if needed + } +} \ No newline at end of file diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt new file mode 100644 index 00000000..f3f5c8b2 --- /dev/null +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt @@ -0,0 +1,122 @@ +package com.skedgo.tripkit.ui.map + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.PorterDuff.Mode.SRC_IN +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Drawable +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.MarkerOptions +import com.skedgo.tripkit.routing.ModeInfo +import com.skedgo.tripkit.ui.utils.DeviceInfo +import com.skedgo.tripkit.ui.utils.StopMarkerUtils.getStaticMapIconUrlForModeInfo +import com.squareup.picasso.Picasso +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import java.lang.ref.WeakReference +import javax.inject.Inject + +class RemoteMarkerIconFetcher @Inject constructor( + private val picasso: Picasso +) { + + companion object { + const val SIZE_CIRCULAR_BITMAP = 60 + const val TINT_BITMAP_RGB = 255 + } + + fun call(markerOptions: MarkerOptions, modeInfo: ModeInfo?) { + modeInfo?.let { + val url = getStaticMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), it) + picasso.load(url).into(MarkerOptionsTarget(WeakReference(markerOptions))) + } + } + + fun callAsync(markerOptions: MarkerOptions, modeInfo: ModeInfo?): Single { + val iconUrl = getStaticMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), modeInfo) + return Single.defer { + Single.create { emitter -> + picasso.load(iconUrl) + .into(object : com.squareup.picasso.Target { + override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { + bitmap?.let { + val circularBitmap = createCircularMarkerBitmap( + it, + Color.rgb(TINT_BITMAP_RGB, TINT_BITMAP_RGB, TINT_BITMAP_RGB), + Color.rgb( + modeInfo?.color?.red ?: 0, + modeInfo?.color?.green ?: 0, + modeInfo?.color?.blue ?: 0 + ), + SIZE_CIRCULAR_BITMAP + ) + + val scaledBitmap = Bitmap.createScaledBitmap( + circularBitmap, + SIZE_CIRCULAR_BITMAP, SIZE_CIRCULAR_BITMAP, false + ) + + val icon = BitmapDescriptorFactory.fromBitmap(scaledBitmap) + markerOptions.icon(icon) + emitter.onSuccess(markerOptions) + } ?: run { + emitter.onError(Throwable("Bitmap is null")) + } + } + + override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { + e?.printStackTrace() + val fallbackIcon = + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) + markerOptions.icon(fallbackIcon) + emitter.onSuccess(markerOptions) + } + + override fun onPrepareLoad(placeHolderDrawable: Drawable?) { + // Placeholder if needed + } + }) + } + }.subscribeOn(AndroidSchedulers.mainThread()) + } + + private fun createCircularMarkerBitmap( + bitmap: Bitmap, + tintColor: Int, + circleColor: Int, + circleRadius: Int + ): Bitmap { + val output = Bitmap.createBitmap(circleRadius * 2, circleRadius * 2, Bitmap.Config.ARGB_8888) + val canvas = Canvas(output) + + // Draw the circular background + val paint = Paint().apply { + isAntiAlias = true + color = circleColor + } + canvas.drawCircle(circleRadius.toFloat(), circleRadius.toFloat(), circleRadius.toFloat(), paint) + + // Apply tint to the bitmap + val tintedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) + val bitmapCanvas = Canvas(tintedBitmap) + val tintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + colorFilter = PorterDuffColorFilter(tintColor, SRC_IN) + } + bitmapCanvas.drawBitmap(tintedBitmap, 0f, 0f, tintPaint) + + // Draw the tinted bitmap onto the circular background + val scaledBitmap = Bitmap.createScaledBitmap( + tintedBitmap, + circleRadius, + circleRadius, + true + ) + val left = (output.width - scaledBitmap.width) / 2f + val top = (output.height - scaledBitmap.height) / 2f + canvas.drawBitmap(scaledBitmap, left, top, null) + + return output + } +} \ No newline at end of file diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/StopPOILocation.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/StopPOILocation.kt index 5b447c9d..f2237ca9 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/StopPOILocation.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/StopPOILocation.kt @@ -5,6 +5,7 @@ import android.content.res.Resources import com.google.android.gms.maps.model.MarkerOptions import com.skedgo.tripkit.common.model.location.Location import com.skedgo.tripkit.common.model.stop.ScheduledStop +import com.skedgo.tripkit.data.locations.StopsFetcher import com.skedgo.tripkit.ui.map.adapter.StopInfoWindowAdapter import com.skedgo.tripkit.ui.tracking.EventTracker import com.squareup.otto.Bus @@ -20,7 +21,7 @@ class StopPOILocation( resources: Resources, picasso: Picasso ): Single { - return scheduledStop.createStopMarkerOptions() + return scheduledStop.createStopMarkerOptions(picasso) } override fun getInfoWindowAdapter(context: Context): StopInfoWindowAdapter? { diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt index 2f622481..8378cf8e 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt @@ -7,27 +7,51 @@ import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.MarkerOptions import com.skedgo.tripkit.common.model.stop.ScheduledStop import com.skedgo.tripkit.ui.utils.BindingConversions +import com.squareup.picasso.Picasso import io.reactivex.Single fun ScheduledStop.createStopMarkerOptions(): Single { - return Single.fromCallable { - val stop = this - val title = stop.getStopDisplayName() - - val markerOptions = MarkerOptions() - markerOptions.title(title) - markerOptions.snippet(stop.services) - markerOptions.position(LatLng(stop.lat, stop.lon)) - markerOptions.draggable(false) + val stop = this + val title = stop.getStopDisplayName() + val markerOptions = MarkerOptions() + .title(title) + .snippet(stop.services) + .position(LatLng(stop.lat, stop.lon)) + .draggable(false) + return Single.fromCallable { val iconRes = BindingConversions.convertStopTypeToMapIconRes(stop.type) - val icon: BitmapDescriptor - if (iconRes == 0) { - icon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) + val icon: BitmapDescriptor = if (iconRes == 0) { + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) } else { - icon = BitmapDescriptorFactory.fromResource(iconRes) + BitmapDescriptorFactory.fromResource(iconRes) } markerOptions.icon(icon) + markerOptions + } +} +fun ScheduledStop.createStopMarkerOptions(picasso: Picasso): Single { + val stop = this + val title = stop.getStopDisplayName() + val markerOptions = MarkerOptions() + .title(title) + .snippet(stop.services) + .position(LatLng(stop.lat, stop.lon)) + .draggable(false) + return if (stop.modeInfo?.remoteIconIsTemplate == true) { + val remoteMarkerIconFetcher = RemoteMarkerIconFetcher(picasso) + remoteMarkerIconFetcher.callAsync(markerOptions, stop.modeInfo) + } else { + Single.fromCallable { + val iconRes = BindingConversions.convertStopTypeToMapIconRes(stop.type) + val icon: BitmapDescriptor = if (iconRes == 0) { + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) + } else { + BitmapDescriptorFactory.fromResource(iconRes) + } + markerOptions.icon(icon) + markerOptions + } } } diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/home/TripKitMapFragment.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/home/TripKitMapFragment.kt index 61710e69..40c2bbc3 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/home/TripKitMapFragment.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/home/TripKitMapFragment.kt @@ -4,9 +4,7 @@ import android.annotation.SuppressLint import android.app.Application import android.content.Context import android.content.SharedPreferences -import android.graphics.Bitmap import android.os.Bundle -import android.util.Log import android.view.View import android.widget.Toast import com.araujo.jordan.excuseme.ExcuseMe @@ -38,8 +36,6 @@ import com.skedgo.tripkit.common.model.region.Region import com.skedgo.tripkit.common.model.region.Region.City import com.skedgo.tripkit.common.model.TransportMode import com.skedgo.tripkit.data.regions.RegionService -import com.skedgo.tripkit.routing.ModeInfo -import com.skedgo.tripkit.routing.VehicleDrawables import com.skedgo.tripkit.tripplanner.NonCurrentType import com.skedgo.tripkit.tripplanner.PinUpdate import com.skedgo.tripkit.ui.R @@ -47,7 +43,6 @@ import com.skedgo.tripkit.ui.TripKitUI import com.skedgo.tripkit.ui.core.addTo import com.skedgo.tripkit.ui.core.module.HomeMapFragmentModule import com.skedgo.tripkit.ui.data.toLocation -import com.skedgo.tripkit.ui.map.BearingMarkerIconBuilder import com.skedgo.tripkit.ui.map.GenericIMapPoiLocation import com.skedgo.tripkit.ui.map.IMapPoiLocation import com.skedgo.tripkit.ui.map.LocationEnhancedMapFragment diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/DeviceInfo.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/DeviceInfo.kt new file mode 100644 index 00000000..fb66d427 --- /dev/null +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/DeviceInfo.kt @@ -0,0 +1,16 @@ +package com.skedgo.tripkit.ui.utils + +import android.content.Context +import com.skedgo.tripkit.common.util.TransportModeUtils + +object DeviceInfo { + var densityDpi: Int = 0 + private set + + fun init(context: Context) { + val metrics = context.resources.displayMetrics + densityDpi = metrics.densityDpi + } + + fun getDensityDpiName(): String = TransportModeUtils.getDensityDpiName(densityDpi) +} \ No newline at end of file diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/StopMarkerUtils.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/StopMarkerUtils.kt index 69f8c612..fc42744e 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/StopMarkerUtils.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/utils/StopMarkerUtils.kt @@ -7,6 +7,7 @@ import com.skedgo.tripkit.configuration.ServerManager import com.skedgo.tripkit.routing.ModeInfo +// object StopMarkerUtils { private val MAP_ICON_URL_TEMPLATE_PRODUCTION = ServerManager.configuration.staticTripGoUrl + "icons/android/%s/ic_map_%s.png" @@ -14,6 +15,8 @@ object StopMarkerUtils { ServerManager.configuration.staticTripGoUrl + "icons/android/%s/ic_map_marker_%s.png" private val MAP_ICON_URL_TEMPLATE_BETA = ServerManager.configuration.bigBangUrl + "modeicons/android/%s/ic_map_%s.png" + private val MAP_ICON_URL_TEMPLATE_STATIC = + ServerManager.configuration.staticTripGoUrl + "icons/android/%s/ic_transport_%s.png" fun getMapIconUrlForModeInfo(resources: Resources, modeInfo: ModeInfo?): String? { if (modeInfo == null || modeInfo.remoteIconName == null) { @@ -29,6 +32,18 @@ object StopMarkerUtils { ) } + fun getStaticMapIconUrlForModeInfo(densityDpiName: String, modeInfo: ModeInfo?): String? { + if (modeInfo?.remoteIconName == null) { + return null + } + + return String.format( + MAP_ICON_URL_TEMPLATE_STATIC, + densityDpiName, + modeInfo.remoteIconName + ) + } + fun getMapIconUrlForModeInfo(resources: Resources, remoteIcon: String?): List? { if (remoteIcon == null) { return null From 7ddf5e6ab50f095d453d36899195b72f05e0f822 Mon Sep 17 00:00:00 2001 From: Michael Angelo Reyes Date: Thu, 16 Jan 2025 12:51:29 +0800 Subject: [PATCH 2/2] [23029] - [TripKitUI] update createStopMarkerOptions to only load marker from RemoteMarkerIconFetcher - [TripKitUI] update RemoteMarkerIconFetcher update circle bitmap with aspect ratio checking to properly render the bitmap inside the circle bitmap and add a white border. Also add error handling to load local resource. - [TripKitUI] update StopMarkerUtils and add function for getting localIconName with density from modeInfo --- .../tripkit/ui/map/RemoteMarkerIconFetcher.kt | 98 ++++++++++++++----- .../tripkit/ui/map/createStopMarkerOptions.kt | 15 +-- .../tripkit/ui/utils/StopMarkerUtils.kt | 14 ++- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt index f3f5c8b2..7521021c 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/RemoteMarkerIconFetcher.kt @@ -4,14 +4,21 @@ import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint +import android.graphics.PorterDuff import android.graphics.PorterDuff.Mode.SRC_IN import android.graphics.PorterDuffColorFilter import android.graphics.drawable.Drawable +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.skedgo.tripkit.common.model.stop.ScheduledStop +import com.skedgo.tripkit.common.model.stop.StopType import com.skedgo.tripkit.routing.ModeInfo +import com.skedgo.tripkit.ui.BuildConfig +import com.skedgo.tripkit.ui.utils.BindingConversions import com.skedgo.tripkit.ui.utils.DeviceInfo -import com.skedgo.tripkit.ui.utils.StopMarkerUtils.getStaticMapIconUrlForModeInfo +import com.skedgo.tripkit.ui.utils.StopMarkerUtils.getLocalMapIconUrlForModeInfo +import com.skedgo.tripkit.ui.utils.StopMarkerUtils.getRemoteMapIconUrlForModeInfo import com.squareup.picasso.Picasso import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers @@ -23,19 +30,25 @@ class RemoteMarkerIconFetcher @Inject constructor( ) { companion object { - const val SIZE_CIRCULAR_BITMAP = 60 + const val SIZE_CIRCULAR_BITMAP = 30 const val TINT_BITMAP_RGB = 255 } fun call(markerOptions: MarkerOptions, modeInfo: ModeInfo?) { modeInfo?.let { - val url = getStaticMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), it) + val url = getRemoteMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), it) picasso.load(url).into(MarkerOptionsTarget(WeakReference(markerOptions))) } } - fun callAsync(markerOptions: MarkerOptions, modeInfo: ModeInfo?): Single { - val iconUrl = getStaticMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), modeInfo) + fun callAsync(markerOptions: MarkerOptions, stop: ScheduledStop): Single { + val modeInfo = stop.modeInfo + val iconUrl = + if(modeInfo?.remoteIconIsTemplate == true) { + getRemoteMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), modeInfo) + } else { + getLocalMapIconUrlForModeInfo(DeviceInfo.getDensityDpiName(), modeInfo) + } return Single.defer { Single.create { emitter -> picasso.load(iconUrl) @@ -53,12 +66,7 @@ class RemoteMarkerIconFetcher @Inject constructor( SIZE_CIRCULAR_BITMAP ) - val scaledBitmap = Bitmap.createScaledBitmap( - circularBitmap, - SIZE_CIRCULAR_BITMAP, SIZE_CIRCULAR_BITMAP, false - ) - - val icon = BitmapDescriptorFactory.fromBitmap(scaledBitmap) + val icon = BitmapDescriptorFactory.fromBitmap(circularBitmap) markerOptions.icon(icon) emitter.onSuccess(markerOptions) } ?: run { @@ -67,52 +75,96 @@ class RemoteMarkerIconFetcher @Inject constructor( } override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { - e?.printStackTrace() - val fallbackIcon = - BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) - markerOptions.icon(fallbackIcon) - emitter.onSuccess(markerOptions) + if(BuildConfig.DEBUG) { + e?.printStackTrace() + } + emitter.onError(e ?: Throwable("Bitmap failed to load")) } override fun onPrepareLoad(placeHolderDrawable: Drawable?) { // Placeholder if needed } }) + }.onErrorResumeNext { + // Fallback to local resource-based marker icon + getMapIconFromResource(markerOptions, stop.type) } }.subscribeOn(AndroidSchedulers.mainThread()) } + private fun getMapIconFromResource(markerOptions: MarkerOptions, type: StopType?): Single { + return Single.fromCallable { + val iconRes = BindingConversions.convertStopTypeToMapIconRes(type) + val icon: BitmapDescriptor = if (iconRes == 0) { + BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW) + } else { + BitmapDescriptorFactory.fromResource(iconRes) + } + markerOptions.icon(icon) + markerOptions + } + } + private fun createCircularMarkerBitmap( bitmap: Bitmap, tintColor: Int, circleColor: Int, circleRadius: Int ): Bitmap { + // Create a new bitmap for the output val output = Bitmap.createBitmap(circleRadius * 2, circleRadius * 2, Bitmap.Config.ARGB_8888) val canvas = Canvas(output) - // Draw the circular background - val paint = Paint().apply { + // Draw the white border + val borderPaint = Paint().apply { + isAntiAlias = true + color = Color.WHITE + style = Paint.Style.STROKE + strokeWidth = circleRadius * 0.1f // Border thickness is 10% of the radius + } + canvas.drawCircle(circleRadius.toFloat(), circleRadius.toFloat(), circleRadius.toFloat() - (borderPaint.strokeWidth / 2), borderPaint) + + // Draw the circular background inside the border + val backgroundPaint = Paint().apply { isAntiAlias = true color = circleColor } - canvas.drawCircle(circleRadius.toFloat(), circleRadius.toFloat(), circleRadius.toFloat(), paint) + canvas.drawCircle(circleRadius.toFloat(), circleRadius.toFloat(), circleRadius.toFloat() - borderPaint.strokeWidth, backgroundPaint) // Apply tint to the bitmap val tintedBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true) val bitmapCanvas = Canvas(tintedBitmap) val tintPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - colorFilter = PorterDuffColorFilter(tintColor, SRC_IN) + colorFilter = PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN) } bitmapCanvas.drawBitmap(tintedBitmap, 0f, 0f, tintPaint) - // Draw the tinted bitmap onto the circular background + // Scale the bitmap to fit inside the circle while maintaining the aspect ratio + val aspectRatio = bitmap.width.toFloat() / bitmap.height.toFloat() + val targetWidth: Int + val targetHeight: Int + + if (aspectRatio > 1) { + // Landscape orientation: Width is greater than height + targetWidth = circleRadius * 2 + targetHeight = (targetWidth / aspectRatio).toInt() + } else if(aspectRatio == 1f) { + targetHeight = circleRadius + targetWidth = circleRadius + } else { + // Portrait orientation: Height is greater than or equal to width + targetHeight = circleRadius * 2 + targetWidth = (targetHeight * aspectRatio).toInt() + } + val scaledBitmap = Bitmap.createScaledBitmap( tintedBitmap, - circleRadius, - circleRadius, + targetWidth, + targetHeight, true ) + + // Draw the scaled bitmap at the center of the circular background val left = (output.width - scaledBitmap.width) / 2f val top = (output.height - scaledBitmap.height) / 2f canvas.drawBitmap(scaledBitmap, left, top, null) diff --git a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt index 8378cf8e..16b8d5ee 100644 --- a/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt +++ b/TripKitAndroidUI/src/main/java/com/skedgo/tripkit/ui/map/createStopMarkerOptions.kt @@ -38,20 +38,9 @@ fun ScheduledStop.createStopMarkerOptions(picasso: Picasso): Single