Java tutorial
package com.ludoscity.findmybikes.activities; import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; import android.location.Location; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CoordinatorLayout; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.util.Pair; import android.support.v4.view.ViewPager; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatDelegate; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.Toolbar; import android.support.v7.widget.helper.ItemTouchHelper; import android.text.format.DateUtils; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; import com.github.amlcurran.showcaseview.ShowcaseView; import com.github.amlcurran.showcaseview.targets.ViewTarget; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.common.api.Status; import com.google.android.gms.location.LocationListener; import com.google.android.gms.location.LocationRequest; import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.LocationSettingsRequest; import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStatusCodes; import com.google.android.gms.location.places.Place; import com.google.android.gms.location.places.ui.PlaceAutocomplete; import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.MapsInitializer; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.maps.model.LatLngBounds; import com.gordonwong.materialsheetfab.MaterialSheetFabEventListener; import com.ludoscity.findmybikes.EditableMaterialSheetFab; import com.ludoscity.findmybikes.Fab; import com.ludoscity.findmybikes.FavoriteItemBase; import com.ludoscity.findmybikes.FavoriteItemPlace; import com.ludoscity.findmybikes.FavoriteItemStation; import com.ludoscity.findmybikes.FavoriteRecyclerViewAdapter; import com.ludoscity.findmybikes.ItemTouchHelperAdapter; import com.ludoscity.findmybikes.R; import com.ludoscity.findmybikes.RootApplication; import com.ludoscity.findmybikes.StationItem; import com.ludoscity.findmybikes.StationListPagerAdapter; import com.ludoscity.findmybikes.StationRecyclerViewAdapter; import com.ludoscity.findmybikes.citybik_es.Citybik_esAPI; import com.ludoscity.findmybikes.citybik_es.model.ListNetworksAnswerRoot; import com.ludoscity.findmybikes.citybik_es.model.NetworkDesc; import com.ludoscity.findmybikes.citybik_es.model.NetworkStatusAnswerRoot; import com.ludoscity.findmybikes.fragments.StationListFragment; import com.ludoscity.findmybikes.fragments.StationMapFragment; import com.ludoscity.findmybikes.helpers.DBHelper; import com.ludoscity.findmybikes.utils.DividerItemDecoration; import com.ludoscity.findmybikes.utils.ScrollingLinearLayoutManager; import com.ludoscity.findmybikes.utils.Utils; import java.io.IOException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; import de.psdev.licensesdialog.LicensesDialog; import retrofit2.Call; import retrofit2.Response; import twitter4j.GeoLocation; import twitter4j.StatusUpdate; import twitter4j.Twitter; import twitter4j.TwitterException; /** * Created by F8Full on 2015-07-26. * Activity used to display the nearby section */ public class NearbyActivity extends AppCompatActivity implements StationMapFragment.OnStationMapFragmentInteractionListener, StationListFragment.OnStationListFragmentInteractionListener, FavoriteRecyclerViewAdapter.OnFavoriteListItemClickListener, //TODO: investigate making the sheet listening and forwarding FavoriteRecyclerViewAdapter.OnFavoriteListItemStartDragListener, //TODO: investigate making the sheet listening and forwarding EditableMaterialSheetFab.OnFavoriteSheetEventListener, SwipeRefreshLayout.OnRefreshListener, ViewPager.OnPageChangeListener, GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener { private StationMapFragment mStationMapFragment = null; private Handler mUpdateRefreshHandler = null; private Runnable mUpdateRefreshRunnableCode = null; private Interpolator mCircularRevealInterpolator; private DownloadWebTask mDownloadWebTask = null; private RedrawMarkersTask mRedrawMarkersTask = null; private FindNetworkTask mFindNetworkTask = null; private UpdateTwitterStatusTask mUpdateTwitterTask = null; private SaveNetworkToDatabaseTask mSaveNetworkToDatabaseTask = null; private LatLng mCurrentUserLatLng = null; private TextView mStatusTextView; private View mStatusBar; private ViewPager mStationListViewPager; private TabLayout mTabLayout; private AppBarLayout mAppBarLayout; private CoordinatorLayout mCoordinatorLayout; private ProgressBar mPlaceAutocompleteLoadingProgressBar; private View mTripDetailsWidget; private TextView mTripDetailsProximityA; private TextView mTripDetailsProximityB; private TextView mTripDetailsProximitySearch; private TextView mTripDetailsProximityTotal; private FrameLayout mTripDetailsSumSeparator; private View mTripDetailsBToDestinationRow; private View mTripDetailsPinSearch; private View mTripDetailsPinFavorite; private View mSplashScreen; private TextView mSplashScreenTextTop; private TextView mSplashScreenTextBottom; private ItemTouchHelper mFavoriteItemTouchHelper; private static final int PLACE_AUTOCOMPLETE_REQUEST_CODE = 1; private static final int SETTINGS_ACTIVITY_REQUEST_CODE = 2; private static final int CHECK_GPS_REQUEST_CODE = 3; private static final int CHECK_PERMISSION_REQUEST_CODE = 4; private FloatingActionButton mDirectionsLocToAFab; private FloatingActionButton mSearchFAB; private FloatingActionButton mAddFavoriteFAB; private EditableMaterialSheetFab mFavoritesSheetFab; private boolean mFavoriteSheetVisible = false; private FloatingActionButton mClearFAB; private Fab mFavoritePickerFAB; private FloatingActionButton mAutoSelectBikeFab; private FavoriteRecyclerViewAdapter mFavoriteRecyclerViewAdapter; private boolean mRefreshMarkers = true; private boolean mRefreshTabs = true; private CameraPosition mSavedInstanceCameraPosition; private static final int[] TABS_ICON_RES_ID = new int[] { R.drawable.ic_pin_a_36dp_white, R.drawable.ic_pin_b_36dp_white }; private GoogleApiClient mGoogleApiClient; private LocationRequest mLocationRequest; private boolean mRequestingLocationUpdates = false; private boolean mClosestBikeAutoSelected = false; private boolean mFavoriteItemEditInProgress = false; private Snackbar mFindBikesSnackbar; private ShowcaseView mOnboardingShowcaseView = null; private Snackbar mOnboardingSnackBar = null; //Used to display hints //Places favorites stressed the previous design //TODO: explore refactoring with the following considerations //-stop relying on mapfragment markers visibility to branch code private boolean mFavoritePicked = false; //True from the moment a favorite is picked until it's cleared //also set to true when a place is converted to a favorite //-consider moving it to some central location (pager adapter also has a copy) private boolean mDataOutdated = false; @Override public void onStart() { mGoogleApiClient.connect(); checkAndAskLocationPermission(); super.onStart(); } @Override public void onStop() { mGoogleApiClient.disconnect(); super.onStop(); } @Override public void onResume() { super.onResume(); if (mGoogleApiClient.isConnected() && !mRequestingLocationUpdates) { startLocationUpdates(); } mUpdateRefreshHandler = new Handler(); mUpdateRefreshRunnableCode = createUpdateRefreshRunnableCode(); mUpdateRefreshHandler.post(mUpdateRefreshRunnableCode); } private void checkAndAskLocationPermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { mSplashScreenTextTop.setText(R.string.location_please); mSplashScreenTextBottom.setText(""); ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, 0); } } private void startLocationUpdates() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) { LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this); mRequestingLocationUpdates = true; } } @Override public void onPause() { super.onPause(); cancelDownloadWebTask(); stopUIRefresh(); if (mGoogleApiClient.isConnected()) { LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this); mRequestingLocationUpdates = false; } } @Override protected void onCreate(Bundle savedInstanceState) { //So that Utils::getBitmapDescriptor works on API < 21 //when doing Drawable vectorDrawable = ResourcesCompat.getDrawable(ctx.getResources(), id, null); //see https://medium.com/@chrisbanes/appcompat-v23-2-age-of-the-vectors-91cbafa87c88#.i8luinewc AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); setTheme(R.style.FindMyBikesTheme); //https://developer.android.com/topic/performance/launch-time.html super.onCreate(savedInstanceState); int mapInit = MapsInitializer.initialize(getApplicationContext()); if (mapInit != 0) { Log.e("NearbyActivity", "GooglePlayServicesNotAvailableException raised with error code :" + mapInit); } boolean autoCompleteLoadingProgressBarVisible = false; String showcaseTripTotalPlaceName = null; if (savedInstanceState != null) { mSavedInstanceCameraPosition = savedInstanceState.getParcelable("saved_camera_pos"); mRequestingLocationUpdates = savedInstanceState.getBoolean("requesting_location_updates"); mCurrentUserLatLng = savedInstanceState.getParcelable("user_location_latlng"); mClosestBikeAutoSelected = savedInstanceState.getBoolean("closest_bike_auto_selected"); mFavoriteSheetVisible = savedInstanceState.getBoolean("favorite_sheet_visible"); autoCompleteLoadingProgressBarVisible = savedInstanceState.getBoolean("place_autocomplete_loading"); mRefreshTabs = savedInstanceState.getBoolean("refresh_tabs"); mFavoritePicked = savedInstanceState.getBoolean("favorite_picked"); mDataOutdated = savedInstanceState.getBoolean("data_outdated"); showcaseTripTotalPlaceName = savedInstanceState.getString("onboarding_showcase_trip_total_place_name", null); } setContentView(R.layout.activity_nearby); setSupportActionBar((Toolbar) findViewById(R.id.toolbar_main)); setupActionBarStrings(); // Update Bar mStatusTextView = (TextView) findViewById(R.id.status_textView); mStatusBar = findViewById(R.id.app_status_bar); if (mDataOutdated) mStatusBar.setBackgroundColor(ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); mStationListViewPager = (ViewPager) findViewById(R.id.station_list_viewpager); mStationListViewPager.setAdapter(new StationListPagerAdapter(getSupportFragmentManager())); mStationListViewPager.addOnPageChangeListener(this); // Give the TabLayout the ViewPager mTabLayout = (TabLayout) findViewById(R.id.sliding_tabs); mTabLayout.setupWithViewPager(mStationListViewPager); //Taking care of tabs icons here as pageradapter handles only title CharSequence for now for (int i = 0; i < mTabLayout.getTabCount() && i < TABS_ICON_RES_ID.length; ++i) { //noinspection ConstantConditions mTabLayout.getTabAt(i).setCustomView(R.layout.tab_custom_view); //noinspection ConstantConditions mTabLayout.getTabAt(i).setIcon(ContextCompat.getDrawable(this, TABS_ICON_RES_ID[i])); } mAppBarLayout = (AppBarLayout) findViewById(R.id.action_toolbar_layout); mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.snackbar_coordinator); mSplashScreen = findViewById(R.id.splashscreen); mSplashScreenTextTop = (TextView) findViewById(R.id.splashscreen_text_top); mSplashScreenTextBottom = (TextView) findViewById(R.id.splashscreen_text_bottom); mTripDetailsWidget = findViewById(R.id.trip_details); mTripDetailsProximityA = (TextView) findViewById(R.id.trip_details_proximity_a); mTripDetailsProximityB = (TextView) findViewById(R.id.trip_details_proximity_b); mTripDetailsProximitySearch = (TextView) findViewById(R.id.trip_details_proximity_search); mTripDetailsProximityTotal = (TextView) findViewById(R.id.trip_details_proximity_total); mTripDetailsSumSeparator = (FrameLayout) findViewById(R.id.trip_details_sum_separator); mTripDetailsBToDestinationRow = findViewById(R.id.trip_details_b_to_search); mTripDetailsPinSearch = findViewById(R.id.trip_details_to_search); mTripDetailsPinFavorite = findViewById(R.id.trip_details_to_favorite); mSearchFAB = (FloatingActionButton) findViewById(R.id.search_fab); mAddFavoriteFAB = (FloatingActionButton) findViewById(R.id.favorite_add_remove_fab); mDirectionsLocToAFab = (FloatingActionButton) findViewById(R.id.directions_loc_to_a_fab); mPlaceAutocompleteLoadingProgressBar = (ProgressBar) findViewById(R.id.place_autocomplete_loading); if (autoCompleteLoadingProgressBarVisible) mPlaceAutocompleteLoadingProgressBar.setVisibility(View.VISIBLE); if (showcaseTripTotalPlaceName != null) { setupShowcaseTripTotal(); mOnboardingShowcaseView .setContentText(String.format(getString(R.string.onboarding_showcase_total_time_text), DBHelper.getBikeNetworkName(this), showcaseTripTotalPlaceName)); mOnboardingShowcaseView.setTag(showcaseTripTotalPlaceName); } if (savedInstanceState == null) mSplashScreen.setVisibility(View.VISIBLE); setupDirectionsLocToAFab(); setupSearchFab(); setupFavoritePickerFab(); if (savedInstanceState != null && savedInstanceState.getParcelable("add_favorite_fab_data") != null) { setupAddFavoriteFab((FavoriteItemPlace) savedInstanceState.getParcelable("add_favorite_fab_data")); mAddFavoriteFAB.show(); } setupClearFab(); setupAutoselectBikeFab(); setStatusBarClickListener(); getListPagerAdapter().setCurrentUserLatLng(mCurrentUserLatLng); setupFavoriteSheet(); //noinspection ConstantConditions findViewById(R.id.trip_details_directions_loc_to_a).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mOnboardingShowcaseView != null) { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } launchGoogleMapsForDirections(mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), true); } }); //noinspection ConstantConditions findViewById(R.id.trip_details_directions_a_to_b).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mOnboardingShowcaseView != null) { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } launchGoogleMapsForDirections(mStationMapFragment.getMarkerALatLng(), mStationMapFragment.getMarkerBVisibleLatLng(), false); } }); //noinspection ConstantConditions findViewById(R.id.trip_details_directions_b_to_destination).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mOnboardingShowcaseView != null) { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } if (mStationMapFragment.isPickedPlaceMarkerVisible()) launchGoogleMapsForDirections(mStationMapFragment.getMarkerBVisibleLatLng(), mStationMapFragment.getMarkerPickedPlaceVisibleLatLng(), true); else //Either Place marker or Favorite marker is visible, but not both at once launchGoogleMapsForDirections(mStationMapFragment.getMarkerBVisibleLatLng(), mStationMapFragment.getMarkerPickedFavoriteVisibleLatLng(), true); } }); findViewById(R.id.trip_details_share).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //Je serai la station Bixi Hutchison/beaubien dans ~15min ! Partag via #findmybikes //I will be at the Bixi station Hutchison/beaubien in ~15min ! Shared via #findmybikes String message = String.format( getResources().getString(R.string.trip_details_share_message_content), DBHelper.getBikeNetworkName(getApplicationContext()), getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.DOCK_STATIONS).getName(), mTripDetailsProximityTotal.getText().toString()); Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, message); sendIntent.setType("text/plain"); startActivity(Intent.createChooser(sendIntent, getString(R.string.trip_details_share_title))); } }); // Create an instance of GoogleAPIClient. if (mGoogleApiClient == null) { mGoogleApiClient = new GoogleApiClient.Builder(this).addConnectionCallbacks(this) .addOnConnectionFailedListener(this).addApi(LocationServices.API).build(); } setupLocationRequest(); mCircularRevealInterpolator = AnimationUtils.loadInterpolator(this, R.interpolator.msf_interpolator); //Not empty if RootApplication::onCreate got database data if (RootApplication.getBikeNetworkStationList().isEmpty()) { tryInitialSetup(); } } private void tryInitialSetup() { if (Utils.Connectivity.isConnected(this)) { mSplashScreenTextTop.setText(getString(R.string.auto_bike_select_finding)); if (DBHelper.isBikeNetworkIdAvailable(this)) { mDownloadWebTask = new DownloadWebTask(); mDownloadWebTask.execute(); Log.i("nearbyActivity", "No stationList data in RootApplication but bike network id available in DBHelper- launching first download"); } else { mFindNetworkTask = new FindNetworkTask(DBHelper.getBikeNetworkName(this)); mFindNetworkTask.execute(); } } else { Utils.Snackbar .makeStyled(mSplashScreen, R.string.connectivity_rationale, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { tryInitialSetup(); } }).show(); } } private void setupActionBarStrings() { //noinspection ConstantConditions getSupportActionBar() .setTitle(Utils.fromHtml(String.format(getResources().getString(R.string.appbar_title_formatting), getResources().getString(R.string.appbar_title_prefix), DBHelper.getHashtaggableNetworkName(this), getResources().getString(R.string.appbar_title_postfix)))); //doesn't scale well, but just a little touch for my fellow Montralers String city_hashtag = ""; String bikeNetworkCity = DBHelper.getBikeNetworkCity(this); if (bikeNetworkCity.contains("Montreal")) { city_hashtag = " @mtlvi"; } String hastagedEnhanced_bikeNetworkCity = bikeNetworkCity + city_hashtag; getSupportActionBar().setSubtitle(Utils.fromHtml(String.format( getResources().getString(R.string.appbar_subtitle_formatted), hastagedEnhanced_bikeNetworkCity))); } private void setupLocationRequest() { mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(10000); mLocationRequest.setFastestInterval(5000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder() .addLocationRequest(mLocationRequest); PendingResult<LocationSettingsResult> result = LocationServices.SettingsApi .checkLocationSettings(mGoogleApiClient, builder.build()); result.setResultCallback(new ResultCallback<LocationSettingsResult>() { @Override public void onResult(@NonNull LocationSettingsResult locationSettingsResult) { final Status status = locationSettingsResult.getStatus(); if (status.getStatusCode() == LocationSettingsStatusCodes.RESOLUTION_REQUIRED) { // Location settings are not satisfied, but this can be fixed // by showing the user a dialog. try { // Show the dialog by calling startResolutionForResult(), // and check the result in onActivityResult(). status.startResolutionForResult(NearbyActivity.this, CHECK_GPS_REQUEST_CODE); } catch (IntentSender.SendIntentException e) { // Ignore the error. } } } }); } private void setupAutoselectBikeFab() { mAutoSelectBikeFab = (FloatingActionButton) findViewById(R.id.autoselect_closest_bike); //Flipping this bool gives way to bike auto selection and found Snackbar animation //TODO : BonPlatDePates. Spaghetti monster must be contained. //In need an FSM of some kind. States being A selected Y/N B selected Y/N .... //TODO: Think about it more mAutoSelectBikeFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mClosestBikeAutoSelected = false; } }); } private void setupDirectionsLocToAFab() { mDirectionsLocToAFab.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { StationItem curSelectedStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); // Seen NullPointerException in crash report. if (null != curSelectedStation) { LatLng tripLegOrigin = isLookingForBike() ? mCurrentUserLatLng : mStationMapFragment.getMarkerALatLng(); LatLng tripLegDestination = curSelectedStation.getLocation(); boolean walkMode = isLookingForBike(); launchGoogleMapsForDirections(tripLegOrigin, tripLegDestination, walkMode); } } }); } //returns true if _toAdd.getId is NOT already in favorites private boolean setupAddFavoriteFab(final FavoriteItemBase _toAdd) { //This is a full add/remove code mAddFavoriteFAB.setTag(_toAdd); boolean alreadyFavorite = DBHelper.getFavoriteItemForId(this, _toAdd.getId()) != null; if (!alreadyFavorite) mAddFavoriteFAB.setImageResource(R.drawable.ic_action_favorite_outline_24dp); else { mAddFavoriteFAB.setImageResource(R.drawable.ic_action_favorite_24dp); return false; } mAddFavoriteFAB.setOnClickListener(new View.OnClickListener() { boolean mIsFavorite = false; @Override public void onClick(View view) { if (mOnboardingShowcaseView != null) { if (checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_CHECKONLY)) animateShowcaseToItinerary(); //TODO: have those elevated to steps and have animate* methods called from checkOnboarding else { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } } if (_toAdd instanceof FavoriteItemPlace) { getListPagerAdapter().showFavoriteHeaderInBTab(); mStationMapFragment.clearMarkerPickedPlace(); mFavoritePicked = true; hideSetupShowTripDetailsWidget(); mStationMapFragment.setPinForPickedFavorite(_toAdd.getDisplayName(), _toAdd.getLocation(), _toAdd.getAttributions()); } else { //_toAdd instanceof FavoriteItemStation == true mStationMapFragment.setPinForPickedFavorite(_toAdd.getDisplayName(), getLatLngForStation(_toAdd.getId()), null); } if (!mIsFavorite) { mAddFavoriteFAB.setImageResource(R.drawable.ic_action_favorite_24dp); addFavorite(_toAdd, false, false); mFavoritesSheetFab.scrollToTop(); mAddFavoriteFAB.hide(); } else { mAddFavoriteFAB.setImageResource(R.drawable.ic_action_favorite_outline_24dp); removeFavorite(_toAdd, false); } mIsFavorite = !mIsFavorite; } }); return true; } private void setupSearchFab() { mSearchFAB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mPlaceAutocompleteLoadingProgressBar.getVisibility() != View.GONE) return; try { Intent intent = new PlaceAutocomplete.IntentBuilder(PlaceAutocomplete.MODE_OVERLAY) .setBoundsBias(DBHelper.getBikeNetworkBounds(NearbyActivity.this, Utils.getAverageBikingSpeedKmh(NearbyActivity.this))) .build(NearbyActivity.this); startActivityForResult(intent, PLACE_AUTOCOMPLETE_REQUEST_CODE); mFavoritesSheetFab.hideSheetThenFab(); mSearchFAB.setBackgroundTintList( ContextCompat.getColorStateList(NearbyActivity.this, R.color.light_gray)); mPlaceAutocompleteLoadingProgressBar.setVisibility(View.VISIBLE); getListPagerAdapter().hideStationRecap(); //onboarding is in progress, showcasing search action button, give way to search //(google provided autocompletewidget) if (mOnboardingShowcaseView != null) { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_HINT)) dismissOnboardingHint(); } catch (GooglePlayServicesRepairableException | GooglePlayServicesNotAvailableException e) { Log.d("mPlacePickerFAB onClick", "oops", e); } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PLACE_AUTOCOMPLETE_REQUEST_CODE) { mPlaceAutocompleteLoadingProgressBar.setVisibility(View.GONE); mSearchFAB.setBackgroundTintList( ContextCompat.getColorStateList(NearbyActivity.this, R.color.theme_primary_dark)); if (resultCode == RESULT_OK) { final Place place = PlaceAutocomplete.getPlace(this, data); mSearchFAB.hide(); //IDs are not guaranteed stable over long periods of time //but searching for a place already in favorites is not a typical use case //TODO: implement best practice of updating favorite places IDs once per month FavoriteItemPlace newFavForPlace = new FavoriteItemPlace(place.getId(), place.getName().toString(), place.getLatLng(), place.getAttributions()); final FavoriteItemBase existingFavForPlace = DBHelper.getFavoriteItemForId(this, newFavForPlace.getId()); if (existingFavForPlace == null) { if (setupAddFavoriteFab(newFavForPlace)) mAddFavoriteFAB.show(); else mAddFavoriteFAB.hide(); //User selected a search result, onboarding showcases total trip time and favorite action button if (checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_TRIP_TOTAL_SHOWCASE)) //Because destination name is available here and can't be passed down checkOnboarding { mOnboardingShowcaseView.setContentText( String.format(getString(R.string.onboarding_showcase_total_time_text), DBHelper.getBikeNetworkName(this), place.getName())); mOnboardingShowcaseView.setTag(place.getName()); } final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (NearbyActivity.this.getListPagerAdapter().isViewPagerReady()) { setupBTabSelectionClosestDock(place); } else handler.postDelayed(this, 10); } }, 50); } else { //Place was already a favorite final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (NearbyActivity.this.getListPagerAdapter().isViewPagerReady()) { setupBTabSelectionClosestDock(existingFavForPlace.getId()); } else handler.postDelayed(this, 10); } }, 50); } } else { //user pressed back, there's no search result available mFavoritesSheetFab.showFab(); mAddFavoriteFAB.hide(); mSearchFAB.show(); getListPagerAdapter().showStationRecap(); //in case of full onboarding, setup search showcase (user cancelled previous showcased search) //... check if full onboarding should happen if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) { //... if it doesn't, display hint if required checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); } } } else if (requestCode == SETTINGS_ACTIVITY_REQUEST_CODE) { DBHelper.resumeAutoUpdate(); //Does NOT start it if user didn't activated it in Setting activity mClosestBikeAutoSelected = false; mRefreshMarkers = true; refreshMap(); } else if (requestCode == CHECK_PERMISSION_REQUEST_CODE) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the thing mSplashScreenTextTop.setText(R.string.auto_bike_select_finding); mStationMapFragment.enableMyLocationCheckingPermission(); } else checkAndAskLocationPermission(); } else if (requestCode == CHECK_GPS_REQUEST_CODE) { //getting here when GPS been activated through system settings dialog if (resultCode != RESULT_OK) { if (mSplashScreen.isShown()) { mSplashScreenTextTop.setText(R.string.sad_emoji); mSplashScreenTextBottom.setText(""); Utils.Snackbar .makeStyled(mSplashScreen, R.string.location_turn_on, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { setupLocationRequest(); } }).show(); } } else { mSplashScreenTextTop.setText(R.string.auto_bike_select_finding); } } } private void setupFavoriteSheet() { //ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, // ItemTouchHelper.LEFT) { ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { ((ItemTouchHelperAdapter) recyclerView.getAdapter()).onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); return true; } @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { FavoriteRecyclerViewAdapter.FavoriteListItemViewHolder favViewHolder = (FavoriteRecyclerViewAdapter.FavoriteListItemViewHolder) viewHolder; removeFavorite(DBHelper.getFavoriteItemForId(NearbyActivity.this, favViewHolder.getFavoriteId()), true); } @Override public boolean isLongPressDragEnabled() { return mFavoriteRecyclerViewAdapter.getSheetEditing(); } @Override public boolean isItemViewSwipeEnabled() { return !mFavoriteRecyclerViewAdapter.getSheetEditing() && !mFavoriteItemEditInProgress; } @Override public void onSelectedChanged(RecyclerView.ViewHolder _viewHolder, int _actionState) { if (_actionState != ItemTouchHelper.ACTION_STATE_IDLE) { if (_viewHolder instanceof FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder) { FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder favoriteItemViewHolder = (FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder) _viewHolder; favoriteItemViewHolder.onItemSelected(); } } super.onSelectedChanged(_viewHolder, _actionState); } @Override public void clearView(RecyclerView _recyclerView, RecyclerView.ViewHolder _viewHolder) { super.clearView(_recyclerView, _viewHolder); if (_viewHolder instanceof FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder) { FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder favoriteItemViewHolder = (FavoriteRecyclerViewAdapter.FavoriteItemTouchHelperViewHolder) _viewHolder; favoriteItemViewHolder.onItemClear(); } } }; mFavoriteItemTouchHelper = new ItemTouchHelper(simpleItemTouchCallback); RecyclerView favoriteRecyclerView = (RecyclerView) findViewById(R.id.favorites_sheet_recyclerview); favoriteRecyclerView .addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); favoriteRecyclerView .setLayoutManager(new ScrollingLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false, 300)); mFavoriteRecyclerViewAdapter = new FavoriteRecyclerViewAdapter(this, this, this); ArrayList<FavoriteItemBase> favoriteList = DBHelper.getFavoriteAll(this); setupFavoriteListFeedback(favoriteList.isEmpty()); mFavoriteRecyclerViewAdapter.setupFavoriteList(favoriteList); favoriteRecyclerView.setAdapter(mFavoriteRecyclerViewAdapter); mFavoriteItemTouchHelper.attachToRecyclerView(favoriteRecyclerView); } @SuppressWarnings("ConstantConditions") private void setupFavoriteListFeedback(boolean _noFavorite) { if (_noFavorite) { ((TextView) findViewById(R.id.favorites_sheet_header_textview)).setText(Utils.fromHtml(String .format(getResources().getString(R.string.no_favorite), DBHelper.getBikeNetworkName(this)))); findViewById(R.id.favorite_sheet_edit_fab).setVisibility(View.INVISIBLE); findViewById(R.id.favorite_sheet_edit_done_fab).setVisibility(View.INVISIBLE); mFavoriteRecyclerViewAdapter.setSheetEditing(false); } else { ((TextView) findViewById(R.id.favorites_sheet_header_textview)) .setText(Utils.fromHtml(String.format(getResources().getString(R.string.favorites_sheet_header), DBHelper.getBikeNetworkName(this)))); ((FloatingActionButton) findViewById(R.id.favorite_sheet_edit_fab)).show(); } } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable("saved_camera_pos", mStationMapFragment.getCameraPosition()); outState.putBoolean("requesting_location_updates", mRequestingLocationUpdates); outState.putParcelable("user_location_latlng", mCurrentUserLatLng); outState.putBoolean("closest_bike_auto_selected", mClosestBikeAutoSelected); outState.putBoolean("favorite_sheet_visible", mFavoriteSheetVisible); outState.putBoolean("place_autocomplete_loading", mPlaceAutocompleteLoadingProgressBar.getVisibility() == View.VISIBLE); outState.putBoolean("refresh_tabs", mRefreshTabs); outState.putBoolean("favorite_picked", mFavoritePicked); outState.putBoolean("data_outdated", mDataOutdated); if (mOnboardingShowcaseView != null && mOnboardingShowcaseView.getTag() != null) { outState.putString("onboarding_showcase_trip_total_place_name", (String) mOnboardingShowcaseView.getTag()); } if (mAddFavoriteFAB.isShown() && mAddFavoriteFAB.getTag() instanceof FavoriteItemPlace) { outState.putParcelable("add_favorite_fab_data", (FavoriteItemPlace) mAddFavoriteFAB.getTag()); } else { outState.putParcelable("add_favorite_fab_data", null); } } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); mStationMapFragment = (StationMapFragment) getSupportFragmentManager() .findFragmentById(R.id.station_map_fragment); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.menu_nearby, menu); return super.onCreateOptionsMenu(menu); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the thing mSplashScreenTextTop.setText(R.string.auto_bike_select_finding); mStationMapFragment.enableMyLocationCheckingPermission(); } else { mSplashScreenTextTop.setText(R.string.sad_emoji); mSplashScreenTextBottom.setText(""); if (ActivityCompat.shouldShowRequestPermissionRationale(NearbyActivity.this, Manifest.permission.ACCESS_FINE_LOCATION)) { Utils.Snackbar .makeStyled(mSplashScreen, R.string.location_rationale, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { checkAndAskLocationPermission(); } }).show(); } else { Utils.Snackbar .makeStyled(mSplashScreen, R.string.location_rationale, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.settings, new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivityForResult(intent, CHECK_PERMISSION_REQUEST_CODE); } }).show(); } } } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.settings_menu_item: Intent settingsIntent = new Intent(this, SettingsActivity.class); startActivityForResult(settingsIntent, SETTINGS_ACTIVITY_REQUEST_CODE); return true; case R.id.about_menu_item: new MaterialDialog.Builder(this) .title(getString(R.string.app_name) + " " + getString(R.string.app_version_name) + " 20152017 F8Full") //http://stackoverflow.com/questions/4471025/how-can-you-get-the-manifest-version-number-from-the-apps-layout-xml-variable--> .items(R.array.about_dialog_items) .icon(ContextCompat.getDrawable(NearbyActivity.this, R.drawable.logo_48dp)).autoDismiss(false) .itemsCallback(new MaterialDialog.ListCallback() { @Override public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) { Intent intent; switch (which) { case 0: startActivity(Utils.getWebIntent(NearbyActivity.this, "http://www.citybik.es", true, text.toString())); break; case 1: intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("market://details?id=com.ludoscity.findmybikes")); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } break; case 2: String url = "https://www.facebook.com/findmybikes/"; Uri uri; try { getPackageManager().getPackageInfo("com.facebook.katana", 0); // http://stackoverflow.com/questions/24526882/open-facebook-page-from-android-app-in-facebook-version-v11 uri = Uri.parse("fb://facewebmodal/f?href=" + url); intent = new Intent(Intent.ACTION_VIEW, uri); } catch (PackageManager.NameNotFoundException e) { intent = Utils.getWebIntent(NearbyActivity.this, url, true, text.toString()); } //Seen ActivityNotFoundException in firebase cloud lab (FB package found but can't be launched) if (intent.resolveActivity(getPackageManager()) == null) intent = Utils.getWebIntent(NearbyActivity.this, url, true, text.toString()); startActivity(intent); break; case 3: intent = new Intent(Intent.ACTION_SENDTO); intent.setData(Uri.parse("mailto:")); // only email apps should handle this intent.putExtra(Intent.EXTRA_EMAIL, new String[] { "ludos+findmybikesfeedback" + getString(R.string.app_version_name) + "@ludoscity.com" }); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.feedback_subject)); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } break; case 4: new LicensesDialog.Builder(NearbyActivity.this).setNotices(R.raw.notices).build() .show(); break; case 5: intent = new Intent(NearbyActivity.this, WebViewActivity.class); intent.putExtra(WebViewActivity.EXTRA_URL, "file:///android_res/raw/privacy_policy.html"); intent.putExtra(WebViewActivity.EXTRA_ACTIONBAR_SUBTITLE, getString(R.string.hashtag_privacy)); startActivity(intent); break; case 6: try { // get the Twitter app if possible getPackageManager().getPackageInfo("com.twitter.android", 0); intent = new Intent(Intent.ACTION_VIEW, Uri.parse("twitter://user?screen_name=findmybikesdata")); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } catch (PackageManager.NameNotFoundException e) { // no Twitter app, revert to browser intent = Utils.getWebIntent(NearbyActivity.this, "https://twitter.com/findmybikesdata", true, text.toString()); } if (intent.resolveActivity(getPackageManager()) == null) intent = Utils.getWebIntent(NearbyActivity.this, "https://twitter.com/findmybikesdata", true, text.toString()); startActivity(intent); break; case 7: intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/f8full/ludOScity/tree/master/FindMyBikes")); startActivity(intent); break; } } }).show(); return true; } return super.onOptionsItemSelected(item); } private void removeFavorite(final FavoriteItemBase _toRemove, boolean _showUndo) { DBHelper.updateFavorite(false, _toRemove, this); ArrayList<FavoriteItemBase> favoriteList = DBHelper.getFavoriteAll(this); setupFavoriteListFeedback(favoriteList.isEmpty()); mFavoriteRecyclerViewAdapter.removeFavorite(_toRemove); //To setup correct name final StationItem closestBikeStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); if (_toRemove instanceof FavoriteItemStation) getListPagerAdapter().notifyStationChangedAll(_toRemove.getId()); if (!_showUndo) { Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.favorite_removed, Snackbar.LENGTH_SHORT, ContextCompat.getColor(this, R.color.theme_primary_dark)).show(); } else { Utils.Snackbar .makeStyled(mCoordinatorLayout, R.string.favorite_removed, Snackbar.LENGTH_LONG, ContextCompat.getColor(this, R.color.theme_primary_dark)) .setAction(R.string.undo, new View.OnClickListener() { @Override public void onClick(View v) { addFavorite(_toRemove, false, false); mFavoritesSheetFab.scrollToTop(); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); } }).show(); } } private void addFavorite(final FavoriteItemBase _toAdd, boolean _silent, boolean _showUndo) { DBHelper.updateFavorite(true, _toAdd, this); ArrayList<FavoriteItemBase> favoriteList = DBHelper.getFavoriteAll(this); setupFavoriteListFeedback(favoriteList.isEmpty()); mFavoriteRecyclerViewAdapter.addFavorite(_toAdd); //To setup correct name final StationItem closestBikeStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); if (_toAdd instanceof FavoriteItemStation) getListPagerAdapter().notifyStationChangedAll(_toAdd.getId()); if (!_silent) { if (!_showUndo) { Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.favorite_added, Snackbar.LENGTH_SHORT, ContextCompat.getColor(this, R.color.theme_primary_dark)).show(); } else { Utils.Snackbar .makeStyled(mCoordinatorLayout, R.string.favorite_added, Snackbar.LENGTH_LONG, ContextCompat.getColor(this, R.color.theme_primary_dark)) .setAction(R.string.undo, new View.OnClickListener() { @Override public void onClick(View v) { removeFavorite(_toAdd, false); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); } }).show(); } } } private void setupFavoritePickerFab() { mFavoritePickerFAB = (Fab) findViewById(R.id.favorite_picker_fab); View sheetView = findViewById(R.id.fab_sheet); View overlay = findViewById(R.id.overlay); int sheetColor = ContextCompat.getColor(this, R.color.cardview_light_background); int fabColor = ContextCompat.getColor(this, R.color.theme_primary_dark); //Caused by: java.lang.NullPointerException (sheetView) // Create material sheet FAB mFavoritesSheetFab = new EditableMaterialSheetFab(mFavoritePickerFAB, sheetView, overlay, sheetColor, fabColor, this); mFavoritesSheetFab.setEventListener(new MaterialSheetFabEventListener() { @Override public void onShowSheet() { mSearchFAB.hide(); mFavoriteSheetVisible = true; if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_ULTRA_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_TAP_FAV_NAME_HINT)) dismissOnboardingHint(); } @Override public void onSheetHidden() { if (!isLookingForBike() && mStationMapFragment.getMarkerBVisibleLatLng() == null) { //B tab with no selection if (Utils.Connectivity.isConnected(NearbyActivity.this)) mSearchFAB.show(); if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE) && !checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT)) { dismissOnboardingHint(); } } mFavoriteSheetVisible = false; } }); } private void setupClearFab() { mClearFAB = (FloatingActionButton) findViewById(R.id.clear_fab); mClearFAB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { clearBSelection(); } }); } private void clearBSelection() { mFavoritePicked = false; mStationMapFragment.setMapPaddingLeft(0); mStationMapFragment.setMapPaddingRight(0); hideTripDetailsWidget(); clearBTab(); if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) { checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); } } private enum eONBOARDING_STEP { ONBOARDING_STEP_CHECKONLY, ONBOARDING_STEP_SEARCH_SHOWCASE, ONBOARDING_STEP_TRIP_TOTAL_SHOWCASE, ONBOARDING_STEP_MAIN_CHOICE_HINT, ONBOARDING_STEP_TAP_FAV_NAME_HINT, ONBOARDING_STEP_SEARCH_HINT } private enum eONBOARDING_LEVEL { ONBOARDING_LEVEL_FULL, ONBOARDING_LEVEL_LIGHT, ONBOARDING_LEVEL_ULTRA_LIGHT } //returns true if conditions satisfied (onboarding is showed) private boolean checkOnboarding(eONBOARDING_LEVEL _level, eONBOARDING_STEP _step) { boolean toReturn = false; int minValidFavorites = -1; if (_level == eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL) minValidFavorites = getApplicationContext().getResources() .getInteger(R.integer.onboarding_light_min_valid_favorites_count); else if (_level == eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT) minValidFavorites = getApplicationContext().getResources() .getInteger(R.integer.onboarding_ultra_light_min_valid_favorites_count); else if (_level == eONBOARDING_LEVEL.ONBOARDING_LEVEL_ULTRA_LIGHT) minValidFavorites = getApplicationContext().getResources() .getInteger(R.integer.onboarding_none_min_valid_favorites_count); //count valid favorites if (!DBHelper.hasAtLeastNValidFavorites( getListPagerAdapter().getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS), minValidFavorites, NearbyActivity.this)) { if (_step == eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE) setupShowcaseSearch(); else if (_step == eONBOARDING_STEP.ONBOARDING_STEP_TRIP_TOTAL_SHOWCASE) setupShowcaseTripTotal(); else if (_step == eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT) setupHintMainChoice(); else if (_step == eONBOARDING_STEP.ONBOARDING_STEP_TAP_FAV_NAME_HINT) setupHintTapFavName(); else if (_step == eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_HINT) setupHintSearch(); toReturn = true; } return toReturn; } @Override public void onBackPressed() { if (mFavoritesSheetFab.isSheetVisible()) { mFavoritesSheetFab.hideSheet(); dismissOnboardingHint(); } else //noinspection StatementWithEmptyBody if (mOnboardingShowcaseView != null) { //do nothing if onboarding is in progress } else if (isLookingForBike()) { //A tab exploring //go back to B tab mStationListViewPager.setCurrentItem(StationListPagerAdapter.DOCK_STATIONS, true); //if no selection in B tab... if (mStationMapFragment.getMarkerBVisibleLatLng() == null) { //... check if full onboarding should happen if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) { //... if it doesn't, display hint setupHintMainChoice(); } } } else if (!isLookingForBike() && mStationMapFragment.getMarkerBVisibleLatLng() != null) { //B tab exploring and a station is selected clearBSelection(); //in case of full onboarding, showcase search checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE); } else if (!isLookingForBike() && mOnboardingSnackBar == null) { setupHintMainChoice(); } else { //otherwise, pass it up ("exiting" app) super.onBackPressed(); } } private void stopUIRefresh() { if (mUpdateRefreshHandler != null) { mUpdateRefreshHandler.removeCallbacks(mUpdateRefreshRunnableCode); mUpdateRefreshRunnableCode = null; mUpdateRefreshHandler = null; } } private void refreshMap() { /*Log.d("nearbyActivity", "refreshMap", new Exception()); Log.d("nearbyActivity", "refreshMap outdateddata status :" + mDataOutdated );*/ if (DBHelper.isBikeNetworkIdAvailable(this)) { if (mStationMapFragment.isMapReady()) { if (mRefreshMarkers && mRedrawMarkersTask == null) { mRedrawMarkersTask = new RedrawMarkersTask(); mRedrawMarkersTask.execute(mDataOutdated, isLookingForBike()); mRefreshMarkers = false; } if (null != mSavedInstanceCameraPosition) { mStationMapFragment.doInitialCameraSetup( CameraUpdateFactory.newCameraPosition(mSavedInstanceCameraPosition), false); mSavedInstanceCameraPosition = null; } } } } private void setupTabPages() { //TAB A getListPagerAdapter().setupUI(StationListPagerAdapter.BIKE_STATIONS, RootApplication.getBikeNetworkStationList(), true, null, R.drawable.ic_walking_24dp_white, "", mCurrentUserLatLng != null ? new StationRecyclerViewAdapter.DistanceComparator(mCurrentUserLatLng) : null); LatLng stationBLatLng = mStationMapFragment.getMarkerBVisibleLatLng(); if (stationBLatLng == null) { //TAB B getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, new ArrayList<StationItem>(), false, null, null, getString(R.string.b_tab_question), null); //TAB A getListPagerAdapter().setClickResponsivenessForPage(StationListPagerAdapter.BIKE_STATIONS, false); } else { StationItem highlighthedDockStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.DOCK_STATIONS); if (highlighthedDockStation != null) { setupBTabSelection(highlighthedDockStation.getId(), isLookingForBike()); FavoriteItemBase newFavForStation = new FavoriteItemStation(highlighthedDockStation.getId(), highlighthedDockStation.getName(), true); boolean showFavoriteAddFab = false; if (!mStationMapFragment.isPickedFavoriteMarkerVisible()) { if (mStationMapFragment.isPickedPlaceMarkerVisible()) showFavoriteAddFab = true; //Don't setup the fab as it's been done in OnActivityResult else if (setupAddFavoriteFab(newFavForStation)) showFavoriteAddFab = true; } if (showFavoriteAddFab) mAddFavoriteFAB.show(); else mAddFavoriteFAB.hide(); } else //B pin on map with no list selection can happen on rapid multiple screen orientation change clearBTab(); } mRefreshTabs = false; } //TODO : This clearly turned into spaghetti. At least extract methods. private Runnable createUpdateRefreshRunnableCode() { return new Runnable() { private boolean mPagerReady = false; private NumberFormat mNumberFormat = NumberFormat.getInstance(); /*private final long startTime = System.currentTimeMillis(); private long lastRunTime; private long lastUpdateTime = System.currentTimeMillis(); //Update should be run automatically ? */ @Override public void run() { long now = System.currentTimeMillis(); if (mRedrawMarkersTask == null && getListPagerAdapter().isViewPagerReady() && (!mPagerReady || mRefreshTabs)) { //When restoring, we don't need to setup everything from here if (!mStationMapFragment.isRestoring()) { //TODO figure out how to properly determine restoration setupTabPages(); if (isLookingForBike()) //onPageSelected is called by framework on B tab restoration onPageSelected(StationListPagerAdapter.BIKE_STATIONS); } mPagerReady = true; } //Update not already in progress if (mPagerReady && mDownloadWebTask == null && mRedrawMarkersTask == null && mFindNetworkTask == null) { long runnableLastRefreshTimestamp = DBHelper.getLastUpdateTimestamp(getApplicationContext()); long difference = now - runnableLastRefreshTimestamp; StringBuilder pastStringBuilder = new StringBuilder(); StringBuilder futureStringBuilder = new StringBuilder(); if (DBHelper.isBikeNetworkIdAvailable(getApplicationContext())) { //First taking care of past time... if (difference < DateUtils.MINUTE_IN_MILLIS) pastStringBuilder.append(getString(R.string.moments)); else pastStringBuilder.append(getString(R.string.il_y_a)) .append(mNumberFormat.format(difference / DateUtils.MINUTE_IN_MILLIS)) .append(" ").append(getString(R.string.min_abbreviated)); } //mStatusTextView.setText(Long.toString(difference / DateUtils.MINUTE_IN_MILLIS) +" "+ getString(R.string.minsAgo) + " " + getString(R.string.fromCitibik_es) ); //long differenceInMinutes = difference / DateUtils.MINUTE_IN_MILLIS; //from : http://stackoverflow.com/questions/25355611/how-to-get-time-difference-between-two-dates-in-android-app //long differenceInSeconds = difference / DateUtils.SECOND_IN_MILLIS; // formatted will be HH:MM:SS or MM:SS //String formatted = DateUtils.formatElapsedTime(differenceInSeconds); //... then about next update if (Utils.Connectivity.isConnected(getApplicationContext())) { getListPagerAdapter().setRefreshEnableAll(true); if (!mSearchFAB.isEnabled()) { mSearchFAB.show(); mSearchFAB.setEnabled(true); if (mOnboardingSnackBar != null && mOnboardingSnackBar.getView().getTag() != null) if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); mStatusBar.setBackgroundColor( ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)); } if (DBHelper.isBikeNetworkIdAvailable(getApplicationContext())) { if (!DBHelper.getAutoUpdate(getApplicationContext())) { futureStringBuilder.append(getString(R.string.pull_to_refresh)); } else { //Should come from something keeping tabs on time, maybe this runnable itself long wishedUpdateTime = runnableLastRefreshTimestamp + NearbyActivity.this.getApplicationContext().getResources() .getInteger(R.integer.update_auto_interval_minute) * 1000 * 60; //comes from Prefs //Debug //long wishedUpdateTime = runnableLastRefreshTimestamp + 15 * 1000; //comes from Prefs if (now >= wishedUpdateTime) { mDownloadWebTask = new DownloadWebTask(); mDownloadWebTask.execute(); } else { futureStringBuilder.append(getString(R.string.nextUpdate)).append(" "); long differenceSecond = (wishedUpdateTime - now) / DateUtils.SECOND_IN_MILLIS; // formatted will be HH:MM:SS or MM:SS futureStringBuilder.append(DateUtils.formatElapsedTime(differenceSecond)); } } if (difference >= NearbyActivity.this.getApplicationContext().getResources() .getInteger(R.integer.outdated_data_time_minute) * 60 * 1000 && !mDataOutdated) { mDataOutdated = true; if (mDownloadWebTask == null) { //Auto update didn't kick in. If task cancels it will execute same code then mRefreshMarkers = true; refreshMap(); mStatusBar.setBackgroundColor( ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); getListPagerAdapter().setOutdatedDataAll(true); } } } } else { futureStringBuilder.append(getString(R.string.no_connectivity)); getListPagerAdapter().setRefreshEnableAll(false); mSearchFAB.setEnabled(false); mSearchFAB.hide(); if (getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS) != null && mOnboardingSnackBar != null && mOnboardingSnackBar.getView().getTag() != null && !((String) mOnboardingSnackBar.getView().getTag()) .equalsIgnoreCase("NO_CONNECTIVITY")) checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); mStatusBar.setBackgroundColor( ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); } if (mDownloadWebTask == null) mStatusTextView.setText(String.format(getString(R.string.status_string), pastStringBuilder.toString(), futureStringBuilder.toString())); //pulling the trigger on auto select if (mDownloadWebTask == null && mRedrawMarkersTask == null && mFindNetworkTask == null && !mClosestBikeAutoSelected && getListPagerAdapter() .isRecyclerViewReadyForItemSelection(StationListPagerAdapter.BIKE_STATIONS) && mStationMapFragment.isMapReady()) { //Requesting raw string with availability String rawClosest = getListPagerAdapter().retrieveClosestRawIdAndAvailability(true); getListPagerAdapter().highlightStationforId(true, Utils.extractClosestAvailableStationIdFromProcessedString(rawClosest)); getListPagerAdapter().smoothScrollHighlightedInViewForPage( StationListPagerAdapter.BIKE_STATIONS, isAppBarExpanded()); final StationItem closestBikeStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); mStationMapFragment.setPinOnStation(true, closestBikeStation.getId()); getListPagerAdapter().notifyStationAUpdate(closestBikeStation.getLocation(), mCurrentUserLatLng); hideSetupShowTripDetailsWidget(); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); if (isLookingForBike()) { if (mTripDetailsWidget.getVisibility() == View.INVISIBLE) { mDirectionsLocToAFab.show(); } mAutoSelectBikeFab.hide(); mStationMapFragment.setMapPaddingRight(0); if (mStationMapFragment.getMarkerBVisibleLatLng() == null) { mStationListViewPager.setCurrentItem(StationListPagerAdapter.DOCK_STATIONS, true); mFavoritesSheetFab.showFab(); //if onboarding not happening... if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE) && !checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT)) { //...open favorites sheet final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (!mFavoritePickerFAB.isShowRunning()) { mFavoritesSheetFab.showSheet(); } else handler.postDelayed(this, 10); } }, 50); } } else { animateCameraToShowUserAndStation(closestBikeStation); } } //Bug on older API levels. Dismissing by hand fixes it. // First biggest bug happened here. Putting defensive code //TODO: investigate how state is maintained, Snackbar is destroyed by framework on screen orientation change //TODO: Refactor this spgathetti is **TOP** priosity //and probably long background state. if (mFindBikesSnackbar != null) { mFindBikesSnackbar.dismiss(); } Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (mDataOutdated) { mFindBikesSnackbar = Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.auto_bike_select_outdated, Snackbar.LENGTH_LONG, ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); } else if (!closestBikeStation.isLocked() && closestBikeStation.getFree_bikes() > DBHelper .getCriticalAvailabilityMax(NearbyActivity.this)) { mFindBikesSnackbar = Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.auto_bike_select_found, Snackbar.LENGTH_LONG, ContextCompat.getColor(NearbyActivity.this, R.color.snackbar_green)); } else { mFindBikesSnackbar = Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.auto_bike_select_none, Snackbar.LENGTH_LONG, ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); } if (mOnboardingSnackBar == null) mFindBikesSnackbar.show(); } }, 500); mClosestBikeAutoSelected = true; //launch twitter task if not already running, pass it the raw String if (Utils.Connectivity.isConnected(getApplicationContext()) && //data network available mUpdateTwitterTask == null && //not already tweeting rawClosest.length() > 32 + StationRecyclerViewAdapter.AOK_AVAILABILITY_POSTFIX.length() && //validate format - 32 is station ID length (rawClosest.contains(StationRecyclerViewAdapter.AOK_AVAILABILITY_POSTFIX) || rawClosest.contains(StationRecyclerViewAdapter.BAD_AVAILABILITY_POSTFIX)) && //validate content !mDataOutdated) { mUpdateTwitterTask = new UpdateTwitterStatusTask(); mUpdateTwitterTask.execute(rawClosest); } } //Checking if station is closest bike if (mDownloadWebTask == null && mRedrawMarkersTask == null && mFindNetworkTask == null && mStationMapFragment.isMapReady()) { if (!isStationAClosestBike()) { if (mStationMapFragment.getMarkerBVisibleLatLng() == null) { mClosestBikeAutoSelected = false; } else if (isLookingForBike() && !mClosestBikeAutoSelected) { mStationMapFragment.setMapPaddingRight( (int) getResources().getDimension(R.dimen.map_fab_padding)); mAutoSelectBikeFab.show(); animateCameraToShowUserAndStation(getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS)); } } } } //UI will be refreshed every second int nextTimeMillis = 1000; if (!mPagerReady) //Except on init nextTimeMillis = 100; mUpdateRefreshHandler.postDelayed(mUpdateRefreshRunnableCode, nextTimeMillis); } }; } private void setupShowcaseSearch() { if (Utils.Connectivity.isConnected(getApplicationContext())) { if (mFavoritesSheetFab.isSheetVisible()) mFavoritesSheetFab.hideSheet(); if (mOnboardingShowcaseView == null) { mOnboardingShowcaseView = new ShowcaseView.Builder(NearbyActivity.this) .setTarget(new ViewTarget(R.id.search_framelayout, NearbyActivity.this)) .setStyle(R.style.OnboardingShowcaseTheme) .setContentTitle(R.string.onboarding_showcase_search_title) .setContentText(R.string.onboarding_showcase_search_text) .withMaterialShowcase().build(); mOnboardingShowcaseView.hideButton(); } else { mOnboardingShowcaseView.setContentTitle(getString(R.string.onboarding_showcase_search_title)); mOnboardingShowcaseView.setContentText(getString(R.string.onboarding_showcase_search_text)); } } else { setupHintMainChoice(); } } private void setupShowcaseTripTotal() { if (mOnboardingShowcaseView != null) mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = new ShowcaseView.Builder(NearbyActivity.this) .setTarget(new ViewTarget(R.id.trip_details_total, NearbyActivity.this)) .setStyle(R.style.OnboardingShowcaseTheme) .setContentTitle(R.string.onboarding_showcase_total_time_title).withMaterialShowcase() .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (!mStationMapFragment.isPickedFavoriteMarkerVisible()) animateShowcaseToAddFavorite(); else animateShowcaseToItinerary(); } }).build(); //TODO: position button depending on screen orientation //RelativeLayout.LayoutParams lps = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); /*lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); lps.addRule(RelativeLayout.RIGHT_OF, R.id.trip_details_total); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { lps.addRule(RelativeLayout.END_OF, R.id.trip_details_total); }*/ //lps.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); //lps.addRule(RelativeLayout.CENTER_IN_PARENT); //int margin = ((Number) (getResources().getDisplayMetrics().density * 12)).intValue(); //lps.setMargins(margin, margin, margin, margin); //mOnboardingShowcaseView.setButtonPosition(lps); } private void setupHintMainChoice() { int messageResourceId = R.string.onboarding_hint_main_choice; if (!Utils.Connectivity.isConnected(getApplicationContext())) messageResourceId = R.string.onboarding_hint_main_choice_no_connectivity; mOnboardingSnackBar = Utils.Snackbar.makeStyled(mCoordinatorLayout, messageResourceId, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(this, R.color.theme_primary_dark)) /*.setAction(R.string.gotit, new View.OnClickListener() { @Override public void onClick(View view) { //Snackbar dismisses itself on click } })*/; if (!Utils.Connectivity.isConnected(getApplicationContext())) mOnboardingSnackBar.getView().setTag("NO_CONNECTIVITY"); else mOnboardingSnackBar.getView().setTag("CONNECTIVITY"); mOnboardingSnackBar.setCallback(new Snackbar.Callback() { @Override public void onDismissed(Snackbar snackbar, int event) { if (event == DISMISS_EVENT_SWIPE) mOnboardingSnackBar = null; } }); mOnboardingSnackBar.show(); } private void setupHintTapFavName() { mOnboardingSnackBar = Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.onboarding_hint_tap_favorite_name, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)); mOnboardingSnackBar.getView().setTag(null); mOnboardingSnackBar.show(); } private void setupHintSearch() { mOnboardingSnackBar = Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.onboarding_hint_search, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)); mOnboardingSnackBar.getView().setTag(null); mOnboardingSnackBar.show(); } private void animateShowcaseToAddFavorite() { mOnboardingShowcaseView.hideButton(); mOnboardingShowcaseView.setShowcase(new ViewTarget(mAddFavoriteFAB), true); mOnboardingShowcaseView.setContentTitle(getString(R.string.onboarding_showcase_add_favorite_title)); mOnboardingShowcaseView.setContentText(getString(R.string.onboarding_showcase_add_favorite_text)); } private void animateShowcaseToItinerary() { mOnboardingShowcaseView.hideButton(); mOnboardingShowcaseView.setShowcase(new ViewTarget(R.id.trip_details_directions_a_to_b, this), true); mOnboardingShowcaseView.setContentTitle(getString(R.string.onboarding_showcase_itinerary_title)); mOnboardingShowcaseView.setContentText(getString(R.string.onboarding_showcase_itinerary_favorite_text)); } private void setStatusBarClickListener() { //Because the citybik.es landing page is javascript heavy if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { mStatusBar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (Utils.Connectivity.isConnected(getApplicationContext())) { // use the android system webview Intent intent = new Intent(NearbyActivity.this, WebViewActivity.class); intent.putExtra(WebViewActivity.EXTRA_URL, "http://www.citybik.es"); intent.putExtra(WebViewActivity.EXTRA_ACTIONBAR_SUBTITLE, getString(R.string.hashtag_cities)); intent.putExtra(WebViewActivity.EXTRA_JAVASCRIPT_ENABLED, true); startActivity(intent); } } }); } } @Override public void onStationMapFragmentInteraction(final Uri uri) { //Will be warned of station details click, will make info fragment to replace list fragment //Map ready if (uri.getPath().equalsIgnoreCase("/" + StationMapFragment.MAP_READY_PATH)) { long wishedUpdateTime = DBHelper.getLastUpdateTimestamp(getApplicationContext()) + NearbyActivity.this.getApplicationContext().getResources() .getInteger(R.integer.update_auto_interval_minute) * 1000 * 60; //comes from Prefs if (mDownloadWebTask == null && !( //if no download task been launched but conditions are met that one will be launched imminently, don't refresh map DBHelper.getAutoUpdate(this) && System.currentTimeMillis() >= wishedUpdateTime && Utils.Connectivity.isConnected(this))) refreshMap(); } //Marker click - ignored if onboarding is in progress else if (uri.getPath().equalsIgnoreCase("/" + StationMapFragment.MARKER_CLICK_PATH) && mOnboardingShowcaseView == null) { if (!isLookingForBike() || mStationMapFragment.getMarkerBVisibleLatLng() != null) { if (isLookingForBike()) { if (getListPagerAdapter().highlightStationForPage( uri.getQueryParameter(StationMapFragment.MARKER_CLICK_TITLE_PARAM), StationListPagerAdapter.BIKE_STATIONS)) { getListPagerAdapter().smoothScrollHighlightedInViewForPage( StationListPagerAdapter.BIKE_STATIONS, isAppBarExpanded()); mStationMapFragment.setPinOnStation(true, uri.getQueryParameter(StationMapFragment.MARKER_CLICK_TITLE_PARAM)); getListPagerAdapter().setupBTabStationARecap(getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS), mDataOutdated); if (mStationMapFragment.getMarkerBVisibleLatLng() != null) { LatLng newALatLng = mStationMapFragment.getMarkerALatLng(); getListPagerAdapter().notifyStationAUpdate(newALatLng, mCurrentUserLatLng); hideSetupShowTripDetailsWidget(); if ((getListPagerAdapter().getClosestBikeLatLng().latitude != newALatLng.latitude) && (getListPagerAdapter() .getClosestBikeLatLng().longitude != newALatLng.longitude)) { mStationMapFragment.setMapPaddingRight( (int) getResources().getDimension(R.dimen.map_fab_padding)); mAutoSelectBikeFab.show(); animateCameraToShowUserAndStation(getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS)); } } } } else { if (mAppBarLayout != null) mAppBarLayout.setExpanded(false, true); //B Tab, looking for dock final String clickedStationId = uri .getQueryParameter(StationMapFragment.MARKER_CLICK_TITLE_PARAM); setupBTabSelection(clickedStationId, false); boolean showFavoriteAddFab = false; if (!mStationMapFragment.isPickedFavoriteMarkerVisible()) { if (mStationMapFragment.isPickedPlaceMarkerVisible()) showFavoriteAddFab = true; //Don't setup the fab as it's been done in OnActivityResult else if (setupAddFavoriteFab(new FavoriteItemStation(clickedStationId, getStation(clickedStationId).getName(), true))) showFavoriteAddFab = true; } if (showFavoriteAddFab) mAddFavoriteFAB.show(); else mAddFavoriteFAB.hide(); } } else { Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.onboarding_hint_main_choice, Snackbar.LENGTH_SHORT, ContextCompat.getColor(this, R.color.theme_primary_dark)).show(); mStationListViewPager.setCurrentItem(StationListPagerAdapter.DOCK_STATIONS, true); } } } //TODO: explore refactoring with the following considerations //-stop relying on mapfragment markers visibility to branch code //Final destination is a place from the search widget //that means no markers are currently on map (due to app flow) private void setupBTabSelectionClosestDock(final Place _from) { dismissOnboardingHint(); //Remove any previous selection getListPagerAdapter().removeStationHighlightForPage(StationListPagerAdapter.DOCK_STATIONS); if (mTripDetailsWidget.getVisibility() == View.INVISIBLE) { mStationMapFragment .setMapPaddingLeft((int) getResources().getDimension(R.dimen.trip_details_widget_width)); setupTripDetailsWidget(); showTripDetailsWidget(); } else { hideSetupShowTripDetailsWidget(); } getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), true, R.drawable.ic_destination_arrow_white_24dp, R.drawable.ic_pin_search_24dp_white, "", new StationRecyclerViewAdapter.TotalTripTimeComparator(Utils.getAverageWalkingSpeedKmh(this), Utils.getAverageBikingSpeedKmh(this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), _from.getLatLng())); mStationMapFragment.setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mClearFAB.show(); mFavoritesSheetFab.hideSheetThenFab(); mSearchFAB.hide(); final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (getListPagerAdapter().isRecyclerViewReadyForItemSelection(StationListPagerAdapter.DOCK_STATIONS) && mRedrawMarkersTask == null) { //highlight B station in list //the following is why the handler is required (to let time for things to settle after calling getListPagerAdapter().setupUI) String stationId = Utils.extractClosestAvailableStationIdFromProcessedString( getListPagerAdapter().retrieveClosestRawIdAndAvailability(false)); getListPagerAdapter().hideStationRecap(); mStationMapFragment.setPinOnStation(false, stationId);//set B pin on closest station with available dock getListPagerAdapter().highlightStationForPage(stationId, StationListPagerAdapter.DOCK_STATIONS); getListPagerAdapter().setClickResponsivenessForPage(StationListPagerAdapter.BIKE_STATIONS, true); mStationMapFragment.setPinForPickedPlace(_from.getName().toString(), _from.getLatLng(), _from.getAttributions()); mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_infowindow_padding)); animateCameraToShow((int) getResources().getDimension(R.dimen.camera_search_infowindow_padding), _from.getLatLng(), mStationMapFragment.getMarkerBVisibleLatLng(), null); getListPagerAdapter().smoothScrollHighlightedInViewForPage( StationListPagerAdapter.DOCK_STATIONS, isAppBarExpanded()); } else { //This is a repost if RecyclerView is not ready for selection //hackfix. On some devices timing issues led to infinite loop with isRecyclerViewReadyForItemSelection always returning false //so, retry setting up the UI before repost //Replace recyclerview content getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), true, R.drawable.ic_destination_arrow_white_24dp, R.drawable.ic_pin_search_24dp_white, "", new StationRecyclerViewAdapter.TotalTripTimeComparator( Utils.getAverageWalkingSpeedKmh(NearbyActivity.this), Utils.getAverageBikingSpeedKmh(NearbyActivity.this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), _from.getLatLng())); //end hackfix handler.postDelayed(this, 10); } } }, 10); } //Final destination is a favorite //that means no markers are currently on map (due to app flow) private void setupBTabSelectionClosestDock(final String _favoriteId) { dismissOnboardingHint(); //Remove any previous selection getListPagerAdapter().removeStationHighlightForPage(StationListPagerAdapter.DOCK_STATIONS); //Silent parameter is ignored because widget needs refresh //TODO: find a more elegant solution than this damn _silent boolean, which is a hackfix - probably a refactor by splitting method in pieces //and call them independently as required from client if (mTripDetailsWidget.getVisibility() == View.INVISIBLE) { mStationMapFragment .setMapPaddingLeft((int) getResources().getDimension(R.dimen.trip_details_widget_width)); setupTripDetailsWidget(); showTripDetailsWidget(); } else { hideSetupShowTripDetailsWidget(); } final FavoriteItemBase favorite = DBHelper.getFavoriteItemForId(this, _favoriteId); getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), true, R.drawable.ic_destination_arrow_white_24dp, R.drawable.ic_pin_favorite_24dp_white, "", new StationRecyclerViewAdapter.TotalTripTimeComparator(Utils.getAverageWalkingSpeedKmh(this), Utils.getAverageBikingSpeedKmh(this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), favorite.getLocation() != null ? favorite.getLocation() : getLatLngForStation(favorite.getId()))); mStationMapFragment.setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mClearFAB.show(); mFavoritesSheetFab.hideSheetThenFab(); mSearchFAB.hide(); final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (getListPagerAdapter() .isRecyclerViewReadyForItemSelection(StationListPagerAdapter.DOCK_STATIONS)) { //highlight B station in list //the following is why the handler is required (to let time for things to settle after calling getListPagerAdapter().setupUI) String stationId = Utils.extractClosestAvailableStationIdFromProcessedString( getListPagerAdapter().retrieveClosestRawIdAndAvailability(false)); getListPagerAdapter().hideStationRecap(); mStationMapFragment.setPinOnStation(false, stationId);//set B pin on closest station with available dock getListPagerAdapter().highlightStationForPage(stationId, StationListPagerAdapter.DOCK_STATIONS); getListPagerAdapter().setClickResponsivenessForPage(StationListPagerAdapter.BIKE_STATIONS, true); if (!stationId.equalsIgnoreCase(favorite.getId())) { //This is a three legged journey (either to a favorite station that has no dock or a place) LatLng location = favorite.getLocation() != null ? favorite.getLocation() : getLatLngForStation(favorite.getId()); mStationMapFragment.setPinForPickedFavorite(favorite.getDisplayName(), location, favorite.getAttributions()); mStationMapFragment.setMapPaddingRight( (int) getResources().getDimension(R.dimen.map_infowindow_padding)); animateCameraToShow( (int) getResources().getDimension(R.dimen.camera_search_infowindow_padding), location, mStationMapFragment.getMarkerBVisibleLatLng(), null); } else //trip to a favorite station that has docks { mStationMapFragment .setPinForPickedFavorite(favorite.getDisplayName(), favorite.getLocation() != null ? favorite.getLocation() : getLatLngForStation(favorite.getId()), favorite.getAttributions()); mStationMapFragment.animateCamera(CameraUpdateFactory .newLatLngZoom(mStationMapFragment.getMarkerBVisibleLatLng(), 15)); } getListPagerAdapter().smoothScrollHighlightedInViewForPage( StationListPagerAdapter.DOCK_STATIONS, isAppBarExpanded()); } else { //This is a repost if RecyclerView is not ready for selection //hackfix. On some devices timing issues led to infinite loop with isRecyclerViewReadyForItemSelection always returning false //so, retry stting up the UI before repost //Replace recyclerview content getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), true, R.drawable.ic_destination_arrow_white_24dp, R.drawable.ic_pin_favorite_24dp_white, "", new StationRecyclerViewAdapter.TotalTripTimeComparator( Utils.getAverageWalkingSpeedKmh(NearbyActivity.this), Utils.getAverageBikingSpeedKmh(NearbyActivity.this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), favorite.getLocation() != null ? favorite.getLocation() : getLatLngForStation(favorite.getId()))); //end hackfix handler.postDelayed(this, 10); } } }, 10); } private void setupBTabSelection(final String _selectedStationId, final boolean _silent) { dismissOnboardingHint(); //Remove any previous selection getListPagerAdapter().removeStationHighlightForPage(StationListPagerAdapter.DOCK_STATIONS); if (mTripDetailsWidget.getVisibility() == View.INVISIBLE) { mStationMapFragment .setMapPaddingLeft((int) getResources().getDimension(R.dimen.trip_details_widget_width)); setupTripDetailsWidget(); showTripDetailsWidget(); } else { hideSetupShowTripDetailsWidget(); } final StationItem selectedStation = getStation(_selectedStationId); getListPagerAdapter().hideStationRecap(); mStationMapFragment.setPinOnStation(false, _selectedStationId); getListPagerAdapter().setClickResponsivenessForPage(StationListPagerAdapter.BIKE_STATIONS, true); if (!mFavoritePicked) mStationMapFragment.clearMarkerPickedFavorite(); if (mStationMapFragment.isPickedPlaceMarkerVisible() || mStationMapFragment.isPickedFavoriteMarkerVisible()) { LatLng locationToShow; if (mStationMapFragment.isPickedPlaceMarkerVisible()) locationToShow = mStationMapFragment.getMarkerPickedPlaceVisibleLatLng(); else locationToShow = mStationMapFragment.getMarkerPickedFavoriteVisibleLatLng(); if (!_silent) { if (locationToShow.latitude != selectedStation.getLocation().latitude || locationToShow.longitude != selectedStation.getLocation().longitude) { mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_infowindow_padding)); animateCameraToShow((int) getResources().getDimension(R.dimen.camera_search_infowindow_padding), selectedStation.getLocation(), //getLatLngForStation(_selectedStationId), locationToShow, null); } else { mStationMapFragment .animateCamera(CameraUpdateFactory.newLatLngZoom(selectedStation.getLocation(), 15)); } } getListPagerAdapter().highlightStationForPage(_selectedStationId, StationListPagerAdapter.DOCK_STATIONS); getListPagerAdapter().smoothScrollHighlightedInViewForPage(StationListPagerAdapter.DOCK_STATIONS, isAppBarExpanded()); } else { //it's just an A-B trip getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), false, null, null, "", new StationRecyclerViewAdapter.TotalTripTimeComparator(Utils.getAverageWalkingSpeedKmh(this), Utils.getAverageBikingSpeedKmh(this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), selectedStation.getLocation())); if (!mFavoritePicked) { FavoriteItemBase fav = DBHelper.getFavoriteItemForId(this, _selectedStationId); if (fav != null) mStationMapFragment.setPinForPickedFavorite(fav.getDisplayName(), getLatLngForStation(_selectedStationId), null); } if (!_silent) { mStationMapFragment.setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mClearFAB.show(); mFavoritesSheetFab.hideSheetThenFab(); mSearchFAB.hide(); } final Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { if (getListPagerAdapter() .isRecyclerViewReadyForItemSelection(StationListPagerAdapter.DOCK_STATIONS)) { //highlight B station in list getListPagerAdapter().highlightStationForPage(_selectedStationId, StationListPagerAdapter.DOCK_STATIONS); if (!_silent && mStationMapFragment.getMarkerBVisibleLatLng() != null) mStationMapFragment.animateCamera(CameraUpdateFactory .newLatLngZoom(mStationMapFragment.getMarkerBVisibleLatLng(), 15)); getListPagerAdapter().smoothScrollHighlightedInViewForPage( StationListPagerAdapter.DOCK_STATIONS, isAppBarExpanded()); } else { //This is a repost if RecyclerView is not ready for selection //hackfix. On some devices timing issues led to infinite loop with isRecyclerViewReadyForItemSelection always returning false //so, retry stting up the UI before repost //Replace recyclerview content getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, RootApplication.getBikeNetworkStationList(), false, null, null, "", new StationRecyclerViewAdapter.TotalTripTimeComparator( Utils.getAverageWalkingSpeedKmh(NearbyActivity.this), Utils.getAverageBikingSpeedKmh(NearbyActivity.this), mCurrentUserLatLng, mStationMapFragment.getMarkerALatLng(), selectedStation.getLocation())); //end hackfix handler.postDelayed(this, 10); } } }, 10); } } private void dismissOnboardingHint() { if (mOnboardingSnackBar != null) { mOnboardingSnackBar.dismiss(); mOnboardingSnackBar = null; } } //Assumption here is that there is an A and a B station selected (or soon will be) private void setupTripDetailsWidget() { final Handler handler = new Handler(); //Need to wait for list selection handler.postDelayed(new Runnable() { @Override public void run() { if (getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.DOCK_STATIONS) != null && getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS) != null) { int locToAMinutes = 0; int AToBMinutes = 0; int BToSearchMinutes = 0; StationItem selectedStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); String formattedProximityString = Utils.getWalkingProximityString(selectedStation.getLocation(), mCurrentUserLatLng, true, null, NearbyActivity.this); if (formattedProximityString.startsWith(">")) locToAMinutes = 61; else if (!formattedProximityString.startsWith("<")) locToAMinutes = Integer.valueOf(formattedProximityString.substring(1, 3)); mTripDetailsProximityA.setText(formattedProximityString); selectedStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.DOCK_STATIONS); formattedProximityString = Utils.getBikingProximityString(selectedStation.getLocation(), getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS) .getLocation(), true, null, NearbyActivity.this); if (formattedProximityString.startsWith(">")) AToBMinutes = 61; else if (!formattedProximityString.startsWith("<")) AToBMinutes = Integer.valueOf(formattedProximityString.substring(1, 3)); mTripDetailsProximityB.setText(formattedProximityString); //TODO: this string of if...elseif...elseif...else needs refactoring. // Explore extract methods or create some kind of tripdetailswidget configurator if (mStationMapFragment.getMarkerPickedPlaceVisibleLatLng() == null && mStationMapFragment.getMarkerPickedFavoriteVisibleLatLng() == null) { //no marker is showed mTripDetailsBToDestinationRow.setVisibility(View.GONE); ViewGroup.LayoutParams param = mTripDetailsSumSeparator.getLayoutParams(); ((RelativeLayout.LayoutParams) param).addRule(RelativeLayout.BELOW, R.id.trip_details_a_to_b); } else if (mStationMapFragment.getMarkerPickedPlaceVisibleLatLng() != null) { //Place marker is showed formattedProximityString = Utils.getWalkingProximityString(selectedStation.getLocation(), mStationMapFragment.getMarkerPickedPlaceVisibleLatLng(), true, null, NearbyActivity.this); if (formattedProximityString.startsWith(">")) BToSearchMinutes = 61; else if (!formattedProximityString.startsWith("<")) BToSearchMinutes = Integer.valueOf(formattedProximityString.substring(1, 3)); mTripDetailsProximitySearch.setText(formattedProximityString); mTripDetailsPinSearch.setVisibility(View.VISIBLE); mTripDetailsPinFavorite.setVisibility(View.INVISIBLE); mTripDetailsBToDestinationRow.setVisibility(View.VISIBLE); ViewGroup.LayoutParams param = mTripDetailsSumSeparator.getLayoutParams(); ((RelativeLayout.LayoutParams) param).addRule(RelativeLayout.BELOW, R.id.trip_details_b_to_search); } else if (mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().latitude != mStationMapFragment .getMarkerBVisibleLatLng().latitude || mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().longitude != mStationMapFragment .getMarkerBVisibleLatLng().longitude) { //Favorite marker is showed and not on B station formattedProximityString = Utils.getWalkingProximityString(selectedStation.getLocation(), mStationMapFragment.getMarkerPickedFavoriteVisibleLatLng(), true, null, NearbyActivity.this); if (formattedProximityString.startsWith(">")) BToSearchMinutes = 61; else if (!formattedProximityString.startsWith("<")) BToSearchMinutes = Integer.valueOf(formattedProximityString.substring(1, 3)); mTripDetailsProximitySearch.setText(formattedProximityString); mTripDetailsPinSearch.setVisibility(View.INVISIBLE); mTripDetailsPinFavorite.setVisibility(View.VISIBLE); mTripDetailsBToDestinationRow.setVisibility(View.VISIBLE); ViewGroup.LayoutParams param = mTripDetailsSumSeparator.getLayoutParams(); ((RelativeLayout.LayoutParams) param).addRule(RelativeLayout.BELOW, R.id.trip_details_b_to_search); } else { //Favorite marker is showed and on B station mTripDetailsBToDestinationRow.setVisibility(View.GONE); ViewGroup.LayoutParams param = mTripDetailsSumSeparator.getLayoutParams(); ((RelativeLayout.LayoutParams) param).addRule(RelativeLayout.BELOW, R.id.trip_details_a_to_b); } int total = locToAMinutes + AToBMinutes + BToSearchMinutes; mTripDetailsProximityTotal .setText(Utils.durationToProximityString(total, false, null, NearbyActivity.this)); } else handler.postDelayed(this, 10); } }, 10); } //For reusable Animators (which most Animators are, apart from the one-shot animator produced by createCircularReveal() private Animator buildTripDetailsWidgetAnimators(boolean _show, long _duration, float _minRadiusMultiplier) { float minRadiusMultiplier = Math.min(1.f, _minRadiusMultiplier); Animator toReturn = null; // Use native circular reveal on Android 5.0+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Native circular reveal uses coordinates relative to the view int revealStartX = 0; int revealStartY = mTripDetailsWidget.getHeight(); float radiusMax = (float) Math.hypot(mTripDetailsWidget.getHeight(), mTripDetailsWidget.getWidth()); float radiusMin = radiusMax * minRadiusMultiplier; if (_show) { toReturn = ViewAnimationUtils.createCircularReveal(mTripDetailsWidget, revealStartX, revealStartY, radiusMin, radiusMax); } else { toReturn = ViewAnimationUtils.createCircularReveal(mTripDetailsWidget, revealStartX, revealStartY, radiusMax, radiusMin); } toReturn.setDuration(_duration); toReturn.setInterpolator(mCircularRevealInterpolator); } return toReturn; } private void showTripDetailsWidget() { mTripDetailsWidget.setVisibility(View.VISIBLE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { try { buildTripDetailsWidgetAnimators(true, getResources().getInteger(R.integer.camera_animation_duration), 0).start(); } catch (IllegalStateException e) { Log.i("NearbyActivity", "Trip widget show animation end encountered some trouble, skipping", e); } } } private void hideSetupShowTripDetailsWidget() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Animator hideAnimator = buildTripDetailsWidgetAnimators(false, getResources().getInteger(R.integer.camera_animation_duration) / 2, .23f); hideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); try { setupTripDetailsWidget(); buildTripDetailsWidgetAnimators(true, getResources().getInteger(R.integer.camera_animation_duration) / 2, .23f).start(); } catch (IllegalStateException e) { Log.i("NearbyActivity", "Trip widget hide animation end encountered some trouble, skipping", e); } } }); hideAnimator.start(); } else { setupTripDetailsWidget(); } } private void hideTripDetailsWidget() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Animator hideAnimator = buildTripDetailsWidgetAnimators(false, getResources().getInteger(R.integer.camera_animation_duration), 0); // make the view invisible when the animation is done hideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); mTripDetailsWidget.setVisibility(View.INVISIBLE); } }); hideAnimator.start(); } else mTripDetailsWidget.setVisibility(View.INVISIBLE); } private void clearBTab() { getListPagerAdapter().removeStationHighlightForPage(StationListPagerAdapter.DOCK_STATIONS); getListPagerAdapter().setupUI(StationListPagerAdapter.DOCK_STATIONS, new ArrayList<StationItem>(), false, null, null, getString(R.string.b_tab_question), null); mStationMapFragment.clearMarkerB(); mStationMapFragment.clearMarkerPickedPlace(); mStationMapFragment.clearMarkerPickedFavorite(); //A TAB getListPagerAdapter().setClickResponsivenessForPage(StationListPagerAdapter.BIKE_STATIONS, false); if (!isLookingForBike()) { mStationMapFragment .animateCamera(CameraUpdateFactory.newLatLngZoom(mStationMapFragment.getMarkerALatLng(), 13)); mFavoritesSheetFab.showFab(); if (Utils.Connectivity.isConnected(NearbyActivity.this)) mSearchFAB.show(); mClearFAB.hide(); mAddFavoriteFAB.hide(); } else { StationItem highlightedStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); animateCameraToShowUserAndStation(highlightedStation); } } private boolean isLookingForBike() { return mStationListViewPager.getCurrentItem() == StationListPagerAdapter.BIKE_STATIONS; } private LatLng getLatLngForStation(String _stationId) { LatLng toReturn = null; StationItem station = getStation(_stationId); if (station != null) toReturn = station.getLocation(); return toReturn; } private StationItem getStation(String _stationId) { StationItem toReturn = null; ArrayList<StationItem> networkStationList = RootApplication.getBikeNetworkStationList(); for (StationItem station : networkStationList) { if (station.getId().equalsIgnoreCase(_stationId)) { toReturn = station; break; } } return toReturn; } private void cancelDownloadWebTask() { if (mDownloadWebTask != null && !mDownloadWebTask.isCancelled()) { mDownloadWebTask.cancel(false); mDownloadWebTask = null; } } @Override public void onStationListFragmentInteraction(final Uri uri) { if (uri.getPath().equalsIgnoreCase("/" + StationListFragment.STATION_LIST_ITEM_CLICK_PATH)) { if (!isLookingForBike() || mStationMapFragment.getMarkerBVisibleLatLng() != null) { //if null, means the station was clicked twice, hence unchecked final StationItem clickedStation = getListPagerAdapter() .getHighlightedStationForPage(mTabLayout.getSelectedTabPosition()); if (isLookingForBike()) { if (mStationMapFragment.getMarkerBVisibleLatLng() != null) { LatLng newALatLng = clickedStation.getLocation(); getListPagerAdapter().notifyStationAUpdate(newALatLng, mCurrentUserLatLng); mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mAutoSelectBikeFab.show(); animateCameraToShowUserAndStation(getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS)); hideSetupShowTripDetailsWidget(); } else { animateCameraToShowUserAndStation(clickedStation); } mStationMapFragment.setPinOnStation(true, clickedStation.getId()); getListPagerAdapter().setupBTabStationARecap(clickedStation, mDataOutdated); } else { if (mStationMapFragment.isPickedFavoriteMarkerVisible()) { if (clickedStation.getLocation().latitude != mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().latitude || clickedStation.getLocation().longitude != mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().longitude) { mStationMapFragment.pickedFavoriteMarkerInfoWindowShow(); } else { mStationMapFragment.pickedFavoriteMarkerInfoWindowHide(); } } setupBTabSelection(clickedStation.getId(), false); FavoriteItemStation newFavForStation = new FavoriteItemStation(clickedStation.getId(), clickedStation.getName(), true); boolean showFavoriteAddFab = false; if (!mStationMapFragment.isPickedFavoriteMarkerVisible()) { if (mStationMapFragment.isPickedPlaceMarkerVisible()) showFavoriteAddFab = true; //Don't setup the fab as it's been done in OnActivityResult else if (setupAddFavoriteFab(newFavForStation)) showFavoriteAddFab = true; } if (showFavoriteAddFab) mAddFavoriteFAB.show(); else mAddFavoriteFAB.hide(); } } } else if (uri.getPath() .equalsIgnoreCase("/" + StationListFragment.STATION_LIST_INACTIVE_ITEM_CLICK_PATH)) { mStationListViewPager.setCurrentItem(StationListPagerAdapter.DOCK_STATIONS, true); setupHintMainChoice(); } else if (uri.getPath().equalsIgnoreCase("/" + StationListFragment.STATION_LIST_FAVORITE_FAB_CLICK_PATH)) { StationItem clickedStation = getListPagerAdapter() .getHighlightedStationForPage(mTabLayout.getSelectedTabPosition()); if (null != clickedStation) { boolean newState = !clickedStation.isFavorite(this); if (newState) { if (mOnboardingShowcaseView != null) { mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } if (mStationMapFragment.getMarkerPickedPlaceVisibleName().isEmpty()) addFavorite(clickedStation.getFavoriteItemForDisplayName(clickedStation.getName()), false, false); else { //there's a third destination addFavorite(clickedStation.getFavoriteItemForDisplayName( mStationMapFragment.getMarkerPickedPlaceVisibleName()), false, false); } mFavoritesSheetFab.scrollToTop(); } else { removeFavorite(DBHelper.getFavoriteItemForId(this, clickedStation.getId()), false); } } } else if (uri.getPath() .equalsIgnoreCase("/" + StationListFragment.STATION_LIST_DIRECTIONS_FAB_CLICK_PATH)) { //http://stackoverflow.com/questions/6205827/how-to-open-standard-google-map-application-from-my-application final StationItem curSelectedStation = getListPagerAdapter() .getHighlightedStationForPage(mTabLayout.getSelectedTabPosition()); // Seen NullPointerException in crash report. if (null != curSelectedStation) { LatLng tripLegOrigin = isLookingForBike() ? mCurrentUserLatLng : mStationMapFragment.getMarkerALatLng(); LatLng tripLegDestination = curSelectedStation.getLocation(); boolean walkMode = isLookingForBike(); launchGoogleMapsForDirections(tripLegOrigin, tripLegDestination, walkMode); } } } private void launchGoogleMapsForDirections(LatLng _origin, LatLng _destination, boolean _walking) { StringBuilder builder = new StringBuilder("http://maps.google.com/maps?&saddr="); builder.append(_origin.latitude).append(",").append(_origin.longitude); builder.append("&daddr=").append(_destination.latitude).append(",").append(_destination.longitude). //append("B"). Labeling doesn't work :'( append("&dirflg="); if (_walking) builder.append("w"); else builder.append("b"); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(builder.toString())); intent.setClassName("com.google.android.apps.maps", "com.google.android.maps.MapsActivity"); if (getPackageManager().queryIntentActivities(intent, 0).size() > 0) { startActivity(intent); // launch the map activity } else { Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.google_maps_not_installed, Snackbar.LENGTH_SHORT, ContextCompat.getColor(this, R.color.theme_primary_dark)).show(); } } private boolean isAppBarExpanded() { return mAppBarLayout.getHeight() - mAppBarLayout.getBottom() == 0; } private void animateCameraToShowUserAndStation(StationItem station) { if (mCurrentUserLatLng != null) { if (mTripDetailsWidget.getVisibility() != View.VISIBLE) //Directions to A fab is visible animateCameraToShow((int) getResources().getDimension(R.dimen.camera_fab_padding), station.getLocation(), mCurrentUserLatLng, null); else //Map id padded on the left and interface is clear on the right animateCameraToShow((int) getResources().getDimension(R.dimen.camera_ab_pin_padding), station.getLocation(), mCurrentUserLatLng, null); } else { mStationMapFragment.animateCamera(CameraUpdateFactory.newLatLngZoom(station.getLocation(), 15)); } } //TODO: refactor this method such as //-passing only one valid LatLng leads to a regular animateCamera //-passing identical LatLng leads to a regular animateCamera, maybe with client code provided zoom level or a default one private void animateCameraToShow(int _cameraPaddingPx, LatLng _latLng0, LatLng _latLng1, LatLng _latLng2) { LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); boundsBuilder.include(_latLng0).include(_latLng1); if (_latLng2 != null) boundsBuilder.include(_latLng2); mStationMapFragment .animateCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), _cameraPaddingPx)); //Pin icon is 36 dp } //Callback from pull-to-refresh @Override public void onRefresh() { if (mDownloadWebTask == null) { mDownloadWebTask = new DownloadWebTask(); mDownloadWebTask.execute(); } } private StationListPagerAdapter getListPagerAdapter() { return (StationListPagerAdapter) mStationListViewPager.getAdapter(); } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { } @Override public void onPageSelected(final int position) { StationItem stationA = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); if (stationA != null) { if (!getListPagerAdapter().setupBTabStationARecap(stationA, mDataOutdated)) mClosestBikeAutoSelected = false; //to handle rapid multiple screen orientation change } //Happens on screen orientation change if (mStationMapFragment == null || (mStationMapFragment.getMarkerBVisibleLatLng() != null && getListPagerAdapter().getHighlightedStationForPage(position) == null) || !mStationMapFragment.isMapReady()) { Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { onPageSelected(position); } }, 1000); //second long delay gives a nice UX with camera animation } else { StationItem highlightedStation = getListPagerAdapter().getHighlightedStationForPage(position); //A TAB if (position == StationListPagerAdapter.BIKE_STATIONS) { dismissOnboardingHint(); mStationMapFragment.setScrollGesturesEnabled(false); if (mStationMapFragment.getMarkerBVisibleLatLng() == null) { mStationMapFragment.setMapPaddingLeft(0); hideTripDetailsWidget(); mDirectionsLocToAFab.show(); } mAppBarLayout.setExpanded(false, true); getListPagerAdapter().smoothScrollHighlightedInViewForPage(position, true); mSearchFAB.hide(); mAddFavoriteFAB.hide(); mFavoritesSheetFab.hideSheetThenFab(); mClearFAB.hide(); mStationMapFragment.setMapPaddingRight(0); if (!isStationAClosestBike()) { mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mAutoSelectBikeFab.show(); } //just to be on the safe side if (highlightedStation != null) { mStationMapFragment.setPinOnStation(true, highlightedStation.getId()); animateCameraToShowUserAndStation(highlightedStation); //if mDataOutdated is true, a Download task will be launched if auto update is also true and a connection is available //That's because autoupdate max interval is SMALLER than outdating one if (!(mDataOutdated && DBHelper.getAutoUpdate(this) && Utils.Connectivity.isConnected(this))) mStationMapFragment.lookingForBikes(mDataOutdated, true); } } else { //B TAB mAutoSelectBikeFab.hide(); mStationMapFragment.setMapPaddingRight(0); //TODO: Should I lock that for regular users ? mStationMapFragment.setScrollGesturesEnabled(true); mAppBarLayout.setExpanded(true, true); if (mStationMapFragment.getMarkerBVisibleLatLng() == null) { //check if showcasing should happen, if not check if hint should happen if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); mStationMapFragment.animateCamera( CameraUpdateFactory.newLatLngZoom(mStationMapFragment.getMarkerALatLng(), 13.75f)); if (mPlaceAutocompleteLoadingProgressBar.getVisibility() != View.GONE) { mSearchFAB.show(); mSearchFAB.setBackgroundTintList(ContextCompat.getColorStateList(this, R.color.light_gray)); } else { mDirectionsLocToAFab.hide(); mFavoritesSheetFab.showFab(); if (Utils.Connectivity.isConnected(NearbyActivity.this)) mSearchFAB.show(); } mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); } else { getListPagerAdapter().smoothScrollHighlightedInViewForPage(position, false); mStationMapFragment.setMapPaddingLeft( (int) getResources().getDimension(R.dimen.trip_details_widget_width)); if (mTripDetailsWidget.getVisibility() == View.INVISIBLE) { showTripDetailsWidget(); } mStationMapFragment .setMapPaddingRight((int) getResources().getDimension(R.dimen.map_fab_padding)); mClearFAB.show(); LatLng locationToShow = null; if (mStationMapFragment.isPickedPlaceMarkerVisible()) { locationToShow = mStationMapFragment.getMarkerPickedPlaceVisibleLatLng(); mAddFavoriteFAB.show(); } else if (mStationMapFragment.isPickedFavoriteMarkerVisible() && (mStationMapFragment .getMarkerBVisibleLatLng().latitude != mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().latitude || mStationMapFragment.getMarkerBVisibleLatLng().longitude != mStationMapFragment .getMarkerPickedFavoriteVisibleLatLng().longitude)) locationToShow = mStationMapFragment.getMarkerPickedFavoriteVisibleLatLng(); else if (!mStationMapFragment.isPickedFavoriteMarkerVisible() && DBHelper.getFavoriteItemForId(this, highlightedStation.getId()) == null) mAddFavoriteFAB.show(); if (locationToShow != null) { mStationMapFragment.setMapPaddingRight( (int) getResources().getDimension(R.dimen.map_infowindow_padding)); animateCameraToShow( (int) getResources().getDimension(R.dimen.camera_search_infowindow_padding), mStationMapFragment.getMarkerBVisibleLatLng(), locationToShow, null); } else mStationMapFragment.animateCamera(CameraUpdateFactory .newLatLngZoom(mStationMapFragment.getMarkerBVisibleLatLng(), 15)); } //Log.d("NearbyActivity", "onPageSelected - about to update markers with mDataOutdated : " + mDataOutdated, new Exception()); //if mDataOutdated is true, a Download task will be launched if auto update is also true and a connection is available //That's because autoupdate max interval is SMALLER than outdating one if (!(mDataOutdated && DBHelper.getAutoUpdate(this) && Utils.Connectivity.isConnected(this))) mStationMapFragment.lookingForBikes(mDataOutdated, false); } } } @Override public void onPageScrollStateChanged(int state) { //pager will always transition from tab A to tab B on app launch if (state == ViewPager.SCROLL_STATE_IDLE && mSplashScreen.isShown()) { //If update mode is set to manual and user are out of the bound of the bike network //let's check if there's a better bike network avaialble. if (!DBHelper.getAutoUpdate(this) && Utils.Connectivity.isConnected(NearbyActivity.this)) { if (mCurrentUserLatLng != null && !DBHelper.getBikeNetworkBounds(NearbyActivity.this, 5).contains(mCurrentUserLatLng)) { //This task will possibly auto cancel if it can't find a better bike network mFindNetworkTask = new FindNetworkTask(DBHelper.getBikeNetworkName(NearbyActivity.this)); mFindNetworkTask.execute(); } } if (mFindNetworkTask == null) mSplashScreen.setVisibility(View.GONE); } } //Google API client @Override public void onConnected(Bundle bundle) { startLocationUpdates(); } //Google API client @Override public void onConnectionSuspended(int i) { } //Google API client @Override public void onConnectionFailed(@NonNull ConnectionResult connectionResult) { } @Override public void onLocationChanged(Location location) { if (mCurrentUserLatLng != null && mCurrentUserLatLng.latitude == location.getLatitude() && mCurrentUserLatLng.longitude == location.getLongitude()) return; mCurrentUserLatLng = new LatLng(location.getLatitude(), location.getLongitude()); boolean highlightedStationAVisibleInRecyclerViewBefore = getListPagerAdapter() .isHighlightedVisibleInRecyclerView(); getListPagerAdapter().setCurrentUserLatLng(mCurrentUserLatLng); if (mStationMapFragment != null) { mStationMapFragment.onUserLocationChange(location); if (mStationMapFragment.getMarkerBVisibleLatLng() != null && mTripDetailsWidget.getVisibility() == View.VISIBLE) setupTripDetailsWidget(); } if (highlightedStationAVisibleInRecyclerViewBefore && !getListPagerAdapter().isHighlightedVisibleInRecyclerView()) getListPagerAdapter().smoothScrollHighlightedInViewForPage(StationListPagerAdapter.BIKE_STATIONS, true); } private boolean isStationAClosestBike() { String stationAId = mStationMapFragment.getMarkerAStationId(); String closestBikeId = Utils.extractClosestAvailableStationIdFromProcessedString( getListPagerAdapter().retrieveClosestRawIdAndAvailability(true)); return stationAId.equalsIgnoreCase(closestBikeId); } @Override public void onFavoriteListItemClick(String _favoriteID) { StationItem stationA = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); if (stationA.getId().equalsIgnoreCase(_favoriteID)) { Utils.Snackbar.makeStyled(mCoordinatorLayout, R.string.such_short_trip, Snackbar.LENGTH_SHORT, ContextCompat.getColor(this, R.color.theme_primary_dark)).show(); } else { mFavoritePicked = true; setupBTabSelectionClosestDock(_favoriteID); } } @Override public void onFavoristeListItemNameEditBegin() { mFavoritesSheetFab.hideEditFab(); mFavoriteItemEditInProgress = true; } @Override public void onFavoristeListItemNameEditAbort() { mFavoritesSheetFab.showEditFab(); mFavoriteItemEditInProgress = false; } @Override public void onFavoriteListItemDelete(String _favoriteId) { removeFavorite(DBHelper.getFavoriteItemForId(this, _favoriteId), true); } @Override public void onFavoristeListItemNameEditDone(String _favoriteId, String _newName) { if (!_favoriteId.startsWith(FavoriteItemPlace.PLACE_ID_PREFIX)) { DBHelper.updateFavorite(true, getStation(_favoriteId).getFavoriteItemForDisplayName(_newName), this); StationItem closestBikeStation = getListPagerAdapter() .getHighlightedStationForPage(StationListPagerAdapter.BIKE_STATIONS); getListPagerAdapter().setupBTabStationARecap(closestBikeStation, mDataOutdated); getListPagerAdapter().notifyStationChangedAll(_favoriteId); } else { DBHelper.updateFavorite(true, new FavoriteItemPlace(DBHelper.getFavoriteItemForId(this, _favoriteId), _newName), this); } mFavoritesSheetFab.showEditFab(); mFavoriteRecyclerViewAdapter.setupFavoriteList(DBHelper.getFavoriteAll(this)); mFavoriteItemEditInProgress = false; } @Override public void onFavoriteListItemStartDrag(RecyclerView.ViewHolder _viewHolder) { mFavoriteItemTouchHelper.startDrag(_viewHolder); } @Override public void onFavoriteSheetEditDone() { ArrayList<FavoriteItemBase> newlyOrderedFavList = new ArrayList<>(); newlyOrderedFavList.addAll(mFavoriteRecyclerViewAdapter.getCurrentFavoriteList()); DBHelper.dropFavoriteAll(this); mFavoriteRecyclerViewAdapter.clearFavoriteList(); ListIterator<FavoriteItemBase> li = newlyOrderedFavList.listIterator(newlyOrderedFavList.size()); while (li.hasPrevious()) { addFavorite(li.previous(), true, false); } } @Override public void onFavoriteSheetEditCancel() { mFavoriteRecyclerViewAdapter.setupFavoriteList(DBHelper.getFavoriteAll(this)); } private class RedrawMarkersTask extends AsyncTask<Boolean, Void, Void> { /*public RedrawMarkersTask(){ Log.d("NearbyActivity", "redraw markers construction, mDataOutdated : " + mDataOutdated, new Exception()); }*/ @Override protected void onPreExecute() { super.onPreExecute(); mStatusTextView.setText(getString(R.string.refreshing_map)); mSplashScreenTextBottom.setText(getString(R.string.refreshing_map)); mStationMapFragment.hideAllStations(); } @Override protected Void doInBackground(Boolean... bools) { //This improves the UX by giving time to the listview to render try { Thread.sleep(250); } catch (InterruptedException e) { e.printStackTrace(); } //Log.d("NearbyActivity", "redraw markers doInBackground, outdated : " + bools[0], new Exception()); mStationMapFragment.clearMarkerGfxData(); //SETUP MARKERS DATA ArrayList<StationItem> networkStationList = RootApplication.getBikeNetworkStationList(); for (StationItem item : networkStationList) { mStationMapFragment.addMarkerForStationItem(bools[0], item, bools[1]); } return null; } @Override protected void onCancelled(Void aVoid) { //super.onCancelled(aVoid); //https://developer.android.com/reference/android/os/AsyncTask.html#onCancelled(Result) //" If you write your own implementation, do not call super.onCancelled(result)." mRefreshMarkers = true; mRedrawMarkersTask = null; mStationMapFragment.showAllStations(); } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); mStationMapFragment.redrawMarkers(); if (getListPagerAdapter().isViewPagerReady()) { StationItem highlighted = getListPagerAdapter() .getHighlightedStationForPage(mTabLayout.getSelectedTabPosition()); if (null != highlighted) mStationMapFragment.setPinOnStation(isLookingForBike(), highlighted.getId()); } mRedrawMarkersTask = null; mStationMapFragment.showAllStations(); } } public class FindNetworkTask extends AsyncTask<Void, Void, Map<String, String>> { private static final String NEW_YORK_HUDSON_BIKESHARE_ID = "hudsonbikeshare-hoboken"; String mOldBikeNetworkName = ""; FindNetworkTask(String _currentNetworkName) { mOldBikeNetworkName = _currentNetworkName; } private static final String ERROR_KEY_NO_BETTER = "NO_BETTER_NETWORK"; private static final String ERROR_KEY_IOEXCEPTION = "IOEXCEPTION"; @Override protected void onPreExecute() { super.onPreExecute(); mStatusTextView.setText(getString(R.string.searching_wait_location)); mSplashScreenTextBottom.setText(getString(R.string.searching_wait_location)); if (getListPagerAdapter().isViewPagerReady()) getListPagerAdapter().setRefreshingAll(true); } @Override protected void onCancelled(Map<String, String> _result) { //super.onCancelled(aVoid); //https://developer.android.com/reference/android/os/AsyncTask.html#onCancelled(Result) //" If you write your own implementation, do not call super.onCancelled(result)." getListPagerAdapter().setRefreshingAll(false); //This was initial setup if (!DBHelper.isBikeNetworkIdAvailable(NearbyActivity.this)) { mSplashScreenTextTop.setText(getString(R.string.sad_emoji)); mSplashScreenTextBottom.setText(""); Utils.Snackbar .makeStyled(mSplashScreen, R.string.connectivity_rationale, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { tryInitialSetup(); } }).show(); } else if (_result.containsKey(ERROR_KEY_IOEXCEPTION)) { //HTTP session ran into troubles Utils.Snackbar .makeStyled(mCoordinatorLayout, R.string.auto_download_failed, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.resume, new View.OnClickListener() { @Override public void onClick(View v) { DBHelper.resumeAutoUpdate(); } }).show(); } else {//_result.containsValue(ERROR_VALUE_NO_BETTER) if (mSplashScreen.isShown()) { mSplashScreen.setVisibility(View.GONE); } mClosestBikeAutoSelected = false; mDataOutdated = false; mStatusBar.setBackgroundColor( ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)); getListPagerAdapter().setOutdatedDataAll(false); mRefreshMarkers = true; mRefreshTabs = true; refreshMap(); if (mSaveNetworkToDatabaseTask == null) { //new SaveNetworkToDatabaseTask().execute(); //Saving to database executes in parallel. Maybe a service should be used in place mSaveNetworkToDatabaseTask = new SaveNetworkToDatabaseTask(); mSaveNetworkToDatabaseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } mFindNetworkTask = null; } @Override protected Map<String, String> doInBackground(Void... voids) { //noinspection StatementWithEmptyBody while (!getListPagerAdapter().isViewPagerReady()) { //Waiting on viewpager init } publishProgress(); //This mambo jambo because a NullPointerException on mCurrentUserLatLng been seen on Galaxy Nexus /////////////////////// LatLng userLoc; final LatLng finalUserLoc; while (true) { userLoc = mCurrentUserLatLng; //Waiting on location if (userLoc != null) { finalUserLoc = userLoc; break; } } //////////////////////// publishProgress(); Map<String, String> toReturn = new HashMap<>(); Citybik_esAPI api = ((RootApplication) getApplication()).getCitybik_esApi(); final Call<ListNetworksAnswerRoot> call = api.listNetworks(); Response<ListNetworksAnswerRoot> listAnswer; try { listAnswer = call.execute(); ArrayList<NetworkDesc> answerList = listAnswer.body().networks; Collections.sort(answerList, new Comparator<NetworkDesc>() { @Override public int compare(NetworkDesc networkDesc, NetworkDesc t1) { //NullPointerException on mCurrentUserLatLng been seen on Galaxy Nexus return (int) (networkDesc.getMeterFromLatLng(finalUserLoc) - t1.getMeterFromLatLng(finalUserLoc)); } }); NetworkDesc closestNetwork = answerList.get(0); if (closestNetwork.id.equalsIgnoreCase(NEW_YORK_HUDSON_BIKESHARE_ID)) { closestNetwork = answerList.get(1); } //It seems we don't have a better candidate than the one we're presently using if (closestNetwork.id.equalsIgnoreCase(DBHelper.getBikeNetworkId(NearbyActivity.this))) { toReturn.put(ERROR_KEY_NO_BETTER, "dummy"); cancel(false); } else { if (DBHelper.isBikeNetworkIdAvailable(NearbyActivity.this)) { toReturn.put("old_network_name", DBHelper.getBikeNetworkName(NearbyActivity.this)); } toReturn.put("new_network_city", closestNetwork.location.city); DBHelper.saveBikeNetworkDesc(closestNetwork, NearbyActivity.this); } } catch (IOException e) { DBHelper.pauseAutoUpdate(); toReturn.put(ERROR_KEY_IOEXCEPTION, "dummy"); cancel(false); //No need to try to interrupt the thread } return toReturn; } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); if (getListPagerAdapter().isViewPagerReady()) getListPagerAdapter().setRefreshingAll(true); if (mCurrentUserLatLng != null) { mStatusTextView.setText(getString(R.string.searching_bike_network)); mSplashScreenTextBottom.setText(getString(R.string.searching_bike_network)); } } @Override protected void onPostExecute(Map<String, String> backgroundResults) { super.onPostExecute(backgroundResults); //We get here only if the network was actually changed if (mStationMapFragment != null && mStationMapFragment.getMarkerBVisibleLatLng() != null) { clearBTab(); } mClosestBikeAutoSelected = false; //noinspection ConstantConditions setupActionBarStrings(); setupFavoriteSheet(); if (mSplashScreen.isShown()) { mSplashScreen.setVisibility(View.GONE); } AlertDialog alertDialog = new AlertDialog.Builder(NearbyActivity.this).create(); //alertDialog.setTitle(getString(R.string.network_found_title)); if (!backgroundResults.keySet().contains("old_network_name")) { alertDialog.setTitle(Utils.fromHtml(String.format(getResources().getString(R.string.hello_city), "", backgroundResults.get("new_network_city")))); alertDialog.setMessage(Utils.fromHtml(String.format(getString(R.string.bike_network_found_message), DBHelper.getBikeNetworkName(NearbyActivity.this)))); Message toPass = null; //To resolve ambiguous call //noinspection ConstantConditions alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getResources().getString(R.string.ok), toPass); } else { alertDialog.setTitle(Utils.fromHtml(String.format(getResources().getString(R.string.hello_city), getResources().getString(R.string.hello_travel), backgroundResults.get("new_network_city")))); alertDialog.setMessage(Utils.fromHtml(String.format(getString(R.string.bike_network_change_message), DBHelper.getBikeNetworkName(NearbyActivity.this), mOldBikeNetworkName))); Message toPass = null; //To resolve ambiguous call //noinspection ConstantConditions alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getResources().getString(R.string.ok), toPass); mStationMapFragment.doInitialCameraSetup(CameraUpdateFactory.newLatLngZoom(mCurrentUserLatLng, 15), true); } alertDialog.show(); if (!checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_FULL, eONBOARDING_STEP.ONBOARDING_STEP_SEARCH_SHOWCASE)) { checkOnboarding(eONBOARDING_LEVEL.ONBOARDING_LEVEL_LIGHT, eONBOARDING_STEP.ONBOARDING_STEP_MAIN_CHOICE_HINT); } mDownloadWebTask = new DownloadWebTask(); mDownloadWebTask.execute(); mFindNetworkTask = null; } } //TODO: NOT use an asynchtask for this long running database operation private class SaveNetworkToDatabaseTask extends AsyncTask<Void, Void, Void> { @Override protected void onPreExecute() { super.onPreExecute(); DBHelper.notifyBeginSavingStations(NearbyActivity.this); } @Override protected Void doInBackground(Void... params) { ArrayList<StationItem> networkStationList = RootApplication.getBikeNetworkStationList(); try { //TODO: This is ugly. //This process shouldn't use an asynctask anyway as it's long running :/ DBHelper.deleteAllStations(); for (StationItem station : networkStationList) { DBHelper.saveStation(station); } } catch (Exception e) { Log.d("NearbyActivity", "Error saving network", e); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); DBHelper.notifyEndSavingStations(NearbyActivity.this); //must be done last mSaveNetworkToDatabaseTask = null; } } public class UpdateTwitterStatusTask extends AsyncTask<String, Void, Void> { private static final int REPLY_STATION_NAME_MAX_LENGTH = 54; private static final int STATION_ID_LENGTH = 32; @Override protected Void doInBackground(String... params) { List<StationItem> networkStationList = RootApplication.getBikeNetworkStationList(); Map<String, StationItem> networkStationMap = new HashMap<>(networkStationList.size()); for (StationItem station : networkStationList) { networkStationMap.put(station.getId(), station); } Twitter api = ((RootApplication) getApplication()).getTwitterApi(); //Extract all stations from raw string //if only one station, call updateStatus with intended one and selected one + deduplication //if multiple stations, post selected one first and then all other in replies ////////////////////////////////////////////////////////////////////////////////////// //FORMAT -- ROOT STATUS /*#findmybixibikes bike is not closest! Bikes:X BAD at IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID ~XXmin walk stationnamestationnamestation deduplicateZ #findmybixibikes bike is not closest! Bikes:XX AOK at IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID ~XXmin walk stationnamestationnamestatio deduplicateZ -- REPLIES #findmybixibikes discarded closer! Bikes:Y CRI at IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID stationnamestationnamestationnamestationnamestationnam #findmybixibikes discarded closer! Bikes:Y LCK at IDIDIDIDIDIDIDIDIDIDIDIDIDIDIDID stationnamestationnamestationnamestationnamestationnam */ String systemHashtag = getResources().getString(R.string.appbar_title_prefix) + DBHelper.getHashtaggableNetworkName(NearbyActivity.this) + //must be hastagable getResources().getString(R.string.appbar_title_postfix); int selectedNbBikes = -1; String selectedBadorAok = "BAD"; //hashtagged String selectedStationId = ""; String selectedProximityString = "XXmin"; String selectedStationName = "Laurier / De Lanaudire"; String deduplicate = "deduplicate"; //hashtagged //Pair of station id and availability code (always 'CRI' as of now) List<Pair<String, String>> discardedStations = new ArrayList<>(); StationItem selectedStation = null; List<String> extracted = Utils.extractOrderedStationIdsFromProcessedString(params[0]); //extracted will contain as firt element // 359f354466083c962d243bc238c95245_AVAILABILITY_AOK //OR 359f354466083c962d243bc238c95245_AVAILABILITY_BAD //followed by 1 or more string in the form of // 3c3bf5e74cb938e7d57641edaf909d24_AVAILABILITY_CRI boolean firstString = true; for (String e : extracted) { if (firstString) { //359f354466083c962d243bc238c95245_AVAILABILITY_BAD or //359f354466083c962d243bc238c95245_AVAILABILITY_AOK selectedStationId = e.substring(0, STATION_ID_LENGTH); selectedBadorAok = e.substring( STATION_ID_LENGTH + StationRecyclerViewAdapter.AVAILABILITY_POSTFIX_START_SEQUENCE.length(), STATION_ID_LENGTH + StationRecyclerViewAdapter.AVAILABILITY_POSTFIX_START_SEQUENCE.length() + 3); //'BAD' or 'AOK' selectedStation = networkStationMap.get(selectedStationId); selectedNbBikes = selectedStation.getFree_bikes(); selectedProximityString = Utils.getWalkingProximityString(selectedStation.getLocation(), mCurrentUserLatLng, false, null, NearbyActivity.this); //station name will be truncated to fit everything in a single tweet //see R.string.twitter_not_closest_bike_data_format int maxStationNameIdx = 138 - (deduplicate.length() + " ".length() + " walk ".length() + selectedProximityString.length() + " ".length() + STATION_ID_LENGTH + " at ".length() + selectedBadorAok.length() + " #".length() + Integer.toString(selectedNbBikes).length() + " bike is not closest! Bikes:".length() + systemHashtag.length()); selectedStationName = selectedStation.getName().substring(0, Math.min(selectedStation.getName().length(), maxStationNameIdx)); firstString = false; } else { //3c3bf5e74cb938e7d57641edaf909d24_AVAILABILITY_CRI Pair<String, String> discarded = new Pair<>(e.substring(0, STATION_ID_LENGTH), e.substring( STATION_ID_LENGTH + StationRecyclerViewAdapter.AVAILABILITY_POSTFIX_START_SEQUENCE.length(), STATION_ID_LENGTH + StationRecyclerViewAdapter.AVAILABILITY_POSTFIX_START_SEQUENCE.length() + 3)); discardedStations.add(discarded); } } int deduplicateCounter = 0; deduplicate = deduplicate + deduplicateCounter; String newStatusString = String.format( getResources().getString(R.string.twitter_not_closest_bike_data_format), systemHashtag, selectedNbBikes, selectedBadorAok, selectedStationId, selectedProximityString, selectedStationName, deduplicate); StatusUpdate newStatus = new StatusUpdate(newStatusString); //noinspection ConstantConditions newStatus.displayCoordinates(true).location(new GeoLocation(selectedStation.getLocation().latitude, selectedStation.getLocation().longitude)); boolean deduplicationDone = false; while (!deduplicationDone) { //post status before adding replies try { //can be interrupted here (duplicate) twitter4j.Status answerStatus = api.updateStatus(newStatus); long replyToId = answerStatus.getId(); for (Pair<String, String> discarded : discardedStations) { StationItem discardedStationItem = networkStationMap.get(discarded.first); String replyStatusString = String.format( getResources().getString(R.string.twitter_closer_discarded_reply_data_format), systemHashtag, discardedStationItem.getFree_bikes(), discarded.second, discarded.first, discardedStationItem.getName().substring(0, Math.min( discardedStationItem.getName().length(), REPLY_STATION_NAME_MAX_LENGTH))); StatusUpdate replyStatus = new StatusUpdate(replyStatusString); replyStatus.inReplyToStatusId(replyToId).displayCoordinates(true) .location(new GeoLocation(discardedStationItem.getLocation().latitude, discardedStationItem.getLocation().longitude)); //that can also raise exception api.updateStatus(replyStatus); } deduplicationDone = true; } catch (TwitterException e) { String errorMessage = e.getErrorMessage(); if (errorMessage.contains("Status is a duplicate.")) { ++deduplicateCounter; deduplicate = "deduplicate" + deduplicateCounter; newStatusString = String.format( getResources().getString(R.string.twitter_not_closest_bike_data_format), systemHashtag, selectedNbBikes, selectedBadorAok, selectedStationId, selectedProximityString, selectedStationName, deduplicate); newStatus = new StatusUpdate(newStatusString); //noinspection ConstantConditions newStatus.displayCoordinates(true).location(new GeoLocation( selectedStation.getLocation().latitude, selectedStation.getLocation().longitude)); Log.d("TwitterUpdate", "TwitterUpdate duplication -- deduplicating now", e); } else { deduplicationDone = true; } } } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); mUpdateTwitterTask = null; } @Override protected void onCancelled() { super.onCancelled(); mUpdateTwitterTask = null; } } public class DownloadWebTask extends AsyncTask<Void, Void, Void> { private LatLngBounds mDownloadedBikeNetworkBounds; /*DownloadWebTask(){ Log.d("nearbyActivity", "spawning new Download task", new Exception()); }*/ @Override protected Void doInBackground(Void... aVoid) { //noinspection StatementWithEmptyBody while (!getListPagerAdapter().isViewPagerReady()) { //Waiting on viewpager init } publishProgress(); Map<String, String> UrlParams = new HashMap<>(); UrlParams.put("fields", "stations"); Citybik_esAPI api = ((RootApplication) getApplication()).getCitybik_esApi(); final Call<NetworkStatusAnswerRoot> call = api .getNetworkStatus(DBHelper.getBikeNetworkHRef(NearbyActivity.this), UrlParams); Response<NetworkStatusAnswerRoot> statusAnswer; try { statusAnswer = call.execute(); ArrayList<StationItem> newBikeNetworkStationList = RootApplication .addAllToBikeNetworkStationList(statusAnswer.body().network.stations, NearbyActivity.this); //Calculate bounds LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); for (StationItem station : newBikeNetworkStationList) { boundsBuilder.include(station.getLocation()); } mDownloadedBikeNetworkBounds = boundsBuilder.build(); } catch (IOException e) { cancel(false); //No need to try to interrupt the thread } //Log.d("NearbyActivity", "DownloadTaskBackground - start waiting"); while (true) { //Must hang in the background if data saving is in progress already if (mSaveNetworkToDatabaseTask == null) break; try { //This has to happen otherwise release code seems to loop infinitely Thread.sleep(250); //Empirically determined } catch (InterruptedException e) { Log.d("NearbyActivity", "Exception while trying to sleep", e); } //Log.d("NearbyActivity", "DownloadTaskBackground - waiting"); } //Log.d("NearbyActivity", "DownloadTaskBackground - stopped waiting"); return null; } @Override protected void onProgressUpdate(Void... values) { super.onProgressUpdate(values); getListPagerAdapter().setRefreshingAll(true); } @Override protected void onPreExecute() { super.onPreExecute(); mStatusTextView.setText(getString(R.string.downloading)); mSplashScreenTextBottom.setText(getString(R.string.downloading)); DBHelper.resumeAutoUpdate(); if (getListPagerAdapter().isViewPagerReady()) getListPagerAdapter().setRefreshingAll(true); } @Override protected void onCancelled(Void aVoid) { //super.onCancelled(aVoid); //https://developer.android.com/reference/android/os/AsyncTask.html#onCancelled(Result) //" If you write your own implementation, do not call super.onCancelled(result)." //Set interface back getListPagerAdapter().setRefreshingAll(false); //wasn't initial download if (!RootApplication.getBikeNetworkStationList().isEmpty()) { if (mDataOutdated) { mRefreshMarkers = true; refreshMap(); mStatusBar .setBackgroundColor(ContextCompat.getColor(NearbyActivity.this, R.color.theme_accent)); getListPagerAdapter().setOutdatedDataAll(true); } DBHelper.pauseAutoUpdate(); //Must be done in all cases : getAutoUpdate factorizes in the suspend flag if (DBHelper.getAutoUpdate(NearbyActivity.this)) { Utils.Snackbar .makeStyled(mCoordinatorLayout, R.string.auto_download_failed, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.resume, new View.OnClickListener() { @Override public void onClick(View v) { DBHelper.resumeAutoUpdate(); } }).show(); } else { Utils.Snackbar .makeStyled(mCoordinatorLayout, R.string.manual_download_failed, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View v) { mDownloadWebTask = new DownloadWebTask(); mDownloadWebTask.execute(); } }).show(); } } else { mSplashScreenTextTop.setText(getString(R.string.sad_emoji)); mSplashScreenTextBottom.setText(""); Utils.Snackbar .makeStyled(mSplashScreen, R.string.connectivity_rationale, Snackbar.LENGTH_INDEFINITE, ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)) .setAction(R.string.retry, new View.OnClickListener() { @Override public void onClick(View view) { tryInitialSetup(); } }).show(); } //must be done last mDownloadWebTask = null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); //switch progressbar view visibility getListPagerAdapter().setRefreshingAll(false); DBHelper.saveLastUpdateTimestampAsNow(getApplicationContext()); DBHelper.saveBikeNetworkBounds(mDownloadedBikeNetworkBounds, NearbyActivity.this); Log.d("nearbyActivity", RootApplication.getBikeNetworkStationList().size() + " stations downloaded from citibik.es"); //users are inside bounds if (mCurrentUserLatLng == null || DBHelper.getBikeNetworkBounds(NearbyActivity.this, 5).contains(mCurrentUserLatLng)) { mClosestBikeAutoSelected = false; mDataOutdated = false; mStatusBar.setBackgroundColor( ContextCompat.getColor(NearbyActivity.this, R.color.theme_primary_dark)); getListPagerAdapter().setOutdatedDataAll(false); mRefreshMarkers = true; mRefreshTabs = true; refreshMap(); //new SaveNetworkToDatabaseTask().execute(); //Saving to database executes in parallel. Maybe a service should be used in place mSaveNetworkToDatabaseTask = new SaveNetworkToDatabaseTask(); mSaveNetworkToDatabaseTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (mCurrentUserLatLng != null) { //users are outside bounds //This task will possibly auto cancel if it can't find a better bike network mFindNetworkTask = new FindNetworkTask(DBHelper.getBikeNetworkName(NearbyActivity.this)); mFindNetworkTask.execute(); } //must be done last mDownloadWebTask = null; //special case for test versions in firebase lab //full onboarding prevents meaningful coverage (robo test don't input anything in search autocomplete widget) if (getString(R.string.app_version_name).contains("test") || getString(R.string.app_version_name).contains("alpha")) { int addedCount = 0; ArrayList<StationItem> networkStationList = RootApplication.getBikeNetworkStationList(); for (StationItem station : networkStationList) { if (!DBHelper.isFavorite(station.getId(), NearbyActivity.this)) { if (addedCount > 3) break; else if (addedCount % 2 == 0) { //non default favorite name //station.setFavorite(true, NearbyActivity.this); //We want to manipulate everything, hence go directly to DBHelper DBHelper.updateFavorite(true, new FavoriteItemStation(station.getId(), station.getName() + "-test", false), NearbyActivity.this); ArrayList<FavoriteItemBase> favoriteList = DBHelper.getFavoriteAll(NearbyActivity.this); setupFavoriteListFeedback(favoriteList.isEmpty()); mFavoriteRecyclerViewAdapter.setupFavoriteList(favoriteList); } else { //default favorite name DBHelper.updateFavorite(true, new FavoriteItemStation(station.getId(), station.getName(), true), NearbyActivity.this); ArrayList<FavoriteItemBase> favoriteList = DBHelper.getFavoriteAll(NearbyActivity.this); setupFavoriteListFeedback(favoriteList.isEmpty()); mFavoriteRecyclerViewAdapter.setupFavoriteList(favoriteList); } ++addedCount; } } if (mOnboardingShowcaseView != null) mOnboardingShowcaseView.hide(); mOnboardingShowcaseView = null; } } } }