Updated UI regarding place visualization in map

This commit is contained in:
Pau 2025-05-21 00:31:40 +02:00
parent 3e167da742
commit 598978ee15
11 changed files with 794 additions and 129 deletions

View File

@ -86,16 +86,12 @@ public class MyReportsAdapter extends RecyclerView.Adapter<MyReportsAdapter.View
AlertDialog dialog = DialogUtils.confirmationDialog( AlertDialog dialog = DialogUtils.confirmationDialog(
context, context,
confirmationText, confirmationText,
(dialogInterface, i) -> { (dialogInterface, i) -> removeReport(report.getUuid())
removeReport(report.getUuid());
}
); );
dialog.show(); dialog.show();
}); });
holder.ivEdit.setOnClickListener(v -> { holder.ivEdit.setOnClickListener(v -> Helper.goTo(context, AddReportActivity.class, Constants.REPORT, report));
Helper.goTo(context, AddReportActivity.class, Constants.REPORT, report);
});
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error in MyReports Adapter", e); Log.e(TAG, "Error in MyReports Adapter", e);

View File

@ -26,6 +26,7 @@ public class PlaceReportsAdapter extends RecyclerView.Adapter<PlaceReportsAdapte
this.context = context; this.context = context;
this.reportList = reportList; this.reportList = reportList;
} }
public void clearReports() { public void clearReports() {
reportList.clear(); reportList.clear();
notifyDataSetChanged(); notifyDataSetChanged();
@ -60,14 +61,14 @@ public class PlaceReportsAdapter extends RecyclerView.Adapter<PlaceReportsAdapte
holder.tvDescription.setText(report.getDescription()); holder.tvDescription.setText(report.getDescription());
if (report.getReportRating() == 1) { if (report.getReportRating() == 1) {
holder.tvRating.setText(context.getString(R.string.Rating_BAD)); holder.tvRating.setText(context.getString(R.string.Rating_BAD));
holder.ivRating.setImageResource(R.drawable.ic_thumbs_down);} else if (report.getReportRating() == 2) { holder.ivRating.setImageResource(R.drawable.ic_thumbs_down);
} else if (report.getReportRating() == 2) {
holder.tvRating.setText(context.getString(R.string.Rating_AVERAGE)); holder.tvRating.setText(context.getString(R.string.Rating_AVERAGE));
holder.ivRating.setImageResource(R.drawable.ic_thumb_up_average); holder.ivRating.setImageResource(R.drawable.ic_thumb_up_average);
} else if (report.getReportRating() == 3) { } else if (report.getReportRating() == 3) {
holder.tvRating.setText(context.getString(R.string.Rating_GOOD)); holder.tvRating.setText(context.getString(R.string.Rating_GOOD));
holder.ivRating.setImageResource(R.drawable.ic_thumbs_up); holder.ivRating.setImageResource(R.drawable.ic_thumbs_up);
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Error in PlaceReportsAdapter", e); Log.e(TAG, "Error in PlaceReportsAdapter", e);

View File

@ -0,0 +1,374 @@
package com.example.acloc.dialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatButton;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.acloc.activity.AddNewPlaceActivity;
import com.example.acloc.activity.AddReportActivity;
import com.example.acloc.activity.PlaceDetailActivity;
import com.example.acloc.adapter.PlaceReportsAdapter;
import com.example.acloc.api.LocationApiClient;
import com.example.acloc.model.Place;
import com.example.acloc.model.Report;
import com.example.acloc.service.FavoriteService;
import com.example.acloc.service.ReportService;
import com.example.acloc.utility.Constants;
import com.example.acloc.utility.DialogUtils;
import com.example.acloc.utility.Helper;
import com.example.acloc.utility.SharedPref;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.ieslamar.acloc.R;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class PlaceBottomSheetDialog extends BottomSheetDialogFragment {
private static final String TAG = "PlaceBottomSheetDialog";
private final Place place;
private Context context;
private boolean isFavorite = false;
private TextView tvPlaceName, tvAddress, tvDescription, tvNoReports;
private ImageView ivFavorite, ivEdit, ivExpand;
private AppCompatButton btnAddReport;
private RecyclerView rvReports;
private PlaceReportsAdapter adapter;
private final List<Report> reportList = new ArrayList<>();
private BottomSheetBehavior<View> behavior;
public PlaceBottomSheetDialog(Place place) {
this.place = place;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
dialog.setOnShowListener(dialogInterface -> {
BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface;
View bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);
if (bottomSheet != null) {
behavior = BottomSheetBehavior.from(bottomSheet);
behavior.setPeekHeight(getResources().getDisplayMetrics().heightPixels / 2);
behavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
}
});
return dialog;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_place_detail, container, false);
context = getContext();
initUI(view);
setPlaceData();
initListeners();
checkIfPlaceIsFavorite(SharedPref.getUserUid(context), place.getUuid());
loadReports();
return view;
}
private void initUI(View view) {
tvPlaceName = view.findViewById(R.id.tvPlaceName);
tvAddress = view.findViewById(R.id.tvAddress);
tvDescription = view.findViewById(R.id.tvDescription);
tvNoReports = view.findViewById(R.id.tvNoData);
ivFavorite = view.findViewById(R.id.ivFavorite);
ivEdit = view.findViewById(R.id.ivEdit);
ivExpand = view.findViewById(R.id.ivExpand);
btnAddReport = view.findViewById(R.id.btnAddReport);
rvReports = view.findViewById(R.id.rvReports);
// Set up RecyclerView
rvReports.setLayoutManager(new LinearLayoutManager(context));
}
private void setPlaceData() {
tvPlaceName.setText(place.getName());
tvAddress.setText(place.getAddress());
tvDescription.setText(place.getDescription());
}
private void initListeners() {
ivFavorite.setOnClickListener(v -> {
if (isFavorite) {
removePlaceFromFavorites(SharedPref.getUserUid(context), place.getUuid());
} else {
addPlaceToFavorites(SharedPref.getUserUid(context), place.getUuid());
}
});
ivEdit.setOnClickListener(v -> {
Helper.goTo(context, AddNewPlaceActivity.class, Constants.PLACE, place);
dismiss();
});
ivExpand.setOnClickListener(v -> {
if (behavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
behavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED);
ivExpand.setImageResource(R.drawable.ic_expand_less);
} else {
behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
ivExpand.setImageResource(R.drawable.ic_expand_more);
}
});
btnAddReport.setOnClickListener(v -> {
Helper.goTo(context, AddReportActivity.class, Constants.PLACE, place);
dismiss();
});
// Open full screen details on click
View.OnClickListener fullScreenListener = v -> {
Helper.goTo(context, PlaceDetailActivity.class, Constants.PLACE, place);
dismiss();
};
tvPlaceName.setOnClickListener(fullScreenListener);
tvAddress.setOnClickListener(fullScreenListener);
tvDescription.setOnClickListener(fullScreenListener);
}
private void loadReports() {
String token = "Bearer " + SharedPref.getAccessToken(context);
ReportService reportService = LocationApiClient.getInstance().getReportService();
Call<JsonObject> call = reportService.getPlaceReports(token, place.getUuid());
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful() && response.body() != null) {
JsonObject responseBody = response.body();
JsonObject data = responseBody.getAsJsonObject("_data");
if (data != null && data.has("reports")) {
reportList.clear();
for (JsonElement element : data.getAsJsonArray("reports")) {
JsonObject reportObject = element.getAsJsonObject();
Report report = new Report();
report.setUuid(reportObject.get("uuid").getAsString());
report.setReportRating(reportObject.get("rating").getAsInt());
report.setDescription(reportObject.get("description").getAsString());
report.setPlaceName(reportObject.get("place_name").getAsString());
report.setPlaceUuid(reportObject.get("place_uuid").getAsString());
reportList.add(report);
}
// Show latest reports first
Collections.reverse(reportList);
List<Report> latestReports = reportList.size() > 3 ?
reportList.subList(0, 3) : reportList;
updateReportsUI(latestReports);
} else {
showNoReports();
}
} else {
showNoReports();
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
showNoReports();
}
});
}
private void updateReportsUI(List<Report> reports) {
if (reports != null && !reports.isEmpty()) {
adapter = new PlaceReportsAdapter(context, reports);
rvReports.setAdapter(adapter);
rvReports.setVisibility(View.VISIBLE);
tvNoReports.setVisibility(View.GONE);
} else {
showNoReports();
}
}
private void showNoReports() {
rvReports.setVisibility(View.GONE);
tvNoReports.setVisibility(View.VISIBLE);
}
private void checkIfPlaceIsFavorite(String userUuid, String placeUuid) {
String token = "Bearer " + SharedPref.getAccessToken(context);
FavoriteService favoriteService = LocationApiClient.getInstance().getFavoriteService();
Call<JsonObject> call = favoriteService.getFavoritePlaces(token, userUuid);
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
if (response.isSuccessful() && response.body() != null) {
JsonObject body = response.body();
JsonArray favoritesArray = body.getAsJsonObject("_data").getAsJsonArray("favorites");
isFavorite = false;
for (JsonElement item : favoritesArray) {
JsonObject favoriteObj = item.getAsJsonObject();
String favPlaceUuid = favoriteObj.get("place_uuid").getAsString();
if (favPlaceUuid.equals(placeUuid)) {
isFavorite = true;
break;
}
}
updateFavoriteIcon();
} else {
isFavorite = false;
updateFavoriteIcon();
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
isFavorite = false;
updateFavoriteIcon();
}
});
}
private void updateFavoriteIcon() {
if (isFavorite) {
ivFavorite.setImageResource(R.drawable.ic_favorite);
} else {
ivFavorite.setImageResource(R.drawable.ic_favorite_border);
}
}
private void addPlaceToFavorites(String userUuid, String placeUuid) {
DialogUtils.showLoadingDialog(context, getString(R.string.Adding_to_favorites));
JsonObject body = new JsonObject();
body.addProperty("place_uuid", placeUuid);
String token = "Bearer " + SharedPref.getAccessToken(context);
FavoriteService favoriteService = LocationApiClient.getInstance().getFavoriteService();
Call<JsonObject> call = favoriteService.restorePlaceToFavorites(token, userUuid, body);
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
DialogUtils.dismissDialog();
if (response.isSuccessful()) {
isFavorite = true;
updateFavoriteIcon();
Helper.showToast(context, getString(R.string.Place_added_to_favorites));
} else {
try {
if (response.errorBody() != null) {
String errorBody = response.errorBody().string();
// Try restoring if it's a duplicate (409 Conflict)
if (response.code() == 409 && errorBody.contains("ER_DUP_ENTRY")) {
restorePlaceToFavorites(userUuid, placeUuid);
return;
}
}
} catch (IOException e) {
Log.e(TAG, "ERROR: ", e);
}
Helper.showToast(context, getString(R.string.Failed_to_add_favorite_Server_error));
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
DialogUtils.dismissDialog();
Helper.showToast(context, getString(R.string.Network_error_Try_again));
}
});
}
private void restorePlaceToFavorites(String userUuid, String placeUuid) {
DialogUtils.showLoadingDialog(context, getString(R.string.Restoring_favorite));
JsonObject body = new JsonObject();
body.addProperty("place_uuid", placeUuid);
String token = "Bearer " + SharedPref.getAccessToken(context);
FavoriteService favoriteService = LocationApiClient.getInstance().getFavoriteService();
Call<JsonObject> call = favoriteService.restorePlaceToFavorites(token, userUuid, body);
call.enqueue(new Callback<JsonObject>() {
@Override
public void onResponse(Call<JsonObject> call, Response<JsonObject> response) {
DialogUtils.dismissDialog();
if (response.isSuccessful()) {
isFavorite = true;
updateFavoriteIcon();
Helper.showToast(context, getString(R.string.Place_restored_to_favorites));
} else {
Helper.showToast(context, getString(R.string.Failed_to_restore_favorite));
}
}
@Override
public void onFailure(Call<JsonObject> call, Throwable t) {
DialogUtils.dismissDialog();
Helper.showToast(context, getString(R.string.Network_error_Try_again));
}
});
}
private void removePlaceFromFavorites(String userUuid, String placeUuid) {
DialogUtils.showLoadingDialog(context, getString(R.string.Removing_from_favorites));
String token = "Bearer " + SharedPref.getAccessToken(context);
FavoriteService favoriteService = LocationApiClient.getInstance().getFavoriteService();
Call<Void> call = favoriteService.removePlaceFromFavorites(token, userUuid, placeUuid);
call.enqueue(new Callback<Void>() {
@Override
public void onResponse(Call<Void> call, Response<Void> response) {
DialogUtils.dismissDialog();
if (response.isSuccessful()) {
isFavorite = false;
updateFavoriteIcon();
Helper.showToast(context, getString(R.string.Removed_from_favorites));
} else {
Helper.showToast(context, getString(R.string.Failed_to_remove_from_favorites));
}
}
@Override
public void onFailure(Call<Void> call, Throwable t) {
DialogUtils.dismissDialog();
Helper.showToast(context, getString(R.string.Network_error_Try_again));
}
});
}
}

