package com.sanchez.fmf.fragment;
import android.Manifest;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.drawable.ColorDrawable;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatDialogFragment;
import android.support.v7.widget.AppCompatButton;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
import android.widget.Button;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.sanchez.fmf.MainActivity;
import com.sanchez.fmf.MarketDetailActivity;
import com.sanchez.fmf.MarketListActivity;
import com.sanchez.fmf.R;
import com.sanchez.fmf.adapter.FavoriteListAdapter;
import com.sanchez.fmf.adapter.PlaceAutocompleteAdapter;
import com.sanchez.fmf.application.FMFApplication;
import com.sanchez.fmf.event.FavoriteClickEvent;
import com.sanchez.fmf.event.FavoriteRemoveEvent;
import com.sanchez.fmf.event.PermissionResultEvent;
import com.sanchez.fmf.util.LocationUtil;
import com.sanchez.fmf.util.ViewUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import butterknife.Bind;
import butterknife.ButterKnife;
import de.greenrobot.event.EventBus;
public class MainFragment extends Fragment implements GoogleApiClient.OnConnectionFailedListener {
public String TAG = MainFragment.class.getSimpleName();
@Bind(R.id.search_autocomplete)
AutoCompleteTextView mSearchAutocomplete;
@Bind(R.id.search_icon)
View mSearchIcon;
@Bind(R.id.clear_icon)
View mClearSearch;
@Bind(R.id.use_location_button)
Button mUseLocationButton;
@Bind(R.id.market_favorites_list)
RecyclerView mFavoritesList;
@Bind(R.id.no_favorites_text)
View mNoFavorites;
// parent of all visible UI in main fragment
View contentView;
final public static int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
private static final int GOOGLE_API_CLIENT_ID = 0;
private static final LatLngBounds BOUNDS_NORTH_AMERICA = new LatLngBounds(new LatLng(18.000000,
-64.000000), new LatLng(67.000000, -165.000000));
private GoogleApiClient mGoogleApiClient = null;
private PlaceAutocompleteAdapter mAutocompleteAdapter = null;
private String mSelectedPlace = null;
private String mSelectedPlaceId = null;
private boolean mGettingPermission = false;
private Snackbar mFetchingSnackbar;
private Runnable delayedShowFetching = this::showFetching;
private Runnable delayedCancelShowFetching = this::cancelShowFetching;
public abstract class OnGetCoordinatesFromLocationListener {
public abstract void onFinished(ArrayList<Double> results);
}
public static MainFragment newInstance() {
return new MainFragment();
}
public MainFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// register client for Google APIs
mGoogleApiClient = new GoogleApiClient
.Builder(getActivity())
.addApi(Places.GEO_DATA_API)
.enableAutoManage(getActivity(), GOOGLE_API_CLIENT_ID, this)
.build();
}
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
@Override
public void onResume() {
super.onResume();
if(!mGettingPermission) {
cancelShowFetching();
}
updateFavoritesList(null);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.bind(this, rootView);
contentView = getActivity().findViewById(android.R.id.content);
// linear RecyclerView
RecyclerView.LayoutManager linearLM = new LinearLayoutManager(getContext());
mFavoritesList.setLayoutManager(linearLM);
mFavoritesList.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
mFavoritesList.setAdapter(new FavoriteListAdapter(new LinkedHashMap<>()));
// customize place autocomplete adapter
mAutocompleteAdapter = new PlaceAutocompleteAdapter(getActivity(),
android.R.layout.simple_list_item_1, mGoogleApiClient, BOUNDS_NORTH_AMERICA, null);
mSearchAutocomplete.setAdapter(mAutocompleteAdapter);
// customize autocomplete
mSearchAutocomplete.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
mSearchAutocomplete.setDropDownVerticalOffset(8); // just below search box
mSearchAutocomplete.setDropDownBackgroundDrawable(new ColorDrawable(getResources()
.getColor(R.color.pure_white)));
mSearchAutocomplete.setDropDownAnchor(R.id.card_search);
// remember place ID of selected dropdown item
mSearchAutocomplete.setOnItemClickListener(mAutocompleteClickListener);
// search when search IME option pressed
mSearchAutocomplete.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mSearchAutocomplete.setEnabled(false);
mUseLocationButton.setEnabled(false);
String searchText = mSearchAutocomplete.getText().toString();
String[] tokens = searchText.split(",");
mSelectedPlace = tokens[0];
getCoordinatesFromLocation(searchText, new OnGetCoordinatesFromLocationListener() {
@Override
public void onFinished(ArrayList<Double> results) {
//TODO: checkout the coords to make sure they're valid
launchMarketList(new double[] { results.get(0), results.get(1) }, false, mSelectedPlace);
}
});
ViewUtils.hideKeyboard(getActivity());
mSearchAutocomplete.dismissDropDown();
contentView.postDelayed(delayedShowFetching, 300);
// As a fail safe if something errors out
contentView.postDelayed(delayedCancelShowFetching, 7000);
return true;
}
return false;
});
// show search 'clear' icon when text is present
mSearchAutocomplete.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.length() > 0) {
if (mClearSearch.getVisibility() == View.GONE) {
mClearSearch.setVisibility(View.VISIBLE);
}
} else {
mClearSearch.setVisibility(View.GONE);
}
}
});
// icon press triggers search also
mSearchIcon.setOnClickListener((v) -> {
mSearchAutocomplete.requestFocus();
ViewUtils.showKeyboard(getActivity(), mSearchAutocomplete);
});
mClearSearch.setOnClickListener((v) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mSearchAutocomplete.setText("", false);
} else {
mSearchAutocomplete.setText("");
}
});
// set backgroundTint programmatically as xml method is undefined...
int primaryColor = getResources().getColor(R.color.primary);
ColorStateList cSL = new ColorStateList(new int[][]{new int[0]}, new int[]{primaryColor});
((AppCompatButton)mUseLocationButton).setSupportBackgroundTintList(cSL);
mUseLocationButton.setOnClickListener((v) -> getLocationWrapper());
// no keyboard popup on launch
removeFocusFromAll();
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
// wrap permission requester around location fetching (required for location access in API 23+)
private void getLocationWrapper() {
mSearchAutocomplete.setEnabled(false);
mUseLocationButton.setEnabled(false);
List<String> permissionsNeeded = new ArrayList<>();
final List<String> permissionsList = new ArrayList<>();
if (!addPermission(permissionsList, Manifest.permission.ACCESS_COARSE_LOCATION)) {
permissionsNeeded.add("Access coarse location");
}
if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION)) {
permissionsNeeded.add("Access fine location");
}
if (permissionsList.size() > 0) {
mGettingPermission = true;
if (permissionsNeeded.size() > 0) {
showPermissionRationale(getString(R.string.location_permission_rationale));
return;
}
ActivityCompat.requestPermissions(
getActivity(),
new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION},
MainFragment.REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
getLocationAndLaunchList();
}
private void showPermissionRationale(String message) {
RationaleDialogFragment frag = RationaleDialogFragment.newInstance(message);
frag.show(getActivity().getSupportFragmentManager(), "dialog2");
}
private boolean addPermission(List<String> permissionList, String permission) {
if (ContextCompat.checkSelfPermission(getActivity(), permission) != PackageManager.PERMISSION_GRANTED) {
permissionList.add(permission);
if (shouldShowRequestPermissionRationale(permission)) {
return false;
}
}
return true;
}
private void getLocationAndLaunchList() {
boolean locEnabled = new LocationUtil().getLocation(getContext(),
new LocationUtil.LocationResult() {
@Override
public void gotLocation(Location location) {
getActivity().runOnUiThread(() -> {
if (location == null) {
Snackbar.make(contentView, R.string.location_error,
Snackbar.LENGTH_LONG).show();
} else {
double[] coords = {location.getLatitude(), location.getLongitude()};
launchMarketList(coords, true, null);
}
});
}
});
if (locEnabled) {
contentView.postDelayed(delayedShowFetching, 300);
// As a fail safe if something errors out
contentView.postDelayed(delayedCancelShowFetching, 7000);
} else {
final Snackbar s = Snackbar.make(contentView,
R.string.enable_location_prompt,
Snackbar.LENGTH_INDEFINITE);
s.setAction(R.string.enable, (view) -> {
s.dismiss();
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
});
s.setActionTextColor(getResources().getColor(R.color.pure_white));
s.show();
}
}
private void showFetching() {
mFetchingSnackbar = Snackbar.make(contentView, R.string.fetching_location, Snackbar.LENGTH_INDEFINITE);
mFetchingSnackbar.show();
}
private void cancelShowFetching() {
contentView.removeCallbacks(delayedShowFetching);
if(mFetchingSnackbar != null) {
mFetchingSnackbar.dismiss();
}
mSearchAutocomplete.setEnabled(true);
mUseLocationButton.setEnabled(true);
}
private void updateFavoritesList(LinkedHashMap<String, String> favorites) {
// check to see if we got a parameter
if(null == favorites) {
favorites = FMFApplication
.getGlobalPreferences()
.getFavoriteMarkets();
}
// see if the persistent data lookup returned anything
if (null == favorites) {
mNoFavorites.setVisibility(View.VISIBLE);
} else {
((FavoriteListAdapter)mFavoritesList.getAdapter()).replaceData(favorites);
if(favorites.size() > 0) {
mNoFavorites.setVisibility(View.GONE);
} else {
mNoFavorites.setVisibility(View.VISIBLE);
}
}
}
private void launchMarketList(double[] coords, boolean usedDeviceCoordinates, String placeTitle) {
mGettingPermission = false;
// start market list activity with coordinates from search
Intent i = new Intent(getActivity(), MarketListActivity.class);
i.putExtra(MarketListActivity.EXTRA_COORDINATES, coords);
i.putExtra(MarketListActivity.EXTRA_PLACE_TITLE, placeTitle);
i.putExtra(MarketListActivity.EXTRA_PLACE_ID, mSelectedPlaceId);
i.putExtra(MarketListActivity.EXTRA_USED_DEVICE_COORDINATES, usedDeviceCoordinates);
startActivity(i);
contentView.removeCallbacks(delayedShowFetching);
contentView.removeCallbacks(delayedCancelShowFetching);
if(mFetchingSnackbar != null) {
mFetchingSnackbar.dismiss();
}
}
private void removeFocusFromAll() {
contentView.requestFocus();
}
private AdapterView.OnItemClickListener mAutocompleteClickListener
= new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// retrieve the place ID of the selected item from the Adapter.
PlaceAutocompleteAdapter.PlaceAutocomplete item = mAutocompleteAdapter.getItem(position);
mSelectedPlaceId = String.valueOf(item.placeId);
/*
Issue a request to the Places Geo Data API to retrieve a Place object with additional
details about the place.
*/
// PendingResult<PlaceBuffer> placeResult = Places.GeoDataApi
// .getPlaceById(mGoogleApiClient, placeId);
// placeResult.setResultCallback(mUpdatePlaceDetailsCallback);
//
//
// Log.i(TAG, "Called getPlaceById to get Place details for " + item.placeId);
}
};
/**
* Callback for results from a Places Geo Data API query that shows the first place result in
* the details view on screen.
*/
// to display attributions and extra place info
// private ResultCallback<PlaceBuffer> mUpdatePlaceDetailsCallback
// = new ResultCallback<PlaceBuffer>() {
// @Override
// public void onResult(PlaceBuffer places) {
// if (!places.getStatus().isSuccess()) {
// // Request did not complete successfully
// Log.e(TAG, "Place query did not complete. Error: " + places.getStatus().toString());
// places.release();
// return;
// }
// // Get the Place object from the buffer.
// final Place place = places.get(0);
//
// // Format details of the place for display and show it in a TextView.
// mPlaceDetailsText.setText(formatPlaceDetails(getResources(), place.getName(),
// place.getId(), place.getAddress(), place.getPhoneNumber(),
// place.getWebsiteUri()));
//
// // Display the third party attributions if set.
// final CharSequence thirdPartyAttribution = places.getAttributions();
// if (thirdPartyAttribution == null) {
// mPlaceDetailsAttribution.setVisibility(View.GONE);
// } else {
// mPlaceDetailsAttribution.setVisibility(View.VISIBLE);
// mPlaceDetailsAttribution.setText(Html.fromHtml(thirdPartyAttribution.toString()));
// }
//
// Log.i(TAG, "Place details received: " + place.getName());
//
// places.release();
// }
// };
//
// private Spanned formatPlaceDetails(Resources res, CharSequence name, String id,
// CharSequence address, CharSequence phoneNumber, Uri websiteUri) {
// Log.e(TAG, res.getString(R.string.place_details, name, id, address, phoneNumber,
// websiteUri));
// return Html.fromHtml(res.getString(R.string.place_details, name, id, address, phoneNumber,
// websiteUri));
// }
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
Log.e(TAG, "Google Places API connection failed with error code: "
+ connectionResult.getErrorCode());
}
public void getCoordinatesFromLocation(String location,
final OnGetCoordinatesFromLocationListener listener) {
new AsyncTask<Void, Void, ArrayList<Double>>() {
@Override
protected ArrayList<Double> doInBackground(Void... arg0) {
ArrayList<Double> returnList = new ArrayList<Double>();
try {
Geocoder geocoder = new Geocoder(getActivity(), Locale.getDefault());
List<Address> addresses = geocoder.getFromLocationName(location, 1);
if (addresses.size() > 0) {
Address address = addresses.get(0);
returnList.add(address.getLatitude());
returnList.add(address.getLongitude());
return returnList;
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
return null;
}
return returnList;
}
@Override
protected void onPostExecute(ArrayList<Double> results) {
if(null == results) {
Snackbar.make(contentView, "Network error", Snackbar.LENGTH_LONG).show();
cancelShowFetching();
} else if (results.size() > 0) {
listener.onFinished(results);
} else {
Snackbar.make(contentView, "Invalid input", Snackbar.LENGTH_LONG).show();
cancelShowFetching();
}
}
}.execute();
}
public static class RationaleDialogFragment extends AppCompatDialogFragment {
public static final String EXTRA_MESSAGE = "com.sanchez.fmf.extra_message";
public static RationaleDialogFragment newInstance(String message) {
RationaleDialogFragment frag = new RationaleDialogFragment();
Bundle args = new Bundle();
args.putString(EXTRA_MESSAGE, message);
frag.setArguments(args);
return frag;
}
@Override
public Dialog onCreateDialog(Bundle onSaveInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(getArguments().getString(EXTRA_MESSAGE))
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(
getActivity(),
new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION},
MainFragment.REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EventBus.getDefault().post(new PermissionResultEvent(false));
}
})
.create();
}
}
public void onEvent(FavoriteClickEvent event) {
Intent i = new Intent(getActivity(), MarketDetailActivity.class);
i.putExtra(MarketDetailActivity.EXTRA_MARKET_ID, event.getId());
i.putExtra(MarketDetailActivity.EXTRA_MARKET_NAME, event.getName());
startActivity(i);
}
public void onEvent(FavoriteRemoveEvent event) {
LinkedHashMap<String, String> favoritesMap =
FMFApplication.getGlobalPreferences().getFavoriteMarkets();
favoritesMap.remove(event.getId());
FMFApplication.getGlobalPreferences().setFavoriteMarkets(favoritesMap);
updateFavoritesList(favoritesMap);
}
public void onEvent(PermissionResultEvent event) {
if (event.getGranted()) {
getLocationAndLaunchList();
} else {
mSearchAutocomplete.setEnabled(true);
mUseLocationButton.setEnabled(true);
Snackbar.make(contentView, "Location permission denied", Snackbar.LENGTH_LONG).show();
}
}
}