/** * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. * * You are hereby granted a non-exclusive, worldwide, royalty-free license to use, * copy, modify, and distribute this software in source code or binary form for use * in connection with the web services and APIs provided by Facebook. * * As with any software that integrates with the Facebook platform, your use of * this software is subject to the Facebook Developer Principles and Policies * [http://developers.facebook.com/policy/]. This copyright notice shall be * included in all copies or substantial portions of the software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.example.places.fragments; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.ViewCompat; import android.support.v7.widget.CardView; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.example.places.R; import com.example.places.adapters.PlaceListAdapter; import com.example.places.model.Place; import com.example.places.PlacesGraphSDKHelper; import com.example.places.model.PlaceTextUtils; import com.facebook.GraphResponse; import com.facebook.places.PlaceManager; import com.google.android.gms.maps.CameraUpdate; 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.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 java.util.ArrayList; import java.util.List; /** * This fragment illustrates how to use the Places Graph SDK to: * <ul> * <li>Search for nearby places.</li> * <li>Display places on a map.</li> * </ul> */ public class PlaceSearchFragment extends Fragment implements PlacesGraphSDKHelper.PlaceSearchRequestListener, PlaceListAdapter.Listener, OnMapReadyCallback { private static final int SCROLL_DIRECTION_UP = -1; private static final int REQUEST_CODE_GET_CURRENT_PLACE = 1; private Listener listener; private ProgressBar progressBar; private RecyclerView recyclerView; private PlaceListAdapter placeListAdapter; private TextView currentPlaceNameTextView; private TextView currentPlaceAddressTextView; private CardView searchCardView; private CardView currentPlaceCardView; private EditText searchEditText; private FloatingActionButton actionButton; private SupportMapFragment mapFragment; private GoogleMap map; private State state = State.LIST; private List<Place> placesToDisplay = new ArrayList<>(0); public enum State { LIST, MAP }; public interface Listener { void onPlaceSelected(Place place); void onLocationPermissionsError(); boolean hasLocationPermission(); } public static PlaceSearchFragment newInstance() { return new PlaceSearchFragment(); } public void onAttach(Context context) { super.onAttach(context); if (context instanceof Activity) { listener = (Listener) context; } } @Override public void onDetach() { super.onDetach(); } public View onCreateView( LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.place_search_fragment, container, false); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar = (ProgressBar) view.findViewById(R.id.place_search_progressbar); recyclerView = (RecyclerView) view.findViewById(R.id.place_search_recyclerview); recyclerView.setLayoutManager(new LinearLayoutManager(getActivity())); actionButton = (FloatingActionButton) view.findViewById(R.id.place_search_toggle_button); actionButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onActionButtonClicked(); } }); currentPlaceCardView = (CardView) view.findViewById(R.id.current_place_cardview); currentPlaceCardView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { openCurrentPlaceDialog(); } }); currentPlaceNameTextView = (TextView) view.findViewById(R.id.current_place_name); currentPlaceAddressTextView = (TextView) view.findViewById(R.id.current_place_address); searchCardView = (CardView) view.findViewById(R.id.place_search_cardview); searchEditText = (EditText) view.findViewById(R.id.place_search_edittext); searchEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // When the search soft input key is clicked, // hide soft input and search nearby places if (actionId == EditorInfo.IME_ACTION_SEARCH) { InputMethodManager imm = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), 0); searchPlace(searchEditText.getText().toString()); } return true; } }); RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() { @Override public void getItemOffsets( Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (parent.getChildAdapterPosition(view) == 0) { outRect.top = getResources() .getDimensionPixelOffset(R.dimen.place_search_list_header_height); } } }; recyclerView.addItemDecoration(itemDecoration); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); float elevation; if (recyclerView.canScrollVertically(SCROLL_DIRECTION_UP)) { elevation = getResources().getDimension(R.dimen.search_scrolling_elevation); } else { elevation = getResources().getDimension(R.dimen.search_resting_elevation); } ViewCompat.setElevation(currentPlaceCardView, elevation); ViewCompat.setElevation(searchCardView, elevation); } }); } public void onDestroyView() { recyclerView = null; searchEditText = null; searchCardView = null; actionButton = null; progressBar = null; mapFragment = null; map = null; super.onDestroyView(); } private void setLoading(boolean isLoading) { progressBar.setVisibility(isLoading ? View.VISIBLE : View.INVISIBLE); } private void searchPlace(String searchQuery) { setLoading(true); PlacesGraphSDKHelper.searchPlace(searchQuery, this); } private void onActionButtonClicked() { toggleMapAndList(); // Hide soft input InputMethodManager imm = (InputMethodManager) getActivity() .getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(searchEditText.getWindowToken(), 0); } private void toggleMapAndList() { state = state == State.MAP ? State.LIST : State.MAP; if (state == State.LIST) { if (mapFragment != null) { FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.hide(mapFragment).commit(); } recyclerView.setVisibility(View.VISIBLE); currentPlaceCardView.setVisibility(View.VISIBLE); } if (state == State.MAP) { if (mapFragment == null) { mapFragment = SupportMapFragment.newInstance(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); transaction.replace(R.id.place_search_map_placeholder, mapFragment); transaction.commit(); mapFragment.getMapAsync(this); } else { FragmentTransaction ft = getChildFragmentManager().beginTransaction(); ft.show(mapFragment).commit(); } recyclerView.setVisibility(View.INVISIBLE); currentPlaceCardView.setVisibility(View.INVISIBLE); } displayPlaces(placesToDisplay); } private void openCurrentPlaceDialog() { CurrentPlaceDialogFragment currentPlaceDialogFragment = CurrentPlaceDialogFragment.newInstance(); FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); Fragment prev = getFragmentManager().findFragmentByTag("dialog"); if (prev != null) { transaction.remove(prev); } transaction.addToBackStack(null); currentPlaceDialogFragment.setTargetFragment(this, REQUEST_CODE_GET_CURRENT_PLACE); currentPlaceDialogFragment.show(transaction, "dialog"); } @Override public void onPlaceSearchResult(final List<Place> places, GraphResponse response) { if (isAdded()) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { setLoading(false); if (places == null) { // The response object does contain more information on the error Toast.makeText(getActivity(), R.string.error, Toast.LENGTH_SHORT).show(); } else { placesToDisplay = places; displayPlaces(placesToDisplay); } } }); } } @Override public void onLocationError(final PlaceManager.LocationError error) { // This event is invoked when the Places Graph SDK fails to retrieve the device location. if (isAdded()) { getActivity().runOnUiThread(new Runnable() { @Override public void run() { if (error == PlaceManager.LocationError.LOCATION_PERMISSION_DENIED) { // Trigger the activity to prompt the user for location permissions. listener.onLocationPermissionsError(); } else if (error == PlaceManager.LocationError.LOCATION_SERVICES_DISABLED) { String message = getString(R.string.location_error_disabled); Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } else { String message = getString(R.string.location_error_unknown); Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show(); } } }); } } private void displayPlaces(List<Place> places) { if (state == State.LIST) { placeListAdapter = new PlaceListAdapter(R.layout.place_list_item, places, this); recyclerView.setAdapter(placeListAdapter); placeListAdapter.notifyDataSetChanged(); } else if (state == State.MAP) { displayPlacesOnMap(places); } } private void displayPlacesOnMap(List<Place> places) { if (map != null) { map.clear(); if (!places.isEmpty()) { LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); for (Place place : places) { // Creates a marker at the place location and with the place name LatLng position = place.getPosition(); String placeName = place.get(Place.NAME); if (position != null) { MarkerOptions markerOptions = new MarkerOptions() .position(position) .title(placeName); Marker marker = map.addMarker(markerOptions); marker.setTag(place); boundsBuilder.include(position); } } CameraUpdate cameraUpdate = CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 100); map.moveCamera(cameraUpdate); } } } @Override @SuppressWarnings("MissingPermission") public void onMapReady(GoogleMap googleMap) { map = googleMap; map.getUiSettings().setMapToolbarEnabled(false); if (listener.hasLocationPermission()) { map.setMyLocationEnabled(true); map.getUiSettings().setMyLocationButtonEnabled(false); } map.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() { @Override public void onInfoWindowClick(Marker marker) { if (marker.getTag() instanceof Place) { Place place = (Place) marker.getTag(); listener.onPlaceSelected(place); } } }); if (state == State.MAP) { displayPlaces(placesToDisplay); } } @Override public void onPlaceSelected(Place place) { listener.onPlaceSelected(place); } private void onCurrentPlaceSelected(Place place) { currentPlaceNameTextView.setText(place.get(Place.NAME)); currentPlaceNameTextView.setVisibility(View.VISIBLE); currentPlaceAddressTextView.setText(PlaceTextUtils.getAddress(place)); currentPlaceAddressTextView.setVisibility(View.VISIBLE); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_GET_CURRENT_PLACE) { if (resultCode == Activity.RESULT_OK) { Place place = (Place) data.getParcelableExtra( CurrentPlaceDialogFragment.EXTRA_CURRENT_PLACE); onCurrentPlaceSelected(place); } } } }