-
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index fb0608a..00fd6a5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -45,5 +45,17 @@ dependencies {
// Circle ImageView
implementation 'de.hdodenhof:circleimageview:2.2.0'
+
+ // Google marker cluster
+ implementation'com.google.maps.android:android-maps-utils:0.5+'
+
+ //Google directions
+ implementation 'com.google.maps:google-maps-services:0.9.0'
+ implementation 'org.slf4j:slf4j-simple:1.7.25'
+
+ //Google services
+ implementation 'com.google.android.gms:play-services-maps:15.0.1'
+ implementation 'com.google.android.gms:play-services-places:15.0.1'
+ implementation 'com.google.android.gms:play-services-location:15.0.1'
}
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..ffbc148
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,42 @@
+{
+ "project_info": {
+ "project_number": "999529025534",
+ "firebase_url": "https://maps-2018-a3040.firebaseio.com",
+ "project_id": "maps-2018-a3040",
+ "storage_bucket": "maps-2018-a3040.appspot.com"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:999529025534:android:a1d1ce9b8c3681aa",
+ "android_client_info": {
+ "package_name": "com.codingwithmitch.googlemaps2018"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "999529025534-phqa0i38lbvdan5ur18ii05lhopd0431.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyCYuRqkzUIhxXRBSHWTh87a0rRFq227M9M"
+ }
+ ],
+ "services": {
+ "analytics_service": {
+ "status": 1
+ },
+ "appinvite_service": {
+ "status": 1,
+ "other_platform_oauth_client": []
+ },
+ "ads_service": {
+ "status": 2
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2063a53..087540c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/Constants.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/Constants.java
index 0534474..c63171d 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/Constants.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/Constants.java
@@ -3,4 +3,8 @@
public class Constants {
public static final int ERROR_DIALOG_REQUEST = 9001;
+ public static final int PERMISSIONS_REQUEST_ENABLE_GPS = 9002;
+ public static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 9003;
+ public static final String MAPVIEW_BUNDLE_KEY = "MapViewBundleKey";
+
}
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/PlaceAutocompleteAdapter.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/PlaceAutocompleteAdapter.java
new file mode 100644
index 0000000..184d6af
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/PlaceAutocompleteAdapter.java
@@ -0,0 +1,244 @@
+package com.codingwithmitch.googlemaps2018;
+/*
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.common.api.PendingResult;
+import com.google.android.gms.common.api.Status;
+import com.google.android.gms.common.data.DataBufferUtils;
+import com.google.android.gms.location.places.AutocompleteFilter;
+import com.google.android.gms.location.places.AutocompletePrediction;
+import com.google.android.gms.location.places.AutocompletePredictionBuffer;
+import com.google.android.gms.location.places.Places;
+import com.google.android.gms.maps.model.LatLngBounds;
+
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.text.style.CharacterStyle;
+import android.text.style.StyleSpan;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Adapter that handles Autocomplete requests from the Places Geo Data API.
+ * {@link AutocompletePrediction} results from the API are frozen and stored directly in this
+ * adapter. (See {@link AutocompletePrediction#freeze()}.)
+ *
+ * Note that this adapter requires a valid {@link com.google.android.gms.common.api.GoogleApiClient}.
+ * The API client must be maintained in the encapsulating Activity, including all lifecycle and
+ * connection states. The API client must be connected with the {@link Places#GEO_DATA_API} API.
+ */
+public class PlaceAutocompleteAdapter
+ extends ArrayAdapter implements Filterable {
+
+ private static final String TAG = "PlaceAutoCompleteAd";
+ private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
+ /**
+ * Current results returned by this adapter.
+ */
+ private ArrayList mResultList;
+
+ /**
+ * Handles autocomplete requests.
+ */
+ private GoogleApiClient mGoogleApiClient;
+
+ /**
+ * The bounds used for Places Geo Data autocomplete API requests.
+ */
+ private LatLngBounds mBounds;
+
+ /**
+ * The autocomplete filter used to restrict queries to a specific set of place types.
+ */
+ private AutocompleteFilter mPlaceFilter;
+
+ /**
+ * Initializes with a resource for text rows and autocomplete query bounds.
+ *
+ * @see android.widget.ArrayAdapter#ArrayAdapter(android.content.Context, int)
+ */
+ public PlaceAutocompleteAdapter(Context context, GoogleApiClient googleApiClient,
+ LatLngBounds bounds, AutocompleteFilter filter) {
+ super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1);
+ mGoogleApiClient = googleApiClient;
+ mBounds = bounds;
+ mPlaceFilter = filter;
+ }
+
+ /**
+ * Sets the bounds for all subsequent queries.
+ */
+ public void setBounds(LatLngBounds bounds) {
+ mBounds = bounds;
+ }
+
+ /**
+ * Returns the number of results received in the last autocomplete query.
+ */
+ @Override
+ public int getCount() {
+ return mResultList.size();
+ }
+
+ /**
+ * Returns an item from the last autocomplete query.
+ */
+ @Override
+ public AutocompletePrediction getItem(int position) {
+ return mResultList.get(position);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View row = super.getView(position, convertView, parent);
+
+ // Sets the primary and secondary text for a row.
+ // Note that getPrimaryText() and getSecondaryText() return a CharSequence that may contain
+ // styling based on the given CharacterStyle.
+
+ AutocompletePrediction item = getItem(position);
+
+ TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
+ TextView textView2 = (TextView) row.findViewById(android.R.id.text2);
+ textView1.setText(item.getPrimaryText(STYLE_BOLD));
+ textView2.setText(item.getSecondaryText(STYLE_BOLD));
+
+ return row;
+ }
+
+ /**
+ * Returns the filter for the current set of autocomplete results.
+ */
+ @Override
+ public Filter getFilter() {
+ return new Filter() {
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ FilterResults results = new FilterResults();
+
+ // We need a separate list to store the results, since
+ // this is run asynchronously.
+ ArrayList filterData = new ArrayList<>();
+
+ // Skip the autocomplete query if no constraints are given.
+ if (constraint != null) {
+ // Query the autocomplete API for the (constraint) search string.
+ filterData = getAutocomplete(constraint);
+ }
+
+ results.values = filterData;
+ if (filterData != null) {
+ results.count = filterData.size();
+ } else {
+ results.count = 0;
+ }
+
+ return results;
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint, FilterResults results) {
+
+ if (results != null && results.count > 0) {
+ // The API returned at least one result, update the data.
+ mResultList = (ArrayList) results.values;
+ notifyDataSetChanged();
+ } else {
+ // The API did not return any results, invalidate the data set.
+ notifyDataSetInvalidated();
+ }
+ }
+
+ @Override
+ public CharSequence convertResultToString(Object resultValue) {
+ // Override this method to display a readable result in the AutocompleteTextView
+ // when clicked.
+ if (resultValue instanceof AutocompletePrediction) {
+ return ((AutocompletePrediction) resultValue).getFullText(null);
+ } else {
+ return super.convertResultToString(resultValue);
+ }
+ }
+ };
+ }
+
+ /**
+ * Submits an autocomplete query to the Places Geo Data Autocomplete API.
+ * Results are returned as frozen AutocompletePrediction objects, ready to be cached.
+ * objects to store the Place ID and description that the API returns.
+ * Returns an empty list if no results were found.
+ * Returns null if the API client is not available or the query did not complete
+ * successfully.
+ * This method MUST be called off the main UI thread, as it will block until data is returned
+ * from the API, which may include a network request.
+ *
+ * @param constraint Autocomplete query string
+ * @return Results from the autocomplete API or null if the query was not successful.
+ * @see Places#GEO_DATA_API#getAutocomplete(CharSequence)
+ * @see AutocompletePrediction#freeze()
+ */
+ private ArrayList getAutocomplete(CharSequence constraint) {
+ if (mGoogleApiClient.isConnected()) {
+ Log.i(TAG, "Starting autocomplete query for: " + constraint);
+
+ // Submit the query to the autocomplete API and retrieve a PendingResult that will
+ // contain the results when the query completes.
+ PendingResult results =
+ Places.GeoDataApi
+ .getAutocompletePredictions(mGoogleApiClient, constraint.toString(),
+ mBounds, mPlaceFilter);
+
+ // This method should have been called off the main UI thread. Block and wait for at most 60s
+ // for a result from the API.
+ AutocompletePredictionBuffer autocompletePredictions = results
+ .await(60, TimeUnit.SECONDS);
+
+ // Confirm that the query completed successfully, otherwise return null
+ final Status status = autocompletePredictions.getStatus();
+ if (!status.isSuccess()) {
+ Toast.makeText(getContext(), "Error contacting API: " + status.toString(),
+ Toast.LENGTH_SHORT).show();
+ Log.e(TAG, "Error getting autocomplete prediction API call: " + status.toString());
+ autocompletePredictions.release();
+ return null;
+ }
+
+ Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
+ + " predictions.");
+
+ // Freeze the results immutable representation that can be stored safely.
+ return DataBufferUtils.freezeAndClose(autocompletePredictions);
+ }
+ Log.e(TAG, "Google API client is not connected for autocomplete query.");
+ return null;
+ }
+
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/UserClient.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/UserClient.java
index becc91e..a6eb359 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/UserClient.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/UserClient.java
@@ -7,12 +7,17 @@
public class UserClient extends Application {
+ //Extends applications, means we can have only a single user logged in
+ //and authenticated at any given time
+ //Users are unique and do not change very often
+
private User user = null;
public User getUser() {
return user;
}
+ //Set as soon as user is authenticated
public void setUser(User user) {
this.user = user;
}
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/models/ClusterMarker.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/ClusterMarker.java
new file mode 100644
index 0000000..cb10179
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/ClusterMarker.java
@@ -0,0 +1,61 @@
+package com.codingwithmitch.googlemaps2018.models;
+
+import com.google.android.gms.maps.model.LatLng;
+import com.google.maps.android.clustering.ClusterItem;
+
+public class ClusterMarker implements ClusterItem {
+
+ private LatLng position; // required field
+ private String title; // required field
+ private String snippet; // required field
+ private int iconPicture;
+ private User user;
+
+ public ClusterMarker(LatLng position, String title, String snippet, int iconPicture, User user) {
+ this.position = position;
+ this.title = title;
+ this.snippet = snippet;
+ this.iconPicture = iconPicture;
+ this.user = user;
+ }
+
+ public int getIconPicture() {
+ return iconPicture;
+ }
+
+ public void setIconPicture(int iconPicture) {
+ this.iconPicture = iconPicture;
+ }
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public void setPosition(LatLng position) {
+ this.position = position;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void setSnippet(String snippet) {
+ this.snippet = snippet;
+ }
+
+ public LatLng getPosition() {
+ return position;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getSnippet() {
+ return snippet;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/models/MyItem.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/MyItem.java
new file mode 100644
index 0000000..d03e5ff
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/MyItem.java
@@ -0,0 +1,35 @@
+package com.codingwithmitch.googlemaps2018.models;
+
+import com.google.android.gms.maps.model.LatLng;
+import com.google.maps.android.clustering.ClusterItem;
+
+public class MyItem implements ClusterItem {
+ private LatLng mPosition;
+ private String mTitle;
+ private String mSnippet;
+
+ public MyItem(double lat, double lng, String title, String snippet) {
+ mPosition = new LatLng(lat, lng);
+ mTitle = title;
+ mSnippet = snippet;
+ }
+
+ public MyItem(double lat, double lng) {
+ mPosition = new LatLng(lat, lng);
+ }
+
+ @Override
+ public LatLng getPosition() {
+ return mPosition;
+ }
+
+ @Override
+ public String getTitle() {
+ return mTitle;
+ }
+
+ @Override
+ public String getSnippet() {
+ return mSnippet;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PlaceInfo.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PlaceInfo.java
new file mode 100644
index 0000000..52ee9c4
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PlaceInfo.java
@@ -0,0 +1,112 @@
+package com.codingwithmitch.googlemaps2018.models;
+
+
+import android.net.Uri;
+
+import com.google.android.gms.maps.model.LatLng;
+
+public class PlaceInfo {
+
+ private String name;
+ private String address;
+ private String phoneNumber;
+ private String id;
+ private Uri websiteUri;
+ private LatLng latlng;
+ private float rating;
+ private String attributions;
+
+ public PlaceInfo(String name, String address, String phoneNumber, String id, Uri websiteUri,
+ LatLng latlng, float rating, String attributions) {
+ this.name = name;
+ this.address = address;
+ this.phoneNumber = phoneNumber;
+ this.id = id;
+ this.websiteUri = websiteUri;
+ this.latlng = latlng;
+ this.rating = rating;
+ this.attributions = attributions;
+ }
+
+ public PlaceInfo() {
+
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ public void setPhoneNumber(String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public Uri getWebsiteUri() {
+ return websiteUri;
+ }
+
+ public void setWebsiteUri(Uri websiteUri) {
+ this.websiteUri = websiteUri;
+ }
+
+ public LatLng getLatlng() {
+ return latlng;
+ }
+
+ public void setLatlng(LatLng latlng) {
+ this.latlng = latlng;
+ }
+
+ public float getRating() {
+ return rating;
+ }
+
+ public void setRating(float rating) {
+ this.rating = rating;
+ }
+
+ public String getAttributions() {
+ return attributions;
+ }
+
+ public void setAttributions(String attributions) {
+ this.attributions = attributions;
+ }
+
+ @Override
+ public String toString() {
+ return "PlaceInfo{" +
+ "name='" + name + '\'' +
+ ", address='" + address + '\'' +
+ ", phoneNumber='" + phoneNumber + '\'' +
+ ", id='" + id + '\'' +
+ ", websiteUri=" + websiteUri +
+ ", latlng=" + latlng +
+ ", rating=" + rating +
+ ", attributions='" + attributions + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PolylineData.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PolylineData.java
new file mode 100644
index 0000000..5f29dc5
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/PolylineData.java
@@ -0,0 +1,39 @@
+package com.codingwithmitch.googlemaps2018.models;
+
+import com.google.android.gms.maps.model.Polyline;
+import com.google.maps.model.DirectionsLeg;
+
+public class PolylineData {
+
+ private Polyline polyline;
+ private DirectionsLeg leg;
+
+ public PolylineData(Polyline polyline, DirectionsLeg leg) {
+ this.polyline = polyline;
+ this.leg = leg;
+ }
+
+ public Polyline getPolyline() {
+ return polyline;
+ }
+
+ public void setPolyline(Polyline polyline) {
+ this.polyline = polyline;
+ }
+
+ public DirectionsLeg getLeg() {
+ return leg;
+ }
+
+ public void setLeg(DirectionsLeg leg) {
+ this.leg = leg;
+ }
+
+ @Override
+ public String toString() {
+ return "PolylineData{" +
+ "polyline=" + polyline +
+ ", leg=" + leg +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/models/UserLocation.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/UserLocation.java
new file mode 100644
index 0000000..af69c46
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/models/UserLocation.java
@@ -0,0 +1,86 @@
+package com.codingwithmitch.googlemaps2018.models;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.firebase.firestore.GeoPoint;
+import com.google.firebase.firestore.ServerTimestamp;
+
+import java.util.Date;
+
+public class UserLocation implements Parcelable{
+
+ private GeoPoint geo_point;
+ //If you pass null at insertion, it will automatically insert a timestamp
+ private @ServerTimestamp Date timestamp;
+ private User user;
+
+ public UserLocation(GeoPoint geo_point, Date timestamp, User user){
+ this.geo_point = geo_point;
+ this.timestamp = timestamp;
+ this.user = user;
+ }
+
+ public UserLocation(){
+
+ }
+
+ protected UserLocation(Parcel in) {
+ user = in.readParcelable(User.class.getClassLoader());
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public UserLocation createFromParcel(Parcel in) {
+ return new UserLocation(in);
+ }
+
+ @Override
+ public UserLocation[] newArray(int size) {
+ return new UserLocation[size];
+ }
+ };
+
+ public User getUser() {
+ return user;
+ }
+
+ public void setUser(User user) {
+ this.user = user;
+ }
+
+ public GeoPoint getGeo_point() {
+ return geo_point;
+ }
+
+ public void setGeo_point(GeoPoint geo_point) {
+ this.geo_point = geo_point;
+ }
+
+ public Date getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(Date timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return "UserLocation{" +
+ "user=" + user +
+ ", geo_point=" + geo_point +
+ ", timestamp=" + timestamp +
+ '}';
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int i) {
+ parcel.writeParcelable(user, i);
+ }
+}
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/ChatroomActivity.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/ChatroomActivity.java
index b7deaf5..e0035a1 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/ChatroomActivity.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/ChatroomActivity.java
@@ -12,6 +12,7 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.WindowManager;
import android.widget.EditText;
import com.codingwithmitch.googlemaps2018.R;
@@ -20,11 +21,13 @@
import com.codingwithmitch.googlemaps2018.models.ChatMessage;
import com.codingwithmitch.googlemaps2018.models.Chatroom;
import com.codingwithmitch.googlemaps2018.models.User;
+import com.codingwithmitch.googlemaps2018.models.UserLocation;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentReference;
+import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
@@ -56,7 +59,7 @@ public class ChatroomActivity extends AppCompatActivity implements
private Set mMessageIds = new HashSet<>();
private ArrayList mUserList = new ArrayList<>();
private UserListFragment mUserListFragment;
-
+ private ArrayList mUserLocations = new ArrayList<>();
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -134,6 +137,7 @@ public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapsh
for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
User user = doc.toObject(User.class);
mUserList.add(user);
+ getUserLocation(user);
}
Log.d(TAG, "onEvent: user list size: " + mUserList.size());
@@ -142,6 +146,25 @@ public void onEvent(@javax.annotation.Nullable QuerySnapshot queryDocumentSnapsh
});
}
+ private void getUserLocation(User user){
+ //Retreive geopoint for each individual user and put into a list
+
+ DocumentReference locationRef = mDb.collection(getString(R.string.collection_user_locations))
+ .document(user.getUser_id());
+
+ locationRef.get().addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if(task.isSuccessful()){
+ if(task.getResult().toObject(UserLocation.class) != null ){
+ mUserLocations.add(task.getResult().toObject(UserLocation.class));
+ }
+ }
+
+ }
+ });
+ }
+
private void initChatroomRecyclerView(){
mChatMessageRecyclerAdapter = new ChatMessageRecyclerAdapter(mMessages, new ArrayList(), this);
mChatMessageRecyclerView.setAdapter(mChatMessageRecyclerAdapter);
@@ -214,6 +237,7 @@ private void inflateUserListFragment(){
UserListFragment fragment = UserListFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(getString(R.string.intent_user_list), mUserList);
+ bundle.putParcelableArrayList(getString(R.string.intent_user_locations), mUserLocations);
fragment.setArguments(bundle);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/LoginActivity.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/LoginActivity.java
index bd164fc..25a538f 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/LoginActivity.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/LoginActivity.java
@@ -56,8 +56,6 @@ protected void onCreate(Bundle savedInstanceState) {
}
-
-
private void showDialog(){
mProgressBar.setVisibility(View.VISIBLE);
@@ -87,7 +85,7 @@ public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
Toast.makeText(LoginActivity.this, "Authenticated with: " + user.getEmail(), Toast.LENGTH_SHORT).show();
- FirebaseFirestore db = FirebaseFirestore.getInstance();
+ /* FirebaseFirestore db = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setTimestampsInSnapshotsEnabled(true)
.build();
@@ -102,10 +100,14 @@ public void onComplete(@NonNull Task task) {
if(task.isSuccessful()){
Log.d(TAG, "onComplete: successfully set the user client.");
User user = task.getResult().toObject(User.class);
+ //Single user client set here after authentication
((UserClient)(getApplicationContext())).setUser(user);
+
+
+
}
}
- });
+ });*/
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/MainActivity.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/MainActivity.java
index 904dfb1..5f3210d 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/MainActivity.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/MainActivity.java
@@ -1,14 +1,19 @@
package com.codingwithmitch.googlemaps2018.ui;
+import android.Manifest;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -22,23 +27,32 @@
import android.widget.Toast;
import com.codingwithmitch.googlemaps2018.R;
+import com.codingwithmitch.googlemaps2018.UserClient;
import com.codingwithmitch.googlemaps2018.adapters.ChatroomRecyclerAdapter;
import com.codingwithmitch.googlemaps2018.models.Chatroom;
+import com.codingwithmitch.googlemaps2018.models.User;
+import com.codingwithmitch.googlemaps2018.models.UserLocation;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
+import com.google.android.gms.location.FusedLocationProviderClient;
+import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentReference;
+import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.EventListener;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.FirebaseFirestoreException;
import com.google.firebase.firestore.FirebaseFirestoreSettings;
+import com.google.firebase.firestore.GeoPoint;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.firestore.QuerySnapshot;
+import org.w3c.dom.Document;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
@@ -46,12 +60,13 @@
import javax.annotation.Nullable;
import static com.codingwithmitch.googlemaps2018.Constants.ERROR_DIALOG_REQUEST;
+import static com.codingwithmitch.googlemaps2018.Constants.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION;
+import static com.codingwithmitch.googlemaps2018.Constants.PERMISSIONS_REQUEST_ENABLE_GPS;
public class MainActivity extends AppCompatActivity implements
View.OnClickListener,
- ChatroomRecyclerAdapter.ChatroomRecyclerClickListener
-{
+ ChatroomRecyclerAdapter.ChatroomRecyclerClickListener {
private static final String TAG = "MainActivity";
@@ -65,6 +80,9 @@ public class MainActivity extends AppCompatActivity implements
private RecyclerView mChatroomRecyclerView;
private ListenerRegistration mChatroomEventListener;
private FirebaseFirestore mDb;
+ private boolean mLocationPermissionGranted = false;
+ private FusedLocationProviderClient mFusedLocationClient;
+ private UserLocation mUserLocation;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -75,12 +93,200 @@ protected void onCreate(Bundle savedInstanceState) {
findViewById(R.id.fab_create_chatroom).setOnClickListener(this);
+ mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
+
mDb = FirebaseFirestore.getInstance();
initSupportActionBar();
initChatroomRecyclerView();
}
+ //Step 1: Get User Details
+ private void getUserDetails(){
+ if(mUserLocation == null) {
+ mUserLocation = new UserLocation();
+
+ //Get document referenced by User Id
+ DocumentReference userRef = mDb.collection(getString(R.string.collection_users))
+ .document(FirebaseAuth.getInstance().getUid());
+
+ userRef.get().addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if (task.isSuccessful()) {
+ Log.d(TAG, "onComplete: successfully set the user client.");
+ User user = task.getResult().toObject(User.class);
+ mUserLocation.setUser(user);
+ ((UserClient) getApplicationContext()).setUser(user);
+ getLastKnownLocation();
+ }
+ }
+ });
+ }
+ else{
+ getLastKnownLocation();
+ }
+ }
+
+
+ //Step 3: Upload GPS coordinates to firestore for user
+ private void saveUserLocation(){
+
+ if(mUserLocation != null){
+ DocumentReference locationRef = mDb
+ .collection(getString(R.string.collection_user_locations))
+ .document(FirebaseAuth.getInstance().getUid());
+ locationRef.set(mUserLocation).addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if(task.isSuccessful()){
+ Log.d(TAG, "saveUserLocation: \ninserted user location into database." +
+ "\n latitude: " + mUserLocation.getGeo_point().getLatitude() +
+ "\n longitude: " + mUserLocation.getGeo_point().getLongitude());
+ }
+ }
+ });
+ }
+ }
+
+
+ //Step2: Gets GPS coordinates of Users last known location
+ private void getLastKnownLocation() {
+ Log.d(TAG, "getLastKnownLocation: called.");
+
+ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ mFusedLocationClient.getLastLocation().addOnCompleteListener(new OnCompleteListener() {
+ @Override
+ public void onComplete(@NonNull Task task) {
+ if(task.isSuccessful()){
+ Location location = task.getResult();
+ GeoPoint geoPoint = new GeoPoint(location.getLatitude(), location.getLongitude());
+
+ mUserLocation.setGeo_point(geoPoint);
+ mUserLocation.setTimestamp(null);
+ saveUserLocation();
+ }
+ }
+ });
+
+ }
+
+ private boolean checkMapServices(){
+ if(isServicesOK()){
+ if(isMapsEnabled()){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //Gets users to enable GPS in their settings
+ //Tells us if user enabled permissions or not
+ private void buildAlertMessageNoGps() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage("This application requires GPS to work properly, do you want to enable it?")
+ .setCancelable(false)
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(@SuppressWarnings("unused") final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
+ Intent enableGpsIntent = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ startActivityForResult(enableGpsIntent, PERMISSIONS_REQUEST_ENABLE_GPS);
+ }
+ });
+ final AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ public boolean isMapsEnabled(){
+ final LocationManager manager = (LocationManager) getSystemService( Context.LOCATION_SERVICE );
+
+ if ( !manager.isProviderEnabled( LocationManager.GPS_PROVIDER ) ) {
+ buildAlertMessageNoGps();
+ return false;
+ }
+ return true;
+ }
+
+ //If permission granted, get chatrooms
+ //Otherwise, prompt to ask if it's fine to use location
+ private void getLocationPermission() {
+ /*
+ * Request location permission, so that we can get the location of the
+ * device. The result of the permission request is handled by a callback,
+ * onRequestPermissionsResult.
+ */
+ if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
+ android.Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED) {
+ mLocationPermissionGranted = true;
+ getChatrooms();
+ getUserDetails( );
+ } else {
+ ActivityCompat.requestPermissions(this,
+ new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
+ PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
+ }
+ }
+
+ public boolean isServicesOK(){
+ Log.d(TAG, "isServicesOK: checking google services version");
+
+ int available = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(MainActivity.this);
+
+ if(available == ConnectionResult.SUCCESS){
+ //everything is fine and the user can make map requests
+ Log.d(TAG, "isServicesOK: Google Play Services is working");
+ return true;
+ }
+ else if(GoogleApiAvailability.getInstance().isUserResolvableError(available)){
+ //an error occured but we can resolve it
+ Log.d(TAG, "isServicesOK: an error occured but we can fix it");
+ Dialog dialog = GoogleApiAvailability.getInstance().getErrorDialog(MainActivity.this, available, ERROR_DIALOG_REQUEST);
+ dialog.show();
+ }else{
+ Toast.makeText(this, "You can't make map requests", Toast.LENGTH_SHORT).show();
+ }
+ return false;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ @NonNull String permissions[],
+ @NonNull int[] grantResults) {
+ mLocationPermissionGranted = false;
+ switch (requestCode) {
+ case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
+ // If request is cancelled, the result arrays are empty.
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ mLocationPermissionGranted = true;
+ }
+ }
+ }
+ }
+
+ //Check for GPS Permission
+ //Opens chatrooms if they accepted
+ //Otherwise ask for the permissions again
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ Log.d(TAG, "onActivityResult: called.");
+ switch (requestCode) {
+ case PERMISSIONS_REQUEST_ENABLE_GPS: {
+ if(mLocationPermissionGranted){
+ getChatrooms();
+ getUserDetails();
+ }
+ else{
+ getLocationPermission();
+ }
+ }
+ }
+
+ }
+
private void initSupportActionBar(){
setTitle("Chatrooms");
}
@@ -218,6 +424,15 @@ protected void onDestroy() {
protected void onResume() {
super.onResume();
getChatrooms();
+ if(checkMapServices()){
+ if(mLocationPermissionGranted){
+ getChatrooms();
+ getUserDetails();
+ }
+ else{
+ getLocationPermission();
+ }
+ }
}
@Override
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/UserListFragment.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/UserListFragment.java
index 61c4ef1..0316ffc 100644
--- a/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/UserListFragment.java
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/ui/UserListFragment.java
@@ -1,80 +1,583 @@
package com.codingwithmitch.googlemaps2018.ui;
+import android.Manifest;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Camera;
+import android.location.Address;
+import android.location.Geocoder;
+import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
import com.codingwithmitch.googlemaps2018.R;
import com.codingwithmitch.googlemaps2018.adapters.UserRecyclerAdapter;
+import com.codingwithmitch.googlemaps2018.models.ClusterMarker;
+import com.codingwithmitch.googlemaps2018.models.MyItem;
+import com.codingwithmitch.googlemaps2018.models.PolylineData;
import com.codingwithmitch.googlemaps2018.models.User;
-
+import com.codingwithmitch.googlemaps2018.models.UserLocation;
+import com.codingwithmitch.googlemaps2018.util.MyClusterManagerRenderer;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.MapView;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.LatLngBounds;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.maps.model.Polyline;
+import com.google.android.gms.maps.model.PolylineOptions;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.GeoPoint;
+import com.google.maps.DirectionsApiRequest;
+import com.google.maps.GeoApiContext;
+import com.google.maps.PendingResult;
+import com.google.maps.android.clustering.ClusterManager;
+import com.google.maps.android.clustering.view.ClusterRenderer;
+import com.google.maps.internal.PolylineEncoding;
+import com.google.maps.model.DirectionsResult;
+import com.google.maps.model.DirectionsRoute;
+
+import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
+
+import static com.codingwithmitch.googlemaps2018.Constants.MAPVIEW_BUNDLE_KEY;
-public class UserListFragment extends Fragment {
+public class UserListFragment extends Fragment implements
+ OnMapReadyCallback, View.OnClickListener,
+ GoogleMap.OnInfoWindowClickListener,
+ GoogleMap.OnPolylineClickListener {
private static final String TAG = "UserListFragment";
+ private static final float DEFAULT_ZOOM = 15f;
//widgets
private RecyclerView mUserListRecyclerView;
+ private MapView mMapView;
+ private EditText mSearchText;
//vars
private ArrayList mUserList = new ArrayList<>();
+ private ArrayList mUserLocations = new ArrayList<>();
private UserRecyclerAdapter mUserRecyclerAdapter;
-
-
- public static UserListFragment newInstance(){
+ private GoogleMap mGoogleMap;
+ private LatLngBounds mMapBoundary;
+ private UserLocation mUserPosition;
+ private ClusterManager mClusterManager;
+ private MyClusterManagerRenderer mClusterManagerRenderer;
+ private ArrayList mClusterMarkers = new ArrayList<>();
+ private GeoApiContext mGeoApiContext = null;
+ private ArrayList mPolylinesData = new ArrayList<>();
+ private Marker mSelectedMarker = null;
+ private ArrayList mTripMarkers = new ArrayList<>();
+
+ public static UserListFragment newInstance() {
return new UserListFragment();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if(getArguments() != null){
+ if (getArguments() != null) {
mUserList = getArguments().getParcelableArrayList(getString(R.string.intent_user_list));
+ mUserLocations = getArguments().getParcelableArrayList(getString(R.string.intent_user_locations));
}
+
+ }
+
+ private void addPolylinesToMap(final DirectionsResult result){
+ new Handler(Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "run: result routes: " + result.routes.length);
+ if(mPolylinesData.size() > 0 ){
+ for(PolylineData polylineData : mPolylinesData){
+ polylineData.getPolyline().remove();
+ }
+ mPolylinesData.clear();
+ mPolylinesData = new ArrayList<>();
+ }
+
+ double duration = 9999;
+ for(DirectionsRoute route: result.routes){
+ Log.d(TAG, "run: leg: " + route.legs[0].toString());
+ List decodedPath = PolylineEncoding.decode(route.overviewPolyline.getEncodedPath());
+
+ List newDecodedPath = new ArrayList<>();
+
+ // This loops through all the LatLng coordinates of ONE polyline.
+ for(com.google.maps.model.LatLng latLng: decodedPath){
+
+// Log.d(TAG, "run: latlng: " + latLng.toString());
+
+ newDecodedPath.add(new LatLng(
+ latLng.lat,
+ latLng.lng
+ ));
+ }
+ Polyline polyline = mGoogleMap.addPolyline(new PolylineOptions().addAll(newDecodedPath));
+ polyline.setColor(ContextCompat.getColor(getActivity(), R.color.darkGrey));
+ polyline.setClickable(true);
+ mPolylinesData.add(new PolylineData(polyline, route.legs[0]));
+
+ double tempDuration = route.legs[0].duration.inSeconds;
+ if(tempDuration < duration){
+ duration = tempDuration;
+ onPolylineClick(polyline);
+ zoomRoute(polyline.getPoints());
+ }
+
+ mSelectedMarker.setVisible(false);
+ }
+ }
+ });
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_user_list, container, false);
+ View view = inflater.inflate(R.layout.fragment_user_list, container, false);
mUserListRecyclerView = view.findViewById(R.id.user_list_recycler_view);
+ mMapView = (MapView) view.findViewById(R.id.user_list_map);
+ view.findViewById(R.id.btn_reset_map).setOnClickListener(this);
+ mSearchText = (EditText) view.findViewById(R.id.input_search);
initUserListRecyclerView();
+ initGoogleMap(savedInstanceState);
+
+ setUserPosition();
+
return view;
}
+ private void removeTripMarker(){
+ for(Marker marker : mTripMarkers){
+ marker.remove();
+ }
+ }
+
+ private void resetSelectedMarker(){
+ if(mSelectedMarker != null){
+ mSelectedMarker.setVisible(true);
+ mSelectedMarker = null;
+ removeTripMarker();
+ }
+ }
+
+ private void calculateDirections(Marker marker){
+ Log.d(TAG, "calculateDirections: calculating directions.");
+
+ com.google.maps.model.LatLng destination = new com.google.maps.model.LatLng(
+ marker.getPosition().latitude,
+ marker.getPosition().longitude
+ );
+ DirectionsApiRequest directions = new DirectionsApiRequest(mGeoApiContext);
+
+ //For showing all possible routes instead of only most efficient
+ directions.alternatives(true);
+ directions.origin(
+ new com.google.maps.model.LatLng(
+ mUserPosition.getGeo_point().getLatitude(),
+ mUserPosition.getGeo_point().getLongitude()
+ )
+ );
+ Log.d(TAG, "calculateDirections: destination: " + destination.toString());
+ directions.destination(destination).setCallback(new PendingResult.Callback() {
+ @Override
+ public void onResult(DirectionsResult result) {
+ Log.d(TAG, "calculateDirections: routes: " + result.routes[0].toString());
+ Log.d(TAG, "calculateDirections: duration: " + result.routes[0].legs[0].duration);
+ Log.d(TAG, "calculateDirections: distance: " + result.routes[0].legs[0].distance);
+ Log.d(TAG, "calculateDirections: geocodedWayPoints: " + result.geocodedWaypoints[0].toString());
+
+ addPolylinesToMap(result);
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ Log.e(TAG, "calculateDirections: Failed to get directions: " + e.getMessage() );
+
+ }
+ });
+ }
+
+ private void resetMap(){
+ if(mGoogleMap != null) {
+ mGoogleMap.clear();
+
+ if(mClusterManager != null){
+ mClusterManager.clearItems();
+ }
+
+ if (mClusterMarkers.size() > 0) {
+ mClusterMarkers.clear();
+ mClusterMarkers = new ArrayList<>();
+ }
+
+ if(mPolylinesData.size() > 0){
+ mPolylinesData.clear();
+ mPolylinesData = new ArrayList<>();
+ }
+ }
+ }
+
+ private void addMapMarkers() {
+ if (mGoogleMap != null) {
+
+ resetMap();
+
+ if (mClusterManager == null) {
+ mClusterManager = new ClusterManager(getActivity().getApplicationContext(), mGoogleMap);
+ }
+
+ for (UserLocation userLocation : mUserLocations) {
+
+ Log.d(TAG, "addMapMarkers: location: " + userLocation.getGeo_point().toString());
+ try {
+ /*
+ String snippet = "";
+ if(userLocation.getUser().getUser_id().equals(FirebaseAuth.getInstance().getUid())){
+ snippet = "This is you";
+ }
+ else{
+ snippet = "Determine route to " + userLocation.getUser().getUsername() + "?";
+ }
+
+ int avatar = R.drawable.cartman_cop; // set the default avatar
+ try{
+ avatar = Integer.parseInt(userLocation.getUser().getAvatar());
+ }catch (NumberFormatException e){
+ Log.d(TAG, "addMapMarkers: no avatar for " + userLocation.getUser().getUsername() + ", setting default.");
+ }*/
+
+ String snippet = "";
+ MyItem newClusterMarker = new MyItem(
+ userLocation.getGeo_point().getLatitude(), userLocation.getGeo_point().getLongitude(),
+ userLocation.getUser().getUsername(),
+ snippet
+ );
+
+ mClusterManager.addItem(newClusterMarker);
+ mClusterMarkers.add(newClusterMarker);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "addMapMarkers: NullPointerException: " + e.getMessage());
+ }
+
+ }
+ mClusterManager.cluster();
+
+ setCameraView();
+ }
+ }
+
+ private void setCameraView() {
+
+ //Define top right corner and bottom left corner for boundary
+ //Overall map view window takes up area of 0.2 x 0.2 = 0.4 latLng
+ double bottomBoundary = mUserPosition.getGeo_point().getLatitude() - 0.1;
+ double leftBoundary = mUserPosition.getGeo_point().getLongitude() - 0.1;
+
+ double topBoundary = mUserPosition.getGeo_point().getLatitude() + 0.1;
+ double rightBoundary = mUserPosition.getGeo_point().getLongitude() + 0.1;
+
+ mMapBoundary = new LatLngBounds(
+ new LatLng(bottomBoundary, leftBoundary),
+ new LatLng(topBoundary, rightBoundary)
+ );
+
+ mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngBounds(mMapBoundary, 0));
+
+ }
+
+ private void setUserPosition() {
+ for (UserLocation userLocation : mUserLocations) {
+ if (userLocation.getUser().getUser_id().equals(FirebaseAuth.getInstance().getUid())) {
+ mUserPosition = userLocation;
+ }
+
+ }
+ }
+
+ private void initGoogleMap(Bundle savedInstanceState) {
+ // *** IMPORTANT ***
+ // MapView requires that the Bundle you pass contain _ONLY_ MapView SDK
+ // objects or sub-Bundles.
+ Bundle mapViewBundle = null;
+ if (savedInstanceState != null) {
+ mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY);
+ }
+
+ mMapView.onCreate(mapViewBundle);
+
+ mMapView.getMapAsync(this);
+
+ mSearchText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
+ if(actionId == EditorInfo.IME_ACTION_SEARCH
+ || actionId == EditorInfo.IME_ACTION_DONE
+ ||keyEvent.getAction() == KeyEvent.ACTION_DOWN
+ || keyEvent.getAction() == KeyEvent.KEYCODE_ENTER) {
+ geoLocate();
+ }
+ return false;
+ }
+ });
+
+ if(mGeoApiContext == null){
+ mGeoApiContext = new GeoApiContext.Builder()
+ .apiKey(getString(R.string.maps_api_key))
+ .build();
+ }
+ }
+
+ private void geoLocate(){
+ Log.d(TAG, "geoLocate: geolocating");
- private void initUserListRecyclerView(){
+ String searchString = mSearchText.getText().toString();
+
+ Geocoder geocoder = new Geocoder(getContext());
+ List list = new ArrayList<>();
+ try{
+ list = geocoder.getFromLocationName(searchString, 1);
+ }catch (IOException e){
+ Log.e(TAG, "geoLocate: IOException: " + e.getMessage() );
+ }
+
+ if(list.size() > 0){
+ Address address = list.get(0);
+
+ Log.d(TAG, "geoLocate: found a location: " + address.toString());
+ //Toast.makeText(this, address.toString(), Toast.LENGTH_SHORT).show();
+
+ //GeoPoint gp = new GeoPoint(address.getLatitude(), address.getLongitude());
+ //mUserPosition.setGeo_point(gp);
+ //setCameraView();
+
+ moveCamera(new LatLng(address.getLatitude(), address.getLongitude()),
+ DEFAULT_ZOOM,
+ address.getAddressLine(0));
+ }
+ }
+
+ private void moveCamera(LatLng latLng, float zoom, String title){
+ mGoogleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, zoom));
+
+ MarkerOptions options = new MarkerOptions()
+ .position(latLng)
+ .title(title);
+ mGoogleMap.addMarker(options);
+
+ }
+
+
+ private void initUserListRecyclerView() {
mUserRecyclerAdapter = new UserRecyclerAdapter(mUserList);
mUserListRecyclerView.setAdapter(mUserRecyclerAdapter);
mUserListRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
}
-}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ Bundle mapViewBundle = outState.getBundle(MAPVIEW_BUNDLE_KEY);
+ if (mapViewBundle == null) {
+ mapViewBundle = new Bundle();
+ outState.putBundle(MAPVIEW_BUNDLE_KEY, mapViewBundle);
+ }
+ mMapView.onSaveInstanceState(mapViewBundle);
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
+ mMapView.onResume();
+ }
+ @Override
+ public void onStart() {
+ super.onStart();
+ mMapView.onStart();
+ }
+ @Override
+ public void onStop() {
+ super.onStop();
+ mMapView.onStop();
+ }
+ @Override
+ public void onMapReady(GoogleMap map) {
+ map.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
+ if (ActivityCompat.checkSelfPermission(getActivity(), Manifest.permission.ACCESS_FINE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED
+ && ActivityCompat.checkSelfPermission(getActivity(),
+ Manifest.permission.ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ map.setMyLocationEnabled(true);
+ mGoogleMap = map;
+ addMapMarkers();
+ mGoogleMap.setOnPolylineClickListener(this);
+ mGoogleMap.setOnInfoWindowClickListener(this);
+ }
+ @Override
+ public void onPause() {
+ mMapView.onPause();
+ super.onPause();
+ }
+ @Override
+ public void onDestroy() {
+ mMapView.onDestroy();
+ super.onDestroy();
+ }
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mMapView.onLowMemory();
+ }
+ @Override
+ public void onClick(View view) {
+ switch(view.getId()){
+ case R.id.btn_reset_map: {
+ addMapMarkers();
+ break;
+ }
+ }
+ }
+ @Override
+ public void onInfoWindowClick(final Marker marker) {
+
+ if(marker.getSnippet().contains("Duration")){
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage("Open Google Maps?")
+ .setCancelable(true)
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(@SuppressWarnings("unused") final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
+ String latitude = String.valueOf(marker.getPosition().latitude);
+ String longitude = String.valueOf(marker.getPosition().longitude);
+ Uri gmmIntentUri = Uri.parse("google.navigation:q=" + latitude + "," + longitude);
+ Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+ mapIntent.setPackage("com.google.android.apps.maps");
+
+ try{
+ if (mapIntent.resolveActivity(getActivity().getPackageManager()) != null) {
+ startActivity(mapIntent);
+ }
+ }catch (NullPointerException e){
+ Log.e(TAG, "onClick: NullPointerException: Couldn't open map." + e.getMessage() );
+ Toast.makeText(getActivity(), "Couldn't open map", Toast.LENGTH_SHORT).show();
+ }
+
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
+ dialog.cancel();
+ }
+ });
+ final AlertDialog alert = builder.create();
+ alert.show();
+ }
+ else {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ String message = "Do you want to navigate to " + marker.getTitle() + "?";
+ builder.setMessage(message)
+ .setCancelable(true)
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ public void onClick(@SuppressWarnings("unused") final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
+ resetSelectedMarker();
+ mSelectedMarker = marker;
+ calculateDirections(marker);
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ public void onClick(final DialogInterface dialog, @SuppressWarnings("unused") final int id) {
+ dialog.cancel();
+ }
+ });
+ final AlertDialog alert = builder.create();
+ alert.show();
+ }
+ }
+ @Override
+ public void onPolylineClick(Polyline polyline) {
+
+ for(PolylineData polylineData: mPolylinesData){
+ Log.d(TAG, "onPolylineClick: toString: " + polylineData.toString());
+ if(polyline.getId().equals(polylineData.getPolyline().getId())){
+ polylineData.getPolyline().setColor(ContextCompat.getColor(getActivity(), R.color.blue1));
+ polylineData.getPolyline().setZIndex(1);
+
+ LatLng endLocation = new LatLng(
+ polylineData.getLeg().endLocation.lat,
+ polylineData.getLeg().endLocation.lng
+ );
+
+ Marker marker = mGoogleMap.addMarker(new MarkerOptions()
+ .position(endLocation)
+ .title(polylineData.getLeg().endAddress.toString())
+ .snippet("Duration: " + polylineData.getLeg().duration));
+
+ mTripMarkers.add(marker);
+ marker.showInfoWindow();
+ }
+ else{
+ polylineData.getPolyline().setColor(ContextCompat.getColor(getActivity(), R.color.darkGrey));
+ polylineData.getPolyline().setZIndex(0);
+ }
+ }
+ }
+ public void zoomRoute(List lstLatLngRoute) {
+ if (mGoogleMap == null || lstLatLngRoute == null || lstLatLngRoute.isEmpty()) return;
+ LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder();
+ for (LatLng latLngPoint : lstLatLngRoute)
+ boundsBuilder.include(latLngPoint);
+ int routePadding = 120;
+ LatLngBounds latLngBounds = boundsBuilder.build();
+ mGoogleMap.animateCamera(
+ CameraUpdateFactory.newLatLngBounds(latLngBounds, routePadding),
+ 600,
+ null
+ );
+ }
+}
diff --git a/app/src/main/java/com/codingwithmitch/googlemaps2018/util/MyClusterManagerRenderer.java b/app/src/main/java/com/codingwithmitch/googlemaps2018/util/MyClusterManagerRenderer.java
new file mode 100644
index 0000000..6b3f724
--- /dev/null
+++ b/app/src/main/java/com/codingwithmitch/googlemaps2018/util/MyClusterManagerRenderer.java
@@ -0,0 +1,67 @@
+package com.codingwithmitch.googlemaps2018.util;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+
+import com.codingwithmitch.googlemaps2018.R;
+import com.codingwithmitch.googlemaps2018.models.ClusterMarker;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.Marker;
+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;
+
+
+public class MyClusterManagerRenderer extends DefaultClusterRenderer
+{
+
+ private final IconGenerator iconGenerator;
+ private final ImageView imageView;
+ private final int markerWidth;
+ private final int markerHeight;
+
+ public MyClusterManagerRenderer(Context context, GoogleMap googleMap,
+ ClusterManager clusterManager) {
+
+ super(context, googleMap, clusterManager);
+
+ // initialize cluster item icon generator
+ iconGenerator = new IconGenerator(context.getApplicationContext());
+ imageView = new ImageView(context.getApplicationContext());
+ markerWidth = (int) context.getResources().getDimension(R.dimen.custom_marker_image);
+ markerHeight = (int) context.getResources().getDimension(R.dimen.custom_marker_image);
+ imageView.setLayoutParams(new ViewGroup.LayoutParams(markerWidth, markerHeight));
+ int padding = (int) context.getResources().getDimension(R.dimen.custom_marker_padding);
+ imageView.setPadding(padding, padding, padding, padding);
+ iconGenerator.setContentView(imageView);
+
+ }
+
+ /**
+ * Rendering of the individual ClusterItems
+ * @param item
+ * @param markerOptions
+ */
+ @Override
+ protected void onBeforeClusterItemRendered(ClusterMarker item, MarkerOptions markerOptions) {
+
+ imageView.setImageResource(item.getIconPicture());
+ Bitmap icon = iconGenerator.makeIcon();
+ markerOptions.icon(BitmapDescriptorFactory.fromBitmap(icon)).title(item.getTitle());
+ }
+
+
+ @Override
+ protected boolean shouldRenderAsCluster(Cluster cluster) {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable-hdpi/ic_gps.png b/app/src/main/res/drawable-hdpi/ic_gps.png
new file mode 100644
index 0000000..7f81349
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_gps.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_info.png b/app/src/main/res/drawable-hdpi/ic_info.png
new file mode 100644
index 0000000..760e0fa
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_info.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_magnify.png b/app/src/main/res/drawable-hdpi/ic_magnify.png
new file mode 100644
index 0000000..6bed245
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_magnify.png differ
diff --git a/app/src/main/res/drawable-hdpi/ic_menu.png b/app/src/main/res/drawable-hdpi/ic_menu.png
new file mode 100644
index 0000000..b4e3cbc
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_menu.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_gps.png b/app/src/main/res/drawable-mdpi/ic_gps.png
new file mode 100644
index 0000000..7783ebe
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_gps.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_info.png b/app/src/main/res/drawable-mdpi/ic_info.png
new file mode 100644
index 0000000..b127802
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_info.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_magnify.png b/app/src/main/res/drawable-mdpi/ic_magnify.png
new file mode 100644
index 0000000..bd7bab4
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_magnify.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_menu.png b/app/src/main/res/drawable-mdpi/ic_menu.png
new file mode 100644
index 0000000..a62643b
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_menu.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_gps.png b/app/src/main/res/drawable-xhdpi/ic_gps.png
new file mode 100644
index 0000000..c304222
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_gps.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_info.png b/app/src/main/res/drawable-xhdpi/ic_info.png
new file mode 100644
index 0000000..6514a0c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_info.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_magnify.png b/app/src/main/res/drawable-xhdpi/ic_magnify.png
new file mode 100644
index 0000000..d5ca6ac
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_magnify.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_menu.png b/app/src/main/res/drawable-xhdpi/ic_menu.png
new file mode 100644
index 0000000..cd9a25c
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_menu.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_gps.png b/app/src/main/res/drawable-xxhdpi/ic_gps.png
new file mode 100644
index 0000000..713d79f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_gps.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_info.png b/app/src/main/res/drawable-xxhdpi/ic_info.png
new file mode 100644
index 0000000..f9e45fe
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_info.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_magnify.png b/app/src/main/res/drawable-xxhdpi/ic_magnify.png
new file mode 100644
index 0000000..320c412
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_magnify.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_menu.png b/app/src/main/res/drawable-xxhdpi/ic_menu.png
new file mode 100644
index 0000000..85db24a
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_menu.png differ
diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png
new file mode 100644
index 0000000..c6ad80c
Binary files /dev/null and b/app/src/main/res/drawable/logo.png differ
diff --git a/app/src/main/res/drawable/white_border.xml b/app/src/main/res/drawable/white_border.xml
new file mode 100644
index 0000000..f5ac4b6
--- /dev/null
+++ b/app/src/main/res/drawable/white_border.xml
@@ -0,0 +1,13 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index dd2c7a0..ac0e132 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,37 +1,71 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+ android:id="@+id/map"
+ tools:context=".MapsActivity"
+ android:name="com.google.android.gms.maps.SupportMapFragment" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_mainold.xml b/app/src/main/res/layout/activity_mainold.xml
new file mode 100644
index 0000000..dd2c7a0
--- /dev/null
+++ b/app/src/main/res/layout/activity_mainold.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_map.xml b/app/src/main/res/layout/activity_map.xml
new file mode 100644
index 0000000..ac0e132
--- /dev/null
+++ b/app/src/main/res/layout/activity_map.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..d8f6012
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_user_list.xml b/app/src/main/res/layout/fragment_user_list.xml
index f94bc99..3e48fca 100644
--- a/app/src/main/res/layout/fragment_user_list.xml
+++ b/app/src/main/res/layout/fragment_user_list.xml
@@ -1,17 +1,92 @@
-
-
-
+ android:orientation="vertical"
+ android:weightSum="100"
+ android:background="@color/White">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..623d3a5
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b085577..4fa8a98 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -49,5 +49,6 @@
User list
Profile
+ AIzaSyB8Ygc0PYpUHGrovc3p6q6sE8UdZDZJMY4
diff --git a/build.gradle b/build.gradle
index 05da648..caeed0a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.3'
+ classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.google.gms:google-services:4.0.1'
// NOTE: Do not place your application dependencies here; they belong
@@ -19,6 +19,9 @@ allprojects {
repositories {
google()
jcenter()
+ maven{
+ url "https://maven.google.com"
+ }
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 15a6210..67aae4d 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Fri Sep 07 12:32:17 PDT 2018
+#Wed Nov 28 16:48:33 MST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip