diff --git a/README.md b/README.md index b760dc8..3b08923 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,21 @@ ## Description It is a sample app with basic google maps functionality. - - - - - - - - - + + + + + + + + + + + + + + + ## About MLSDev diff --git a/app/build.gradle b/app/build.gradle index b317322..7f9f1b1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,13 +1,12 @@ apply plugin: 'com.android.application' -apply plugin: 'me.tatarka.retrolambda' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-android' android { - compileSdkVersion 25 - buildToolsVersion "25.0.0" + compileSdkVersion 28 defaultConfig { applicationId "com.mlsdev.mapsappsample" - minSdkVersion 18 - targetSdkVersion 25 + minSdkVersion 19 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -19,50 +18,54 @@ android { } } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + dataBinding { enabled = true } - compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 - } } ext { - googleServicesVersion = '9.6.0' - supportVersion = '25.0.0' retrofitVersion = '2.1.0' powermockVersion = '1.6.5' dexmarkVersion = '1.2' junitVersion = '4.12' - supportTestsLibVersion = '0.5' - espressoVersion = '2.2.2' okhttpVersion = '3.4.1' } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile "com.android.support:appcompat-v7:$supportVersion" - testCompile "junit:junit:$junitVersion" + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation "junit:junit:$junitVersion" + + def androidx_version = '1.0.0' + def androidx_appcompat_version = '1.0.2' + implementation "androidx.appcompat:appcompat:$androidx_appcompat_version" + implementation "androidx.cardview:cardview:$androidx_version" + implementation "androidx.recyclerview:recyclerview:$androidx_version" + implementation "com.google.android.material:material:$androidx_version" // Maps, Places and Location - compile "com.google.android.gms:play-services-maps:$googleServicesVersion" - compile "com.google.android.gms:play-services-places:$googleServicesVersion" - compile "com.google.android.gms:play-services-location:$googleServicesVersion" + def googleServicesVersion = '17.0.0' + implementation "com.google.android.gms:play-services-maps:$googleServicesVersion" + implementation "com.google.android.gms:play-services-places:$googleServicesVersion" + implementation "com.google.android.gms:play-services-location:$googleServicesVersion" + implementation "com.google.maps.android:android-maps-utils:0.5" - // UI tests - androidTestCompile "junit:junit:$junitVersion" - androidTestCompile "com.android.support.test:runner:$supportTestsLibVersion" - androidTestCompile "com.android.support.test:rules:$supportTestsLibVersion" - androidTestCompile "com.android.support:support-annotations:$supportVersion" - androidTestCompile "com.android.support.test.espresso:espresso-intents:$espressoVersion" - androidTestCompile "com.android.support.test.espresso:espresso-core:$espressoVersion" - compile 'com.github.bumptech.glide:glide:3.7.0' + implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.github.bumptech.glide:glide:3.7.0' // RxJava - compile 'io.reactivex:rxjava:1.1.6' - compile 'io.reactivex:rxandroid:1.2.1' - compile 'com.tbruyelle.rxpermissions:rxpermissions:0.8.0@aar' + implementation 'io.reactivex:rxjava:1.1.6' + implementation 'io.reactivex:rxandroid:1.2.1' + implementation 'com.tbruyelle.rxpermissions:rxpermissions:0.8.0@aar' + implementation "androidx.core:core-ktx:1.0.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} +repositories { + mavenCentral() } diff --git a/app/src/androidTest/java/com/mlsdev/mapsappsample/MainActivityTest.java b/app/src/androidTest/java/com/mlsdev/mapsappsample/MainActivityTest.java deleted file mode 100644 index c303722..0000000 --- a/app/src/androidTest/java/com/mlsdev/mapsappsample/MainActivityTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.mlsdev.mapsappsample; - - -import android.support.test.espresso.Espresso; -import android.support.test.espresso.action.ViewActions; -import android.support.test.espresso.intent.Intents; -import android.support.test.espresso.intent.matcher.IntentMatchers; -import android.support.test.espresso.intent.rule.IntentsTestRule; -import android.support.test.espresso.matcher.ViewMatchers; - -import org.junit.Rule; -import org.junit.Test; - -public class MainActivityTest { - - @Rule - public IntentsTestRule rule = new IntentsTestRule<>(MainActivity.class); - - @Test - public void testStartPlacePickerActivity() { - Espresso.onView(ViewMatchers.withId(R.id.btn_pick_a_place)) - .perform(ViewActions.click()); - - Intents.intended(IntentMatchers.hasComponent(PlacePickerActivity.class.getName())); - } - -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d6173ad..f38a3d3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,16 +1,23 @@ + + + android:fullBackupContent="true" + android:theme="@style/AppTheme" + tools:ignore="GoogleAppIndexingWarning"> @@ -33,8 +40,22 @@ android:name=".StaticImageMapActivity" android:label="@string/title_static_map" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.java deleted file mode 100644 index 4b4fbc6..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; - -public class BaseActivity extends AppCompatActivity { - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) - finish(); - - return super.onOptionsItemSelected(item); - } - - protected void showBackButton(boolean show) { - if (getSupportActionBar() != null) { - getSupportActionBar().setHomeButtonEnabled(show); - getSupportActionBar().setDisplayHomeAsUpEnabled(show); - } - } - - public void showAlertDialog(@Nullable String title, @NonNull String message) { - AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.AlertDialog); - builder.setTitle(title) - .setMessage(message) - .setPositiveButton("OK", null); - - builder.create().show(); - } - -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.kt new file mode 100644 index 0000000..62c0542 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/BaseActivity.kt @@ -0,0 +1,32 @@ +package com.mlsdev.mapsappsample + +import android.view.MenuItem +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity + +abstract class BaseActivity : AppCompatActivity() { + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) + finish() + + return super.onOptionsItemSelected(item) + } + + protected fun showBackButton(show: Boolean) { + if (supportActionBar != null) { + supportActionBar!!.setHomeButtonEnabled(show) + supportActionBar!!.setDisplayHomeAsUpEnabled(show) + } + } + + fun showAlertDialog(title: String?, message: String) { + val builder = AlertDialog.Builder(this, R.style.AlertDialog) + builder.setTitle(title) + .setMessage(message) + .setPositiveButton("OK", null) + + builder.create().show() + } + +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.java deleted file mode 100644 index 06dd5ca..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.os.Bundle; - -import com.google.android.gms.maps.GoogleMap; -import com.google.android.gms.maps.OnMapReadyCallback; -import com.google.android.gms.maps.SupportMapFragment; -import com.google.android.gms.maps.model.BitmapDescriptorFactory; -import com.google.android.gms.maps.model.LatLng; -import com.google.android.gms.maps.model.MarkerOptions; - -public class BasicMapActivity extends BaseActivity implements OnMapReadyCallback { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_basic_map); - showBackButton(true); - - SupportMapFragment mapFragment = - (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map); - mapFragment.getMapAsync(this); - } - - @Override - public void onMapReady(GoogleMap googleMap) { - googleMap.addMarker(new MarkerOptions() - .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher)) - .position(new LatLng(40.759035, -73.980115)).title("MLSDev")); - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.kt new file mode 100644 index 0000000..5557084 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/BasicMapActivity.kt @@ -0,0 +1,28 @@ +package com.mlsdev.mapsappsample + +import android.os.Bundle + +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions + +class BasicMapActivity : BaseActivity(), OnMapReadyCallback { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_basic_map) + showBackButton(true) + + val mapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment? + mapFragment!!.getMapAsync(this) + } + + override fun onMapReady(googleMap: GoogleMap) { + googleMap.addMarker(MarkerOptions() + .icon(BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher)) + .position(LatLng(40.759035, -73.980115)).title("MLSDev")) + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.java deleted file mode 100644 index ff1d44f..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.app.Dialog; -import android.content.IntentSender; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GooglePlayServicesUtil; -import com.google.android.gms.common.api.GoogleApiClient; -import com.google.android.gms.location.places.Places; - -public class GoogleApiClientActivity extends BaseActivity implements GoogleApiClient.ConnectionCallbacks, - GoogleApiClient.OnConnectionFailedListener { - - protected GoogleApiClient googleApiClient; - private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000; - public static final int PLACE_PICKER_REQUEST = 1; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - buildGoogleApiClient(); - } - - @Override - protected void onStart() { - super.onStart(); - googleApiClient.connect(); - } - - @Override - protected void onStop() { - super.onStop(); - googleApiClient.disconnect(); - } - - private void buildGoogleApiClient() { - googleApiClient = new GoogleApiClient - .Builder(this) - .addApi(Places.GEO_DATA_API) - .addApi(Places.PLACE_DETECTION_API) - .addConnectionCallbacks(this) - .addOnConnectionFailedListener(this) - .build(); - } - - private void showErrorDialog(int errorCode) { - - // Get the error dialog from Google Play services - Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog( - errorCode, - this, - CONNECTION_FAILURE_RESOLUTION_REQUEST); - - // If Google Play services can provide an error dialog - if (errorDialog != null) - errorDialog.show(); - } - - @Override - public void onConnected(@Nullable Bundle bundle) { - Log.d("myLogs", "onConnected()"); - } - - @Override - public void onConnectionSuspended(int i) { - Log.d("myLogs", "onConnectionSuspended()"); - } - - @Override - public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { - Log.d("myLogs", "onConnectionFailed() \r\n Error: " + connectionResult.getErrorMessage()); - if (connectionResult.hasResolution()) { - try { - // Start an Activity that tries to resolve the error - connectionResult.startResolutionForResult( - this, - CONNECTION_FAILURE_RESOLUTION_REQUEST); - /* - * Thrown if Google Play services canceled the original - * PendingIntent - */ - } catch (IntentSender.SendIntentException e) { - e.printStackTrace(); - } - } else { - /* - * If no resolution is available, display a dialog to the - * user with the error. - */ - showErrorDialog(connectionResult.getErrorCode()); - } - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.kt new file mode 100644 index 0000000..691dcd5 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/GoogleApiClientActivity.kt @@ -0,0 +1,88 @@ +package com.mlsdev.mapsappsample + +import android.content.IntentSender +import android.os.Bundle +import android.util.Log +import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.GooglePlayServicesUtil +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.location.places.Places + +abstract class GoogleApiClientActivity : BaseActivity(), GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener { + + protected var googleApiClient: GoogleApiClient? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + buildGoogleApiClient() + } + + override fun onStart() { + super.onStart() + googleApiClient?.connect() + } + + override fun onStop() { + super.onStop() + googleApiClient?.disconnect() + } + + private fun buildGoogleApiClient() { + googleApiClient = GoogleApiClient.Builder(this) + .addApi(Places.GEO_DATA_API) + .addApi(Places.PLACE_DETECTION_API) + .addConnectionCallbacks(this) + .addOnConnectionFailedListener(this) + .build() + } + + private fun showErrorDialog(errorCode: Int) { + + // Get the error dialog from Google Play services + val errorDialog = GooglePlayServicesUtil.getErrorDialog( + errorCode, + this, + CONNECTION_FAILURE_RESOLUTION_REQUEST) + + // If Google Play services can provide an error dialog + errorDialog?.show() + } + + override fun onConnected(bundle: Bundle?) { + Log.d("myLogs", "onConnected()") + } + + override fun onConnectionSuspended(i: Int) { + Log.d("myLogs", "onConnectionSuspended()") + } + + override fun onConnectionFailed(connectionResult: ConnectionResult) { + Log.d("myLogs", "onConnectionFailed() \r\n Error: " + connectionResult.errorMessage!!) + if (connectionResult.hasResolution()) { + try { + // Start an Activity that tries to resolve the error + connectionResult.startResolutionForResult( + this, + CONNECTION_FAILURE_RESOLUTION_REQUEST) + /* + * Thrown if Google Play services canceled the original + * PendingIntent + */ + } catch (e: IntentSender.SendIntentException) { + e.printStackTrace() + } + + } else { + /* + * If no resolution is available, display a dialog to the + * user with the error. + */ + showErrorDialog(connectionResult.errorCode) + } + } + + companion object { + private const val CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000 + const val PLACE_PICKER_REQUEST = 1 + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.java deleted file mode 100644 index dea6f58..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.Manifest; -import android.content.Intent; -import android.databinding.DataBindingUtil; -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.view.View; - -import com.mlsdev.mapsappsample.databinding.ActivityMainBinding; -import com.tbruyelle.rxpermissions.RxPermissions; - -public class MainActivity extends AppCompatActivity { - private ActivityMainBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = DataBindingUtil.setContentView(this, R.layout.activity_main); - binding.setViewModel(new MainViewModel()); - getLocationPermission(); - } - - private void getLocationPermission() { - RxPermissions.getInstance(this) - .request(Manifest.permission.ACCESS_FINE_LOCATION) - .subscribe(granted -> { - if (!granted) - MainActivity.this.finish(); - }); - } - - - public class MainViewModel { - - public void onPickPlaceButtonClick(View view) { - startActivity(new Intent(MainActivity.this, PlacePickerActivity.class)); - } - - public void onMapButtonClick(View view) { - startActivity(new Intent(MainActivity.this, BasicMapActivity.class)); - } - - public void onStaticImageButtonClick(View view) { - startActivity(new Intent(MainActivity.this, StaticImageMapActivity.class)); - } - - public void onPlaceSuggestionsButtonClick(View view) { - startActivity(new Intent(MainActivity.this, PlaceSuggestionsActivity.class)); - } - - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.kt new file mode 100644 index 0000000..4b6a745 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/MainActivity.kt @@ -0,0 +1,51 @@ +package com.mlsdev.mapsappsample + +import android.Manifest +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import com.mlsdev.mapsappsample.databinding.ActivityMainBinding +import com.mlsdev.mapsappsample.markerclustering.MarkerClusteringActivity +import com.mlsdev.mapsappsample.placesuggestions.PlaceSuggestionsActivity +import com.tbruyelle.rxpermissions.RxPermissions + +class MainActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + + binding.btnOpenMap.setOnClickListener { + startActivity(Intent(this@MainActivity, BasicMapActivity::class.java)) + } + + binding.btnPickAPlace.setOnClickListener { + startActivity(Intent(this@MainActivity, PlacePickerActivity::class.java)) + } + + binding.btnPlaceSuggestions.setOnClickListener { + startActivity(Intent(this@MainActivity, PlaceSuggestionsActivity::class.java)) + } + + binding.btnStaticImage.setOnClickListener { + startActivity(Intent(this@MainActivity, StaticImageMapActivity::class.java)) + } + + binding.buttonClusterMarkers.setOnClickListener { + startActivity(Intent(this, MarkerClusteringActivity::class.java)) + } + + getLocationPermission() + } + + private fun getLocationPermission() { + RxPermissions.getInstance(this) + .request(Manifest.permission.ACCESS_FINE_LOCATION) + .subscribe { granted -> + if (!granted) + this@MainActivity.finish() + } + } + +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.java deleted file mode 100644 index ff01c14..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.content.Intent; -import android.databinding.DataBindingUtil; -import android.databinding.ObservableField; -import android.os.Bundle; -import android.view.View; - -import com.google.android.gms.common.GooglePlayServicesNotAvailableException; -import com.google.android.gms.common.GooglePlayServicesRepairableException; -import com.google.android.gms.location.places.Place; -import com.google.android.gms.location.places.ui.PlacePicker; -import com.mlsdev.mapsappsample.databinding.ActivityPlacePickerBinding; - -public class PlacePickerActivity extends GoogleApiClientActivity { - private ActivityPlacePickerBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - showBackButton(true); - binding = DataBindingUtil.setContentView(this, R.layout.activity_place_picker); - binding.setViewModel(new PlacePickerViewModel()); - startPlacesPickerActivity(); - } - - private void startPlacesPickerActivity() { - PlacePicker.IntentBuilder intentBuilder = new PlacePicker.IntentBuilder(); - try { - startActivityForResult(intentBuilder.build(this), PLACE_PICKER_REQUEST); - } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) { - e.printStackTrace(); - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PLACE_PICKER_REQUEST) { - if (resultCode == RESULT_OK) { - Place place = PlacePicker.getPlace(this, data); - binding.getViewModel().setData(place); - } - } - } - - public class PlacePickerViewModel { - public final ObservableField placeName; - public final ObservableField placeAddress; - - public PlacePickerViewModel() { - placeName = new ObservableField<>(); - placeAddress = new ObservableField<>(); - } - - public void onPickPlaceButtonClick(View view) { - startPlacesPickerActivity(); - } - - public void setData(Place place) { - placeName.set(place.getName().toString()); - placeAddress.set(place.getAddress().toString()); - } - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.kt new file mode 100644 index 0000000..bd778fe --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/PlacePickerActivity.kt @@ -0,0 +1,59 @@ +package com.mlsdev.mapsappsample + +import android.app.Activity +import android.content.Intent +import android.os.Bundle + +import androidx.databinding.DataBindingUtil +import androidx.databinding.ObservableField + +import com.google.android.gms.common.GooglePlayServicesNotAvailableException +import com.google.android.gms.common.GooglePlayServicesRepairableException +import com.google.android.gms.location.places.Place +import com.google.android.gms.location.places.ui.PlacePicker +import com.mlsdev.mapsappsample.databinding.ActivityPlacePickerBinding + +class PlacePickerActivity : GoogleApiClientActivity() { + private var binding: ActivityPlacePickerBinding? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + showBackButton(true) + binding = DataBindingUtil.setContentView(this, R.layout.activity_place_picker) + binding!!.viewModel = PlacePickerViewModel() + startPlacesPickerActivity() + + binding!!.btnPickAPlace.setOnClickListener { startPlacesPickerActivity() } + } + + private fun startPlacesPickerActivity() { + val intentBuilder = PlacePicker.IntentBuilder() + try { + startActivityForResult(intentBuilder.build(this), PLACE_PICKER_REQUEST) + } catch (e: GooglePlayServicesRepairableException) { + e.printStackTrace() + } catch (e: GooglePlayServicesNotAvailableException) { + e.printStackTrace() + } + + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == PLACE_PICKER_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + val place = PlacePicker.getPlace(this, data!!) + binding!!.viewModel?.setData(place) + } + } + } + + inner class PlacePickerViewModel { + val placeName: ObservableField = ObservableField() + val placeAddress: ObservableField = ObservableField() + + fun setData(place: Place) { + placeName.set(place.name.toString()) + placeAddress.set(place.address!!.toString()) + } + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/PlaceSuggestionsActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/PlaceSuggestionsActivity.java deleted file mode 100644 index e93069d..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/PlaceSuggestionsActivity.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.os.Bundle; -import android.util.Log; - -import com.google.android.gms.common.api.Status; -import com.google.android.gms.location.places.Place; -import com.google.android.gms.location.places.ui.PlaceAutocompleteFragment; -import com.google.android.gms.location.places.ui.PlaceSelectionListener; - -public class PlaceSuggestionsActivity extends GoogleApiClientActivity { - - private static final String TAG = "places"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_place_suggestions); - showBackButton(true); - - PlaceAutocompleteFragment autocompleteFragment = (PlaceAutocompleteFragment) - getFragmentManager().findFragmentById(R.id.place_autocomplete_fragment); - - autocompleteFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() { - @Override - public void onPlaceSelected(Place place) { - // TODO: Get info about the selected place. - Log.i(TAG, "Place: " + place.getName()); - } - - @Override - public void onError(Status status) { - // TODO: Handle the error. - Log.i(TAG, "An error occurred: " + status); - } - }); - - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.java b/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.java deleted file mode 100644 index b2bd5aa..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.mlsdev.mapsappsample; - -import android.databinding.DataBindingUtil; -import android.databinding.ObservableField; -import android.os.Bundle; -import android.support.v4.util.ArrayMap; -import android.view.View; - -import com.bumptech.glide.Glide; -import com.mlsdev.mapsappsample.databinding.ActivityStaticImageMapBinding; -import com.mlsdev.mapsappsample.listener.OnSeekBarListener; -import com.mlsdev.mapsappsample.url.UrlBuilder; - -import java.util.Map; - -import static com.mlsdev.mapsappsample.url.Constants.CENTER; -import static com.mlsdev.mapsappsample.url.Constants.KEY; -import static com.mlsdev.mapsappsample.url.Constants.MAP_TYPE; -import static com.mlsdev.mapsappsample.url.Constants.MARKERS; -import static com.mlsdev.mapsappsample.url.Constants.SCALE; -import static com.mlsdev.mapsappsample.url.Constants.SIZE; -import static com.mlsdev.mapsappsample.url.Constants.ZOOM; - - -public class StaticImageMapActivity extends BaseActivity { - private ActivityStaticImageMapBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - binding = DataBindingUtil.setContentView(this, R.layout.activity_static_image_map); - binding.setViewModel(new ViewModel()); - showBackButton(true); - } - - public class ViewModel { - public final ObservableField zoomProgress; - public final ObservableField scaleProgress; - - public ViewModel() { - zoomProgress = new ObservableField<>("0"); - scaleProgress = new ObservableField<>("0"); - binding.sbZoom.setOnSeekBarChangeListener(new OnSeekBarListener(zoomProgress)); - binding.sbScale.setOnSeekBarChangeListener(new OnSeekBarListener(scaleProgress)); - } - - public void onGetImageMapButtonClick(View view) { - getStaticImageMap(); - } - - private void getStaticImageMap() { - - if (binding.etCenter.getText().toString().isEmpty()) { - showAlertDialog(null, "Center can\'t be blank!"); - return; - } - - String mapType = (String) binding.spMapTypes.getSelectedItem(); - - Map params = new ArrayMap<>(); - params.put(CENTER, binding.etCenter.getText().toString()); - params.put(ZOOM, String.valueOf(binding.sbZoom.getProgress())); - params.put(SIZE, "550x300"); - params.put(MAP_TYPE, mapType); - params.put(SCALE, String.valueOf(binding.sbScale.getProgress())); - if (binding.cbAddMarker.isChecked()) { - String markerPlace = binding.etCenter.getText().toString(); - // markerPlace can be defined like a lat lon pair (45.523908,-122.669866) - params.put(MARKERS, "size:large|color:blue|label:A|" + markerPlace); - } - params.put(KEY, getString(R.string.google_app_api_key)); - Glide.with(StaticImageMapActivity.this) - .load(UrlBuilder.buildUrl(params)) - .crossFade() - .into(binding.ivStaticMap); - } - - } - - - -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.kt new file mode 100644 index 0000000..ec9a5be --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/StaticImageMapActivity.kt @@ -0,0 +1,73 @@ +package com.mlsdev.mapsappsample + +import android.os.Bundle +import android.util.ArrayMap +import androidx.databinding.DataBindingUtil +import androidx.databinding.ObservableField +import com.bumptech.glide.Glide +import com.mlsdev.mapsappsample.databinding.ActivityStaticImageMapBinding +import com.mlsdev.mapsappsample.listener.OnSeekBarListener +import com.mlsdev.mapsappsample.url.Constants.CENTER +import com.mlsdev.mapsappsample.url.Constants.KEY +import com.mlsdev.mapsappsample.url.Constants.MAP_TYPE +import com.mlsdev.mapsappsample.url.Constants.MARKERS +import com.mlsdev.mapsappsample.url.Constants.SCALE +import com.mlsdev.mapsappsample.url.Constants.SIZE +import com.mlsdev.mapsappsample.url.Constants.ZOOM +import com.mlsdev.mapsappsample.url.UrlBuilder + + +class StaticImageMapActivity : BaseActivity() { + private var binding: ActivityStaticImageMapBinding? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_static_image_map) + binding!!.viewModel = ViewModel() + showBackButton(true) + } + + inner class ViewModel { + val zoomProgress: ObservableField = ObservableField("0") + val scaleProgress: ObservableField = ObservableField("0") + + init { + binding!!.sbZoom.setOnSeekBarChangeListener(OnSeekBarListener(zoomProgress)) + binding!!.sbScale.setOnSeekBarChangeListener(OnSeekBarListener(scaleProgress)) + } + + fun onGetImageMapButtonClick() { + getStaticImageMap() + } + + private fun getStaticImageMap() { + + if (binding!!.etCenter.text!!.toString().isEmpty()) { + showAlertDialog(null, "Center can\'t be blank!") + return + } + + val mapType = binding!!.spMapTypes.selectedItem as String + + val params = ArrayMap() + params[CENTER] = binding!!.etCenter.text!!.toString() + params[ZOOM] = binding!!.sbZoom.progress.toString() + params[SIZE] = "550x300" + params[MAP_TYPE] = mapType + params[SCALE] = binding!!.sbScale.progress.toString() + if (binding!!.cbAddMarker.isChecked) { + val markerPlace = binding!!.etCenter.text!!.toString() + // markerPlace can be defined like a lat lon pair (45.523908,-122.669866) + params[MARKERS] = "size:large|color:blue|label:A|$markerPlace" + } + params[KEY] = getString(R.string.google_app_api_key) + Glide.with(this@StaticImageMapActivity) + .load(UrlBuilder.buildUrl(params)) + .crossFade() + .into(binding!!.ivStaticMap) + } + + } + + +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.java b/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.java deleted file mode 100644 index 8863eea..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.mlsdev.mapsappsample.listener; - -import android.databinding.ObservableField; -import android.widget.SeekBar; - -public class OnSeekBarListener implements SeekBar.OnSeekBarChangeListener { - private final ObservableField progress; - - public OnSeekBarListener(ObservableField progress) { - this.progress = progress; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - progress.set(String.valueOf(seekBar.getProgress())); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - } -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.kt b/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.kt new file mode 100644 index 0000000..127add4 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/listener/OnSeekBarListener.kt @@ -0,0 +1,16 @@ +package com.mlsdev.mapsappsample.listener + +import android.widget.SeekBar + +import androidx.databinding.ObservableField + +class OnSeekBarListener(private val progress: ObservableField) : SeekBar.OnSeekBarChangeListener { + + override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { + progress.set(seekBar.progress.toString()) + } + + override fun onStartTrackingTouch(seekBar: SeekBar) {} + + override fun onStopTrackingTouch(seekBar: SeekBar) {} +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringActivity.kt new file mode 100644 index 0000000..4f17a9c --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringActivity.kt @@ -0,0 +1,215 @@ +package com.mlsdev.mapsappsample.markerclustering + +import android.Manifest +import android.annotation.SuppressLint +import android.location.Location +import android.location.LocationListener +import android.location.LocationManager +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.core.content.ContextCompat +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.Observer +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.SupportMapFragment +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.MarkerOptions +import com.google.maps.android.clustering.Cluster +import com.google.maps.android.clustering.ClusterManager +import com.google.maps.android.clustering.view.DefaultClusterRenderer +import com.google.maps.android.ui.IconGenerator +import com.mlsdev.mapsappsample.BaseActivity +import com.mlsdev.mapsappsample.R +import com.mlsdev.mapsappsample.databinding.ActivityMarkerClusteringBinding +import com.mlsdev.mapsappsample.databinding.LayoutMarkersClusterBinding +import com.mlsdev.mapsappsample.utils.DrawableToBitmapDecoder +import com.mlsdev.mapsappsample.utils.MapUtils +import com.tbruyelle.rxpermissions.RxPermissions + +class MarkerClusteringActivity : + BaseActivity(), + OnMapReadyCallback, + LocationListener, + ClusterManager.OnClusterClickListener, + ClusterManager.OnClusterItemClickListener { + + companion object { + const val MIN_CLUSTER_SIZE = 1 + } + + lateinit var binding: ActivityMarkerClusteringBinding + lateinit var viewModel: MarkerClusteringViewModel + lateinit var clusterManager: ClusterManager + lateinit var markerRenderer: PrimaryMarkerRenderer + private var googleMap: GoogleMap? = null + private var lastLatLng: LatLng? = null + + private val markers = hashMapOf( + MarkerItem.Type.HEALTHCARE_CENTER to R.drawable.ic_marker_healthcare_center, + MarkerItem.Type.LABORATORY to R.drawable.ic_marker_laboratory, + MarkerItem.Type.VACCINATION to R.drawable.ic_marker_vaccination) + + private val markersLarge = hashMapOf( + MarkerItem.Type.HEALTHCARE_CENTER to R.drawable.ic_marker_healthcare_center_large, + MarkerItem.Type.LABORATORY to R.drawable.ic_marker_laboratory_large, + MarkerItem.Type.VACCINATION to R.drawable.ic_marker_vaccination_large) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_marker_clustering) + viewModel = MarkerClusteringViewModel() + binding.viewModel = viewModel + observeMarkers() + setupMap() + } + + private fun setupMap() { + val mapFragment: SupportMapFragment = supportFragmentManager.findFragmentById(R.id.map) as SupportMapFragment + mapFragment.getMapAsync(this) + } + + override fun onMapReady(map: GoogleMap?) { + googleMap = googleMap ?: map + googleMap?.let { + it.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(51.503186, -0.126446), 10f)) + clusterManager = ClusterManager(this, it) + markerRenderer = PrimaryMarkerRenderer(it, clusterManager) + clusterManager.renderer = markerRenderer + clusterManager.setOnClusterClickListener(this) + clusterManager.setOnClusterItemClickListener(this) + it.setOnCameraIdleListener(clusterManager) + it.setOnMarkerClickListener(clusterManager) + getLocationPermission() + setupMarkers() + } + } + + @SuppressLint("MissingPermission") + private fun getLocationPermission() { + RxPermissions.getInstance(this) + .request(Manifest.permission.ACCESS_FINE_LOCATION) + .subscribe { granted -> + if (granted) { + googleMap?.isMyLocationEnabled = true + val locationManager = getSystemService(LOCATION_SERVICE) as LocationManager + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1L, 1f, this) + } + } + } + + private fun setupMarkers() { + val inputStream = resources.openRawResource(R.raw.radar_search) + viewModel.prepareMarkers(inputStream) + } + + private fun observeMarkers() { + viewModel.markers.observe(this, Observer { markers -> + clusterManager.addItems(markers) + }) + } + + inner class PrimaryMarkerRenderer(map: GoogleMap, clusterManager: ClusterManager) + : DefaultClusterRenderer(applicationContext, map, clusterManager) { + + private val clusterIconGenerator = IconGenerator(applicationContext) + private val clusterBinding = LayoutMarkersClusterBinding.inflate(layoutInflater) + var prevSelectedItem: MarkerItem? = null + + override fun onBeforeClusterItemRendered(item: MarkerItem, markerOptions: MarkerOptions) { + val drawableRes = markers[item.type] ?: R.drawable.ic_marker + val bitmap = DrawableToBitmapDecoder.getBitmap(applicationContext, drawableRes) + markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap)) + } + + override fun onBeforeClusterRendered(cluster: Cluster?, markerOptions: MarkerOptions?) { + clusterIconGenerator.setBackground(ContextCompat.getDrawable(applicationContext, android.R.color.transparent)) + clusterIconGenerator.setContentView(clusterBinding.root) + clusterBinding.markersCount.text = cluster?.size.toString() + markerOptions?.icon(BitmapDescriptorFactory.fromBitmap(clusterIconGenerator.makeIcon())) + } + + override fun shouldRenderAsCluster(cluster: Cluster?): Boolean { + return cluster?.size ?: 0 > MIN_CLUSTER_SIZE + } + + } + + override fun onClusterClick(cluster: Cluster): Boolean { + + // Zoom in the cluster. Need to create LatLngBounds and including all the cluster items + // inside of bounds, then animate to center of the bounds. + + // Create the builder to collect all essential cluster items for the bounds. + val builder = LatLngBounds.builder() + for (item in cluster.items) { + builder.include(item.position) + } + // Get the LatLngBounds + val bounds = builder.build() + + // Animate camera to the bounds + try { + googleMap?.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 100)) + } catch (e: Exception) { + e.printStackTrace() + } + + return true + } + + override fun onClusterItemClick(markerItem: MarkerItem?): Boolean { + val prevItem = markerRenderer.prevSelectedItem + val prevMarker = markerRenderer.getMarker(prevItem) + + if (prevItem != null && prevMarker != null) { + val drawableRes = markers[prevItem.type] ?: R.drawable.ic_marker + val bitmap = DrawableToBitmapDecoder.getBitmap(applicationContext, drawableRes) + prevMarker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) + } + + markerRenderer.getMarker(markerItem)?.let { marker -> + markerRenderer.prevSelectedItem = markerItem + val drawableRes = markersLarge[markerItem?.type] ?: R.drawable.ic_marker + val bitmap = DrawableToBitmapDecoder.getBitmap(applicationContext, drawableRes) + marker.setIcon(BitmapDescriptorFactory.fromBitmap(bitmap)) + } + + showMarkerInfoDialog(markerItem) + + return true + } + + private fun showMarkerInfoDialog(markerItem: MarkerItem?) { + val currentLatLng = lastLatLng + + if (currentLatLng != null && markerItem != null) { + val markerLatLng = LatLng(markerItem.lat, markerItem.lng) + val distance = MapUtils.calculationByDistanceInKm(currentLatLng, markerLatLng) + val message = getString(R.string.template_marker_info, markerItem.type.toString(), distance.toString()) + + AlertDialog.Builder(this) + .setTitle(R.string.label_marker_info) + .setMessage(message) + .setPositiveButton(R.string.label_close, null) + .create() + .show() + } + } + + override fun onLocationChanged(location: Location?) { + location?.let { lastLatLng = LatLng(it.latitude, it.longitude) } + } + + override fun onProviderDisabled(provider: String?) { + } + + override fun onProviderEnabled(provider: String?) { + } + + override fun onStatusChanged(provider: String?, p1: Int, p2: Bundle?) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringViewModel.kt b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringViewModel.kt new file mode 100644 index 0000000..22f5122 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerClusteringViewModel.kt @@ -0,0 +1,14 @@ +package com.mlsdev.mapsappsample.markerclustering + +import androidx.lifecycle.MutableLiveData +import java.io.InputStream + +class MarkerClusteringViewModel { + val markers = MutableLiveData>() + + fun prepareMarkers(inputStream: InputStream) { + val markers = MarkerItemReader.read(inputStream) + this.markers.postValue(markers) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItem.kt b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItem.kt new file mode 100644 index 0000000..b0fb1cf --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItem.kt @@ -0,0 +1,31 @@ +package com.mlsdev.mapsappsample.markerclustering + +import com.google.android.gms.maps.model.LatLng +import com.google.gson.annotations.SerializedName +import com.google.maps.android.clustering.ClusterItem + +data class MarkerItem( + @SerializedName("lat") + val lat: Double, + @SerializedName("lng") + val lng: Double, + @SerializedName("title") + val markerTitle: String?, + @SerializedName("snippet") + val markerDescriptor: String? +) : ClusterItem { + + var type: Type = Type.LABORATORY + + override fun getSnippet(): String = markerDescriptor ?: "" + + override fun getTitle(): String = markerTitle ?: "" + + override fun getPosition(): LatLng = LatLng(lat, lng) + + enum class Type { + VACCINATION, + LABORATORY, + HEALTHCARE_CENTER + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItemReader.kt b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItemReader.kt new file mode 100644 index 0000000..fd898ef --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/markerclustering/MarkerItemReader.kt @@ -0,0 +1,44 @@ +package com.mlsdev.mapsappsample.markerclustering + +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import org.json.JSONException +import java.io.InputStream +import java.util.* +import kotlin.collections.ArrayList +import kotlin.random.Random + + +object MarkerItemReader { + + /* + * This matches only once in whole input, + * so Scanner.next returns whole InputStream as a String. + * http://stackoverflow.com/a/5445161/2183804 + */ + private const val REGEX_INPUT_BOUNDARY_BEGINNING = "\\A" + + @Throws(JSONException::class) + fun read(inputStream: InputStream): List { + val random = Random(1989) + val itemTypes = arrayListOf( + MarkerItem.Type.LABORATORY, + MarkerItem.Type.VACCINATION, + MarkerItem.Type.HEALTHCARE_CENTER + ) + val items = ArrayList() + val json = Scanner(inputStream).useDelimiter(REGEX_INPUT_BOUNDARY_BEGINNING).next() + val gson = Gson() + + val listType = object : TypeToken>() {}.type + + items.addAll(gson.fromJson(json, listType)) + + for (item in items) { + item.type = itemTypes[random.nextInt(0, 3)] + } + + return items + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceEntity.kt b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceEntity.kt new file mode 100644 index 0000000..e25a19d --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceEntity.kt @@ -0,0 +1,3 @@ +package com.mlsdev.mapsappsample.placesuggestions + +class PlaceEntity diff --git a/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceSuggestionsActivity.kt b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceSuggestionsActivity.kt new file mode 100644 index 0000000..0407b77 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceSuggestionsActivity.kt @@ -0,0 +1,83 @@ +package com.mlsdev.mapsappsample.placesuggestions + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.util.Log +import android.view.View + +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView + +import com.google.android.gms.common.api.Status +import com.google.android.gms.location.places.Place +import com.google.android.gms.location.places.ui.PlaceAutocompleteFragment +import com.google.android.gms.location.places.ui.PlaceSelectionListener +import com.mlsdev.mapsappsample.GoogleApiClientActivity +import com.mlsdev.mapsappsample.R +import com.mlsdev.mapsappsample.databinding.ActivityPlaceSuggestionsBinding + +class PlaceSuggestionsActivity : GoogleApiClientActivity() { + lateinit var binding: ActivityPlaceSuggestionsBinding + lateinit var viewModel: PlacesViewModel + lateinit var placesAdapter: PlacesAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + showBackButton(true) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_place_suggestions) + viewModel = PlacesViewModel(googleApiClient) + binding.viewModel = viewModel + binding.etSearch.addTextChangedListener(OnSearchTextChangedListener()) + + placesAdapter = PlacesAdapter { viewModel.onPlaceSelected(it) } + + binding.rvPlacesSuggestions.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) + binding.rvPlacesSuggestions.adapter = placesAdapter + + val autocompleteFragment = fragmentManager.findFragmentById(R.id.place_autocomplete_fragment) as PlaceAutocompleteFragment + + autocompleteFragment.setOnPlaceSelectedListener(object : PlaceSelectionListener { + override fun onPlaceSelected(place: Place) { + // TODO: Get info about the selected place. + Log.i(TAG, "Place: " + place.name) + } + + override fun onError(status: Status) { + // TODO: Handle the error. + Log.i(TAG, "An error occurred: $status") + } + }) + + binding.btnClearSearchText.setOnClickListener { binding.etSearch.setText("") } + } + + inner class OnSearchTextChangedListener : TextWatcher { + + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + + } + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + + } + + override fun afterTextChanged(editable: Editable) { + binding.btnClearSearchText.visibility = if (editable.isNotEmpty()) View.VISIBLE else View.INVISIBLE + + if (editable.isEmpty()) + placesAdapter.clearData() + + if (editable.length > 2 && !viewModel.isItemSelected) + viewModel.searchPlaces(editable.toString()) + + binding.etSearch.setSelection(editable.length) + } + } + + companion object { + private const val TAG = "places" + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceViewModel.kt b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceViewModel.kt new file mode 100644 index 0000000..8bd9f66 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlaceViewModel.kt @@ -0,0 +1,15 @@ +package com.mlsdev.mapsappsample.placesuggestions + +import android.util.Log +import android.view.View + +import androidx.databinding.ObservableField + +class PlaceViewModel(private val onItemClickListener: (place: String) -> Unit) { + val placeFullText: ObservableField = ObservableField() + + fun onClickView(view: View) { + Log.d("PlacesItem", placeFullText.get()) + onItemClickListener(placeFullText.get()!!) + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesAdapter.kt b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesAdapter.kt new file mode 100644 index 0000000..7552292 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesAdapter.kt @@ -0,0 +1,42 @@ +package com.mlsdev.mapsappsample.placesuggestions + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.mlsdev.mapsappsample.databinding.PlaceSuggestionsItemBinding +import java.util.* + +class PlacesAdapter( + private val onItemClickListener: (place:String) -> Unit +) : RecyclerView.Adapter() { + private var places: MutableList = ArrayList() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val binding = PlaceSuggestionsItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + + val viewModel = PlaceViewModel(onItemClickListener) + binding.viewModel = viewModel + return ViewHolder(binding.root, viewModel) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.viewModel.placeFullText.set(places[position]) + } + + override fun getItemCount(): Int { + return places.size + } + + inner class ViewHolder(itemView: View, val viewModel: PlaceViewModel) : RecyclerView.ViewHolder(itemView) + + fun setData(places: MutableList) { + this.places = places + notifyDataSetChanged() + } + + fun clearData() { + places.clear() + notifyDataSetChanged() + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesViewModel.kt b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesViewModel.kt new file mode 100644 index 0000000..58337f1 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/placesuggestions/PlacesViewModel.kt @@ -0,0 +1,55 @@ +package com.mlsdev.mapsappsample.placesuggestions + +import android.util.Log +import androidx.databinding.BaseObservable +import androidx.databinding.ObservableField +import androidx.lifecycle.MutableLiveData +import com.google.android.gms.common.api.GoogleApiClient +import com.google.android.gms.common.api.ResultCallbacks +import com.google.android.gms.common.api.Status +import com.google.android.gms.location.places.AutocompleteFilter +import com.google.android.gms.location.places.AutocompletePredictionBuffer +import com.google.android.gms.location.places.Place +import com.google.android.gms.location.places.Places +import java.util.* + +class PlacesViewModel(private val googleApiClient: GoogleApiClient?) : BaseObservable() { + var isItemSelected: Boolean = false + val searchFieldValue: ObservableField = ObservableField() + var placesLiveData = MutableLiveData>() + + fun searchPlaces(value: String) { + isItemSelected = false + val autocompleteFilter = AutocompleteFilter.Builder() + .setTypeFilter(Place.TYPE_STREET_ADDRESS) + .build() + + if (googleApiClient == null) + return + + Places.GeoDataApi.getAutocompletePredictions(googleApiClient, value, null, autocompleteFilter) + .setResultCallback(object : ResultCallbacks() { + override fun onSuccess(autocompletePredictions: AutocompletePredictionBuffer) { + val placesFullTextList = ArrayList(autocompletePredictions.count) + for (prediction in autocompletePredictions) + placesFullTextList.add(prediction.getFullText(null).toString()) + + autocompletePredictions.release() + + placesLiveData.postValue(placesFullTextList) + } + + override fun onFailure(status: Status) { + Log.e("PLACES_API", "Error: Places.GeoDataApi.getAutocompletePredictions()") + } + }) + } + + + + fun onPlaceSelected(placeFullText: String) { + isItemSelected = true + placesLiveData.postValue(emptyList()) + searchFieldValue.set(placeFullText) + } +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.java b/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.java deleted file mode 100644 index 32e57ca..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.mlsdev.mapsappsample.url; - -public class Constants { - public static final String BASE_URL = "https://maps.googleapis.com/maps/api/staticmap"; - - // region Parameters - public static final String CENTER = "center"; - public static final String SCALE = "scale"; - public static final String SIZE = "size"; - public static final String KEY = "key"; - public static final String ZOOM = "zoom"; - public static final String MAP_TYPE = "maptype"; - public static final String MARKERS = "markers"; - // endregion -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.kt b/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.kt new file mode 100644 index 0000000..c8a9d6a --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/url/Constants.kt @@ -0,0 +1,15 @@ +package com.mlsdev.mapsappsample.url + +object Constants { + const val BASE_URL = "https://maps.googleapis.com/maps/api/staticmap" + + // region Parameters + const val CENTER = "center" + const val SCALE = "scale" + const val SIZE = "size" + const val KEY = "key" + const val ZOOM = "zoom" + const val MAP_TYPE = "maptype" + const val MARKERS = "markers" + // endregion +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.java b/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.java deleted file mode 100644 index 0e2c075..0000000 --- a/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mlsdev.mapsappsample.url; - -import android.net.Uri; -import android.support.annotation.NonNull; - -import java.util.Map; - -public class UrlBuilder { - - public static String buildUrl(@NonNull Map params) { - Uri.Builder uriBuilder = Uri.parse(Constants.BASE_URL).buildUpon(); - for (Map.Entry entry : params.entrySet()) - uriBuilder.appendQueryParameter(entry.getKey(), entry.getValue()); - return uriBuilder.build().toString(); - } - -} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.kt b/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.kt new file mode 100644 index 0000000..049e0a9 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/url/UrlBuilder.kt @@ -0,0 +1,14 @@ +package com.mlsdev.mapsappsample.url + +import android.net.Uri + +object UrlBuilder { + + fun buildUrl(params: Map): String { + val uriBuilder = Uri.parse(Constants.BASE_URL).buildUpon() + for ((key, value) in params) + uriBuilder.appendQueryParameter(key, value) + return uriBuilder.build().toString() + } + +} diff --git a/app/src/main/java/com/mlsdev/mapsappsample/utils/DrawableToBitmapDecoder.kt b/app/src/main/java/com/mlsdev/mapsappsample/utils/DrawableToBitmapDecoder.kt new file mode 100644 index 0000000..16db544 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/utils/DrawableToBitmapDecoder.kt @@ -0,0 +1,37 @@ +package com.mlsdev.mapsappsample.utils + +import android.annotation.TargetApi +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.VectorDrawable +import android.os.Build +import androidx.core.content.ContextCompat + + +object DrawableToBitmapDecoder { + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + fun getBitmap(vectorDrawable: VectorDrawable): Bitmap { + val bitmap = Bitmap.createBitmap(vectorDrawable.intrinsicWidth, + vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()) + vectorDrawable.draw(canvas) + return bitmap + } + + fun getBitmap(context: Context, drawableId: Int): Bitmap { + val drawable = ContextCompat.getDrawable(context, drawableId) + return if (drawable is BitmapDrawable) { + BitmapFactory.decodeResource(context.resources, drawableId) + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && drawable is VectorDrawable) { + getBitmap((drawable as VectorDrawable?)!!) + } else { + throw IllegalArgumentException("unsupported drawable type") + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mlsdev/mapsappsample/utils/MapUtils.kt b/app/src/main/java/com/mlsdev/mapsappsample/utils/MapUtils.kt new file mode 100644 index 0000000..36cd724 --- /dev/null +++ b/app/src/main/java/com/mlsdev/mapsappsample/utils/MapUtils.kt @@ -0,0 +1,37 @@ +package com.mlsdev.mapsappsample.utils + +import android.util.Log +import com.google.android.gms.maps.model.LatLng +import java.text.DecimalFormat +import kotlin.math.* + +object MapUtils { + + fun calculationByDistance(from: LatLng, to: LatLng): Double { + val radius = 6371// radius of earth in Km + val lat1 = from.latitude + val lat2 = to.latitude + val lon1 = from.longitude + val lon2 = to.longitude + val dLat = Math.toRadians(lat2 - lat1) + val dLon = Math.toRadians(lon2 - lon1) + val a = sin(dLat / 2) * sin(dLat / 2) + (cos(Math.toRadians(lat1)) + * cos(Math.toRadians(lat2)) * sin(dLon / 2) + * sin(dLon / 2)) + val c = 2 * asin(sqrt(a)) + val valueResult = radius * c + val km = valueResult / 1 + val newFormat = DecimalFormat("####") + val kmInDec = Integer.valueOf(newFormat.format(km)) + val meter = valueResult % 1000 + val meterInDec = Integer.valueOf(newFormat.format(meter)) + Log.i("Radius Value", "" + valueResult + " KM " + kmInDec + + " Meter " + meterInDec) + + return radius * c + } + + fun calculationByDistanceInKm(from: LatLng, to: LatLng): Double = + (calculationByDistance(from, to) * 100.0).roundToInt() / 100.0 + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_markers_cluster.xml b/app/src/main/res/drawable/bg_markers_cluster.xml new file mode 100644 index 0000000..c40304e --- /dev/null +++ b/app/src/main/res/drawable/bg_markers_cluster.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml new file mode 100644 index 0000000..b49149f --- /dev/null +++ b/app/src/main/res/drawable/ic_map.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_marker.xml b/app/src/main/res/drawable/ic_marker.xml new file mode 100644 index 0000000..6a3e8a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_marker_healthcare_center.xml b/app/src/main/res/drawable/ic_marker_healthcare_center.xml new file mode 100644 index 0000000..96586c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_healthcare_center.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_marker_healthcare_center_large.xml b/app/src/main/res/drawable/ic_marker_healthcare_center_large.xml new file mode 100644 index 0000000..f1d0803 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_healthcare_center_large.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_marker_laboratory.xml b/app/src/main/res/drawable/ic_marker_laboratory.xml new file mode 100644 index 0000000..ce8c3b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_laboratory.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_marker_laboratory_large.xml b/app/src/main/res/drawable/ic_marker_laboratory_large.xml new file mode 100644 index 0000000..24d7e41 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_laboratory_large.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_marker_vaccination.xml b/app/src/main/res/drawable/ic_marker_vaccination.xml new file mode 100644 index 0000000..fbb67f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_vaccination.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_marker_vaccination_large.xml b/app/src/main/res/drawable/ic_marker_vaccination_large.xml new file mode 100644 index 0000000..e7e917e --- /dev/null +++ b/app/src/main/res/drawable/ic_marker_vaccination_large.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_markers_cluster.xml b/app/src/main/res/drawable/ic_markers_cluster.xml new file mode 100644 index 0000000..a72cf34 --- /dev/null +++ b/app/src/main/res/drawable/ic_markers_cluster.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_pick_place.xml b/app/src/main/res/drawable/ic_pick_place.xml new file mode 100644 index 0000000..cb60430 --- /dev/null +++ b/app/src/main/res/drawable/ic_pick_place.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_place_suggestion.xml b/app/src/main/res/drawable/ic_place_suggestion.xml new file mode 100644 index 0000000..dfd2612 --- /dev/null +++ b/app/src/main/res/drawable/ic_place_suggestion.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_primary_marker.xml b/app/src/main/res/drawable/ic_primary_marker.xml new file mode 100644 index 0000000..c607491 --- /dev/null +++ b/app/src/main/res/drawable/ic_primary_marker.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_static_image.xml b/app/src/main/res/drawable/ic_static_image.xml new file mode 100644 index 0000000..8f654b9 --- /dev/null +++ b/app/src/main/res/drawable/ic_static_image.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 71ea31d..d9f6cdb 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -4,14 +4,9 @@ - + android:paddingTop="@dimen/activity_vertical_margin"> - + + + + - - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_marker_clustering.xml b/app/src/main/res/layout/activity_marker_clustering.xml new file mode 100644 index 0000000..74aea53 --- /dev/null +++ b/app/src/main/res/layout/activity_marker_clustering.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_place_picker.xml b/app/src/main/res/layout/activity_place_picker.xml index 79c0cbc..1a77e51 100644 --- a/app/src/main/res/layout/activity_place_picker.xml +++ b/app/src/main/res/layout/activity_place_picker.xml @@ -9,7 +9,6 @@ + android:paddingTop="@dimen/activity_vertical_margin"> - - @@ -39,23 +37,22 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - diff --git a/app/src/main/res/layout/activity_place_suggestions.xml b/app/src/main/res/layout/activity_place_suggestions.xml index 16e57db..351c123 100644 --- a/app/src/main/res/layout/activity_place_suggestions.xml +++ b/app/src/main/res/layout/activity_place_suggestions.xml @@ -1,36 +1,94 @@ - - - - - + + + + + + - + android:layout_height="match_parent" + android:orientation="vertical" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin"> + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_static_image_map.xml b/app/src/main/res/layout/activity_static_image_map.xml index c29ffef..6f355f4 100644 --- a/app/src/main/res/layout/activity_static_image_map.xml +++ b/app/src/main/res/layout/activity_static_image_map.xml @@ -18,7 +18,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - - - - - - - - - - @@ -111,21 +111,27 @@ - + + + - diff --git a/app/src/main/res/layout/layout_markers_cluster.xml b/app/src/main/res/layout/layout_markers_cluster.xml new file mode 100644 index 0000000..8e37ec6 --- /dev/null +++ b/app/src/main/res/layout/layout_markers_cluster.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/place_suggestions_item.xml b/app/src/main/res/layout/place_suggestions_item.xml new file mode 100644 index 0000000..e1391c8 --- /dev/null +++ b/app/src/main/res/layout/place_suggestions_item.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-xxhdpi/ic_cancel.png b/app/src/main/res/mipmap-xxhdpi/ic_cancel.png new file mode 100644 index 0000000..55780b6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_cancel.png differ diff --git a/app/src/main/res/raw/radar_search.json b/app/src/main/res/raw/radar_search.json new file mode 100644 index 0000000..84acb66 --- /dev/null +++ b/app/src/main/res/raw/radar_search.json @@ -0,0 +1,202 @@ +[ +{ "lat" : 51.5145160, "lng" : -0.1270060 }, +{ "lat" : 51.5064490, "lng" : -0.1244260, "title" : "Corinthia Hotel London", "snippet": "Whitehall Pl"}, +{ "lat" : 51.5097080, "lng" : -0.1200450, "title" : "Savoy Place", "snippet" : "Covent Garden"}, +{ "lat" : 51.5090680, "lng" : -0.1421420, "title" : "Albemarle St", "snippet": "Mayfair"}, +{ "lat" : 51.4976080, "lng" : -0.1456320, "title" : " Victoria Square", "snippet": " Belgravia" }, +{ "lat" : 51.5046150, "lng" : -0.1473780}, +{ "lat" : 51.5077540, "lng" : -0.1378760, "title" : "Jermyn Street", "snippet": "St. James's" }, +{ "lat" : 51.5074250, "lng" : -0.1323230 , "title" : "Pall Mall", "snippet": "Westminster"}, +{ "lat" : 51.5070030, "lng" : -0.125560 }, +{ "lat" : 51.5061590, "lng" : -0.140280 }, +{ "lat" : 51.5047420, "lng" : -0.1470490 }, +{ "lat" : 51.5126760, "lng" : -0.1189760 }, +{ "lat" : 51.5108480, "lng" : -0.1208480 }, +{ "lat" : 51.5099460, "lng" : -0.1300150 }, +{ "lat" : 51.5076580, "lng" : -0.1424490 }, +{ "lat" : 51.5097160, "lng" : -0.1555350 }, +{ "lat" : 51.5215190, "lng" : -0.1621160 }, +{ "lat" : 51.5177960, "lng" : -0.1438760 }, +{ "lat" : 51.5071840, "lng" : -0.1415940 }, +{ "lat" : 51.5008150, "lng" : -0.1520910 }, +{ "lat" : 51.5179170, "lng" : -0.142740 }, +{ "lat" : 51.50360, "lng" : -0.14980 }, +{ "lat" : 51.512620, "lng" : -0.1476950 }, +{ "lat" : 51.5051890, "lng" : -0.08813600000000001 }, +{ "lat" : 51.4969390, "lng" : -0.1594880 }, +{ "lat" : 51.506020, "lng" : -0.1241340 }, +{ "lat" : 51.5143270, "lng" : -0.1318940 }, +{ "lat" : 51.5070480, "lng" : -0.1521930 }, +{ "lat" : 51.5101640, "lng" : -0.1495920 }, +{ "lat" : 51.5144240, "lng" : -0.1392980 }, +{ "lat" : 51.4816380, "lng" : -0.1489180 }, +{ "lat" : 51.519340, "lng" : -0.1209080 }, +{ "lat" : 51.4982420, "lng" : -0.1435160 }, +{ "lat" : 51.5104310, "lng" : -0.1267850 }, +{ "lat" : 51.504340, "lng" : -0.149940 }, +{ "lat" : 51.5174490, "lng" : -0.1370170 }, +{ "lat" : 51.524370, "lng" : -0.1281460 }, +{ "lat" : 51.5117770, "lng" : -0.1192630 }, +{ "lat" : 51.5026220, "lng" : -0.1527130 }, +{ "lat" : 51.500210, "lng" : -0.1798050 }, +{ "lat" : 51.5293450, "lng" : -0.1260930 }, +{ "lat" : 51.514070, "lng" : -0.0854980 }, +{ "lat" : 51.5154290, "lng" : -0.1571420 }, +{ "lat" : 51.5158980, "lng" : -0.1202010 }, +{ "lat" : 51.5081370, "lng" : -0.1438340 }, +{ "lat" : 51.4990650, "lng" : -0.1343110 }, +{ "lat" : 51.5059290, "lng" : -0.1491020 }, +{ "lat" : 51.5017470, "lng" : -0.1848540 }, +{ "lat" : 51.510820, "lng" : -0.1511460 }, +{ "lat" : 51.5128620, "lng" : -0.192130 }, +{ "lat" : 51.49850, "lng" : -0.1583950 }, +{ "lat" : 51.5094440, "lng" : -0.1362880 }, +{ "lat" : 51.5239250, "lng" : -0.1249180 }, +{ "lat" : 51.4930180, "lng" : -0.159680 }, +{ "lat" : 51.5055380, "lng" : -0.1396880 }, +{ "lat" : 51.4892760, "lng" : -0.180180 }, +{ "lat" : 51.4999860, "lng" : -0.161490 }, +{ "lat" : 51.5183870, "lng" : -0.1350570 }, +{ "lat" : 51.5025980, "lng" : -0.1883030 }, +{ "lat" : 51.4702570, "lng" : -0.1776650 }, +{ "lat" : 51.5184940, "lng" : -0.1452920 }, +{ "lat" : 51.4943450, "lng" : -0.1145180 }, +{ "lat" : 51.5174860, "lng" : -0.1307490 }, +{ "lat" : 51.47570, "lng" : -0.1819760 }, +{ "lat" : 51.4917980, "lng" : -0.161740 }, +{ "lat" : 51.5081480, "lng" : -0.1653260 }, +{ "lat" : 51.5241310, "lng" : -0.18460 }, +{ "lat" : 51.5255790, "lng" : -0.0828410 }, +{ "lat" : 51.4944410, "lng" : -0.1360670 }, +{ "lat" : 51.4924780, "lng" : -0.1483010 }, +{ "lat" : 51.5101220, "lng" : -0.1967860 }, +{ "lat" : 51.4947680, "lng" : -0.1186810 }, +{ "lat" : 51.5108440, "lng" : -0.131580 }, +{ "lat" : 51.4906890, "lng" : -0.1386160 }, +{ "lat" : 51.4991350, "lng" : -0.1125320 }, +{ "lat" : 51.5113950, "lng" : -0.1427780 }, +{ "lat" : 51.4905960, "lng" : -0.1388970 }, +{ "lat" : 51.4908430, "lng" : -0.1440980 }, +{ "lat" : 51.4900210, "lng" : -0.1376870 }, +{ "lat" : 51.5102170, "lng" : -0.1315040 }, +{ "lat" : 51.4903170, "lng" : -0.1377590 }, +{ "lat" : 51.5101040, "lng" : -0.1322540 }, +{ "lat" : 51.5156830, "lng" : -0.1240560 }, +{ "lat" : 51.5116380, "lng" : -0.1384670 }, +{ "lat" : 51.4973190, "lng" : -0.156080 }, +{ "lat" : 51.5180390, "lng" : -0.1497690 }, +{ "lat" : 51.4930840, "lng" : -0.1443660 }, +{ "lat" : 51.498970, "lng" : -0.1062350 }, +{ "lat" : 51.5113380, "lng" : -0.1300990 }, +{ "lat" : 51.4920160, "lng" : -0.1419380 }, +{ "lat" : 51.507070, "lng" : -0.1049530 }, +{ "lat" : 51.5059030, "lng" : -0.1403950 }, +{ "lat" : 51.5160770, "lng" : -0.1353810 }, +{ "lat" : 51.494140, "lng" : -0.1411990 }, +{ "lat" : 51.5225950, "lng" : -0.1253190 }, +{ "lat" : 51.4957540, "lng" : -0.1476890 }, +{ "lat" : 51.5052860, "lng" : -0.150260 }, +{ "lat" : 51.4966970, "lng" : -0.1122920 }, +{ "lat" : 51.5201680, "lng" : -0.1256960 }, +{ "lat" : 51.4929010, "lng" : -0.1572410 }, +{ "lat" : 51.5019440, "lng" : -0.1562350 }, +{ "lat" : 51.489530, "lng" : -0.1366590 }, +{ "lat" : 51.5134410, "lng" : -0.1508490 }, +{ "lat" : 51.5025440, "lng" : -0.118110 }, +{ "lat" : 51.491640, "lng" : -0.1415830 }, +{ "lat" : 51.5115110, "lng" : -0.1183470 }, +{ "lat" : 51.4909510, "lng" : -0.1438210 }, +{ "lat" : 51.5071040, "lng" : -0.1461620 }, +{ "lat" : 51.5186970, "lng" : -0.1355270 }, +{ "lat" : 51.5178830, "lng" : -0.1185430 }, +{ "lat" : 51.492470, "lng" : -0.1455250 }, +{ "lat" : 51.5015740, "lng" : -0.1624430 }, +{ "lat" : 51.5135820, "lng" : -0.1086290 }, +{ "lat" : 51.4920260, "lng" : -0.1418470 }, +{ "lat" : 51.4907580, "lng" : -0.1445780 }, +{ "lat" : 51.4913140, "lng" : -0.144820 }, +{ "lat" : 51.490830, "lng" : -0.1443390 }, +{ "lat" : 51.5139170, "lng" : -0.122090 }, +{ "lat" : 51.4922580, "lng" : -0.1418670 }, +{ "lat" : 51.5160140, "lng" : -0.1578750 }, +{ "lat" : 51.5109460, "lng" : -0.076930 }, +{ "lat" : 51.4930270, "lng" : -0.142960 }, +{ "lat" : 51.49990, "lng" : -0.1781820 }, +{ "lat" : 51.5035550, "lng" : -0.1113130 }, +{ "lat" : 51.4903060, "lng" : -0.1403020 }, +{ "lat" : 51.4936240, "lng" : -0.1500960 }, +{ "lat" : 51.4919830, "lng" : -0.1414270 }, +{ "lat" : 51.5050970, "lng" : -0.104710 }, +{ "lat" : 51.4950920, "lng" : -0.1838080 }, +{ "lat" : 51.5259280, "lng" : -0.1358780 }, +{ "lat" : 51.5057060, "lng" : -0.1221970 }, +{ "lat" : 51.4952410, "lng" : -0.1817070 }, +{ "lat" : 51.4940420, "lng" : -0.1492270 }, +{ "lat" : 51.490370, "lng" : -0.1458720 }, +{ "lat" : 51.5077260, "lng" : -0.1471730 }, +{ "lat" : 51.4651970, "lng" : -0.114940 }, +{ "lat" : 51.5138860, "lng" : -0.1012070 }, +{ "lat" : 51.518720, "lng" : -0.153870 }, +{ "lat" : 51.4913010, "lng" : -0.1428370 }, +{ "lat" : 51.5151350, "lng" : -0.1615710 }, +{ "lat" : 51.5188840, "lng" : -0.1318310 }, +{ "lat" : 51.5020890, "lng" : -0.186790 }, +{ "lat" : 51.4907170, "lng" : -0.1386760 }, +{ "lat" : 51.5182760, "lng" : -0.1582390 }, +{ "lat" : 51.5195190, "lng" : -0.1430740 }, +{ "lat" : 51.5241420, "lng" : -0.1378130 }, +{ "lat" : 51.5077230, "lng" : -0.1279620 }, +{ "lat" : 51.4938750, "lng" : -0.1497910 }, +{ "lat" : 51.5237550, "lng" : -0.1403980 }, +{ "lat" : 51.4933780, "lng" : -0.1500050 }, +{ "lat" : 51.4901580, "lng" : -0.1387650 }, +{ "lat" : 51.5159630, "lng" : -0.1718890 }, +{ "lat" : 51.4908250, "lng" : -0.1450190 }, +{ "lat" : 51.5264680, "lng" : -0.1353710 }, +{ "lat" : 51.5173210, "lng" : -0.156650 }, +{ "lat" : 51.5140620, "lng" : -0.1469230 }, +{ "lat" : 51.5028270, "lng" : -0.0720070 }, +{ "lat" : 51.5206650, "lng" : -0.1003340 }, +{ "lat" : 51.502060, "lng" : -0.1599840 }, +{ "lat" : 51.5277390, "lng" : -0.135320 }, +{ "lat" : 51.5219420, "lng" : -0.1321670 }, +{ "lat" : 51.5152990, "lng" : -0.1600470 }, +{ "lat" : 51.4907870, "lng" : -0.1388240 }, +{ "lat" : 51.4945090, "lng" : -0.174880 }, +{ "lat" : 51.5219720, "lng" : -0.1322210 }, +{ "lat" : 51.4900030, "lng" : -0.0977280 }, +{ "lat" : 51.5222270, "lng" : -0.1425630 }, +{ "lat" : 51.4977620, "lng" : -0.08125599999999999 }, +{ "lat" : 51.5081250, "lng" : -0.07108100000000001 }, +{ "lat" : 51.5179610, "lng" : -0.1529610 }, +{ "lat" : 51.5129390, "lng" : -0.1577410 }, +{ "lat" : 51.4935420, "lng" : -0.1594010 }, +{ "lat" : 51.5157770, "lng" : -0.173090 }, +{ "lat" : 51.4948150, "lng" : -0.1778260 }, +{ "lat" : 51.4964460, "lng" : -0.1557010 }, +{ "lat" : 51.4916430, "lng" : -0.1006060 }, +{ "lat" : 51.5122920, "lng" : -0.1750390 }, +{ "lat" : 51.5202170, "lng" : -0.1025320 }, +{ "lat" : 51.5076930, "lng" : -0.1413620 }, +{ "lat" : 51.491520, "lng" : -0.1426860 }, +{ "lat" : 51.5138990, "lng" : -0.1787940 }, +{ "lat" : 51.5221330, "lng" : -0.1325740 }, +{ "lat" : 51.5135120, "lng" : -0.1631850 }, +{ "lat" : 51.5067690, "lng" : -0.0740210 }, +{ "lat" : 51.5201120, "lng" : -0.1238560 }, +{ "lat" : 51.4925250, "lng" : -0.1451160 }, +{ "lat" : 51.5162150, "lng" : -0.1733980 }, +{ "lat" : 51.5228320, "lng" : -0.155350 }, +{ "lat" : 51.5212420, "lng" : -0.1558640 }, +{ "lat" : 51.5284530, "lng" : -0.1226970 }, +{ "lat" : 51.5223030, "lng" : -0.1325830 }, +{ "lat" : 51.5197430, "lng" : -0.1431740 }, +{ "lat" : 51.5216440, "lng" : -0.1318630 }, +{ "lat" : 51.5172640, "lng" : -0.08090 }, +{ "lat" : 51.474050, "lng" : -0.0895920 }, +{ "lat" : 51.5181490, "lng" : -0.1581730 }, +{ "lat" : 51.5159250, "lng" : -0.1600310 }, +{ "lat" : 51.5243350, "lng" : -0.1165120 }, +{ "lat" : 51.4907560, "lng" : -0.1434930 }, +{ "lat" : 51.4900980, "lng" : -0.1373290 } +] \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3ab3e9c..8a7e673 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -3,4 +3,5 @@ #3F51B5 #303F9F #FF4081 + #009688 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 850c449..6ea8d2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -19,6 +19,14 @@ get image map map type place suggestions + Search + Marker Clustering + Markers Clustering + + %1$s \nDistance to your current location is %2$s km + Marker info + Close + MLSDev maps sandbox roadmap diff --git a/app/src/test/java/com/mlsdev/mapsappsample/ExampleUnitTest.java b/app/src/test/java/com/mlsdev/mapsappsample/ExampleUnitTest.java deleted file mode 100644 index 6f170de..0000000 --- a/app/src/test/java/com/mlsdev/mapsappsample/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.mlsdev.mapsappsample; - -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() throws Exception { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 607a23c..dc354c3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,16 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.3.41' repositories { + google() jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.2' - classpath 'me.tatarka:gradle-retrolambda:3.2.3' - + classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.google.gms:google-services:4.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -15,7 +18,10 @@ buildscript { allprojects { repositories { + google() jcenter() + mavenCentral() + maven { url "https://jitpack.io" } } } diff --git a/gradle.properties b/gradle.properties index aac7c9b..706bcd1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,3 +15,6 @@ org.gradle.jvmargs=-Xmx1536m # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 04e285f..a4b55d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Dec 28 10:00:20 PST 2015 +#Tue Jul 09 14:39:48 EEST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip diff --git a/screenshots/1.png b/screenshots/1.png index b33bc9a..045cdaf 100644 Binary files a/screenshots/1.png and b/screenshots/1.png differ diff --git a/screenshots/10.png b/screenshots/10.png new file mode 100644 index 0000000..0704ab6 Binary files /dev/null and b/screenshots/10.png differ diff --git a/screenshots/11.png b/screenshots/11.png new file mode 100644 index 0000000..6077181 Binary files /dev/null and b/screenshots/11.png differ diff --git a/screenshots/12.png b/screenshots/12.png new file mode 100644 index 0000000..65b9f42 Binary files /dev/null and b/screenshots/12.png differ diff --git a/screenshots/13.png b/screenshots/13.png new file mode 100644 index 0000000..925674d Binary files /dev/null and b/screenshots/13.png differ diff --git a/screenshots/14.png b/screenshots/14.png new file mode 100644 index 0000000..bd83225 Binary files /dev/null and b/screenshots/14.png differ diff --git a/screenshots/15.png b/screenshots/15.png new file mode 100644 index 0000000..5abb14d Binary files /dev/null and b/screenshots/15.png differ diff --git a/screenshots/16.png b/screenshots/16.png new file mode 100644 index 0000000..7168e55 Binary files /dev/null and b/screenshots/16.png differ diff --git a/screenshots/17.png b/screenshots/17.png new file mode 100644 index 0000000..18f5f31 Binary files /dev/null and b/screenshots/17.png differ