├── vanillaplacepicker ├── .kotlintest │ └── spec_failures ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── integer.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── font │ │ │ │ └── opensans_reg.ttf │ │ │ ├── values-ar │ │ │ │ ├── integer.xml │ │ │ │ └── strings.xml │ │ │ ├── drawable-hdpi │ │ │ │ └── ic_current_location.png │ │ │ ├── drawable-mdpi │ │ │ │ └── ic_current_location.png │ │ │ ├── drawable-xhdpi │ │ │ │ └── ic_current_location.png │ │ │ ├── drawable-xxhdpi │ │ │ │ └── ic_current_location.png │ │ │ ├── drawable │ │ │ │ ├── bg_white_corner_round.xml │ │ │ │ ├── ic_arrow_back_black_24dp.xml │ │ │ │ ├── ic_done_black_24dp.xml │ │ │ │ ├── ic_location_on_red_24dp.xml │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── drawable-anydpi │ │ │ │ └── ic_current_location.xml │ │ │ ├── values-hi │ │ │ │ └── strings.xml │ │ │ ├── values-es │ │ │ │ └── strings.xml │ │ │ ├── values-fr │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ ├── activity_vanilla_map.xml │ │ │ │ └── custom_toolbar.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── vanillaplacepicker │ │ │ │ ├── extenstion │ │ │ │ ├── StringExts.kt │ │ │ │ ├── ViewExts.kt │ │ │ │ ├── AppCompatActivityExtension.kt │ │ │ │ ├── AlertDialogExtension.kt │ │ │ │ └── ContextWrapperExts.kt │ │ │ │ ├── utils │ │ │ │ ├── PickerType.kt │ │ │ │ ├── PickerLanguage.kt │ │ │ │ ├── ToastLength.kt │ │ │ │ ├── MapType.kt │ │ │ │ ├── BundleUtils.kt │ │ │ │ ├── Logger.kt │ │ │ │ ├── ToastUtils.kt │ │ │ │ ├── KeyUtils.kt │ │ │ │ ├── AutoCompleteUtils.kt │ │ │ │ └── SharedPrefs.kt │ │ │ │ ├── domain │ │ │ │ └── common │ │ │ │ │ └── SafeObserver.kt │ │ │ │ ├── presentation │ │ │ │ ├── builder │ │ │ │ │ ├── SearchZoneRect.kt │ │ │ │ │ ├── VanillaConfig.kt │ │ │ │ │ └── VanillaPlacePicker.kt │ │ │ │ ├── map │ │ │ │ │ ├── VanillaMapViewModelFactory.kt │ │ │ │ │ ├── VanillaMapViewModel.kt │ │ │ │ │ └── VanillaMapActivity.kt │ │ │ │ └── common │ │ │ │ │ └── VanillaBaseViewModelActivity.kt │ │ │ │ ├── data │ │ │ │ ├── common │ │ │ │ │ ├── BaseMapper.kt │ │ │ │ │ ├── AddressMapperGoogleMap.kt │ │ │ │ │ └── AutoCompleteAddressDetailsMapper.kt │ │ │ │ ├── VanillaAddress.kt │ │ │ │ └── GeocoderAddressResponse.kt │ │ │ │ └── service │ │ │ │ └── FetchAddressIntentService.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── vanillaplacepicker │ │ │ ├── presentation │ │ │ ├── common │ │ │ │ ├── TestRulesListener.kt │ │ │ │ └── TestRules.kt │ │ │ └── map │ │ │ │ └── VanillaMapViewModelTest.kt │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── vanillaplacepicker │ │ └── ExampleInstrumentedTest.java ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── media ├── vanillaplacepicker-map.gif └── vanillaplacepicker-autocomplete.gif ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ └── styles.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── raw │ │ │ │ └── style_json.json │ │ │ ├── drawable │ │ │ │ ├── ic_undraw_address.xml │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ ├── ic_undraw_map.xml │ │ │ │ └── ic_undraw_note_list.xml │ │ │ └── layout │ │ │ │ └── activity_sample.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── mindinventory │ │ │ └── placepicker │ │ │ └── activity │ │ │ └── SampleActivity.kt │ └── test │ │ └── java │ │ └── com │ │ └── mindinventory │ │ └── placepicker │ │ └── ExampleUnitTest.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── LICENSE ├── gradle.properties ├── gradlew.bat ├── .gitignore ├── README.md └── gradlew /vanillaplacepicker/.kotlintest/spec_failures: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':vanillaplacepicker' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /media/vanillaplacepicker-map.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/media/vanillaplacepicker-map.gif -------------------------------------------------------------------------------- /media/vanillaplacepicker-autocomplete.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/media/vanillaplacepicker-autocomplete.gif -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/font/opensans_reg.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/vanillaplacepicker/src/main/res/font/opensans_reg.ttf -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values-ar/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 180 4 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable-hdpi/ic_current_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/vanillaplacepicker/src/main/res/drawable-hdpi/ic_current_location.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable-mdpi/ic_current_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/vanillaplacepicker/src/main/res/drawable-mdpi/ic_current_location.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable-xhdpi/ic_current_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/vanillaplacepicker/src/main/res/drawable-xhdpi/ic_current_location.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable-xxhdpi/ic_current_location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mindinventory/vanilla-place-picker/HEAD/vanillaplacepicker/src/main/res/drawable-xxhdpi/ic_current_location.png -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/extenstion/StringExts.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.extenstion 2 | 3 | fun String?.isRequiredField(): Boolean { 4 | return this != null && isNotEmpty() && isNotBlank() 5 | } 6 | 7 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/PickerType.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | enum class PickerType(val value: Int) { 4 | MAP_WITH_AUTO_COMPLETE(1), // Default 5 | AUTO_COMPLETE(2), 6 | MAP(3) 7 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/PickerLanguage.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | enum class PickerLanguage(val value: String) { 4 | ENGLISH("en"), 5 | FRENCH("fr"), 6 | HINDI("hi"), 7 | SPANISH("es"), 8 | ARABIC("ar") 9 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable/bg_white_corner_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 01 18:23:58 IST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/ToastLength.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.widget.Toast 4 | 5 | sealed class ToastLength(val value: Int) { 6 | object Short : ToastLength(Toast.LENGTH_SHORT) 7 | object Long : ToastLength(Toast.LENGTH_LONG) 8 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2196F3 4 | #1976D2 5 | #D81B60 6 | #FFC107 7 | 8 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #2196F3 4 | #1976D2 5 | #D81B60 6 | #5B5B5B 7 | 8 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/extenstion/ViewExts.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.extenstion 2 | 3 | import android.view.View 4 | import androidx.core.view.isVisible 5 | 6 | fun View.hide() { 7 | this.isVisible = false 8 | } 9 | 10 | fun View.show() { 11 | this.isVisible = true 12 | } -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/domain/common/SafeObserver.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.domain.common 2 | 3 | import androidx.lifecycle.Observer 4 | 5 | class SafeObserver(private val notifier: (T) -> Unit) : Observer { 6 | override fun onChanged(t: T?) { 7 | t?.let { notifier(t) } 8 | } 9 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/builder/SearchZoneRect.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.builder 2 | 3 | import android.os.Parcelable 4 | import com.google.android.gms.maps.model.LatLng 5 | import kotlinx.android.parcel.Parcelize 6 | 7 | @Parcelize 8 | data class SearchZoneRect(val lowerLeft: LatLng, val upperRight: LatLng) : Parcelable -------------------------------------------------------------------------------- /vanillaplacepicker/src/test/java/com/vanillaplacepicker/presentation/common/TestRulesListener.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.common 2 | 3 | import io.kotlintest.Spec 4 | import io.kotlintest.extensions.TestListener 5 | 6 | object TestRulesListener : TestListener { 7 | 8 | override fun beforeSpec(spec: Spec) { 9 | instantTaskExecutorRule() 10 | } 11 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Vanilla Place Picker 3 | Place Picker\n(Search) 4 | Place Picker\n(Map) 5 | Selected Place 6 | 7 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/MapType.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import com.google.android.gms.maps.GoogleMap 4 | 5 | enum class MapType(val value: Int) { 6 | NORMAL(GoogleMap.MAP_TYPE_NORMAL), 7 | SATELLITE(GoogleMap.MAP_TYPE_SATELLITE), 8 | TERRAIN(GoogleMap.MAP_TYPE_TERRAIN), 9 | HYBRID(GoogleMap.MAP_TYPE_HYBRID), 10 | NONE(GoogleMap.MAP_TYPE_NONE) 11 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable/ic_arrow_back_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable/ic_done_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/data/common/BaseMapper.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.data.common 2 | 3 | abstract class BaseMapper { 4 | 5 | fun apply(oldItem: T): R { 6 | return map(oldItem) 7 | } 8 | 9 | fun apply(oldItem: List): List { 10 | return oldItem.map { 11 | apply(it) 12 | } 13 | } 14 | 15 | protected abstract fun map(oldItem: T): R 16 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/mindinventory/placepicker/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.placepicker 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Generated files 9 | bin/ 10 | gen/ 11 | 12 | # Gradle files 13 | .gradle/ 14 | /*/build/ 15 | gradle/ 16 | gradlew 17 | gradlew.bat 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | .gradle 32 | build/ 33 | /captures 34 | release/ 35 | /app/schemas -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/BundleUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import android.os.Bundle 6 | 7 | object BundleUtils { 8 | // Get MetaData from AndroidManifest file of given context 9 | fun getMetaData(context: Context): Bundle { 10 | return context.packageManager.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA).metaData 11 | } 12 | } -------------------------------------------------------------------------------- /vanillaplacepicker/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Generated files 9 | bin/ 10 | gen/ 11 | 12 | # Gradle files 13 | .gradle/ 14 | /*/build/ 15 | gradle/ 16 | gradlew 17 | gradlew.bat 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # OSX files 23 | .DS_Store 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio 29 | *.iml 30 | .idea 31 | .gradle 32 | build/ 33 | /captures 34 | release/ 35 | /app/schemas -------------------------------------------------------------------------------- /vanillaplacepicker/src/test/java/com/vanillaplacepicker/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/map/VanillaMapViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.map 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.vanillaplacepicker.utils.SharedPrefs 6 | 7 | class VanillaMapViewModelFactory(private val sharedPrefs: SharedPrefs) : ViewModelProvider.Factory { 8 | override fun create(modelClass: Class): T { 9 | return modelClass.cast(VanillaMapViewModel(sharedPrefs)) as T 10 | } 11 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable/ic_location_on_red_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/data/VanillaAddress.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.data 2 | 3 | import java.io.Serializable 4 | 5 | data class VanillaAddress( 6 | var formattedAddress: String? = null, 7 | var name: String? = null, 8 | var placeId: String? = null, 9 | var latitude: Double? = null, 10 | var longitude: Double? = null, 11 | var locality: String? = null, 12 | var subLocality: String? = null, 13 | var postalCode: String? = null, 14 | var countryCode: String? = null, 15 | var countryName: String? = null 16 | ) : Serializable -------------------------------------------------------------------------------- /vanillaplacepicker/src/test/java/com/vanillaplacepicker/presentation/common/TestRules.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.common 2 | 3 | import androidx.arch.core.executor.ArchTaskExecutor 4 | import androidx.arch.core.executor.TaskExecutor 5 | 6 | fun instantTaskExecutorRule() { 7 | ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() { 8 | override fun executeOnDiskIO(runnable: Runnable) { 9 | runnable.run() 10 | } 11 | 12 | override fun postToMainThread(runnable: Runnable) { 13 | runnable.run() 14 | } 15 | 16 | override fun isMainThread(): Boolean = true 17 | }) 18 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/Logger.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.util.Log 4 | import com.vanillaplacepicker.BuildConfig 5 | 6 | object Logger { 7 | fun d(tag: String, message: Any?) { 8 | if (BuildConfig.DEBUG) { 9 | Log.d(tag, message.toString()) 10 | } 11 | } 12 | 13 | fun e(tag: String, message: Any?) { 14 | if (BuildConfig.DEBUG) { 15 | Log.e(tag, message.toString()) 16 | } 17 | } 18 | 19 | fun i(tag: String, message: Any?) { 20 | if (BuildConfig.DEBUG) { 21 | Log.i(tag, message.toString()) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable-anydpi/ic_current_location.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/extenstion/AppCompatActivityExtension.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.extenstion 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.provider.Settings 6 | import androidx.appcompat.app.AppCompatActivity 7 | 8 | /** 9 | * this function will help to open app setting using Intent. 10 | */ 11 | fun AppCompatActivity.openAppSetting() { 12 | val intent = Intent() 13 | intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS 14 | val uri = Uri.fromParts("package", packageName, null) 15 | intent.data = uri 16 | startActivity(intent) 17 | } 18 | 19 | fun AppCompatActivity.hasExtra(key: String): Boolean { 20 | return this.intent.hasExtra(key) 21 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/data/GeocoderAddressResponse.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.data 2 | 3 | import android.os.Bundle 4 | import java.io.Serializable 5 | 6 | data class GeoCoderAddressResponse( 7 | var addressLine: String?, 8 | var featureName: String?, 9 | var adminArea: String?, 10 | var subAdminArea: String?, 11 | var locality: String?, 12 | var thoroughfare: String?, 13 | var postalCode: String?, 14 | var countryCode: String?, 15 | var countryName: String?, 16 | var hasLatitude: Boolean?, 17 | var latitude: Double?, 18 | var hasLongitude: Boolean?, 19 | var longitude: Double?, 20 | var phone: String?, 21 | var url: String?, 22 | var extras: Bundle? 23 | ) : Serializable -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/ToastUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | 6 | object ToastUtils { 7 | private lateinit var toast: Toast 8 | 9 | fun showToast(context: Context, message: String?, duration: ToastLength = ToastLength.Long) { 10 | if (::toast.isInitialized) 11 | toast.cancel() 12 | message?.let { 13 | toast = Toast.makeText(context, message, duration.value) 14 | toast.show() 15 | } 16 | } 17 | 18 | fun showToast(context: Context, message: Int, duration: ToastLength = ToastLength.Long) { 19 | if (::toast.isInitialized) toast.cancel() 20 | toast = Toast.makeText(context, message, duration.value) 21 | toast.show() 22 | } 23 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /vanillaplacepicker/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/androidTest/java/com/vanillaplacepicker/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | @Test 19 | public void useAppContext() { 20 | // Context of the app under test. 21 | Context appContext = InstrumentationRegistry.getTargetContext(); 22 | 23 | assertEquals("com.miplacepicker.test", appContext.getPackageName()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/map/VanillaMapViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.map 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import com.google.android.gms.maps.model.LatLng 6 | import com.vanillaplacepicker.utils.SharedPrefs 7 | 8 | class VanillaMapViewModel(private val sharedPrefs: SharedPrefs) : ViewModel() { 9 | 10 | var latLngLiveData = MutableLiveData() 11 | 12 | fun fetchSavedLocation() { 13 | val latitude = sharedPrefs.deviceLatitude.toDouble() 14 | val longitude = sharedPrefs.deviceLongitude.toDouble() 15 | latLngLiveData.postValue(LatLng(latitude, longitude)) 16 | } 17 | 18 | fun saveLatLngToSharedPref(latitude: Double, longitude: Double) { 19 | sharedPrefs.deviceLatitude = latitude.toFloat() 20 | sharedPrefs.deviceLongitude = longitude.toFloat() 21 | } 22 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/extenstion/AlertDialogExtension.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.extenstion 2 | 3 | import androidx.appcompat.app.AlertDialog 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.vanillaplacepicker.R 6 | 7 | fun AppCompatActivity.showAlertDialog( 8 | message: Int = 0, title: Int = 0, 9 | positiveBtnText: Int = R.string.ok, 10 | negativeBtnText: Int = R.string.cancel, 11 | accepted: () -> Unit = {}, 12 | rejected: () -> Unit = {} 13 | ) { 14 | AlertDialog.Builder(this) 15 | .setTitle(title) 16 | .setMessage(message) 17 | .setPositiveButton(positiveBtnText) { dialog, _ -> 18 | accepted.invoke() 19 | dialog.dismiss() 20 | }.setNegativeButton(negativeBtnText) { dialog, _ -> 21 | rejected.invoke() 22 | dialog.dismiss() 23 | }.show() 24 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/data/common/AddressMapperGoogleMap.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.data.common 2 | 3 | import com.vanillaplacepicker.data.GeoCoderAddressResponse 4 | import com.vanillaplacepicker.data.VanillaAddress 5 | 6 | object AddressMapperGoogleMap : BaseMapper() { 7 | 8 | override fun map(oldItem: GeoCoderAddressResponse): VanillaAddress { 9 | return VanillaAddress().apply { 10 | this.formattedAddress = oldItem.addressLine 11 | this.name = oldItem.featureName 12 | this.locality = oldItem.locality 13 | this.subLocality = oldItem.subAdminArea 14 | this.latitude = oldItem.latitude 15 | this.longitude = oldItem.longitude 16 | this.postalCode = oldItem.postalCode 17 | this.countryCode = oldItem.countryCode 18 | this.countryName = oldItem.countryName 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/KeyUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | object KeyUtils { 4 | const val SUCCESS_RESULT = 0 5 | const val FAILURE_RESULT = 1 6 | private const val PACKAGE_NAME = "com.google.android.gms.location.sample.locationaddress" 7 | const val RECEIVER = "$PACKAGE_NAME.RECEIVER" 8 | const val RESULT_DATA_KEY = "$PACKAGE_NAME.RESULT_DATA_KEY" 9 | const val RESULT_MESSAGE_KEY = "$PACKAGE_NAME.RESULT_MESSAGE_KEY" 10 | const val LOCATION_DATA_EXTRA = "$PACKAGE_NAME.LOCATION_DATA_EXTRA" 11 | const val DEFAULT_ZOOM_LEVEL = 18f 12 | const val GOOGLE_MAP_CAMERA_ANIMATE_DURATION = 2000 13 | const val REQUEST_PERMISSIONS_REQUEST_CODE = 102 14 | const val LOCATION_UPDATE_INTERVAL = 3000L 15 | const val DEFAULT_LOCATION = 0.0 16 | const val DEFAULT_STYLE_JSON_RESID = 0 17 | const val DEFAULT_FETCH_LOCATION_INTERVAL = 5000L 18 | const val SELECTED_PLACE = "place" 19 | const val EXTRA_CONFIG = "extraConfig" 20 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mindinventory 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/builder/VanillaConfig.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.builder 2 | 3 | import android.os.Parcelable 4 | import androidx.annotation.DrawableRes 5 | import com.vanillaplacepicker.utils.KeyUtils 6 | import com.vanillaplacepicker.utils.MapType 7 | import com.vanillaplacepicker.utils.PickerType 8 | import kotlinx.parcelize.Parcelize 9 | 10 | @Parcelize 11 | data class VanillaConfig( 12 | var apiKey: String? = "", 13 | var pickerType: PickerType = PickerType.MAP_WITH_AUTO_COMPLETE, 14 | var country: String? = null, 15 | var latitude: Double = KeyUtils.DEFAULT_LOCATION, 16 | var longitude: Double = KeyUtils.DEFAULT_LOCATION, 17 | var radius: Int? = null, 18 | var language: String? = null, 19 | var types: String? = null, 20 | var zoneRect: SearchZoneRect? = null, 21 | var enableSatelliteView: Boolean = false, 22 | var mapStyleJSONResId: Int = KeyUtils.DEFAULT_STYLE_JSON_RESID, 23 | var mapType: MapType = MapType.NORMAL, 24 | var enableShowMapAfterSearchResult: Boolean = false, 25 | @DrawableRes var mapPinDrawable: Int? = null 26 | ) : Parcelable -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/data/common/AutoCompleteAddressDetailsMapper.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.data.common 2 | 3 | import com.google.android.libraries.places.api.model.Place 4 | import com.vanillaplacepicker.data.VanillaAddress 5 | 6 | object AutoCompleteAddressDetailsMapper : BaseMapper() { 7 | 8 | override fun map(oldItem: Place): VanillaAddress { 9 | return VanillaAddress().apply { 10 | this.formattedAddress = oldItem.address 11 | this.name = oldItem.name 12 | this.latitude = oldItem.latLng?.latitude 13 | this.longitude = oldItem.latLng?.longitude 14 | oldItem.addressComponents?.asList()?.forEach { 15 | when { 16 | it.types.contains("locality") -> this.locality = it.name 17 | it.types.contains("sublocality_level_1") -> this.subLocality = it.name 18 | it.types.contains("postal_code") -> this.postalCode = it.name 19 | it.types.contains("country") -> { 20 | this.countryCode = it.shortName 21 | this.countryName = it.name 22 | } 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/common/VanillaBaseViewModelActivity.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.common 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import androidx.annotation.CallSuper 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.lifecycle.ViewModel 8 | import androidx.viewbinding.ViewBinding 9 | 10 | abstract class VanillaBaseViewModelActivity : AppCompatActivity() { 11 | 12 | private var _binding: VB? = null 13 | protected val binding get() = _binding!! 14 | protected val viewModel by lazy { buildViewModel() } 15 | 16 | protected open fun getBundle() { 17 | } 18 | 19 | protected abstract fun buildViewModel(): T 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | _binding = inflateLayout(layoutInflater) 24 | setContentView(binding.root) 25 | getBundle() 26 | initViews() 27 | initLiveDataObservers() 28 | } 29 | 30 | abstract fun inflateLayout(layoutInflater: LayoutInflater): VB 31 | 32 | @CallSuper 33 | protected open fun initLiveDataObservers() = Unit 34 | 35 | @CallSuper 36 | protected open fun initViews() { 37 | } 38 | 39 | override fun onDestroy() { 40 | super.onDestroy() 41 | _binding = null 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-android-extensions' 5 | } 6 | 7 | android { 8 | compileSdk 33 9 | defaultConfig { 10 | applicationId "com.mindinventory.placepicker" 11 | minSdk 21 12 | targetSdk 33 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | vectorDrawables.useSupportLibrary = true 17 | multiDexEnabled true 18 | 19 | // Load credentials. 20 | def properties = new Properties() 21 | file("../local.properties").withInputStream { properties.load(it) } 22 | 23 | // Share the key with your `AndroidManifest.xml` 24 | manifestPlaceholders = [ googleMapsApiKey:"${properties.getProperty('google.maps_api_key')}"] 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | buildFeatures { 33 | viewBinding true 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(dir: 'libs', include: ['*.jar']) 39 | implementation "androidx.appcompat:appcompat:1.5.1" 40 | implementation "androidx.cardview:cardview:1.0.0" 41 | 42 | testImplementation 'junit:junit:4.13.2' 43 | implementation project(path: ':vanillaplacepicker') 44 | implementation 'androidx.multidex:multidex:2.0.1' 45 | } 46 | 47 | apply plugin: 'com.google.gms.google-services' 48 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/extenstion/ContextWrapperExts.kt: -------------------------------------------------------------------------------- 1 | import android.annotation.TargetApi 2 | import android.content.ContextWrapper 3 | import android.os.Build 4 | import java.util.* 5 | 6 | @Suppress("DEPRECATION") 7 | fun ContextWrapper.wrap(language: String): ContextWrapper { 8 | val config = baseContext.resources.configuration 9 | val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 10 | this.getSystemLocale() 11 | } else { 12 | this.getSystemLocaleLegacy() 13 | } 14 | 15 | if (language.isNotEmpty() && sysLocale.language != language) { 16 | val locale = Locale(language) 17 | Locale.setDefault(locale) 18 | 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 20 | this.setSystemLocale(locale) 21 | } else { 22 | this.setSystemLocaleLegacy(locale) 23 | } 24 | } 25 | baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics) 26 | return ContextWrapper(baseContext) 27 | } 28 | 29 | @Suppress("DEPRECATION") 30 | fun ContextWrapper.getSystemLocaleLegacy(): Locale { 31 | val config = baseContext.resources.configuration 32 | return config.locale 33 | } 34 | 35 | @TargetApi(Build.VERSION_CODES.N) 36 | fun ContextWrapper.getSystemLocale(): Locale { 37 | val config = baseContext.resources.configuration 38 | return config.locales[0] 39 | } 40 | 41 | 42 | @Suppress("DEPRECATION") 43 | fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) { 44 | val config = baseContext.resources.configuration 45 | config.locale = locale 46 | } 47 | 48 | @TargetApi(Build.VERSION_CODES.N) 49 | fun ContextWrapper.setSystemLocale(locale: Locale) { 50 | val config = baseContext.resources.configuration 51 | config.setLocale(locale) 52 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values-ar/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | لم يتم تقديم بيانات الموقع 3 | عذرا ، الخدمة غير متوفرة 4 | خط العرض أو خط الطول غير صالح 5 | آسف ، لم يتم العثور على عنوان 6 | تم العثور على العنوان 7 | لم يتم العثور على اقتراحات. 8 | وافق المستخدم على إجراء تغييرات إعدادات الموقع المطلوبة. 9 | يختار المستخدم عدم إجراء تغييرات إعدادات الموقع المطلوبة. 10 | 11 | تم إلغاء تفاعل المستخدم. 12 | راضون جميع إعدادات الموقع. 13 | إعدادات الموقع غير كافية ولا يمكن إصلاحها هنا. إصلاح في الإعدادات. 14 | مفقود إذن 15 | يرجى السماح لنا بالوصول إلى إذنك لخدمتك بشكل أفضل. 16 | حسنا 17 | إلغاء 18 | الإذن 19 | 20 | الإذن… 21 | 22 | 23 | أدخل عنوان أو مكان 24 | نتائج البحث تظهر هنا 25 | حدث خطأ ما. أعد المحاولة من فضلك. 26 | VanillaPlacePicker 27 | 28 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values-hi/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | कोई स्थान डेटा प्रदान नहीं किया गया 3 | क्षमा करें, सेवा उपलब्ध नहीं है 4 | उपयोगकर्ता सहभागिता रद्द कर दी गई थी। 5 | क्षमा करें, कोई पता नहीं मिला 6 | पता मिला 7 | कोई सुझाव नहीं मिला। 8 | उपयोगकर्ता आवश्यक स्थान सेटिंग्स परिवर्तन करने 9 | के लिए सहमत हुए। 10 | 11 | उपयोगकर्ता ने आवश्यक स्थान सेटिंग परिवर्तन 12 | नहीं करना चुना। 13 | 14 | उपयोगकर्ता सहभागिता रद्द कर दी गई थी। 15 | सभी स्थान सेटिंग संतुष्ट हैं। 16 | स्थान सेटिंग अपर्याप्त हैं और यहां तय नहीं 17 | की जा सकती हैं। सेटिंग्स में ठीक करें। 18 | 19 | गुम अनुमति 20 | कृपया हमें आपकी सेवा के लिए अनुमति दें ताकि आप बेहतर सेवा कर सकें। 21 | ठीक 22 | रद्द करना 23 | अनुमति 24 | 25 | 26 | 27 | खोज कर… 28 | 29 | 30 | एक पता या जगह दर्ज करें 31 | खोज परिणाम यहां दिखाई देते हैं 32 | कुछ गलत हो गया। कृपया पुन: प्रयास करें। 33 | VanillaPlacePicker 34 | 35 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/AutoCompleteUtils.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import com.google.android.libraries.places.api.Places 6 | import com.google.android.libraries.places.api.model.Place 7 | import com.google.android.libraries.places.api.model.RectangularBounds 8 | import com.google.android.libraries.places.widget.Autocomplete 9 | import com.google.android.libraries.places.widget.model.AutocompleteActivityMode 10 | import com.vanillaplacepicker.presentation.builder.VanillaConfig 11 | 12 | object AutoCompleteUtils { 13 | private val fields = listOf( 14 | Place.Field.ADDRESS, 15 | Place.Field.ADDRESS_COMPONENTS, 16 | Place.Field.ID, 17 | Place.Field.LAT_LNG, 18 | Place.Field.NAME, 19 | Place.Field.OPENING_HOURS, 20 | Place.Field.PHONE_NUMBER, 21 | Place.Field.PHOTO_METADATAS, 22 | Place.Field.PLUS_CODE, 23 | Place.Field.PRICE_LEVEL, 24 | Place.Field.RATING, 25 | Place.Field.TYPES, 26 | Place.Field.USER_RATINGS_TOTAL, 27 | Place.Field.UTC_OFFSET, 28 | Place.Field.VIEWPORT, 29 | Place.Field.WEBSITE_URI 30 | ) 31 | 32 | fun getAutoCompleteIntent(context: Context, vanillaConfig: VanillaConfig): Intent { 33 | 34 | if (!Places.isInitialized()) 35 | vanillaConfig.apiKey?.let { Places.initialize(context.applicationContext, it) } 36 | 37 | val autocomplete = Autocomplete.IntentBuilder( 38 | AutocompleteActivityMode.FULLSCREEN, 39 | fields 40 | ) 41 | 42 | if (!vanillaConfig.country.isNullOrBlank()) { 43 | autocomplete.setCountry(vanillaConfig.country) 44 | } 45 | vanillaConfig.zoneRect?.let { 46 | autocomplete.setLocationRestriction( 47 | RectangularBounds.newInstance( 48 | it.lowerLeft, 49 | it.upperRight 50 | ) 51 | ) 52 | } 53 | return autocomplete.build(context) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | No se han proporcionado datos de ubicación 3 | Lo sentimos, el servicio no está disponible. 4 | La interacción del usuario fue cancelada. 5 | Lo sentimos, no se encontró la dirección 6 | Dirección encontrada 7 | No se encontró ninguna sugerencia. 8 | El usuario acordó realizar los cambios de 9 | configuración de ubicación requeridos. 10 | 11 | El usuario optó por no realizar los cambios 12 | de configuración de ubicación requeridos. 13 | 14 | La interacción del usuario fue cancelada. 15 | Todos los ajustes de ubicación están satisfechos. 16 | La configuración de la ubicación es 17 | inadecuada y no se puede arreglar aquí. Arreglo en Configuraciones. 18 | 19 | Permiso faltante 20 | Permítanos tener acceso a su permiso para servirle mejor. 21 | DE ACUERDO 22 | Cancelar 23 | Permiso 24 | 25 | 26 | 27 | Buscando… 28 | 29 | 30 | Introduce una dirección o lugar 31 | Los resultados de búsqueda aparecen aquí 32 | Algo salió mal. Por favor, vuelva a intentarlo. 33 | VanillaPlacePicker 34 | 35 | -------------------------------------------------------------------------------- /vanillaplacepicker/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-parcelize' 5 | } 6 | 7 | group='com.github.Mindinventory' 8 | 9 | android { 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | minSdk 21 14 | targetSdk 33 15 | versionCode 16 16 | versionName "0.3.1" 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | vectorDrawables.useSupportLibrary = true 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility = 1.8 30 | targetCompatibility = 1.8 31 | } 32 | 33 | kotlinOptions { 34 | jvmTarget = "1.8" 35 | } 36 | 37 | testOptions { 38 | unitTests.returnDefaultValues = true 39 | } 40 | 41 | buildFeatures { 42 | viewBinding true 43 | } 44 | } 45 | 46 | dependencies { 47 | implementation fileTree(dir: 'libs', include: ['*.jar']) 48 | implementation 'androidx.appcompat:appcompat:1.5.1' 49 | implementation 'androidx.core:core-ktx:1.9.0' 50 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 51 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' 52 | implementation "androidx.recyclerview:recyclerview:1.2.1" 53 | testImplementation 'junit:junit:4.13.2' 54 | 55 | // Location 56 | api 'com.google.android.gms:play-services-location:21.0.1' 57 | api 'com.google.android.gms:play-services-maps:18.1.0' 58 | 59 | // Google material 60 | implementation 'com.google.android.material:material:1.7.0' 61 | 62 | implementation "com.google.android.libraries.places:places:3.0.0" 63 | 64 | //unittest 65 | testImplementation 'org.mockito:mockito-core:4.0.0' 66 | testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0' 67 | testImplementation 'org.mockito:mockito-inline:3.5.13' 68 | testImplementation 'android.arch.core:core-testing:1.1.1' 69 | testImplementation 'io.kotlintest:kotlintest-runner-junit5:3.3.2' 70 | } 71 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Aucune donnée de localisation fournie 3 | Désolé, le service n\'est pas disponible 4 | L\'interaction de l\'utilisateur a été annulée. 5 | Désolé, aucune adresse trouvée 6 | Adresse trouvée 7 | Aucune suggestion trouvée. 8 | L\'utilisateur a accepté de modifier les 9 | paramètres d\'emplacement requis. 10 | 11 | L\'utilisateur a choisi de ne pas modifier 12 | les paramètres d\'emplacement requis. 13 | 14 | L\'interaction de l\'utilisateur a été annulée. 15 | Tous les paramètres de localisation sont satisfaits. 16 | Les paramètres de localisation sont 17 | inadéquats et ne peuvent pas être corrigés ici. Correction dans les paramètres. 18 | 19 | Permission manquante 20 | S\'il vous plaît, permettez-nous d\'accéder à votre permission pour mieux 21 | vous servir. 22 | 23 | D\'accord 24 | Annuler 25 | Autorisation 26 | 27 | 28 | Recherche… 29 | 30 | 31 | Entrez une adresse ou un lieu 32 | Les résultats de recherche apparaissent ici 33 | Une erreur s\'est produite. Veuillez réessayer. 34 | VanillaPlacePicker 35 | 36 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | No location data provided 3 | Sorry, the service is not available 4 | Invalid latitude or longitude used 5 | Sorry, no address found 6 | Address found 7 | No suggestion found. 8 | User agreed to make required location settings 9 | changes. 10 | 11 | User choose not to make required location 12 | settings changes. 13 | 14 | User interaction was cancelled. 15 | All location settings are satisfied. 16 | Location settings are inadequate and cannotbe fixed here. Fix in Settings. 17 | Missing Permission 18 | Please allow us to access your permission to serve you better. 19 | OK 20 | Cancel 21 | Permission 22 | 23 | Searching… 24 | 25 | 26 | Enter an address or place 27 | Search results appear here 28 | 29 | Location settings are not satisfied. Attempting to upgrade 30 | location settings 31 | 32 | PendingIntent unable to execute request. 33 | Something went wrong, please try again. 34 | VanillaPlacePicker 35 | 36 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/utils/SharedPrefs.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.utils 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | 6 | class SharedPrefs(context: Context) { 7 | private val sharedPreferences: SharedPreferences = 8 | context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) 9 | 10 | var deviceLatitude: Float 11 | set(value) = put(PREF_DEVICE_LATITUDE, value) 12 | get() = get(PREF_DEVICE_LATITUDE, Float::class.java) 13 | 14 | var deviceLongitude: Float 15 | set(value) = put(PREF_DEVICE_LONGITUDE, value) 16 | get() = get(PREF_DEVICE_LONGITUDE, Float::class.java) 17 | 18 | @Suppress("UNCHECKED_CAST", "IMPLICIT_CAST_TO_ANY") 19 | private fun get(key: String, clazz: Class): T = 20 | when (clazz) { 21 | String::class.java -> sharedPreferences.getString(key, "") 22 | Boolean::class.java -> sharedPreferences.getBoolean(key, false) 23 | Float::class.java -> sharedPreferences.getFloat(key, 0f) 24 | Double::class.java -> sharedPreferences.getFloat(key, 0f) 25 | Int::class.java -> sharedPreferences.getInt(key, -1) 26 | Long::class.java -> sharedPreferences.getLong(key, -1L) 27 | else -> null 28 | } as T 29 | 30 | private fun put(key: String, data: T) { 31 | val editor = sharedPreferences.edit() 32 | when (data) { 33 | is String -> editor.putString(key, data) 34 | is Boolean -> editor.putBoolean(key, data) 35 | is Float -> editor.putFloat(key, data) 36 | is Double -> editor.putFloat(key, data.toFloat()) 37 | is Int -> editor.putInt(key, data) 38 | is Long -> editor.putLong(key, data) 39 | } 40 | editor.apply() 41 | } 42 | 43 | fun clear() { 44 | sharedPreferences.edit().clear().apply() 45 | } 46 | 47 | companion object { 48 | const val PREFS_NAME = "KFCSharedPreferences" 49 | private const val PREFIX = "kfc_" 50 | const val PREF_SESSION_ACCESS_TOKEN = PREFIX + "access_token" 51 | const val PREF_DEVICE_LATITUDE = PREFIX + "device_latitude" 52 | const val PREF_DEVICE_LONGITUDE = PREFIX + "device_longitude" 53 | } 54 | } -------------------------------------------------------------------------------- /vanillaplacepicker/src/test/java/com/vanillaplacepicker/presentation/map/VanillaMapViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.map 2 | 3 | import androidx.lifecycle.Observer 4 | import com.google.android.gms.maps.model.LatLng 5 | import com.nhaarman.mockitokotlin2.argumentCaptor 6 | import com.nhaarman.mockitokotlin2.mock 7 | import com.nhaarman.mockitokotlin2.times 8 | import com.nhaarman.mockitokotlin2.whenever 9 | import com.vanillaplacepicker.presentation.common.TestRulesListener 10 | import com.vanillaplacepicker.utils.SharedPrefs 11 | import io.kotlintest.extensions.TestListener 12 | import io.kotlintest.shouldBe 13 | import io.kotlintest.specs.BehaviorSpec 14 | import org.mockito.Mockito 15 | 16 | class VanillaMapViewModelTest : BehaviorSpec() { 17 | 18 | private val latitude = 23.058759689331055 19 | private val longitude = 72.53572845458984 20 | private val latLng = LatLng(latitude, longitude) 21 | 22 | //output value 23 | private val successLatLng = latLng 24 | 25 | //mock dependencies 26 | private val sharedPrefs = mock() 27 | private val vanillaMapViewModel by lazy { VanillaMapViewModel(sharedPrefs) } 28 | private var vanillaMapObserver = mock>() 29 | 30 | override fun listeners(): List = listOf(TestRulesListener) 31 | 32 | init { 33 | fetchSavedLocationSuccess() 34 | } 35 | 36 | private fun fetchSavedLocationSuccess() { 37 | Given("Fetch saved location") { 38 | When("LatLng Available") { 39 | stubFetchSavedLocationSuccess() 40 | vanillaMapViewModel.latLngLiveData.observeForever(vanillaMapObserver) 41 | vanillaMapViewModel.fetchSavedLocation() 42 | Then("Fetch saved location success") { 43 | argumentCaptor().apply { 44 | Mockito.verify(vanillaMapObserver, times(1)).onChanged(capture()) 45 | LatLng(firstValue.latitude, firstValue.longitude) shouldBe successLatLng 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | //stubbing 53 | private fun stubFetchSavedLocationSuccess() { 54 | whenever(sharedPrefs.deviceLatitude).thenReturn(latitude.toFloat()) 55 | whenever(sharedPrefs.deviceLongitude).thenReturn(longitude.toFloat()) 56 | } 57 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/layout/activity_vanilla_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 28 | 31 | 32 | 44 | 45 | 59 | 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/mindinventory/placepicker/activity/SampleActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mindinventory.placepicker.activity 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.activity.result.contract.ActivityResultContracts 7 | import androidx.appcompat.app.AppCompatActivity 8 | import com.mindinventory.placepicker.R 9 | import com.mindinventory.placepicker.databinding.ActivitySampleBinding 10 | import com.vanillaplacepicker.extenstion.show 11 | import com.vanillaplacepicker.presentation.builder.VanillaPlacePicker 12 | import com.vanillaplacepicker.utils.MapType 13 | import com.vanillaplacepicker.utils.PickerLanguage 14 | import com.vanillaplacepicker.utils.PickerType 15 | 16 | class SampleActivity : AppCompatActivity(), View.OnClickListener { 17 | 18 | private var _binding: ActivitySampleBinding? = null 19 | private val binding get() = _binding!! 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | _binding = ActivitySampleBinding.inflate(layoutInflater) 24 | setContentView(binding.root) 25 | 26 | initClickListener() 27 | } 28 | 29 | private fun initClickListener() { 30 | binding.cardviewPlacePickerSearch.setOnClickListener(this) 31 | binding.cardviewPlacePickerMap.setOnClickListener(this) 32 | } 33 | 34 | private var placePickerResultLauncher = 35 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 36 | if (result.resultCode == Activity.RESULT_OK && result.data != null) { 37 | val vanillaAddress = VanillaPlacePicker.getPlaceResult(result.data) 38 | vanillaAddress?.let { 39 | binding.cardviewSelectedPlace.show() 40 | binding.tvSelectedPlace.text = it.formattedAddress 41 | } 42 | } 43 | } 44 | 45 | override fun onClick(v: View?) { 46 | when (v?.id) { 47 | R.id.cardviewPlacePickerSearch -> { 48 | val intent = VanillaPlacePicker.Builder(this) 49 | .with(PickerType.AUTO_COMPLETE) 50 | .withLocation(23.0710, 72.5181) 51 | .setPickerLanguage(PickerLanguage.ENGLISH) 52 | .build() 53 | placePickerResultLauncher.launch(intent) 54 | } 55 | 56 | R.id.cardviewPlacePickerMap -> { 57 | val intent = VanillaPlacePicker.Builder(this) 58 | .with(PickerType.MAP_WITH_AUTO_COMPLETE) 59 | .setMapType(MapType.SATELLITE) 60 | .withLocation(23.0710, 72.5181) 61 | .setPickerLanguage(PickerLanguage.ENGLISH) 62 | .enableShowMapAfterSearchResult(true) 63 | .build() 64 | placePickerResultLauncher.launch(intent) 65 | } 66 | } 67 | } 68 | 69 | override fun onDestroy() { 70 | super.onDestroy() 71 | _binding = null 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/layout/custom_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 19 | 20 | 31 | 32 | 43 | 44 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/raw/style_json.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "featureType": "all", 4 | "elementType": "geometry", 5 | "stylers": [ 6 | { 7 | "color": "#242f3e" 8 | } 9 | ] 10 | }, 11 | { 12 | "featureType": "all", 13 | "elementType": "labels.text.stroke", 14 | "stylers": [ 15 | { 16 | "lightness": -80 17 | } 18 | ] 19 | }, 20 | { 21 | "featureType": "administrative", 22 | "elementType": "labels.text.fill", 23 | "stylers": [ 24 | { 25 | "color": "#746855" 26 | } 27 | ] 28 | }, 29 | { 30 | "featureType": "administrative.locality", 31 | "elementType": "labels.text.fill", 32 | "stylers": [ 33 | { 34 | "color": "#d59563" 35 | } 36 | ] 37 | }, 38 | { 39 | "featureType": "poi", 40 | "elementType": "labels.text.fill", 41 | "stylers": [ 42 | { 43 | "color": "#d59563" 44 | } 45 | ] 46 | }, 47 | { 48 | "featureType": "poi.park", 49 | "elementType": "geometry", 50 | "stylers": [ 51 | { 52 | "color": "#263c3f" 53 | } 54 | ] 55 | }, 56 | { 57 | "featureType": "poi.park", 58 | "elementType": "labels.text.fill", 59 | "stylers": [ 60 | { 61 | "color": "#6b9a76" 62 | } 63 | ] 64 | }, 65 | { 66 | "featureType": "road", 67 | "elementType": "geometry.fill", 68 | "stylers": [ 69 | { 70 | "color": "#2b3544" 71 | } 72 | ] 73 | }, 74 | { 75 | "featureType": "road", 76 | "elementType": "labels.text.fill", 77 | "stylers": [ 78 | { 79 | "color": "#9ca5b3" 80 | } 81 | ] 82 | }, 83 | { 84 | "featureType": "road.arterial", 85 | "elementType": "geometry.fill", 86 | "stylers": [ 87 | { 88 | "color": "#38414e" 89 | } 90 | ] 91 | }, 92 | { 93 | "featureType": "road.arterial", 94 | "elementType": "geometry.stroke", 95 | "stylers": [ 96 | { 97 | "color": "#212a37" 98 | } 99 | ] 100 | }, 101 | { 102 | "featureType": "road.highway", 103 | "elementType": "geometry.fill", 104 | "stylers": [ 105 | { 106 | "color": "#746855" 107 | } 108 | ] 109 | }, 110 | { 111 | "featureType": "road.highway", 112 | "elementType": "geometry.stroke", 113 | "stylers": [ 114 | { 115 | "color": "#1f2835" 116 | } 117 | ] 118 | }, 119 | { 120 | "featureType": "road.highway", 121 | "elementType": "labels.text.fill", 122 | "stylers": [ 123 | { 124 | "color": "#f3d19c" 125 | } 126 | ] 127 | }, 128 | { 129 | "featureType": "road.local", 130 | "elementType": "geometry.fill", 131 | "stylers": [ 132 | { 133 | "color": "#38414e" 134 | } 135 | ] 136 | }, 137 | { 138 | "featureType": "road.local", 139 | "elementType": "geometry.stroke", 140 | "stylers": [ 141 | { 142 | "color": "#212a37" 143 | } 144 | ] 145 | }, 146 | { 147 | "featureType": "transit", 148 | "elementType": "geometry", 149 | "stylers": [ 150 | { 151 | "color": "#2f3948" 152 | } 153 | ] 154 | }, 155 | { 156 | "featureType": "transit.station", 157 | "elementType": "labels.text.fill", 158 | "stylers": [ 159 | { 160 | "color": "#d59563" 161 | } 162 | ] 163 | }, 164 | { 165 | "featureType": "water", 166 | "elementType": "geometry", 167 | "stylers": [ 168 | { 169 | "color": "#17263c" 170 | } 171 | ] 172 | }, 173 | { 174 | "featureType": "water", 175 | "elementType": "labels.text.fill", 176 | "stylers": [ 177 | { 178 | "color": "#515c6d" 179 | } 180 | ] 181 | }, 182 | { 183 | "featureType": "water", 184 | "elementType": "labels.text.stroke", 185 | "stylers": [ 186 | { 187 | "lightness": -20 188 | } 189 | ] 190 | } 191 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Android ### 2 | # Built application files 3 | *.apk 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # IntelliJ 38 | *.iml 39 | .idea/workspace.xml 40 | .idea/tasks.xml 41 | .idea/gradle.xml 42 | .idea/assetWizardSettings.xml 43 | .idea/dictionaries 44 | .idea/libraries 45 | .idea/caches 46 | # Android Studio 3 in .gitignore file. 47 | .idea/caches/build_file_checksums.ser 48 | .idea/modules.xml 49 | 50 | # Keystore files 51 | # Uncomment the following lines if you do not want to check your keystore files in. 52 | #*.jks 53 | #*.keystore 54 | 55 | # External native build folder generated in Android Studio 2.2 and later 56 | .externalNativeBuild 57 | 58 | # Google Services (e.g. APIs or Firebase) 59 | google-services.json 60 | 61 | # Freeline 62 | freeline.py 63 | freeline/ 64 | freeline_project_description.json 65 | 66 | # fastlane 67 | fastlane/report.xml 68 | fastlane/Preview.html 69 | fastlane/screenshots 70 | fastlane/test_output 71 | fastlane/readme.md 72 | 73 | # Version control 74 | vcs.xml 75 | 76 | # lint 77 | lint/intermediates/ 78 | lint/generated/ 79 | lint/outputs/ 80 | lint/tmp/ 81 | # lint/reports/ 82 | 83 | ### Android Patch ### 84 | gen-external-apklibs 85 | output.json 86 | 87 | ### AndroidStudio ### 88 | # Covers files to be ignored for android development using Android Studio. 89 | 90 | # Built application files 91 | 92 | # Files for the ART/Dalvik VM 93 | 94 | # Java class files 95 | 96 | # Generated files 97 | 98 | # Gradle files 99 | .gradle 100 | 101 | # Signing files 102 | .signing/ 103 | 104 | # Local configuration file (sdk path, etc) 105 | 106 | # Proguard folder generated by Eclipse 107 | 108 | # Log Files 109 | 110 | # Android Studio 111 | /*/build/ 112 | /*/local.properties 113 | /*/out 114 | /*/*/build 115 | /*/*/production 116 | *.ipr 117 | *~ 118 | *.swp 119 | 120 | # Android Patch 121 | 122 | # External native build folder generated in Android Studio 2.2 and later 123 | 124 | # NDK 125 | obj/ 126 | 127 | # IntelliJ IDEA 128 | *.iws 129 | /out/ 130 | 131 | # User-specific configurations 132 | .idea/caches/ 133 | .idea/libraries/ 134 | .idea/shelf/ 135 | .idea/.name 136 | .idea/compiler.xml 137 | .idea/copyright/profiles_settings.xml 138 | .idea/encodings.xml 139 | .idea/misc.xml 140 | .idea/scopes/scope_settings.xml 141 | .idea/vcs.xml 142 | .idea/jsLibraryMappings.xml 143 | .idea/datasources.xml 144 | .idea/dataSources.ids 145 | .idea/sqlDataSources.xml 146 | .idea/dynamic.xml 147 | .idea/uiDesigner.xml 148 | 149 | # OS-specific files 150 | .DS_Store 151 | .DS_Store? 152 | ._* 153 | .Spotlight-V100 154 | .Trashes 155 | ehthumbs.db 156 | Thumbs.db 157 | 158 | # Legacy Eclipse project files 159 | .classpath 160 | .project 161 | .cproject 162 | .settings/ 163 | 164 | # Mobile Tools for Java (J2ME) 165 | .mtj.tmp/ 166 | 167 | # Package Files # 168 | *.war 169 | *.ear 170 | 171 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 172 | hs_err_pid* 173 | 174 | ## Plugin-specific files: 175 | 176 | # mpeltonen/sbt-idea plugin 177 | .idea_modules/ 178 | 179 | # JIRA plugin 180 | atlassian-ide-plugin.xml 181 | 182 | # Mongo Explorer plugin 183 | .idea/mongoSettings.xml 184 | 185 | # Crashlytics plugin (for Android Studio and IntelliJ) 186 | com_crashlytics_export_strings.xml 187 | crashlytics.properties 188 | crashlytics-build.properties 189 | fabric.properties 190 | 191 | ### AndroidStudio Patch ### 192 | 193 | !/gradle/wrapper/gradle-wrapper.jar 194 | 195 | ### Firebase ### 196 | .idea 197 | **/node_modules/* 198 | **/.firebaserc 199 | 200 | ### Firebase Patch ### 201 | .runtimeconfig.json 202 | .firebase/ -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_undraw_address.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 24 | 25 | 26 | 27 | 30 | 35 | 36 | 37 | 44 | 45 | 46 | 49 | 52 | 53 | 56 | 59 | 62 | 65 | 68 | 73 | 76 | 79 | 82 | 85 | 86 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 40 | 42 | 44 | 46 | 48 | 50 | 52 | 54 | 56 | 58 | 60 | 62 | 64 | 66 | 68 | 70 | 72 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vanilla Place Picker 2 | [![](https://jitpack.io/v/Mindinventory/VanillaPlacePicker.svg)](https://jitpack.io/#Mindinventory/VanillaPlacePicker) ![](https://img.shields.io/github/license/mindinventory/VanillaPlacePicker) 3 | 4 | Vanilla Place Picker provides a UI that displays an interactive map to get the place details and Autocomplete functionality, which displays place predictions based on user search input. 5 | 6 | Developers often come across a requirement of adding precise location. So, a place picker which is easy to implement, less time consuming, and simple enough for users to use it is always in demand and here we have a Vanilla Place Picker which developer can add it in quick simple steps. 7 | 8 | ## Preview 9 | ![image](/media/vanillaplacepicker-autocomplete.gif) ![image](/media/vanillaplacepicker-map.gif) 10 | 11 | ## Key features 12 | * Android 13 support 13 | * Simple implementation for place picker either using Autocomplete, Map or both 14 | * Set your own custom map styles 15 | * Customise map pin icon 16 | * Set default location position 17 | * Use it without location permission 18 | * Choose to show only open businesses or all 19 | * Highly customise attributes 20 | * Multi languages support 21 | * RTL layout support 22 | 23 | ## Usage 24 | ### Dependencies 25 | 26 | - **Step 1. Add the JitPack repository in your project build.gradle:** 27 | ```bash 28 | allprojects { 29 | repositories { 30 | ... 31 | maven { url 'https://jitpack.io' } 32 | } 33 | } 34 | ``` 35 | **or** 36 | **If Android studio version is Arctic Fox or higher then add it in your settings.gradle** 37 | 38 | ```bash 39 | dependencyResolutionManagement { 40 | repositories { 41 | ... 42 | maven { url 'https://jitpack.io' } 43 | } 44 | } 45 | ``` 46 | - **Step 2. Add the dependency in your app module build.gradle file** 47 | ```bash 48 | dependencies { 49 | ... 50 | implementation 'com.github.Mindinventory:VanillaPlacePicker:X.X.X' 51 | } 52 | ``` 53 | 54 | ### Implementation 55 | - **Step 1. Add Google MAP API KEY in your local.properties file with the same variable name as defined below (google.maps_api_key)** 56 | ```bash 57 | google.maps_api_key=PLACE YOUR API KEY HERE 58 | ``` 59 | 60 | - **Step 2. To get Google MAP Api key from local.properties file, write below defined code in your app module build.gradle file** 61 | ```bash 62 | android { 63 | ... 64 | defaultConfig { 65 | ... 66 | #Access Google MAP Api Key from local.properties file 67 | def properties = new Properties() 68 | file("../local.properties").withInputStream { properties.load(it) 69 | #Share the key with your `AndroidManifest.xml` 70 | manifestPlaceholders = [ googleMapsApiKey:"${properties.getProperty('google.maps_api_key')}"] 71 | } 72 | ``` 73 | - **Step 3. Add below defined meta-data code to your AndroidManifest.xml:** 74 | 75 | ```bash 76 | 77 | ... 78 | 79 | 82 | 83 | 84 | ``` 85 | - **Step 4. Add VanillaPlacePicker Builder in to your activity class:** 86 | ```bash 87 | 88 | #startActivityForResult is deprecated so better to use registerForActivityResult 89 | var placePickerResultLauncher = 90 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 91 | if (result.resultCode == Activity.RESULT_OK && result.data != null) { 92 | val vanillaAddress = VanillaPlacePicker.getPlaceResult(result.data) 93 | } 94 | } 95 | 96 | #Launch caller with Intent 97 | val intent = VanillaPlacePicker.Builder(this) 98 | .with(PickerType.MAP_WITH_AUTO_COMPLETE) // Select Picker type to enable autocompelte, map or both 99 | .withLocation(23.057582, 72.534458) 100 | .setPickerLanguage(PickerLanguage.HINDI) // Apply language to picker 101 | .setLocationRestriction(LatLng(23.0558088,72.5325067), LatLng(23.0587592,72.5357321)) // Restrict location bounds in map and autocomplete 102 | .setCountry("IN") // Only for Autocomplete 103 | .enableShowMapAfterSearchResult(true) // To show the map after selecting the place from place picker only for PickerType.MAP_WITH_AUTO_C 104 | /* 105 | * Configuration for Map UI 106 | */ 107 | .setMapType(MapType.SATELLITE) // Choose map type (Only applicable for map screen) 108 | .setMapStyle(R.raw.style_json) // Containing the JSON style declaration for night-mode styling 109 | .setMapPinDrawable(android.R.drawable.ic_menu_mylocation) // To give custom pin image for map marker 110 | .build() 111 | 112 | placePickerResultLauncher.launch(intent) 113 | 114 | ``` 115 | 116 | ## Requirements 117 | 118 | * minSdkVersion >= 21 119 | * Androidx 120 | 121 | # LICENSE! 122 | 123 | Vanilla Place Picker is [MIT-licensed](/LICENSE). 124 | 125 | # Let us know! 126 | We’d be really happy if you send us links to your projects where you use our component. Just send an email to sales@mindinventory.com And do let us know if you have any questions or suggestion regarding our work. 127 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/builder/VanillaPlacePicker.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.builder 2 | 3 | import android.content.Context 4 | import android.content.ContextWrapper 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.util.Log 8 | import com.google.android.gms.maps.model.LatLng 9 | import com.google.android.libraries.places.widget.Autocomplete 10 | import com.vanillaplacepicker.data.VanillaAddress 11 | import com.vanillaplacepicker.data.common.AutoCompleteAddressDetailsMapper 12 | import com.vanillaplacepicker.extenstion.isRequiredField 13 | import com.vanillaplacepicker.presentation.map.VanillaMapActivity 14 | import com.vanillaplacepicker.utils.* 15 | import wrap 16 | 17 | 18 | class VanillaPlacePicker { 19 | 20 | companion object { 21 | private val TAG = VanillaPlacePicker::class.java.simpleName 22 | 23 | fun getPlaceResult(data: Intent?): VanillaAddress? { 24 | if (data != null) { 25 | val selectedPlace = data.getSerializableExtra(KeyUtils.SELECTED_PLACE) 26 | return if (selectedPlace is VanillaAddress) { 27 | selectedPlace 28 | } else { 29 | val place = Autocomplete.getPlaceFromIntent(data) 30 | AutoCompleteAddressDetailsMapper.apply(place) 31 | } 32 | } 33 | return null 34 | } 35 | } 36 | 37 | class Builder(private val context: Context) { 38 | private val vanillaConfig = VanillaConfig() 39 | 40 | /** 41 | * To enable map view with place picker 42 | */ 43 | fun with(pickerType: PickerType): Builder { 44 | vanillaConfig.pickerType = pickerType 45 | return this 46 | } 47 | 48 | /** 49 | * Filter addresses by country name ex. "US" 50 | */ 51 | fun setCountry(country: String): Builder { 52 | vanillaConfig.country = country 53 | return this 54 | } 55 | 56 | /** 57 | * Request with default latitude & longitude for near by places 58 | */ 59 | fun withLocation(latitude: Double, longitude: Double): Builder { 60 | vanillaConfig.latitude = latitude 61 | vanillaConfig.longitude = longitude 62 | return this 63 | } 64 | 65 | /** 66 | * To restrict request zone by rect 67 | */ 68 | fun setLocationRestriction(leftLatLng: LatLng, rightLatLng: LatLng): Builder { 69 | vanillaConfig.zoneRect = SearchZoneRect(leftLatLng, rightLatLng) 70 | return this 71 | } 72 | 73 | /** 74 | * Add a map with custom styling 75 | * With style options you can customize the presentation of the standard Google map styles, 76 | * changing the visual display of features like roads, parks, businesses, and other points of interest. 77 | * Add a resource containing a JSON style object (Use a raw resource) 78 | * */ 79 | fun setMapStyle(jsonResourceIdMapStyle: Int): Builder { 80 | vanillaConfig.mapStyleJSONResId = jsonResourceIdMapStyle 81 | return this 82 | } 83 | 84 | /** 85 | * Add a map with custom styling 86 | * With style options you can customize the presentation of the standard Google map styles, 87 | * changing the visual display of features like roads, parks, businesses, and other points of interest. 88 | * Add a resource containing a JSON style object (Use a string resource) 89 | * */ 90 | fun setMapType(mapType: MapType): Builder { 91 | vanillaConfig.mapType = mapType 92 | return this 93 | } 94 | 95 | /** 96 | * To show the map after selecting the place from place picker 97 | * To insure that user needs to navigate to map screen after the selecting the place from the place picker 98 | * @param enableShowMapAfterSearchResult true to show the map after search result, false otherwise 99 | * @return Returns a VanillaPlacePicker.Builder instance. 100 | */ 101 | fun enableShowMapAfterSearchResult(enableShowMapAfterSearchResult: Boolean): Builder { 102 | vanillaConfig.enableShowMapAfterSearchResult = enableShowMapAfterSearchResult 103 | return this 104 | } 105 | 106 | /** 107 | * Set custom Map Pin image 108 | * */ 109 | fun setMapPinDrawable(mapPinDrawableResId: Int): Builder { 110 | vanillaConfig.mapPinDrawable = mapPinDrawableResId 111 | return this 112 | } 113 | 114 | /** 115 | * Set picker language 116 | */ 117 | fun setPickerLanguage(pickerLanguage: PickerLanguage): Builder { 118 | ContextWrapper(context).wrap(pickerLanguage.value) 119 | return this 120 | } 121 | 122 | /** 123 | * Get Google Places API key 124 | */ 125 | private fun getApiKey(): String? { 126 | val metadataBundle: Bundle = BundleUtils.getMetaData(context) 127 | return if (metadataBundle.getString("com.google.android.geo.API_KEY").isRequiredField()) 128 | metadataBundle.getString("com.google.android.geo.API_KEY") 129 | else { 130 | Log.e( 131 | TAG, 132 | "Couldn't get Google API key from application meta data. Was it set in your AndroidManifest.xml?" 133 | ) 134 | "" 135 | } 136 | } 137 | 138 | fun build(): Intent { 139 | vanillaConfig.apiKey = getApiKey() 140 | val intent = if (vanillaConfig.pickerType == PickerType.AUTO_COMPLETE) { 141 | AutoCompleteUtils.getAutoCompleteIntent(context, vanillaConfig) 142 | } else { 143 | Intent(context, VanillaMapActivity::class.java) 144 | } 145 | intent.putExtra(KeyUtils.EXTRA_CONFIG, vanillaConfig) 146 | return intent 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_sample.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 20 | 21 | 22 | 29 | 30 | 37 | 38 | 45 | 46 | 54 | 55 | 56 | 57 | 58 | 65 | 66 | 73 | 74 | 81 | 82 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 103 | 104 | 111 | 112 | 119 | 120 | 129 | 130 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/service/FetchAddressIntentService.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.service 2 | 3 | import android.app.IntentService 4 | import android.content.Intent 5 | import android.location.Address 6 | import android.location.Geocoder 7 | import android.os.Bundle 8 | import android.os.ResultReceiver 9 | import android.util.Log 10 | import com.google.android.gms.maps.model.LatLng 11 | import com.google.gson.Gson 12 | import com.vanillaplacepicker.R 13 | import com.vanillaplacepicker.data.GeoCoderAddressResponse 14 | import com.vanillaplacepicker.utils.KeyUtils 15 | import com.vanillaplacepicker.utils.Logger 16 | import java.io.IOException 17 | import java.util.* 18 | 19 | class FetchAddressIntentService : IntentService("FetchAddressIntentService") { 20 | private val TAG = FetchAddressIntentService::class.java.simpleName 21 | /** 22 | * The receiver where results are forwarded from this service. 23 | */ 24 | private var receiver: ResultReceiver? = null 25 | 26 | /** 27 | * Tries to get the location address using a Geocoder. If successful, sends an address to a 28 | * result receiver. If unsuccessful, sends an error message instead. 29 | * Note: We define a [android.os.ResultReceiver] in * VanillaMapActivity to process content 30 | * sent from this service. 31 | * 32 | * This service calls this method from the default worker thread with the intent that started 33 | * the service. When this method returns, the service automatically stops. 34 | */ 35 | override fun onHandleIntent(intent: Intent?) { 36 | var errorMessage = "" 37 | 38 | receiver = intent?.getParcelableExtra(KeyUtils.RECEIVER) 39 | 40 | // Check if receiver was properly registered. 41 | if (intent == null || receiver == null) { 42 | Log.wtf(TAG, "No receiver received. There is nowhere to send the results.") 43 | return 44 | } 45 | 46 | // Get the location passed to this service through an extra. 47 | val latlng = intent.getParcelableExtra(KeyUtils.LOCATION_DATA_EXTRA) 48 | 49 | // Make sure that the location data was really sent over through an extra. If it wasn't, 50 | // send an error error message and return. 51 | if (latlng == null) { 52 | errorMessage = getString(R.string.no_location_data_provided) 53 | Log.wtf(TAG, errorMessage) 54 | deliverResultToReceiver(KeyUtils.FAILURE_RESULT, errorMessage, null) 55 | return 56 | } 57 | 58 | // Errors could still arise from using the Geocoder (for example, if there is no 59 | // connectivity, or if the Geocoder is given illegal location data). Or, the Geocoder may 60 | // simply not have an address for a location. In all these cases, we communicate with the 61 | // receiver using a resultCode indicating failure. If an address is found, we use a 62 | // resultCode indicating success. 63 | 64 | // The Geocoder used in this sample. The Geocoder's responses are localized for the given 65 | // Locale, which represents a specific geographical or linguistic region. Locales are used 66 | // to alter the presentation of information such as numbers or dates to suit the conventions 67 | // in the region they describe. 68 | val geocoder = Geocoder(this, Locale.getDefault()) 69 | 70 | // Address found using the Geocoder. 71 | var addresses: List
= emptyList() 72 | 73 | try { 74 | // Using getFromLocation() returns an array of Addresses for the area immediately 75 | // surrounding the given latitude and longitude. The results are a best guess and are 76 | // not guaranteed to be accurate. 77 | addresses = geocoder.getFromLocation( 78 | latlng.latitude, 79 | latlng.longitude, 80 | // In this sample, we get just a single address. 81 | 1 82 | ) as List
83 | } catch (ioException: IOException) { 84 | // Catch network or other I/O problems. 85 | Logger.e(TAG, ioException.toString()) 86 | deliverResultToReceiver(KeyUtils.FAILURE_RESULT, getString(R.string.service_not_available), null) 87 | } catch (illegalArgumentException: IllegalArgumentException) { 88 | // Catch invalid latitude or longitude values. 89 | Logger.e( 90 | TAG, "Latitude = $latlng.latitude , " + 91 | "Longitude = $latlng.longitude $illegalArgumentException" 92 | ) 93 | deliverResultToReceiver(KeyUtils.FAILURE_RESULT, getString(R.string.invalid_lat_long_used), null) 94 | } 95 | 96 | // Handle case where no address was found. 97 | if (addresses.isEmpty()) { 98 | if (errorMessage.isEmpty()) { 99 | errorMessage = getString(R.string.no_address_found) 100 | Logger.e(TAG, errorMessage) 101 | } 102 | deliverResultToReceiver(KeyUtils.FAILURE_RESULT, errorMessage, null) 103 | } else { 104 | Logger.d(TAG, "Addresses >> ${Gson().toJson(addresses)}") 105 | val address = addresses[0] 106 | // Fetch the address lines using {@code getAddressLine}, 107 | // join them, and send them to the thread. The {@link android.location.address} 108 | // class provides other options for fetching address details that you may prefer 109 | // to use. Here are some examples: 110 | // getLocality() ("Mountain View", for example) 111 | // getAdminArea() ("CA", for example) 112 | // getPostalCode() ("94043", for example) 113 | // getCountryCode() ("US", for example) 114 | // getCountryName() ("United States", for example) 115 | val addressFragments = with(address) { 116 | (0..maxAddressLineIndex).map { getAddressLine(it) } 117 | } 118 | 119 | val selectedPlace = GeoCoderAddressResponse( 120 | addressFragments.joinToString(separator = "\n"), 121 | address.featureName, 122 | address.adminArea, 123 | address.subAdminArea, 124 | address.locality, 125 | address.subThoroughfare, 126 | address.postalCode, 127 | address.countryCode, 128 | address.countryName, 129 | address.hasLatitude(), 130 | address.latitude, 131 | address.hasLongitude(), 132 | address.longitude, 133 | address.phone, 134 | address.url, 135 | address.extras 136 | ) 137 | 138 | Logger.i(TAG, getString(R.string.address_found)) 139 | deliverResultToReceiver( 140 | KeyUtils.SUCCESS_RESULT, 141 | getString(R.string.address_found), 142 | selectedPlace 143 | ) 144 | } 145 | } 146 | 147 | /** 148 | * Sends a resultCode and message to the receiver. 149 | */ 150 | private fun deliverResultToReceiver(resultCode: Int, message: String, selectedPlace: GeoCoderAddressResponse?) { 151 | val bundle = Bundle().apply { 152 | putString(KeyUtils.RESULT_MESSAGE_KEY, message) 153 | putSerializable(KeyUtils.RESULT_DATA_KEY, selectedPlace) 154 | } 155 | receiver?.send(resultCode, bundle) 156 | } 157 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_undraw_map.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 35 | 40 | 43 | 46 | 51 | 58 | 65 | 72 | 79 | 86 | 93 | 100 | 107 | 114 | 121 | 128 | 135 | 142 | 145 | 148 | 153 | 156 | 161 | 164 | 167 | 170 | 173 | 176 | 179 | 182 | 185 | 190 | 195 | 198 | 201 | 204 | 207 | 210 | 213 | 216 | 219 | 224 | 225 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_undraw_note_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 14 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 85 | 88 | 91 | 94 | 97 | 100 | 103 | 106 | 109 | 112 | 115 | 118 | 121 | 124 | 127 | 130 | 135 | 140 | 143 | 148 | 153 | 156 | 161 | 166 | 169 | 175 | 181 | 187 | 193 | 198 | 203 | 209 | 215 | 220 | 223 | 226 | 229 | 232 | 237 | 242 | 247 | 252 | 255 | 260 | 263 | 266 | 269 | 272 | 277 | 282 | 285 | 288 | 293 | 296 | 299 | 302 | 305 | 308 | 311 | 314 | 317 | 320 | 323 | 328 | 333 | 334 | -------------------------------------------------------------------------------- /vanillaplacepicker/src/main/java/com/vanillaplacepicker/presentation/map/VanillaMapActivity.kt: -------------------------------------------------------------------------------- 1 | package com.vanillaplacepicker.presentation.map 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.content.IntentSender 7 | import android.content.pm.PackageManager 8 | import android.os.* 9 | import android.util.Log 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.widget.RelativeLayout 13 | import androidx.activity.result.IntentSenderRequest 14 | import androidx.activity.result.contract.ActivityResultContracts 15 | import androidx.core.app.ActivityCompat 16 | import androidx.core.content.ContextCompat 17 | import androidx.lifecycle.ViewModelProvider 18 | import com.google.android.gms.common.api.ApiException 19 | import com.google.android.gms.common.api.ResolvableApiException 20 | import com.google.android.gms.location.* 21 | import com.google.android.gms.maps.CameraUpdateFactory 22 | import com.google.android.gms.maps.GoogleMap 23 | import com.google.android.gms.maps.OnMapReadyCallback 24 | import com.google.android.gms.maps.SupportMapFragment 25 | import com.google.android.gms.maps.model.LatLng 26 | import com.google.android.gms.maps.model.LatLngBounds 27 | import com.google.android.gms.maps.model.MapStyleOptions 28 | import com.vanillaplacepicker.R 29 | import com.vanillaplacepicker.data.GeoCoderAddressResponse 30 | import com.vanillaplacepicker.data.common.AddressMapperGoogleMap 31 | import com.vanillaplacepicker.databinding.ActivityVanillaMapBinding 32 | import com.vanillaplacepicker.domain.common.SafeObserver 33 | import com.vanillaplacepicker.extenstion.* 34 | import com.vanillaplacepicker.presentation.builder.VanillaConfig 35 | import com.vanillaplacepicker.presentation.builder.VanillaPlacePicker 36 | import com.vanillaplacepicker.presentation.common.VanillaBaseViewModelActivity 37 | import com.vanillaplacepicker.service.FetchAddressIntentService 38 | import com.vanillaplacepicker.utils.* 39 | 40 | class VanillaMapActivity : 41 | VanillaBaseViewModelActivity(), 42 | OnMapReadyCallback, 43 | View.OnClickListener { 44 | companion object { 45 | private val TAG = this::class.java.simpleName 46 | } 47 | 48 | private var mapFragment: SupportMapFragment? = null 49 | private var googleMap: GoogleMap? = null 50 | private lateinit var resultReceiver: AddressResultReceiver 51 | private var selectedPlace: GeoCoderAddressResponse? = null 52 | private var midLatLng: LatLng? = null 53 | private var defaultZoomLoaded = false 54 | private var fusedLocationProviderClient: FusedLocationProviderClient? = null 55 | private val startLocationHandler = Handler(Looper.getMainLooper()) 56 | 57 | private var vanillaConfig: VanillaConfig? = null 58 | 59 | // Belows are used in PlacePickerActivity 60 | private var isRequestedWithLocation = false 61 | private val sharedPrefs by lazy { SharedPrefs(this) } 62 | private var fetchLocationForFirstTime = false 63 | 64 | private var placePickerResultLauncher = 65 | registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> 66 | when (result.resultCode) { 67 | Activity.RESULT_OK -> { 68 | // data contains Place object 69 | if (vanillaConfig?.enableShowMapAfterSearchResult == true) { 70 | navigateMapToResult(result.data) 71 | } else { 72 | setResult(Activity.RESULT_OK, result.data) 73 | finish() 74 | } 75 | } 76 | } 77 | } 78 | 79 | override fun inflateLayout(layoutInflater: LayoutInflater) = 80 | ActivityVanillaMapBinding.inflate(layoutInflater) 81 | 82 | override fun buildViewModel(): VanillaMapViewModel { 83 | return ViewModelProvider(this, VanillaMapViewModelFactory(sharedPrefs)).get( 84 | VanillaMapViewModel::class.java 85 | ) 86 | } 87 | 88 | private val launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result -> 89 | when (result.resultCode) { 90 | Activity.RESULT_CANCELED -> viewModel.fetchSavedLocation() 91 | Activity.RESULT_OK -> postDelayed() 92 | } 93 | } 94 | 95 | override fun initViews() { 96 | super.initViews() 97 | // HIDE ActionBar(if exist in style) of root project module 98 | supportActionBar?.hide() 99 | setMapPinDrawable() 100 | with(binding.customToolbar) { 101 | tvAddress.isSelected = true 102 | ivBack.setOnClickListener(this@VanillaMapActivity) 103 | ivDone.setOnClickListener(this@VanillaMapActivity) 104 | if (vanillaConfig?.pickerType == PickerType.MAP_WITH_AUTO_COMPLETE) { 105 | tvAddress.setOnClickListener(this@VanillaMapActivity) 106 | } 107 | } 108 | binding.fabLocation.setOnClickListener(this) 109 | 110 | fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) 111 | 112 | if (!isRequestedWithLocation) { 113 | startLocationUpdates() 114 | } 115 | mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? 116 | mapFragment?.getMapAsync(this) 117 | resultReceiver = AddressResultReceiver(Handler(Looper.getMainLooper())) 118 | } 119 | 120 | override fun getBundle() { 121 | if (hasExtra(KeyUtils.EXTRA_CONFIG)) { 122 | vanillaConfig = intent.getParcelableExtra(KeyUtils.EXTRA_CONFIG) 123 | } 124 | if (vanillaConfig?.latitude != KeyUtils.DEFAULT_LOCATION && vanillaConfig?.longitude != KeyUtils.DEFAULT_LOCATION) { 125 | isRequestedWithLocation = true 126 | } 127 | startLocationRequestPermission() 128 | } 129 | 130 | // Set custom image drawable to Map Pin 131 | private fun setMapPinDrawable() { 132 | try { 133 | vanillaConfig?.mapPinDrawable?.let { pinDrawableResId -> 134 | binding.ivMarker.setImageDrawable(ContextCompat.getDrawable(this, pinDrawableResId)) 135 | } 136 | } catch (e: Exception) { 137 | Logger.e(TAG, "Invalid drawable resource ID. Error: $e") 138 | } 139 | } 140 | 141 | /** 142 | * this runnable will help to provide adequate delay before fetching device location. 143 | */ 144 | private val startLocationRunnable = Runnable { 145 | getLocationFromFusedLocation() 146 | } 147 | 148 | private fun removeCallbacks() { 149 | startLocationHandler.removeCallbacks(startLocationRunnable) 150 | } 151 | 152 | private fun postDelayed() { 153 | startLocationHandler.postDelayed(startLocationRunnable, KeyUtils.LOCATION_UPDATE_INTERVAL) 154 | } 155 | 156 | override fun onClick(v: View?) { 157 | when (v?.id) { 158 | R.id.ivBack -> onBackPressed() 159 | R.id.ivDone -> { 160 | selectedPlace ?: return 161 | setResult(Activity.RESULT_OK, Intent().apply { 162 | putExtra( 163 | KeyUtils.SELECTED_PLACE, 164 | selectedPlace?.let { AddressMapperGoogleMap.apply(it) }) 165 | }) 166 | finish() 167 | } 168 | R.id.tvAddress -> 169 | placePickerResultLauncher.launch(vanillaConfig?.let { 170 | AutoCompleteUtils.getAutoCompleteIntent( 171 | this, 172 | it 173 | ) 174 | }) 175 | R.id.fabLocation -> isGpsEnabled() 176 | } 177 | } 178 | 179 | /** 180 | * Receiver for data sent from FetchAddressIntentService. 181 | */ 182 | private inner class AddressResultReceiver( 183 | handler: Handler, 184 | ) : ResultReceiver(handler) { 185 | /** 186 | * Receives data sent from FetchAddressIntentService and updates the UI. 187 | */ 188 | override fun onReceiveResult(resultCode: Int, resultData: Bundle) { 189 | if (!resultData.containsKey(KeyUtils.RESULT_DATA_KEY)) { 190 | return 191 | } 192 | when (resultCode) { 193 | KeyUtils.SUCCESS_RESULT -> { 194 | selectedPlace = 195 | resultData.getSerializable(KeyUtils.RESULT_DATA_KEY) as GeoCoderAddressResponse 196 | with(binding.customToolbar) { 197 | if (selectedPlace?.addressLine.isRequiredField()) { 198 | Logger.d( 199 | TAG, 200 | "AddressResultReceiver.onReceiveResult: address: ${selectedPlace?.addressLine}" 201 | ) 202 | ivDone.show() 203 | tvAddress.text = selectedPlace?.addressLine 204 | } else { 205 | ivDone.hide() 206 | } 207 | } 208 | } 209 | KeyUtils.FAILURE_RESULT -> { 210 | val errorMessage = resultData.getString(KeyUtils.RESULT_MESSAGE_KEY) 211 | ToastUtils.showToast(this@VanillaMapActivity, errorMessage) 212 | } 213 | else -> { 214 | // make address empty 215 | } 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * Manipulates the map when it's available. 222 | * The API invokes this callback when the map is ready for use. 223 | */ 224 | override fun onMapReady(googleMap: GoogleMap) { 225 | this.googleMap = googleMap 226 | this.googleMap?.clear() 227 | if (vanillaConfig?.enableSatelliteView == true) 228 | this.googleMap?.mapType = GoogleMap.MAP_TYPE_SATELLITE 229 | 230 | // Customise the styling of the base map using a JSON object defined... 231 | try { 232 | // ...in a raw resource file. 233 | if (vanillaConfig?.mapStyleJSONResId != KeyUtils.DEFAULT_STYLE_JSON_RESID) { 234 | this.googleMap?.setMapStyle( 235 | vanillaConfig?.let { 236 | MapStyleOptions.loadRawResourceStyle( 237 | this@VanillaMapActivity, 238 | it.mapStyleJSONResId 239 | ) 240 | } 241 | ) 242 | } 243 | // ...in a string resource file. 244 | this.googleMap?.mapType = vanillaConfig.let { it?.mapType?.value!! } 245 | } catch (e: Exception) { 246 | Logger.e(TAG, "Can't find map style or Style parsing failed. Error: $e") 247 | } 248 | val cameraUpdateDefaultLocation = 249 | vanillaConfig?.let { vanillaConfig?.latitude?.let { it1 -> LatLng(it1, it.longitude) } } 250 | ?.let { 251 | CameraUpdateFactory.newLatLngZoom( 252 | it, 253 | if (vanillaConfig?.latitude == KeyUtils.DEFAULT_LOCATION) 0f else KeyUtils.DEFAULT_ZOOM_LEVEL 254 | ) 255 | } 256 | if (cameraUpdateDefaultLocation != null) { 257 | this.googleMap?.animateCamera( 258 | cameraUpdateDefaultLocation, 259 | KeyUtils.GOOGLE_MAP_CAMERA_ANIMATE_DURATION, 260 | null 261 | ) 262 | } 263 | /** 264 | * Set Padding: Top to show CompassButton at visible position on map 265 | * (Before allow Location runtime permission, After that changed position of CompassButton) 266 | * */ 267 | this.googleMap?.setPadding(0, 256, 0, 0) 268 | vanillaConfig?.zoneRect?.let { 269 | this.googleMap?.setLatLngBoundsForCameraTarget( 270 | LatLngBounds( 271 | it.lowerLeft, 272 | it.upperRight 273 | ) 274 | ) 275 | } 276 | 277 | this.googleMap?.setOnCameraMoveListener { 278 | with(binding.customToolbar) 279 | { 280 | tvAddress.text = getString(R.string.searching) 281 | ivDone.hide() 282 | } 283 | } 284 | this.googleMap?.setOnCameraIdleListener { 285 | val newLatLng = this@VanillaMapActivity.googleMap?.cameraPosition?.target 286 | newLatLng?.let { 287 | midLatLng?.let { midLatLng -> 288 | if (it.latitude == midLatLng.latitude && it.longitude == midLatLng.longitude) { 289 | return@setOnCameraIdleListener 290 | } 291 | } 292 | midLatLng = this@VanillaMapActivity.googleMap?.cameraPosition?.target 293 | midLatLng?.let { centerPoint -> 294 | startReverseGeoCodingService(centerPoint) 295 | } 296 | } 297 | } 298 | } 299 | 300 | override fun initLiveDataObservers() { 301 | super.initLiveDataObservers() 302 | viewModel.latLngLiveData.observe(this, SafeObserver(this::moveCameraToLocation)) 303 | } 304 | 305 | private fun moveCameraToLocation(latLng: LatLng?) { 306 | latLng ?: return 307 | 308 | val zoomLevel = if (defaultZoomLoaded) { 309 | defaultZoomLoaded = false 310 | googleMap?.cameraPosition?.zoom ?: KeyUtils.DEFAULT_ZOOM_LEVEL 311 | } else { 312 | KeyUtils.DEFAULT_ZOOM_LEVEL 313 | } 314 | val location = CameraUpdateFactory.newLatLngZoom(latLng, zoomLevel) 315 | googleMap?.animateCamera(location) 316 | 317 | changeLocationCompassButtonPosition() 318 | if (ActivityCompat.checkSelfPermission( 319 | this, 320 | Manifest.permission.ACCESS_FINE_LOCATION 321 | ) == PackageManager.PERMISSION_GRANTED 322 | ) { 323 | this.googleMap?.isMyLocationEnabled = true 324 | this.googleMap?.uiSettings?.isMyLocationButtonEnabled = false 325 | changeMyLocationButtonPosition() 326 | } 327 | } 328 | 329 | /* 330 | * Change location compass button position 331 | * From: Default 332 | * To: Top - Right 333 | * */ 334 | private fun changeLocationCompassButtonPosition() { 335 | try { 336 | val locationCompassButton = 337 | (mapFragment?.view?.findViewById(Integer.parseInt("1"))?.parent as View) 338 | .findViewById(Integer.parseInt("5")) 339 | val rlp = locationCompassButton.layoutParams as RelativeLayout.LayoutParams 340 | rlp.addRule(RelativeLayout.ALIGN_PARENT_START, 0) 341 | rlp.addRule(RelativeLayout.ALIGN_PARENT_END, RelativeLayout.TRUE) 342 | rlp.marginEnd = 16 343 | } catch (ex: Exception) { 344 | ex.printStackTrace() 345 | } 346 | } 347 | 348 | /* 349 | * Change my location button position 350 | * From: Default 351 | * To: Bottom - Right 352 | * */ 353 | private fun changeMyLocationButtonPosition() { 354 | val locationButton = 355 | (mapFragment?.view?.findViewById(Integer.parseInt("1"))?.parent as View) 356 | .findViewById(Integer.parseInt("2")) 357 | val rlp = locationButton.layoutParams as RelativeLayout.LayoutParams 358 | rlp.addRule(RelativeLayout.ALIGN_PARENT_TOP, 0) 359 | rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, RelativeLayout.TRUE) 360 | rlp.setMargins(0, 0, 0, 32) 361 | } 362 | 363 | /** 364 | * Creates an intent, adds location data to it as an extra, and starts the intent service for 365 | * fetching an address. 366 | */ 367 | private fun startReverseGeoCodingService(latLng: LatLng) { 368 | val lat = latLng.latitude 369 | val lng = latLng.longitude 370 | 371 | if (lat != KeyUtils.DEFAULT_LOCATION && lng != KeyUtils.DEFAULT_LOCATION) { 372 | // Create an intent for passing to the intent service responsible for fetching the address. 373 | val intent = Intent(this, FetchAddressIntentService::class.java).apply { 374 | // Pass the result receiver as an extra to the service. 375 | putExtra(KeyUtils.RECEIVER, resultReceiver) 376 | // Pass the location data as an extra to the service. 377 | putExtra(KeyUtils.LOCATION_DATA_EXTRA, latLng) 378 | } 379 | // Start the service. If the service isn't already running, it is instantiated and started 380 | // (creating a process for it if needed); if it is running then it remains running. The 381 | // service kills itself automatically once all intents are processed. 382 | startService(intent) 383 | } 384 | } 385 | 386 | private fun navigateMapToResult(data: Intent?) { 387 | val vanillaAddress = VanillaPlacePicker.getPlaceResult(data) 388 | this.googleMap?.animateCamera( 389 | CameraUpdateFactory.newLatLngZoom( 390 | LatLng( 391 | vanillaAddress?.latitude ?: KeyUtils.DEFAULT_LOCATION, 392 | vanillaAddress?.longitude ?: KeyUtils.DEFAULT_LOCATION 393 | ), 394 | KeyUtils.DEFAULT_ZOOM_LEVEL 395 | ), 396 | KeyUtils.GOOGLE_MAP_CAMERA_ANIMATE_DURATION, 397 | null 398 | ) 399 | } 400 | 401 | override fun onRequestPermissionsResult( 402 | requestCode: Int, 403 | permissions: Array, 404 | grantResults: IntArray, 405 | ) { 406 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 407 | when (requestCode) { 408 | KeyUtils.REQUEST_PERMISSIONS_REQUEST_CODE -> { 409 | when { 410 | grantResults.isEmpty() -> { 411 | // If user interaction was interrupted, the permission request is cancelled and you receive empty arrays. 412 | Log.d(TAG, resources.getString(R.string.user_interaction_was_cancelled)) 413 | } 414 | grantResults[0] == PackageManager.PERMISSION_GRANTED -> { 415 | if (!isRequestedWithLocation) { 416 | startLocationUpdates() 417 | } 418 | 419 | } 420 | else -> showAlertDialog( 421 | R.string.missing_permission_message, 422 | R.string.missing_permission_title, 423 | R.string.permission, 424 | R.string.cancel, { 425 | // this mean user has clicked on permission button to update run time permission. 426 | openAppSetting() 427 | } 428 | ) 429 | } 430 | } 431 | } 432 | } 433 | 434 | private fun startLocationRequestPermission() { 435 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 436 | // this mean device os is greater or equal to Marshmallow. 437 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 438 | != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( 439 | this, 440 | Manifest.permission.ACCESS_COARSE_LOCATION 441 | ) != PackageManager.PERMISSION_GRANTED 442 | ) { 443 | // here we are going to request location run time permission. 444 | ActivityCompat.requestPermissions( 445 | this, 446 | arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 447 | KeyUtils.REQUEST_PERMISSIONS_REQUEST_CODE 448 | ) 449 | return 450 | } 451 | } 452 | } 453 | 454 | private val locationRequest = LocationRequest.create().apply { 455 | this.priority = Priority.PRIORITY_HIGH_ACCURACY 456 | this.interval = KeyUtils.DEFAULT_FETCH_LOCATION_INTERVAL 457 | } 458 | 459 | private val locationSettingRequest = LocationSettingsRequest.Builder() 460 | .addLocationRequest(locationRequest) 461 | 462 | /** 463 | * this method will check required for location and according to result it will go ahead for fetching location. 464 | */ 465 | private fun startLocationUpdates() { 466 | // Begin by checking if the device has the necessary location settings. 467 | LocationServices.getSettingsClient(this) 468 | .checkLocationSettings(locationSettingRequest.build()) 469 | .addOnSuccessListener(this) { 470 | getLocationFromFusedLocation() 471 | }.addOnFailureListener(this) { e -> 472 | when ((e as ApiException).statusCode) { 473 | LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> { 474 | Logger.i( 475 | TAG, 476 | resources.getString(R.string.location_settings_are_not_satisfied) 477 | ) 478 | try { 479 | val rae = e as ResolvableApiException 480 | launcher.launch(IntentSenderRequest.Builder(rae.resolution).build()) 481 | } catch (sie: IntentSender.SendIntentException) { 482 | Logger.i( 483 | TAG, 484 | getString(R.string.pendingintent_unable_to_execute_request) 485 | ) 486 | viewModel.fetchSavedLocation() 487 | sie.printStackTrace() 488 | } 489 | } 490 | LocationSettingsStatusCodes.SETTINGS_CHANGE_UNAVAILABLE -> { 491 | val errorMessage = 492 | resources.getString(R.string.location_settings_are_inadequate_and_cannot_be_fixed_here) 493 | Logger.e(TAG, errorMessage) 494 | viewModel.fetchSavedLocation() 495 | } 496 | } 497 | } 498 | } 499 | 500 | private fun getLocationFromFusedLocation() { 501 | if (ActivityCompat.checkSelfPermission( 502 | this, 503 | Manifest.permission.ACCESS_FINE_LOCATION 504 | ) != PackageManager.PERMISSION_GRANTED 505 | && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) 506 | != PackageManager.PERMISSION_GRANTED 507 | ) { 508 | return 509 | } 510 | 511 | fusedLocationProviderClient?.flushLocations() 512 | Looper.myLooper()?.let { 513 | fusedLocationProviderClient?.requestLocationUpdates( 514 | locationRequest, 515 | object : LocationCallback() { 516 | override fun onLocationResult(locationResult: LocationResult) { 517 | super.onLocationResult(locationResult) 518 | val location = locationResult.lastLocation 519 | Log.e("location", location.toString()) 520 | if (location != null) { 521 | viewModel.saveLatLngToSharedPref(location.latitude, location.longitude) 522 | } 523 | if (!fetchLocationForFirstTime) { 524 | viewModel.fetchSavedLocation() 525 | fetchLocationForFirstTime = true 526 | } 527 | } 528 | 529 | override fun onLocationAvailability(locationAvailability: LocationAvailability) { 530 | super.onLocationAvailability(locationAvailability) 531 | if (!locationAvailability.isLocationAvailable) { 532 | viewModel.fetchSavedLocation() 533 | } 534 | } 535 | }, 536 | it 537 | ) 538 | } 539 | } 540 | 541 | 542 | private fun stopLocationUpdates() { 543 | // here, we are removing callback from runnable for handler so it will get called ahead. 544 | removeCallbacks() 545 | } 546 | 547 | override fun onDestroy() { 548 | super.onDestroy() 549 | stopLocationUpdates() 550 | } 551 | 552 | private fun isGpsEnabled() { 553 | if (!fetchLocationForFirstTime) { 554 | startLocationUpdates() 555 | } else { 556 | viewModel.fetchSavedLocation() 557 | } 558 | } 559 | } 560 | --------------------------------------------------------------------------------