diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bd635d3..d7aed23 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,8 +39,9 @@ dependencies { implementation ("com.google.android.gms:play-services-maps:18.2.0") implementation ("com.google.android.gms:play-services-location:21.0.1") implementation ("com.google.android.libraries.places:places:3.3.0") + implementation("com.google.firebase:firebase-firestore:24.10.0") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") -} \ No newline at end of file +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c5c0844..3909bf4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ android:value="@string/api_key" /> @@ -43,4 +43,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/example/map/MainActivity.java b/app/src/main/java/com/example/map/MapFragment.java similarity index 68% rename from app/src/main/java/com/example/map/MainActivity.java rename to app/src/main/java/com/example/map/MapFragment.java index a7434a6..626e5ae 100644 --- a/app/src/main/java/com/example/map/MainActivity.java +++ b/app/src/main/java/com/example/map/MapFragment.java @@ -1,20 +1,14 @@ package com.example.map; import android.Manifest; +import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Color; -import android.location.Address; -import android.location.Geocoder; import android.location.Location; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.util.Log; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; @@ -28,30 +22,31 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; +import com.google.android.gms.common.api.Status; import com.google.android.gms.location.FusedLocationProviderClient; import com.google.android.gms.location.LocationServices; 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.BitmapDescriptor; import com.google.android.gms.maps.model.BitmapDescriptorFactory; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.MarkerOptions; import com.google.android.gms.maps.model.PointOfInterest; -import com.google.android.gms.tasks.OnSuccessListener; import com.google.android.gms.tasks.Task; import com.google.android.libraries.places.api.Places; import com.google.android.libraries.places.api.model.Place; -import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest; -import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse; -import com.google.android.material.textfield.TextInputEditText; import com.google.android.libraries.places.widget.AutocompleteSupportFragment; import com.google.android.libraries.places.widget.listener.PlaceSelectionListener; -import com.google.android.libraries.places.api.model.RectangularBounds; -import com.google.android.libraries.places.api.net.PlacesClient; -import com.google.android.gms.common.api.Status; -//import com.google.android.libraries.places.widget.model.AutocompletePrediction; +import com.google.firebase.FirebaseApp; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.CollectionReference; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.GeoPoint; +import com.google.firebase.firestore.ListenerRegistration; +import com.google.firebase.firestore.QueryDocumentSnapshot; import org.json.JSONException; import org.json.JSONObject; @@ -72,7 +67,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; -public class MainActivity extends AppCompatActivity implements OnMapReadyCallback , GoogleMap.OnPoiClickListener { +public class MapFragment extends AppCompatActivity implements OnMapReadyCallback , GoogleMap.OnPoiClickListener { private GoogleMap mMap; private static final String FINE_LOCATION = Manifest.permission.ACCESS_FINE_LOCATION; @@ -81,18 +76,49 @@ public class MainActivity extends AppCompatActivity implements OnMapReadyCallbac private static final float DEFAULT_ZOOM = 15f; private Boolean mLocationPermissionsGranted = false; private FusedLocationProviderClient mfusedLocationProviderClient; - private static final String TAG = "MainActivity"; + private static final String TAG = "MapReport"; private Toolbar toolbar; private LinearLayout legendLayout; // Add this line - private ImageButton legendButton; // Add this line private List crimeData = new ArrayList<>(); private double currentLat = 0; private double currentLng = 0; - + private FirebaseFirestore db; + private ListenerRegistration reportListener; + private boolean isShowingNearbyPlaces = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + setContentView(R.layout.fragment_map); + FirebaseApp.initializeApp(this); + // Initialize Firestore + db = FirebaseFirestore.getInstance(); + // Reference to the "report" collection + CollectionReference reportCollectionRef = db.collection("report"); + + // Fetch all documents from the "report" collection + reportCollectionRef.get() + .addOnSuccessListener(queryDocumentSnapshots -> { + for (QueryDocumentSnapshot document : queryDocumentSnapshots) { + // Convert each document to a Report object + + Report report = document.toObject(Report.class); + + // Access the fields of the Report object + String desc = report.getDesc(); + GeoPoint location = report.getLocation(); + Timestamp timestamp = report.getTimestamp(); + String user = report.getUserId(); + Log.d(TAG, "desc: " + desc); + Log.d(TAG, "location " + location.toString()); + Log.d(TAG, "timestamp: " + timestamp.toString()); + Log.d(TAG, "user: " + user); + // Use the data as needed + // For example, you can update UI or perform other operations + } + }) + .addOnFailureListener(e -> { + Log.d(TAG, "Error: " + e.getMessage()); + }); // Initialize Toolbar toolbar = findViewById(R.id.toolbar); @@ -119,6 +145,10 @@ protected void onCreate(Bundle savedInstanceState) { ImageButton specificButton3 = findViewById(R.id.btnFireDepartment); // Replace with your button's ID specificButton3.setOnClickListener(v -> onSpecificButtonClicked3()); + + // Start listening for report updates + startReportListener(); + } private void initLegend() { // Reference to the legend layout @@ -227,7 +257,7 @@ private void getDeviceLocation() { moveCamera(new LatLng(currentLat, currentLng), DEFAULT_ZOOM, "My Location"); } else { Log.d(TAG, "onComplete: current location is null"); - Toast.makeText(MainActivity.this, "Unable to get current location", Toast.LENGTH_SHORT).show(); + Toast.makeText(MapFragment.this, "Unable to get current location", Toast.LENGTH_SHORT).show(); } }); } @@ -254,13 +284,13 @@ private float getMarkerColor(String crimeType) { Log.d(TAG, "getMarkerColor: Crime Type: " + crimeType); if ("Violent Crime".equals(crimeType)) { Log.d(TAG, "getMarkerColor: Setting color to Red for Violent Crime"); - return BitmapDescriptorFactory.HUE_RED; + return BitmapDescriptorFactory.HUE_VIOLET; } else if ("Property Crime".equals(crimeType)) { Log.d(TAG, "getMarkerColor: Setting color to Blue for Property Crime"); - return BitmapDescriptorFactory.HUE_BLUE; + return BitmapDescriptorFactory.HUE_VIOLET; } Log.d(TAG, "getMarkerColor: Setting default color (Green)"); - return BitmapDescriptorFactory.HUE_GREEN; // Default color + return BitmapDescriptorFactory.HUE_RED; // Default color } private void iniMap() { @@ -304,7 +334,13 @@ public void onMapReady(@NonNull GoogleMap googleMap) { mMap.getUiSettings().setZoomControlsEnabled(true); mMap.getUiSettings().setCompassEnabled(true); - + mMap.setOnMarkerClickListener(new GoogleMap.OnMarkerClickListener() { + @Override + public boolean onMarkerClick(@NonNull Marker marker) { + marker.showInfoWindow(); + return true; + } + }); // Set custom info window adapter mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() { @Nullable @@ -315,14 +351,35 @@ public View getInfoContents(@NonNull Marker marker) { @Override public View getInfoWindow(Marker marker) { + LatLng destinationLatLng = marker.getPosition(); View infoWindow = getLayoutInflater().inflate(R.layout.custom_info_window, null); // Inflate your custom layout TextView title = infoWindow.findViewById(R.id.infoTitle); + TextView lat = infoWindow.findViewById(R.id.Loclat); + TextView Long = infoWindow.findViewById(R.id.LocLong); + title.setText(marker.getTitle()); // Set the crime type as title + lat.setText(Double.toString(destinationLatLng.latitude)); + Long.setText(Double.toString(destinationLatLng.longitude)); + // Add more information if needed return infoWindow; } }); + mMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { + @Override + public void onInfoWindowClick(@NonNull Marker marker) { + LatLng destinationLatLng = marker.getPosition(); + + // Start Google Maps intent for directions + Intent intent = new Intent( + Intent.ACTION_VIEW, + Uri.parse("http://maps.google.com/maps?daddr=" + destinationLatLng.latitude + "," + destinationLatLng.longitude) + ); + intent.setPackage("com.google.android.apps.maps"); + startActivity(intent); + } + }); for (Crime crime : crimeData) { Log.d(TAG, "Crime: " + crime.toString()); // This will print the details of each crime @@ -366,45 +423,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis } } - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_main, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(@NonNull MenuItem item) { - int id = item.getItemId(); - - if (id == R.id.mapNone) { - setMapTypeWithDelay(GoogleMap.MAP_TYPE_NONE); - } else if (id == R.id.mapNormal) { - setMapTypeWithDelay(GoogleMap.MAP_TYPE_NORMAL); - } else if (id == R.id.mapSatellite) { - setMapTypeWithDelay(GoogleMap.MAP_TYPE_SATELLITE); - } else if (id == R.id.mapHybrid) { - setMapTypeWithDelay(GoogleMap.MAP_TYPE_HYBRID); - } else if (id == R.id.mapTerrain) { - setMapTypeWithDelay(GoogleMap.MAP_TYPE_TERRAIN); - } - - return super.onOptionsItemSelected(item); - } - - private void setMapTypeWithDelay(final int mapType) { - new android.os.Handler().postDelayed( - () -> { - if (mMap != null) { - try { - mMap.setMapType(mapType); - } catch (Exception e) { - Log.e(TAG, "Error changing map type: " + e.getMessage()); - } - } - }, - 1000 // 1 second delay, you can adjust this - ); - } @Override public void onPoiClick(PointOfInterest poi) { try { @@ -417,7 +435,7 @@ public void onPoiClick(PointOfInterest poi) { Log.e(TAG, "Error handling POI click: " + e.getMessage()); } } - private void parseResult(String data) { + private void parseResult(String data, String placeType) { try { JSONObject object = new JSONObject(data); @@ -435,10 +453,15 @@ private void parseResult(String data) { double lng = Double.parseDouble(Objects.requireNonNull(hashMapList.get("lng"))); String name = hashMapList.get("name"); LatLng latLng = new LatLng(lat, lng); + float markerColor = getMarkerColorForPlaceType(placeType); + if (i == 0) { mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 12)); } - mMap.addMarker(new MarkerOptions().position(latLng).title(name)); + mMap.addMarker(new MarkerOptions() + .position(latLng) + .title(name) + .icon(BitmapDescriptorFactory.defaultMarker(markerColor))); // Apply the marker color here } }); } else { @@ -455,7 +478,8 @@ private void parseResult(String data) { } } - private void getNearbyPlace(String command) { + + private void getNearbyPlace(String command, String placeType) { String url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" + "?location=" + currentLat + "," + currentLng + "&radius=5000" + // specify the radius in meters @@ -472,7 +496,7 @@ private void getNearbyPlace(String command) { } catch (IOException e) { throw new RuntimeException(e); } - }, executor).thenAccept(this::parseResult) + }, executor).thenAccept(data -> parseResult(data, placeType)) .exceptionally(ex -> { Log.e(TAG, "Exception in CompletableFuture: " + ex.getMessage()); return null; @@ -505,17 +529,124 @@ private String downloadUrl(String urlString) throws IOException { // Method to be called when the specific button is clicked private void onSpecificButtonClicked() { getDeviceLocation(); - // Replace with desired place type: "police", "hospital", or "fire_station" - getNearbyPlace("police"); + if (isShowingNearbyPlaces) { + showAllNearbyCrimes(); + } else { + getNearbyPlace("police", "police"); + isShowingNearbyPlaces = true; + } } + private void onSpecificButtonClicked2() { getDeviceLocation(); - // Replace with desired place type: "police", "hospital", or "fire_station" - getNearbyPlace("hospital"); + if (isShowingNearbyPlaces) { + showAllNearbyCrimes(); + } else { + getNearbyPlace("hospital", "hospital"); + isShowingNearbyPlaces = true; + } } + private void onSpecificButtonClicked3() { getDeviceLocation(); - // Replace with desired place type: "police", "hospital", or "fire_station" - getNearbyPlace("fire%20station"); + if (isShowingNearbyPlaces) { + showAllNearbyCrimes(); + } else { + getNearbyPlace("fire station", "fire_station"); + isShowingNearbyPlaces = true; + } + } + private float getMarkerColorForPlaceType(String placeType) { + switch (placeType) { + case "police": + return BitmapDescriptorFactory.HUE_BLUE; // Set the color for police markers + case "hospital": + return BitmapDescriptorFactory.HUE_GREEN; // Set the color for hospital markers + case "fire_station": + return BitmapDescriptorFactory.HUE_ORANGE; // Set the color for fire station markers + default: + return BitmapDescriptorFactory.HUE_YELLOW; // Default color + } + } + + private void showAllNearbyCrimes() { + // Assuming crime markers are already added to the map during onMapReady + moveCameraToUserLocation(); + addCrimeMarkers(); // Add crime markers back to the map + isShowingNearbyPlaces = false; + } + + private void addCrimeMarkers() { + for (Crime crime : crimeData) { + LatLng crimeLocation = new LatLng(crime.getLatitude(), crime.getLongitude()); + float markerColor = getMarkerColor(crime.getCrimeType()); + + Log.d(TAG, "Adding marker: " + crime.getCrimeType() + " with color: " + markerColor); + mMap.addMarker(new MarkerOptions() + .position(crimeLocation) + .title(crime.getCrimeType()) + .icon(BitmapDescriptorFactory.defaultMarker(markerColor))); + } } -} \ No newline at end of file + private void moveCameraToUserLocation() { + if (currentLat != 0 && currentLng != 0) { + LatLng userLocation = new LatLng(currentLat, currentLng); + mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(userLocation, DEFAULT_ZOOM)); + } + } + + private void startReportListener() { + try { + Log.d(TAG, "startReportListener: Start listening for reports"); + FirebaseFirestore fStore = FirebaseFirestore.getInstance(); + CollectionReference reportsRef = fStore.collection("report"); + + reportsRef.get() + .addOnSuccessListener(queryDocumentSnapshots -> { + Toast.makeText(this, "Obtaining Report location nearby", Toast.LENGTH_LONG).show(); + for (QueryDocumentSnapshot document : queryDocumentSnapshots) { + Report report = document.toObject(Report.class); + + // Log details of the report + + // Add marker for the report + addReportMarker(report); + } + }) + .addOnFailureListener(e -> { + Log.e(TAG, "Error fetching reports: ", e); + }); + } catch (Exception e) { + Log.e(TAG, "Exception in startReportListener: " + e.getMessage()); + } + } + + + private void addReportMarker(Report report) { + // Access the GeoPoint from the report + GeoPoint geoPoint = report.getLocation(); // Assuming report now has a GeoPoint field + + // Create a LatLng object from the GeoPoint + LatLng reportLocation = new LatLng(geoPoint.getLatitude(), geoPoint.getLongitude()); + // Create a purple marker icon + BitmapDescriptor markerIcon = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_VIOLET); + + // Add the marker to the map + mMap.addMarker(new MarkerOptions() + .position(reportLocation) + .title(report.getDesc()) + .icon(markerIcon) // Set the marker icon to the purple color + ); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + // Stop listening for updates when the activity is destroyed + if (reportListener != null) { + reportListener.remove(); + } + } + + +} diff --git a/app/src/main/java/com/example/map/Report.java b/app/src/main/java/com/example/map/Report.java new file mode 100644 index 0000000..4540ecd --- /dev/null +++ b/app/src/main/java/com/example/map/Report.java @@ -0,0 +1,33 @@ +package com.example.map; + +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.GeoPoint; + +public class Report { + private String desc; + private GeoPoint location; + private Timestamp timestamp; + private String user; + + // Constructor, getters, and setters + + public Report() { + // Default constructor required for Firestore + } + + public String getDesc() { + return desc; + } + + public GeoPoint getLocation() { + return location; + } + + public Timestamp getTimestamp() { + return timestamp; + } + + public String getUser() { + return user; + } +} diff --git a/app/src/main/res/drawable/ic_purple.png b/app/src/main/res/drawable/ic_purple.png new file mode 100644 index 0000000..03a92db Binary files /dev/null and b/app/src/main/res/drawable/ic_purple.png differ diff --git a/app/src/main/res/drawable/orange.png b/app/src/main/res/drawable/orange.png new file mode 100644 index 0000000..a611d33 Binary files /dev/null and b/app/src/main/res/drawable/orange.png differ diff --git a/app/src/main/res/layout/custom_info_window.xml b/app/src/main/res/layout/custom_info_window.xml index 948d29d..2f2636a 100644 --- a/app/src/main/res/layout/custom_info_window.xml +++ b/app/src/main/res/layout/custom_info_window.xml @@ -11,6 +11,22 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:textStyle="bold"/> - + + + - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/fragment_map.xml similarity index 76% rename from app/src/main/res/layout/activity_main.xml rename to app/src/main/res/layout/fragment_map.xml index 63ca89d..4f040ca 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -11,7 +11,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@id/autocomplete_fragment_container" - tools:context=".MainActivity" /> + android:layout_marginTop="4dp" + tools:context=".MapFragment" /> + + - - + + @@ -104,7 +107,7 @@ + android:src="@drawable/red" /> + android:src="@drawable/ic_purple" /> + + + + + + + + +