Skip to content

the-best-is-best/kgoogle-map

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

30 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

KGoogleMap


KGoogleMap provides a unified API that allows developers to implement Google Maps functionalities in both Android and iOS applications with minimal platform-specific code


Maven Central

KGoogleMap is available on mavenCentral().

Thumbnail

Install

implementation("io.github.the-best-is-best:kgoogle-map:1.0.2")
implementation("io.github.the-best-is-best:klocation:1.0.6")

Need add this in pod file if not exist run pod init

  pod 'KGoogleMap', '0.1.5'

How to use it

First in iosMain

fun MainViewController(): UIViewController
{
    IOSKLocationServices().requestPermission()
    IOSKGoogleMap.init("YOUR GOOGLE MAP KEY")

    return ComposeUIViewController { App() }
}

Second in android Main

AndroidKGoogleMap.initialization(this, "YOUR GOOGLE MAP KEY")
setContent {
    // add this
    KLocationService().ListenerToPermission()
    App()
}

in commonMain

@Composable
internal fun App() = AppTheme {
    val mapController = remember {
        KMapController(
            initPosition = LatLng(30.01306, 31.20885),
            zoom = 15f

        )
    }


    val viewModel by remember { mutableStateOf(GoogleMapViewModel()) }
    val scope = rememberCoroutineScope()

    if (viewModel.requestPermission) {
        KLocationService().EnableLocation()
        viewModel.requestPermission = false
    }


    Column(
        modifier = Modifier
            .fillMaxSize()
            .windowInsetsPadding(WindowInsets.safeDrawing)
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Column(modifier = Modifier.fillMaxSize()) {
            TypeAhead(
                itemsProvider = { query -> viewModel.onQueryChanged(query) },
                itemToString = { it.fullText },
                onItemSelected = {
                    scope.launch {
                        viewModel.getPlaceDetails(it.placeId, 1)
                    }
                }
            )

            Spacer(Modifier.height(10.dp))

            TypeAhead(
                itemsProvider = { query -> viewModel.onQueryChanged(query) },
                itemToString = { it.fullText },
                onItemSelected = {
                    scope.launch {
                        viewModel.getPlaceDetails(it.placeId, 2)
                    }
                }
            )

            Spacer(Modifier.height(10.dp))

            ElevatedButton(onClick = { viewModel.getRoad() }) {
                Text("Fetch Road")
            }

            Box(modifier = Modifier.fillMaxSize()) {
                KGoogleMapView(
                    controller = mapController,
                    onMapLoaded ={
                        println("map loaded")
                    },
                    onMapClick = {
                        println("click loc :${it}")
                    },
                    onMapLongClick = {
                        println("long click loc :${it}")

                    }
                )

                Icon(
                    imageVector = Icons.Filled.Restore,
                    contentDescription = "Reset Location",
                    modifier = Modifier
                        .size(60.dp)
                        .align(Alignment.TopStart)
                        .padding(16.dp)
                        .clickable {
                            mapController.resetCamera()
                            println("Reset location")
                        },
                    tint = Color.Black
                )

                Icon(
                    imageVector = Icons.Filled.Place,
                    contentDescription = "Add Markers",
                    modifier = Modifier
                        .size(60.dp)
                        .align(Alignment.TopEnd)
                        .padding(16.dp)
                        .clickable {
                            mapController.addMarkers(
                                listOf(
                                    Markers(
                                        LatLng(30.09167, 31.248662),
                                        "Marker 1",
                                        "Marker snippet 1"
                                    ),
                                    Markers(
                                        LatLng(30.10167, 31.250662),
                                        "Marker 2",
                                        "Marker snippet 2"
                                    )
                                )
                            )
                            mapController.goToLocation(LatLng(30.10167, 31.250662))
                            println("Add markers")
                        },
                    tint = Color.Black
                )

                Icon(
                    imageVector = Icons.Filled.Remove,
                    contentDescription = "Clear Markers",
                    modifier = Modifier
                        .size(120.dp)
                        .align(Alignment.BottomEnd)
                        .padding(16.dp)
                        .clickable {
                            mapController.clearMarkers()
                            println("Clear markers")
                        },
                    tint = Color.Black
                )

                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .align(Alignment.BottomCenter)
                        .background(Color.White.copy(alpha = 0.7f))
                        .padding(16.dp),
                    contentAlignment = Alignment.Center
                ) {
                    Button(
                        onClick = {
                            val start = viewModel.selectedAddress1
                            val end = viewModel.selectedAddress2
                            val directions = viewModel.directions

                            if (start != null && end != null && directions != null) {
                                mapController.goToLocation(
                                    LatLng(
                                        start.latitude!!,
                                        start.longitude!!
                                    )
                                )
                                mapController.renderRoad(directions.routes.first().overview_polyline.points)
                            }
                        },
                        colors = ButtonDefaults.buttonColors(containerColor = Color.Black)
                    ) {
                        Text("Render Road", color = Color.White, fontSize = 14.sp)
                    }
                }
            }
        }
    }

    TopToast(
        isVisible = !viewModel.isGPSEnabled,
        message = "GPS is not active. Tap to enable.",
        onClick = {
            viewModel.enableGPSAndLocation()
        }
    )

}

@Composable
fun TopToast(
    message: String,
    isVisible: Boolean,
    onClick: () -> Unit,
) {


    AnimatedVisibility(
        visible = isVisible,
        enter = fadeIn(),
        exit = fadeOut()
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp, vertical = 8.dp)
                .background(
                    color = MaterialTheme.colorScheme.error,
                    shape = MaterialTheme.shapes.medium
                )
                .clickable {
                    onClick()
                }
                .padding(12.dp),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = message,
                color = MaterialTheme.colorScheme.onError,
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}

class GoogleMapViewModel : ViewModel() {
    private var debounceJob: Job? = null
    private val googlePlaces = KPlacesHelper()


    var selectedAddress1: PlaceDetails? = null
        private set

    var selectedAddress2: PlaceDetails? = null
        private set

    var directions: DirectionsResponse? = null
        private set

    private val locationService = KLocationService()

    var isGPSEnabled by mutableStateOf(true)
        private set

    var requestPermission by mutableStateOf(false)

    init {
        viewModelScope.launch {
            locationService.gpsStateFlow().collect { gps ->
                isGPSEnabled = gps
            }
        }
    }

    // Function that will be called from the composable
    suspend fun onQueryChanged(query: String): List<AutocompleteSuggestion> {
        debounceJob?.cancel() // Cancel any ongoing job

        return if (query.isNotEmpty()) {
            delay(500) // Debounce delay
            fetchSuggestions(query)
        } else {
            emptyList()
        }
    }

    fun enableGPSAndLocation() {
        requestPermission = true
    }

    private suspend fun fetchSuggestions(query: String): List<AutocompleteSuggestion> {
        return withContext(Dispatchers.IO) {
            try {
                // Use suspendCancellableCoroutine to bridge the callback-based API with Kotlin coroutines
                suspendCancellableCoroutine { continuation ->
                    googlePlaces.fetchSuggestions(query) { suggestions ->
                        // Check if suggestions are not null and resume the coroutine with the result
                        continuation.resume(suggestions)
                    }
                }
            } catch (e: Exception) {
                // Handle any exceptions that occur during the fetching
                emptyList() // Return an empty list on error
            }
        }
    }

    suspend fun getPlaceDetails(placeId: String, searchId: Int) {
        withContext(Dispatchers.IO) {
            googlePlaces.fetchPlaceDetails(placeId, {
                if (searchId == 1) {
                    selectedAddress1 = it
                } else {
                    selectedAddress2 = it
                }
            })
        }
    }

    fun getRoad() {
        viewModelScope.launch {
            if (selectedAddress1 != null && selectedAddress2 != null) {
                val ktorServices = KtorServices()
                directions = ktorServices.getRoadPoints(
                    selectedAddress1!!.address!!,
                    selectedAddress2!!.address!!,
                )
            }
        }
    }
}

controller functionality

  fun resetCamera()
     fun renderRoad(points: String)
     fun addMarkers(markers: List<Markers>)
     fun clearMarkers()
     fun goToLocation(location: LatLng , zoom:Float = 15f)
     fun showLocationUser(show:Boolean)
     fun showRoad(show:Boolean)

Example use controller

mapController.addMarkers([]) // sent new list
mapController.clearMarker() // remove all markers

mapController.goToLocation(location) // camera will move animated to new location

mapController.showLocationUser(show) // can show or hide current location user marker

mapController.renderRoad(points) // for render poly


 mapController.showRoad(show) // display poly or hide it

Google places

  • Note no ui for search available make custom ui

Functionality

expect class KPlacesHelper() {
    /**
     * Fetches autocomplete suggestions for a query string.
     *
     * @param query The search query string.
     * @param onResult A callback invoked with a list of suggestions or an empty list if no suggestions are found.
     */
    fun fetchSuggestions(query: String, onResult: (List<AutocompleteSuggestion>) -> Unit)

    /**
     * Fetches detailed information about a place given its place ID.
     *
     * @param placeId The ID of the place.
     * @param onResult A callback invoked with place details or `null` if the fetch operation fails.
     */
    fun fetchPlaceDetails(placeId: String, onResult: (PlaceDetails?) -> Unit)

}

How use it

  KPlacesHelper().fetchSuggestions(query = "Ramses") {
                    print(it.size)
                    val sizeAddress = it.size
                    if (sizeAddress > 0) {
                        println(KPlacesHelper().fetchPlaceDetails(it[0].placeId, {
                            println("data address selected lat ${it?.latitude}")
                        }))
                    }
                }

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages