public class TimeForCoffeeApplication extends Application {

    private ObjectGraph graph;

    @Override public void onCreate() {
        super.onCreate();

        initModules();
    }

    private void initModules() {
        List<Object> modules = Collections.<Object>singletonList(new TimeForCoffeeModule());
        this.graph = ObjectGraph.create(modules.toArray());
    }

    public void inject(Object object) {
        graph.inject(object);
    }
} public class MainActivity extends AppCompatActivity implements StationListFragment.Callbacks, FavoritesListFragment.Callbacks {

    private MainPresenter mPresenter;
    private StationListFragment mStationListFragment;
    private FavoritesListFragment mFavoriteListFragment;
    private BottomNavigationView bottomNav;

    public static final String STATION_LIST_FRAGMENT_KEY = "station_list";
    public static final String FAVORITE_LIST_FRAGMENT_KEY = "favorite_list";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_station_list);

        // Presenter
        mPresenter = new MainPresenter(this);

        // Fragments
        Bundle stationsFragmentArgs = new Bundle();
        Bundle favoritesFragmentArgs = new Bundle();
        stationsFragmentArgs.putBoolean(StationListFragment.ARG_SEARCH_MODE, false);
        favoritesFragmentArgs.putInt(FavoritesListFragment.ARG_MODE, FavoritesListFragment.ARG_MODE_STATIONS);

        if (savedInstanceState == null) {
            mStationListFragment = (StationListFragment) Fragment.instantiate(this, StationListFragment.class.getName(), stationsFragmentArgs);
            mFavoriteListFragment = (FavoritesListFragment) Fragment.instantiate(this, FavoritesListFragment.class.getName(), favoritesFragmentArgs);
        }
        else{
            mStationListFragment = (StationListFragment) getSupportFragmentManager().getFragment(savedInstanceState, STATION_LIST_FRAGMENT_KEY);
            mFavoriteListFragment = (FavoritesListFragment) getSupportFragmentManager().getFragment(savedInstanceState, FAVORITE_LIST_FRAGMENT_KEY);
        }
        getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, mFavoriteListFragment).commit();


        // Bottom navigation
        bottomNav = findViewById(R.id.bottom_navigation);
        bottomNav.setOnNavigationItemSelectedListener(navLister);
        bottomNav.setSelectedItemId(R.id.action_stations);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if(mStationListFragment.isAdded()) {
            getSupportFragmentManager().putFragment(outState, STATION_LIST_FRAGMENT_KEY, mStationListFragment);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mPresenter.onResumeView();
    }


    @Override
    public void onPause() {
        super.onPause();
        mPresenter.onPauseView();
    }

    @Override
    public void onRefresh() {
        mPresenter.onRefreshView();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter.onDestroy();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        mPresenter.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    public void onStationSelected(Station station) {
        selectStation(station);
    }

    @Override
    public void onFavoriteStationSelected(Station station) {
        selectStation(station);
    }

    @Override
    public void onFavoriteDepartureSelected(Departure departure) { }

    @Override
    public void onStationFavoriteToggled(Station station, boolean isFavorite) {
        mPresenter.updateStationIsFavorite(station, isFavorite);
    }

    @Override
    public void onDepartureFavoriteToggled(Departure station, boolean isFavorite) { }

    private void selectStation(Station station) {
        Intent detailIntent = new Intent(this, DeparturesActivity.class);
        detailIntent.putExtra(DeparturesActivity.ARG_STATION_ID, station.getId());
        detailIntent.putExtra(DeparturesActivity.ARG__STATION_NAME, station.getName());
        detailIntent.putExtra(DeparturesActivity.ARG_STATION_DISTANCE, station.getDistance());
        detailIntent.putExtra(DeparturesActivity.ARG_STATION_LATITUDE, station.getLocation().getLatitude());
        detailIntent.putExtra(DeparturesActivity.ARG_STATION_LONGITUDE, station.getLocation().getLongitude());
        detailIntent.putExtra(DeparturesActivity.ARG_IS_FAVORITE, station.getIsFavorite());

        startActivity(detailIntent);
    }

    public void performStationsUpdate() {
        mPresenter.updateStations();
    }

    public void updateStations(List<Station> stations) {
        mStationListFragment.setStations(stations);
    }

    public void updateFavorites(List<Station> favoriteStations) {
        mFavoriteListFragment.setStations(favoriteStations);
    }

    public void setIsPositionLoading(boolean loading) {
        mStationListFragment.showLoadingPositionLayout(loading);
    }

    private BottomNavigationView.OnNavigationItemSelectedListener navLister = new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
            Fragment selectedFragment = null;

            switch (menuItem.getItemId()) {
                case R.id.action_search:
                    selectedFragment = new StationSearchFragment();
                    break;
                case R.id.action_stations:
                    selectedFragment = mStationListFragment;
                    break;
                case R.id.action_favorites:
                    selectedFragment = mFavoriteListFragment;
                    break;
                case R.id.action_about:
                    selectedFragment = new AboutFragment();
                    break;
            }

            getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, selectedFragment).commit();
            return true;
        }
    };

    public void showBottomNavigation() {
        bottomNav.setVisibility(View.VISIBLE);
    }

    public void hideBottomNavigation() {
        bottomNav.setVisibility(View.GONE);
    }
} public class StationService {

    private final EventBus eventBus;
    private final OpenDataService openDataService;

    @Inject
    public StationService(EventBus eventBus, OpenDataService openDataService) {
        this.openDataService = openDataService;
        this.eventBus = eventBus;
        this.eventBus.register(this);
    }

    @Subscribe
    public void onEvent(FetchStationsSearchEvent event) {
        Map<String, String> query = new HashMap<>();
        query.put("type", "station");
        query.put("query", event.getSearchQuery());

        openDataService.getStations(query)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Stations>() {
                    @Override
                    public void onCompleted() { }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchStationsSearchErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Stations backendStations) {
                        ArrayList<Station> stations = new ArrayList<>();
                        for (ch.liip.timeforcoffee.backend.Station backendStation : backendStations.getStations()) {
                            stations.add(StationMapper.fromBackend(backendStation));
                        }

                        eventBus.post(new StationsSearchFetchedEvent(stations));
                    }
                });
    }

    @Subscribe
    public void onEvent(FetchStationsLocationEvent event) {
        Map<String, String> query = new HashMap<>();
        query.put("type", "station");
        query.put("x", Double.toString(event.getX()));
        query.put("y", Double.toString(event.getY()));

        openDataService.getStations(query)
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<ch.liip.timeforcoffee.backend.Stations>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {
                        eventBus.post(new FetchStationsLocationErrorEvent(e));
                    }

                    @Override
                    public void onNext(ch.liip.timeforcoffee.backend.Stations backendStations) {
                        ArrayList<Station> stations = new ArrayList<>();
                        for (ch.liip.timeforcoffee.backend.Station backendStation : backendStations.getStations()) {
                            if (backendStation.getId() == 0) {
                                //the API returns a station with null id corresponding to current location, we exclude it
                                continue;
                            }
                            stations.add(StationMapper.fromBackend(backendStation));
                        }

                        eventBus.post(new StationsLocationFetchedEvent(stations));
                    }
                });
    }
} public class FetchStationsLocationErrorEvent {

    private Throwable throwable;

    public FetchStationsLocationErrorEvent(Throwable throwable) {
        this.throwable = throwable;
    }

    public Throwable getThrowable() {
        return throwable;
    }
} public class FetchStationsLocationEvent {

    private double x;
    private double y;

    public FetchStationsLocationEvent(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }
} public class StationsLocationFetchedEvent {

    private List<Station> stations;

    public StationsLocationFetchedEvent(List<Station> stations) {
        this.stations = stations;
    }

    public List<Station> getStations() {
        return stations;
    }
} public class Station {

    private final int id;
    private final String name;
    private final Location location;
    private final float distance;
    private boolean isFavorite;

    public Station(int id, String name, float distance, Location location, boolean isFavorite) {
        this.id = id;
        this.name = name;
        this.location = location;
        this.distance = distance;
        this.isFavorite = isFavorite;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public Location getLocation() {
        return location;
    }

    public float getDistance() {
        return distance;
    }

    public boolean getIsFavorite() {
        return isFavorite;
    }

    public void setIsFavorite(boolean isFavorite) {
        this.isFavorite = isFavorite;
    }

    public String getDistanceForDisplay(Location userLocation) {

        if (userLocation != null) {
            int directDistance = getDistanceInMeter(userLocation);

            if (directDistance > 5000) {
                int km = (int) (Math.round((double) (directDistance) / 1000));
                return km + " km";
            } else {
                return directDistance + " m";
            }
        }
        return null;
    }

    private int getDistanceInMeter(Location userLocation) {
        if (userLocation != null) {
            return (int) userLocation.distanceTo(location);
        }
        return 0;
    }

    @Override
    public boolean equals(Object object) {
        boolean sameName = false;

        if (object instanceof Station) {
            sameName = this.name.equals(((Station) object).getName());
        }

        return sameName;
    }
} public interface BackendService {

    @Headers("Cache-Control:public, max-age=20")
    @GET("/api/ch/stationboard/{stationId}")
    Observable<Departures> getDepartures(@Path("stationId") String stationId);

    @Headers("Cache-Control:public, max-age=20")
    @GET("/api/ch/connections/{stationId}/{destinationId}/{departureDate}/{arrivalDate}")
    Observable<Connections> getConnections(
            @Path("stationId") String stationId,
            @Path("destinationId") String destinationId,
            @Path("departureDate") String departureDate,
            @Path("arrivalDate") String arrivalDate);
} public interface Presenter {

    void onResumeView();

    void onRefreshView();

    void onPauseView();

    void onDestroy();

} public class FavoritesDataSource {

    private SQLiteDatabase database;

    private String[] allStationColumns = {FavoriteStationColumn.COLUMN_ID, FavoriteStationColumn.COLUMN_STATION_ID,
            FavoriteStationColumn.COLUMN_NAME, FavoriteStationColumn.COLUMN_LATITUDE, FavoriteStationColumn.COLUMN_LONGITUDE};

    private String[] allLineColumns = {FavoriteLineColumn.COLUMN_ID, FavoriteLineColumn.COLUMN_NAME, FavoriteLineColumn.COLUMN_DESTINATION_ID};

    public void insertFavoriteStation(Context context, Station station) {
        open(context);

        ContentValues values = new ContentValues();
        values.put(FavoriteStationColumn.COLUMN_STATION_ID, station.getId());
        values.put(FavoriteStationColumn.COLUMN_NAME, station.getName());
        values.put(FavoriteStationColumn.COLUMN_LATITUDE, station.getLocation().getLatitude());
        values.put(FavoriteStationColumn.COLUMN_LONGITUDE, station.getLocation().getLongitude());
        database.insert(FavoriteStationColumn.TABLE_NAME, null, values);

        close();
    }

    public void deleteFavoriteStation(Context context, Station station) {
        open(context);
        database.delete(FavoriteStationColumn.TABLE_NAME, FavoriteStationColumn.COLUMN_STATION_ID + " = " + station.getId(), null);

        close();
    }

    public void insertFavoriteLine(Context context, Departure departure) {
        open(context);

        ContentValues values = new ContentValues();
        values.put(FavoriteLineColumn.COLUMN_NAME, departure.getLine());
        values.put(FavoriteLineColumn.COLUMN_DESTINATION_ID, departure.getDestinationId());
        database.insert(FavoriteLineColumn.TABLE_NAME, null, values);

        close();
    }

    public void deleteFavoriteLine(Context context, Departure departure) {
        open(context);
        database.delete(FavoriteLineColumn.TABLE_NAME, FavoriteLineColumn.COLUMN_NAME + " = '" + departure.getLine()  + "' AND " +
                FavoriteLineColumn.COLUMN_DESTINATION_ID + " = " + departure.getDestinationId(), null);

        close();
    }

    public List<Station> getAllFavoriteStations(Context context) {
        open(context);

        List<Station> stations = new ArrayList<>();
        Cursor cursor = database.query(FavoriteStationColumn.TABLE_NAME, allStationColumns, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Station station = cursorToStation(cursor);
            stations.add(station);
            cursor.moveToNext();
        }

        cursor.close();
        close();

        return stations;
    }

    public List<Departure> getAllFavoriteLines(Context context) {
        open(context);

        List<Departure> departures = new ArrayList<>();
        Cursor cursor = database.query(FavoriteLineColumn.TABLE_NAME, allLineColumns, null, null, null, null, null);
        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Departure departure = cursorToLine(cursor);
            departures.add(departure);
            cursor.moveToNext();
        }

        cursor.close();
        close();

        return departures;
    }

    private void open(Context context) throws SQLException {
        database = new FavoritesDatabaseHelper(context).getWritableDatabase();
    }

    private void close() {
        if (database != null && database.isOpen()) {
            database.close();
        }
    }

    private Station cursorToStation(Cursor cursor) {
        int id = cursor.getInt(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_STATION_ID));
        String name = cursor.getString(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_NAME));
        double latitude = cursor.getDouble(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_LATITUDE));
        double longitude = cursor.getDouble(cursor.getColumnIndexOrThrow(FavoriteStationColumn.COLUMN_LONGITUDE));

        Location location = new Location("fav");
        location.setLatitude(latitude);
        location.setLongitude(longitude);

        return new Station(id, name, 0, location, true);
    }

    private Departure cursorToLine(Cursor cursor) {
        int destinationId = cursor.getInt(cursor.getColumnIndexOrThrow(FavoriteLineColumn.COLUMN_DESTINATION_ID));
        String line = cursor.getString(cursor.getColumnIndexOrThrow(FavoriteLineColumn.COLUMN_NAME));

        return new Departure(destinationId, line, true);
    }
} public class PermissionsChecker {
    Context context;

    public PermissionsChecker(Context context) {
        this.context = context;
    }

    public boolean LacksPermission(String permission) {
        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED;
    }

    public void RequestPermission(final Activity activity, final String permission, final int requestCode, String message) {
        //Returns true if the app has requested this permission previously and the user denied the request.
        //Return false if first time that permission is request or user checked "never ask again"
        if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) {
            AlertDialog.Builder builder = new AlertDialog.Builder(activity);
            builder.setMessage(message);
            builder.setPositiveButton(activity.getResources().getString(R.string.permission_allow), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
                }
            });
            builder.setNegativeButton(context.getResources().getString(R.string.permission_dont_allow), new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(context, R.string.permission_no_location, Toast.LENGTH_LONG).show();
                }
            });
            builder.show();
            return;
        }
        ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
    }


} public class SnackBars {

    public static void showNetworkError(Activity activity, View.OnClickListener listener) {
        Snackbar snackbar = Snackbar
                .make(activity.findViewById(R.id.content), activity.getResources().getString(R.string.network_error), Snackbar.LENGTH_INDEFINITE)
                .setAction(activity.getResources().getString(R.string.retry), listener);

        snackbar.show();
    }

    public static void showLocalisationServiceOff(final Activity activity) {
        Snackbar snackbar = Snackbar
                .make(activity.findViewById(R.id.content), activity.getResources().getString(R.string.location_service_off), Snackbar.LENGTH_INDEFINITE)
                .setAction(activity.getResources().getString(R.string.activate), new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        activity.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
                    }
                });

        snackbar.show();
    }

    public static void showLocalisationServiceSetToDeviceOnly(final Activity activity) {
        Snackbar snackbar = Snackbar
                .make(activity.findViewById(R.id.content), activity.getResources().getString(R.string.location_service_gps_only), Snackbar.LENGTH_INDEFINITE)
                .setAction(activity.getResources().getString(R.string.location_service_gps_only_settings), new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        activity.startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
                    }
                });

        snackbar.show();
    }

    public static void showLocalisationSettings(final Activity activity) {
        Snackbar snackbar = Snackbar
                .make(activity.findViewById(R.id.content), activity.getResources().getString(R.string.permission_no_location), Snackbar.LENGTH_INDEFINITE)
                .setAction(activity.getResources().getString(R.string.grant), new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        Intent intent = new Intent();
                        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                        Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
                        intent.setData(uri);
                        activity.startActivity(intent);
                    }
                });

        snackbar.show();
    }
}
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.util.Log;
import android.view.View;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import ch.liip.timeforcoffee.R;
import ch.liip.timeforcoffee.TimeForCoffeeApplication;
import ch.liip.timeforcoffee.activity.MainActivity;
import ch.liip.timeforcoffee.api.StationService;
import ch.liip.timeforcoffee.api.events.stationsLocationEvents.FetchStationsLocationErrorEvent;
import ch.liip.timeforcoffee.api.events.stationsLocationEvents.FetchStationsLocationEvent;
import ch.liip.timeforcoffee.api.events.stationsLocationEvents.StationsLocationFetchedEvent;
import ch.liip.timeforcoffee.api.models.Station;
import ch.liip.timeforcoffee.backend.BackendService;
import ch.liip.timeforcoffee.common.presenter.Presenter;
import ch.liip.timeforcoffee.helper.FavoritesDataSource;
import ch.liip.timeforcoffee.helper.PermissionsChecker;
import ch.liip.timeforcoffee.widget.SnackBars;
import io.nlopez.smartlocation.OnLocationUpdatedListener;
import io.nlopez.smartlocation.SmartLocation;
import io.nlopez.smartlocation.location.config.LocationAccuracy;
import io.nlopez.smartlocation.location.config.LocationParams;
        permissionsChecker = new PermissionsChecker(activity);
    }

    @Override
    public void onResumeView() { }

    @Override
    public void onPauseView() {
        if (mIsCapturingLocation) {
            stopLocation();
        }
    }

    @Override
    public void onRefreshView() {
        loadStationsWithLastPositionForce();
    }

    public void onDestroy() {
        mActivity = null;
        mEventBus.unregister(this);
    }

    public void updateStations() {
        if (!mIsCapturingLocation) {
            startLocation();
        }
    }

    public void updateFavorites() {
        mFavoriteStations = favoritesDataSource.getAllFavoriteStations(mActivity);
        mActivity.updateFavorites(mFavoriteStations);

        if(mStations != null) {
            for(Station station : mStations) {
                station.setIsFavorite(mFavoriteStations.contains(station));
            }

            mActivity.updateStations(mStations);
        }
    }

    public void updateStationIsFavorite(Station station, boolean isFavorite) {
        if (isFavorite) {
            favoritesDataSource.insertFavoriteStation(mActivity, station);
        }
        else {
            favoritesDataSource.deleteFavoriteStation(mActivity, station);
        }

        updateFavoritesOnFavoriteList();
    }

    private void updateFavoritesOnFavoriteList() {
        mFavoriteStations = favoritesDataSource.getAllFavoriteStations(mActivity);
        mActivity.updateFavorites(mFavoriteStations);
    }

    private void startLocation() {
        if (!permissionsChecker.LacksPermission(locationPermission)) {
            if(mStations == null || mStations.size() == 0) {
                mActivity.setIsPositionLoading(true);
            }

            if (!SmartLocation.with(mActivity).location().state().locationServicesEnabled()) {
                SnackBars.showLocalisationServiceOff(mActivity);
                mActivity.setIsPositionLoading(false);
                return;
            }
            else if(SmartLocation.with(mActivity).location().state().isGpsAvailable() && !SmartLocation.with(mActivity).location().state().isNetworkAvailable()) {
                SnackBars.showLocalisationServiceSetToDeviceOnly(mActivity);
                mActivity.setIsPositionLoading(false);
                return;
            }

            mIsCapturingLocation = true;
            LocationParams locationParams = new LocationParams.Builder().setAccuracy(LocationAccuracy.MEDIUM).setDistance(LOCATION_SMALLEST_DISPLACEMENT).setInterval(LOCATION_INTERVAL).build();
            SmartLocation.with(mActivity).location()
                    .config(locationParams)
                    .oneFix()
                    .start(this);
        }
        else {
            permissionsChecker.RequestPermission(mActivity, locationPermission, PERMISSION_REQUEST_CODE, mActivity.getResources().getString(R.string.permission_message));
        }
    }

    private void stopLocation() {
        mIsCapturingLocation = false;
        SmartLocation.with(mActivity).location().stop();
    }

    private void loadStationsWithLastPosition() {
        if(mStations == null || mStations.size() == 0) {
            mActivity.setIsPositionLoading(true);
        }

        if (mLastLocation != null) {
            loadStations(mLastLocation);
        }
        else {
            startLocation();
        }
    }

    private void loadStationsWithLastPositionForce() {
        mActivity.updateStations(new ArrayList<Station>());
        mActivity.setIsPositionLoading(true);

        if (mLastLocation != null) {
            loadStations(mLastLocation);
        }
        else {
            startLocation();
        }
    }

    private void loadStations(Location location) {
        if (location != null) {
            Log.i(LOG_TAG, "get stations for lat =  " + location.getLatitude() + " and long = " + location.getLongitude());
            mEventBus.post(new FetchStationsLocationEvent(location.getLatitude(), location.getLongitude()));