View File

@ -12,23 +12,29 @@ import android.location.Geocoder;
import android.location.Location; import android.location.Location;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import androidx.annotation.NonNull; import android.os.Looper;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import android.provider.Settings; import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import com.ieslamar.acloc.R; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import com.example.acloc.activity.AddNewPlaceActivity; import com.example.acloc.activity.AddNewPlaceActivity;
import com.example.acloc.activity.PlaceDetailActivity; import com.example.acloc.activity.PlaceDetailActivity;
import com.example.acloc.api.LocationApiClient; import com.example.acloc.api.LocationApiClient;
import com.example.acloc.dialog.PlaceBottomSheetDialog;
import com.example.acloc.model.Place; import com.example.acloc.model.Place;
import com.example.acloc.service.PlaceService; import com.example.acloc.service.PlaceService;
import com.example.acloc.utility.Constants; import com.example.acloc.utility.Constants;
@ -40,8 +46,10 @@ import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment; import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng; 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.MarkerOptions;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
import com.ieslamar.acloc.R;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -59,19 +67,26 @@ import retrofit2.Response;
public class MapFragment extends Fragment { public class MapFragment extends Fragment {
public static final String TAG = MapFragment.class.getSimpleName(); public static final String TAG = MapFragment.class.getSimpleName();
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001; private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
private static final float DEFAULT_ZOOM = 14f;
private static final float SEARCH_ZOOM = 16f;
private RelativeLayout rlMap; private RelativeLayout rlMap;
private View view; private View view;
private TextInputEditText etSearchLocation; private TextInputEditText etSearchLocation;
private ListView lvSuggestions;
private GoogleMap googleMap; private GoogleMap googleMap;
private FusedLocationProviderClient fusedLocationClient; private FusedLocationProviderClient fusedLocationClient;
private Geocoder geocoder; private Geocoder geocoder;
private Dialog dialog; private Dialog dialog;
private Context context; private Context context;
private Handler searchHandler = new Handler(Looper.getMainLooper());
private Runnable searchRunnable;
private List<String> suggestionsList = new ArrayList<>();
private ArrayAdapter<String> suggestionsAdapter;
private List<Address> addressResults = new ArrayList<>();
private List<Place> placeList = new ArrayList<>(); private List<Place> placeList = new ArrayList<>();
public MapFragment() { public MapFragment() {
} }
@ -94,10 +109,32 @@ public class MapFragment extends Fragment {
private void initUI() { private void initUI() {
rlMap = view.findViewById(R.id.rlMap); rlMap = view.findViewById(R.id.rlMap);
etSearchLocation = view.findViewById(R.id.etSearchLocation); etSearchLocation = view.findViewById(R.id.etSearchLocation);
// Initialize suggestions ListView
lvSuggestions = view.findViewById(R.id.lvSuggestions);
if (lvSuggestions == null) {
// If the ListView doesn't exist in the layout, create it programmatically
lvSuggestions = new ListView(getContext());
lvSuggestions.setId(View.generateViewId());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
params.addRule(RelativeLayout.BELOW, etSearchLocation.getId());
lvSuggestions.setLayoutParams(params);
lvSuggestions.setVisibility(View.GONE);
lvSuggestions.setBackgroundColor(getResources().getColor(android.R.color.white));
rlMap.addView(lvSuggestions);
}
} }
private void initObj() { private void initObj() {
context = getContext(); context = getContext();
// Initialize suggestions adapter
suggestionsAdapter = new ArrayAdapter<>(context,
android.R.layout.simple_list_item_1, suggestionsList);
lvSuggestions.setAdapter(suggestionsAdapter);
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -126,6 +163,7 @@ public class MapFragment extends Fragment {
String address = Helper.getStringFromInput(etSearchLocation); String address = Helper.getStringFromInput(etSearchLocation);
if (!address.isEmpty()) { if (!address.isEmpty()) {
searchLocationByAddress(address); searchLocationByAddress(address);
lvSuggestions.setVisibility(View.GONE);
} }
return true; return true;
} }
@ -134,16 +172,106 @@ public class MapFragment extends Fragment {
return false; return false;
}); });
// Add TextWatcher for search suggestions
etSearchLocation.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Cancel any pending searches
if (searchRunnable != null) {
searchHandler.removeCallbacks(searchRunnable);
}
// If text is empty, hide suggestions
if (s.length() == 0) {
lvSuggestions.setVisibility(View.GONE);
return;
}
// Delay search to avoid too many API calls while typing
searchRunnable = () -> getSuggestions(s.toString());
searchHandler.postDelayed(searchRunnable, 300);
}
@Override
public void afterTextChanged(Editable s) {
}
});
// Handle suggestion click
lvSuggestions.setOnItemClickListener((parent, view, position, id) -> {
String selectedAddress = suggestionsList.get(position);
etSearchLocation.setText(selectedAddress);
// Get the corresponding Address object
if (position < addressResults.size()) {
Address address = addressResults.get(position);
LatLng latLng = new LatLng(address.getLatitude(), address.getLongitude());
// Zoom to the selected location
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, SEARCH_ZOOM));
// Find the closest place
Place closestPlace = findClosestPlace(latLng);
if (closestPlace != null) {
// Show the place details in a bottom sheet
showPlaceBottomSheet(closestPlace);
}
}
lvSuggestions.setVisibility(View.GONE);
});
// trigger on Enter/Done key // trigger on Enter/Done key
etSearchLocation.setOnEditorActionListener((v, actionId, event) -> { etSearchLocation.setOnEditorActionListener((v, actionId, event) -> {
String address = Helper.getStringFromInput(etSearchLocation); String address = Helper.getStringFromInput(etSearchLocation);
if (!address.isEmpty()) { if (!address.isEmpty()) {
searchLocationByAddress(address); searchLocationByAddress(address);
lvSuggestions.setVisibility(View.GONE);
} }
return true; return true;
}); });
} }
private void getSuggestions(String query) {
if (query.length() < 3) {
lvSuggestions.setVisibility(View.GONE);
return;
}
try {
// Clear previous results
suggestionsList.clear();
addressResults.clear();
// Get suggestions from Geocoder
List<Address> addresses = geocoder.getFromLocationName(query, 5);
if (addresses != null && !addresses.isEmpty()) {
for (Address address : addresses) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
if (i > 0) sb.append(", ");
sb.append(address.getAddressLine(i));
}
String addressText = sb.toString();
suggestionsList.add(addressText);
addressResults.add(address);
}
suggestionsAdapter.notifyDataSetChanged();
lvSuggestions.setVisibility(View.VISIBLE);
} else {
lvSuggestions.setVisibility(View.GONE);
}
} catch (IOException e) {
Log.e(TAG, "Error getting suggestions", e);
lvSuggestions.setVisibility(View.GONE);
}
}
private void initMap() { private void initMap() {
fetchAndShowAllPlaces(); fetchAndShowAllPlaces();
@ -158,7 +286,7 @@ public class MapFragment extends Fragment {
fusedLocationClient.getLastLocation().addOnSuccessListener(location -> { fusedLocationClient.getLastLocation().addOnSuccessListener(location -> {
if (location != null) { if (location != null) {
LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude()); LatLng currentLatLng = new LatLng(location.getLatitude(), location.getLongitude());
googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, 14f)); googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(currentLatLng, DEFAULT_ZOOM));
showNearbyPlaces(currentLatLng); showNearbyPlaces(currentLatLng);
} }
}); });
@ -166,7 +294,7 @@ public class MapFragment extends Fragment {
googleMap.setOnMapClickListener(latLng -> { googleMap.setOnMapClickListener(latLng -> {
Place existingPlace = getPlaceIfExists(latLng); Place existingPlace = getPlaceIfExists(latLng);
if (existingPlace != null) { if (existingPlace != null) {
Helper.goTo(getContext(), PlaceDetailActivity.class, Constants.PLACE, existingPlace); //Navigate to PlaceDetailActivity if the place already exists showPlaceBottomSheet(existingPlace);
return; return;
} else { } else {
try { try {
@ -193,7 +321,6 @@ public class MapFragment extends Fragment {
placeEntity.setDescription(""); // Empty description placeEntity.setDescription(""); // Empty description
placeEntity.setUuid(null); // No UUID for a new place placeEntity.setUuid(null); // No UUID for a new place
Helper.goTo(getContext(), AddNewPlaceActivity.class, Constants.PLACE, placeEntity); Helper.goTo(getContext(), AddNewPlaceActivity.class, Constants.PLACE, placeEntity);
} }
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
@ -208,14 +335,12 @@ public class MapFragment extends Fragment {
Place existingPlace = getPlaceIfExists(latLng); Place existingPlace = getPlaceIfExists(latLng);
if (existingPlace != null) { if (existingPlace != null) {
Helper.goTo(getContext(), PlaceDetailActivity.class, Constants.PLACE, existingPlace); showPlaceBottomSheet(existingPlace);
return true; return true;
} }
return true; return true;
}); });
} else { } else {
requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
} }
@ -239,6 +364,39 @@ public class MapFragment extends Fragment {
return null; return null;
} }
private Place findClosestPlace(LatLng searchLatLng) {
if (placeList.isEmpty()) {
return null;
}
Place closestPlace = null;
float minDistance = Float.MAX_VALUE;
float[] result = new float[1];
for (Place place : placeList) {
double lat = Double.parseDouble(place.getLatitude());
double lng = Double.parseDouble(place.getLongitude());
Location.distanceBetween(
searchLatLng.latitude, searchLatLng.longitude,
lat, lng,
result);
if (result[0] < minDistance) {
minDistance = result[0];
closestPlace = place;
}
}
// Only return if within reasonable distance (1000 meters)
return minDistance < 1000 ? closestPlace : null;
}
private void showPlaceBottomSheet(Place place) {
PlaceBottomSheetDialog bottomSheet = new PlaceBottomSheetDialog(place);
bottomSheet.show(getChildFragmentManager(), "PlaceBottomSheet");
}
private void showNearbyPlaces(LatLng latLng) { private void showNearbyPlaces(LatLng latLng) {
googleMap.clear(); googleMap.clear();
} }
@ -249,8 +407,17 @@ public class MapFragment extends Fragment {
if (addresses != null && !addresses.isEmpty()) { if (addresses != null && !addresses.isEmpty()) {
Address location = addresses.get(0); Address location = addresses.get(0);
LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude()); LatLng latLng = new LatLng(location.getLatitude(), location.getLongitude());
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f)); googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, SEARCH_ZOOM));
showNearbyPlaces(latLng);
// Find the closest place to the search result
Place closestPlace = findClosestPlace(latLng);
if (closestPlace != null) {
// Show the place details in a bottom sheet
showPlaceBottomSheet(closestPlace);
} else {
// No close places found
Helper.makeSnackBar(rlMap, "No places found near this location");
}
} else { } else {
Helper.makeSnackBar(rlMap, "Location not found"); Helper.makeSnackBar(rlMap, "Location not found");
} }
@ -294,6 +461,7 @@ public class MapFragment extends Fragment {
private void fetchAndShowAllPlaces() { private void fetchAndShowAllPlaces() {
String token = "Bearer " + SharedPref.getAccessToken(context); // get saved token String token = "Bearer " + SharedPref.getAccessToken(context); // get saved token
// Using the new PlaceService through LocationApiClient
PlaceService placeService = LocationApiClient.getInstance().getPlaceService(); PlaceService placeService = LocationApiClient.getInstance().getPlaceService();
Call<ResponseBody> call = placeService.getAllPlaces(token); Call<ResponseBody> call = placeService.getAllPlaces(token);
@ -305,6 +473,9 @@ public class MapFragment extends Fragment {
JSONObject jsonObject = new JSONObject(response.body().string()); JSONObject jsonObject = new JSONObject(response.body().string());
JSONArray placesArray = jsonObject.getJSONObject("_data").getJSONArray("places"); JSONArray placesArray = jsonObject.getJSONObject("_data").getJSONArray("places");
// Clear existing places
placeList.clear();
for (int i = 0; i < placesArray.length(); i++) { for (int i = 0; i < placesArray.length(); i++) {
JSONObject placeObj = placesArray.getJSONObject(i); JSONObject placeObj = placesArray.getJSONObject(i);
String name = placeObj.getString("name"); String name = placeObj.getString("name");

View File

@ -3,9 +3,9 @@ package com.example.acloc.model;
import java.io.Serializable; import java.io.Serializable;
public class Report implements Serializable { public class Report implements Serializable {
String uuid, fkUser, fkPlace, fkReportType, description, createdBy; private String uuid, fkUser, fkPlace, fkReportType, description, created, createdBy;
String placeName, placeUuid; private String placeName, placeUuid;
int reportRating; private int reportRating;
public String getUuid() { public String getUuid() {
return uuid; return uuid;
@ -46,7 +46,9 @@ public class Report implements Serializable {
public void setDescription(String description) { public void setDescription(String description) {
this.description = description; this.description = description;
} }
public String getCreationDate(){
return created;
}
public String getCreatedBy() { public String getCreatedBy() {
return createdBy; return createdBy;
} }
@ -78,4 +80,7 @@ public class Report implements Serializable {
public void setPlaceUuid(String placeUuid) { public void setPlaceUuid(String placeUuid) {
this.placeUuid = placeUuid; this.placeUuid = placeUuid;
} }
public String getCreatedDate(){
return created;
}
} }

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/black"
android:pathData="M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/black"
android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6z"/>
</vector>

View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Header section -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tvPlaceName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_toStartOf="@+id/ivExpand"
android:textColor="@android:color/black"
android:textSize="20sp"
android:textStyle="bold"
tools:text="Place Name" />
<ImageView
android:id="@+id/ivExpand"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:src="@drawable/ic_expand_less"
android:contentDescription="@string/expand" />
</RelativeLayout>
<!-- Address and description -->
<TextView
android:id="@+id/tvAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"
tools:text="123 Main St, City, Country" />
<TextView
android:id="@+id/tvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="14sp"
tools:text="Description of the place" />
<!-- Action buttons -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<ImageView
android:id="@+id/ivFavorite"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_alignParentStart="true"
android:padding="6dp"
android:src="@drawable/ic_favorite_border"
android:contentDescription="@string/Favorite" />
<ImageView
android:id="@+id/ivEdit"
android:layout_width="36dp"
android:layout_height="36dp"
android:layout_marginStart="16dp"
android:layout_toEndOf="@id/ivFavorite"
android:padding="6dp"
android:src="@drawable/ic_edit"
android:contentDescription="@string/edit" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/btnAddReport"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_alignParentEnd="true"
android:background="@color/primaryColor_dark"
android:textColor="@android:color/white"
android:text="@string/Add_Report" />
</RelativeLayout>
<!-- Reports section -->
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="#DDDDDD" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:textStyle="bold"
android:text="@string/recent_reports" />
<TextView
android:id="@+id/tvNoData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="@string/no_reports_yet"
android:visibility="gone" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvReports"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -4,8 +4,7 @@
android:id="@+id/rlFavorite" android:id="@+id/rlFavorite"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize" android:layout_marginTop="?attr/actionBarSize">
tools:context=".fragment.FavoriteFragment">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFavorite" android:id="@+id/rvFavorite"

View File

@ -2,37 +2,53 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rlMap"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/rlMap" tools:context="com.example.acloc.fragments.MapFragment">
android:layout_marginTop="?attr/actionBarSize"
tools:context=".fragment.MapFragment">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilSearch"
android:padding="@dimen/content_padding"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:startIconDrawable="@drawable/ic_location">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etSearchLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableEnd="@drawable/ic_search"
android:hint="@string/Search_Place"
android:importantForAutofill="no"
android:imeOptions="actionSearch"
android:inputType="textPersonName"
android:drawableTint="?attr/colorOnBackground"/>
</com.google.android.material.textfield.TextInputLayout>
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
android:id="@+id/mapFrame" android:id="@+id/mapFrame"
android:name="com.google.android.gms.maps.SupportMapFragment" android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:layout_below="@+id/tilSearch" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/tilSearchLocation"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:elevation="4dp"
app:endIconMode="custom"
app:endIconDrawable="@android:drawable/ic_menu_search"
app:boxCornerRadiusBottomEnd="8dp"
app:boxCornerRadiusBottomStart="8dp"
app:boxCornerRadiusTopEnd="8dp"
app:boxCornerRadiusTopStart="8dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etSearchLocation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/Search_Place"
android:imeOptions="actionSearch"
android:background="@color/primaryVariant_dark"
android:inputType="text"
android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>
<!-- ListView for search suggestions -->
<ListView
android:id="@+id/lvSuggestions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/tilSearchLocation"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:background="@color/primaryVariant_dark"
android:elevation="4dp"
android:visibility="gone" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,91 +1,50 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="8dp"
app:cardElevation="2dp">
<com.google.android.material.card.MaterialCardView <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:cardCornerRadius="12dp" android:orientation="vertical"
app:cardElevation="4dp" android:padding="12dp">
app:cardUseCompatPadding="true"
app:strokeColor="?attr/colorPrimary"
app:strokeWidth="2dp"
tools:ignore="MissingConstraints">
<!--Report rating--> <TextView
<LinearLayout android:id="@+id/tvDescription"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:baselineAligned="false" android:ellipsize="end"
android:maxLines="3"
android:textSize="14sp"
tools:text="This is a report description that might be multiple lines long. It describes the accessibility features or issues at this location." />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal">
android:padding="@dimen/content_padding"
android:weightSum="2">
<LinearLayout <ImageView
android:layout_width="0dp" android:id="@+id/ivRating"
android:layout_height="wrap_content" android:layout_width="16dp"
android:layout_weight="0.6" android:layout_height="16dp"
android:gravity="center_horizontal" android:layout_marginEnd="4dp"
android:orientation="vertical"> android:src="@drawable/ic_thumbs_up"
app:tint="@color/primaryColor_dark" />
<ImageView <TextView
android:id="@+id/ivRating" android:id="@+id/tvRating"
android:layout_width="@dimen/small_image_icon_height"
android:layout_height="@dimen/small_image_icon_height"
android:contentDescription="rating"
android:src="@drawable/ic_thumbs_up" />
<TextView
android:id="@+id/tvRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/Rating_GOOD"
android:textStyle="bold"
android:gravity="center_horizontal"
android:textColor="?attr/colorOnBackground"
/>
</LinearLayout>
<!--Report description-->
<LinearLayout
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/small_padding" android:textSize="12sp"
android:layout_weight="1.5" android:textStyle="bold"
android:orientation="vertical"> tools:text="GOOD" />
<com.google.android.material.textview.MaterialTextView
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_padding"
android:text="@string/Report_Description"
android:textColor="?attr/colorPrimary"
android:textSize="@dimen/textSizeInEditText"
android:textStyle="bold"
app:startIconDrawable="@drawable/ic_location" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/tvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="false"
android:focusable="false"
android:hint="@string/Report_Description"
android:importantForAutofill="no"
android:inputType="textMultiLine"
android:text="@string/Report_Description"
android:textColor="@color/neutralGrey"
android:textSize="@dimen/textSizeSmall"
tools:ignore="HardcodedText" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView> </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>