res) {
SystemClock.sleep(5000);
res.set(result.SERVER_ERROR);
return new Palina();
}
}
private ArrivalsFetcher[] ArrivalFetchers = {new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher(), new MockFetcher()};*/
private ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
private StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
/*
* Position
*/
//Fine location criteria
private final Criteria cr = new Criteria();
private boolean pendingNearbyStopsRequest = false;
private LocationManager locmgr;
private FragmentHelper fh;
///////////////////////////////// EVENT HANDLERS ///////////////////////////////////////////////
/*
* @see swipeRefreshLayout
*/
private final Handler theHandler = new Handler();
private final Runnable refreshing = new Runnable() {
public void run() {
if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
String stopName = fragment.getStopID();
new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray()).execute(stopName);
} else //we create a new fragment, which is WRONG
new AsyncDataDownload(fh, arrivalsFetchers).execute();
}
};
//// MAIN METHOD ///
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
framan = getSupportFragmentManager();
final SharedPreferences theShPr = getMainSharedPreferences();
/*
* UI
*/
setContentView(R.layout.activity_main);
Toolbar defToolbar = findViewById(R.id.that_toolbar);
setSupportActionBar(defToolbar);
busStopSearchByIDEditText = findViewById(R.id.busStopSearchByIDEditText);
busStopSearchByNameEditText = findViewById(R.id.busStopSearchByNameEditText);
progressBar = findViewById(R.id.progressBar);
howDoesItWorkTextView = findViewById(R.id.howDoesItWorkTextView);
hideHintButton = findViewById(R.id.hideHintButton);
swipeRefreshLayout = findViewById(R.id.listRefreshLayout);
floatingActionButton = findViewById(R.id.floatingActionButton);
framan.addOnBackStackChangedListener(() -> Log.d("MainActivity, BusTO", "BACK STACK CHANGED"));
busStopSearchByIDEditText.setSelectAllOnFocus(true);
busStopSearchByIDEditText
.setOnEditorActionListener((v, actionId, event) -> {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
});
busStopSearchByNameEditText
.setOnEditorActionListener((v, actionId, event) -> {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
});
// Called when the layout is pulled down
swipeRefreshLayout
.setOnRefreshListener(() -> theHandler.post(refreshing));
/**
* @author Marco Gagino!!!
*/
//swipeRefreshLayout.setColorSchemeColors(R.color.blue_500, R.color.orange_500); // setColorScheme is deprecated, setColorSchemeColors isn't
swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500);
fh = new FragmentHelper(this, R.id.listRefreshLayout, R.id.resultFrame);
setSearchModeBusStopID();
//---------------------------- START INTENT CHECK QUEUE ------------------------------------
// Intercept calls from URL intent
boolean tryedFromIntent = false;
String busStopID = null;
String busStopDisplayName = null;
Uri data = getIntent().getData();
if (data != null) {
busStopID = getBusStopIDFromUri(data);
tryedFromIntent = true;
}
// Intercept calls from other activities
if (!tryedFromIntent) {
Bundle b = getIntent().getExtras();
if (b != null) {
busStopID = b.getString("bus-stop-ID");
busStopDisplayName = b.getString("bus-stop-display-name");
/**
* I'm not very sure if you are coming from an Intent.
* Some launchers work in strange ways.
*/
tryedFromIntent = busStopID != null;
}
}
//---------------------------- END INTENT CHECK QUEUE --------------------------------------
if (busStopID == null) {
// Show keyboard if can't start from intent
// JUST DON'T
// showKeyboard();
// You haven't obtained anything... from an intent?
if (tryedFromIntent) {
// This shows a luser warning
Toast.makeText(getApplicationContext(),
R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show();
}
} else {
// If you are here an intent has worked successfully
setBusStopSearchByIDEditText(busStopID);
/*
//THIS PART SHOULDN'T BE NECESSARY SINCE THE LAST SUCCESSFULLY SEARCHED BUS
// STOP IS ADDED AUTOMATICALLY
Stop nextStop = new Stop(busStopID);
// forcing it as user name even though it could be standard name, it doesn't really matter
nextStop.setStopUserName(busStopDisplayName);
//set stop as last succe
fh.setLastSuccessfullySearchedBusStop(nextStop);
*/
- createFragmentForStop(busStopID);
+ requestArrivalsForStopID(busStopID);
}
//Try (hopefully) database update
//TODO: Check if service shows the notification
//Old code for the db update
//DatabaseUpdateService.startDBUpdate(getApplicationContext());
PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS)
.setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.build())
.build();
final WorkManager workManager = WorkManager.getInstance(this);
final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
if (version >= 0)
workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
ExistingPeriodicWorkPolicy.KEEP, wr);
else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
ExistingPeriodicWorkPolicy.REPLACE, wr);
/*
Set database update
*/
workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG)
.observe(this, workInfoList -> {
// If there are no matching work info, do nothing
if (workInfoList == null || workInfoList.isEmpty()) {
return;
}
Log.d(DEBUG_TAG, "WorkerInfo: "+workInfoList);
boolean showProgress = false;
for (WorkInfo workInfo : workInfoList) {
if (workInfo.getState() == WorkInfo.State.RUNNING) {
showProgress = true;
}
}
if (showProgress) {
createDefaultSnackbar();
} else {
if(snackbar!=null) {
snackbar.dismiss();
snackbar = null;
}
}
});
//locationHandler = new GPSLocationAdapter(getApplicationContext());
//--------- NEARBY STOPS--------//
//SETUP LOCATION
locmgr = (LocationManager) getSystemService(LOCATION_SERVICE);
cr.setAccuracy(Criteria.ACCURACY_FINE);
cr.setAltitudeRequired(false);
cr.setBearingRequired(false);
cr.setCostAllowed(true);
cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
//We want the nearby bus stops!
theHandler.post(new NearbyStopsRequester());
//If there are no providers available, then, wait for them
Log.d("MainActivity", "Created");
}
/*
* Reload bus stop timetable when it's fulled resumed from background.
* @Override protected void onPostResume() {
* super.onPostResume();
* Log.d("ActivityMain", "onPostResume fired. Last successfully bus stop ID: " + fh.getLastSuccessfullySearchedBusStop());
* if (searchMode == SEARCH_BY_ID && fh.getLastSuccessfullySearchedBusStop() != null) {
* setBusStopSearchByIDEditText(fh.getLastSuccessfullySearchedBusStop().ID);
* new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute();
* } else {
* //we have new activity or we don't have a new searched stop.
* //Let's search stops nearby
* LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE);
* Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.resultFrame);
*
*
* }
* //show the FAB since it remains hidden
* floatingActionButton.show();
*
* }
**/
@Override
protected void onPause() {
super.onPause();
fh.stopLastRequestIfNeeded();
fh.setBlockAllActivities(true);
locmgr.removeUpdates(locListener);
}
@Override
protected void onResume() {
super.onResume();
fh.setBlockAllActivities(false);
//TODO: check if current LiveData-bound observer works
if (pendingNearbyStopsRequest)
theHandler.post(new NearbyStopsRequester());
ActionBar bar = getSupportActionBar();
if(bar!=null) bar.show();
else Log.w(DEBUG_TAG, "ACTION BAR IS NULL");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
actionHelpMenuItem = menu.findItem(R.id.action_help);
return true;
}
/**
* Callback fired when a MenuItem is selected
*
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
Resources res = getResources();
switch (item.getItemId()) {
case android.R.id.home:
// Respond to the action bar's Up/Home button
NavUtils.navigateUpFromSameTask(this);
return true;
case R.id.action_help:
showHints();
return true;
case R.id.action_favorites:
startActivity(new Intent(ActivityMain.this, ActivityFavorites.class));
return true;
case R.id.action_map:
//ensure storage permission is granted
final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ);
switch (result) {
case PERMISSION_OK:
startActivity(new Intent(ActivityMain.this, ActivityMap.class));
break;
case PERMISSION_ASKING:
permissionDoneRunnables.put(permission,
() -> startActivity(new Intent(ActivityMain.this, ActivityMap.class)));
break;
case PERMISSION_NEG_CANNOT_ASK:
String storage_perm = res.getString(R.string.storage_permission);
String text = res.getString(R.string.too_many_permission_asks, storage_perm);
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
return true;
case R.id.action_about:
startActivity(new Intent(ActivityMain.this, ActivityAbout.class));
return true;
case R.id.action_hack:
openIceweasel(res.getString(R.string.hack_url));
return true;
case R.id.action_source:
openIceweasel("https://gitpull.it/source/libre-busto/");
return true;
case R.id.action_licence:
openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html");
return true;
case R.id.action_settings:
Log.d("MAINBusTO", "Pressed button preferences");
startActivity(new Intent(ActivityMain.this, ActivitySettings.class));
return true;
case R.id.action_experiments:
startActivity(new Intent(this, ActivityPrincipal.class));
}
return super.onOptionsItemSelected(item);
}
/**
* OK this is pure shit
*
* @param v View clicked
*/
public void onSearchClick(View v) {
if (searchMode == SEARCH_BY_ID) {
String busStopID = busStopSearchByIDEditText.getText().toString();
- createFragmentForStop(busStopID);
+ requestArrivalsForStopID(busStopID);
} else { // searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
//new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
new AsyncDataDownload(fh, stopsFinderByNames).execute(query);
}
}
/**
* PERMISSION STUFF
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PERMISSION_REQUEST_POSITION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setOption(LOCATION_PERMISSION_GIVEN, true);
//if we sent a request for a new NearbyStopsFragment
if (pendingNearbyStopsRequest) {
pendingNearbyStopsRequest = false;
theHandler.post(new NearbyStopsRequester());
}
} else {
//permission denied
setOption(LOCATION_PERMISSION_GIVEN, false);
}
//add other cases for permissions
break;
case STORAGE_PERMISSION_REQ:
final String storageKey = Manifest.permission.WRITE_EXTERNAL_STORAGE;
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(DEBUG_TAG, "Permissions check: " + Arrays.toString(permissions));
if (permissionDoneRunnables.containsKey(storageKey)) {
Runnable toRun = permissionDoneRunnables.get(storageKey);
if (toRun != null)
toRun.run();
permissionDoneRunnables.remove(storageKey);
}
} else {
//permission denied
showToastMessage(R.string.permission_storage_maps_msg, false);
/*final int canGetPermission = askForPermissionIfNeeded(Manifest.permission.ACCESS_FINE_LOCATION, STORAGE_PERMISSION_REQ);
switch (canGetPermission) {
case PERMISSION_ASKING:
break;
case PERMISSION_NEG_CANNOT_ASK:
permissionDoneRunnables.remove(storageKey);
showToastMessage(R.string.closing_act_crash_msg, false);
}*/
}
}
}
@Override
- public void createFragmentForStop(String ID) {
+ public void requestArrivalsForStopID(String ID) {
if (ID == null || ID.length() <= 0) {
// we're still in UI thread, no need to mess with Progress
showToastMessage(R.string.insert_bus_stop_number_error, true);
toggleSpinner(false);
} else if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
if (fragment !=null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){
// Run with previous fetchers
//fragment.getCurrentFetchers().toArray()
new AsyncDataDownload(fh,fragment.getCurrentFetchersAsArray()).execute(ID);
} else{
new AsyncDataDownload(fh, arrivalsFetchers).execute(ID);
}
}
else {
new AsyncDataDownload(fh,arrivalsFetchers).execute(ID);
Log.d("MainActiv", "Started search for arrivals of stop " + ID);
}
}
/**
* QR scan button clicked
*
* @param v View QRButton clicked
*/
public void onQRButtonClick(View v) {
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.initiateScan();
}
/**
* Receive the Barcode Scanner Intent
*/
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
Uri uri;
try {
uri = Uri.parse(scanResult != null ? scanResult.getContents() : null); // this apparently prevents NullPointerException. Somehow.
} catch (NullPointerException e) {
Toast.makeText(getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
return;
}
String busStopID = getBusStopIDFromUri(uri);
busStopSearchByIDEditText.setText(busStopID);
- createFragmentForStop(busStopID);
+ requestArrivalsForStopID(busStopID);
}
public void onHideHint(View v) {
hideHints();
setOption(OPTION_SHOW_LEGEND, false);
}
public void onToggleKeyboardLayout(View v) {
if (searchMode == SEARCH_BY_NAME) {
setSearchModeBusStopID();
if (busStopSearchByIDEditText.requestFocus()) {
showKeyboard();
}
} else { // searchMode == SEARCH_BY_ID
setSearchModeBusStopName();
if (busStopSearchByNameEditText.requestFocus()) {
showKeyboard();
}
}
}
private void createDefaultSnackbar() {
if (snackbar == null) {
snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
}
snackbar.show();
}
///////////////////////////////// POSITION STUFF//////////////////////////////////////////////
private void resolveStopRequest(String provider) {
Log.d(DEBUG_TAG, "Provider " + provider + " got enabled");
if (locmgr != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) {
pendingNearbyStopsRequest = false;
theHandler.post(new NearbyStopsRequester());
}
}
final LocationListener locListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.d(DEBUG_TAG, "Location changed");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(DEBUG_TAG, "Location provider status: " + status);
if (status == LocationProvider.AVAILABLE) {
resolveStopRequest(provider);
}
}
@Override
public void onProviderEnabled(String provider) {
resolveStopRequest(provider);
}
@Override
public void onProviderDisabled(String provider) {
}
};
/**
* Run location requests separately and asynchronously
*/
class NearbyStopsRequester implements Runnable {
@Override
public void run() {
final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false);
final boolean noPermission = ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
//if we don't have the permission, we have to ask for it, if we haven't
// asked too many times before
if (noPermission) {
if (!canRunPosition) {
pendingNearbyStopsRequest = true;
assertLocationPermissions();
Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission);
return;
} else {
Toast.makeText(getApplicationContext(), "Asked for permission position too many times", Toast.LENGTH_LONG).show();
}
} else setOption(LOCATION_PERMISSION_GIVEN, true);
LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (locManager == null) {
Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment");
return;
}
if (anyLocationProviderMatchesCriteria(locManager, cr, true)
&& fh.getLastSuccessfullySearchedBusStop() == null
&& !framan.isDestroyed()) {
//Go ahead with the request
Log.d("mainActivity", "Recreating stop fragment");
swipeRefreshLayout.setVisibility(View.VISIBLE);
NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS);
Fragment oldFrag = framan.findFragmentById(R.id.resultFrame);
FragmentTransaction ft = framan.beginTransaction();
if (oldFrag != null)
ft.remove(oldFrag);
ft.add(R.id.resultFrame, fragment, "nearbyStop_correct");
ft.commit();
framan.executePendingTransactions();
pendingNearbyStopsRequest = false;
} else if (!anyLocationProviderMatchesCriteria(locManager, cr, true)) {
//Wait for the providers
Log.d(DEBUG_TAG, "Queuing position request");
pendingNearbyStopsRequest = true;
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10, 0.1f, locListener);
}
}
}
private boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled) {
List providers = mng.getProviders(cr, enabled);
Log.d(DEBUG_TAG, "Getting enabled location providers: ");
for (String s : providers) {
Log.d(DEBUG_TAG, "Provider " + s);
}
return providers.size() > 0;
}
///////////////////////////////// OTHER STUFF //////////////////////////////////////////////////
/**
* Get the last successfully searched bus stop or NULL
*
* @return
*/
@Override
public Stop getLastSuccessfullySearchedBusStop() {
return fh.getLastSuccessfullySearchedBusStop();
}
/**
* Get the last successfully searched bus stop ID or NULL
*
* @return
*/
@Override
public String getLastSuccessfullySearchedBusStopID() {
Stop stop = getLastSuccessfullySearchedBusStop();
return stop == null ? null : stop.ID;
}
/**
* Update the star "Add to favorite" icon
*/
@Override
public void updateStarIconFromLastBusStop() {
+ //TODO: move this INSIDE the arrivalsFragment
// no favorites no party!
addToFavorites = (ImageButton) findViewById(R.id.addToFavorites);
if (addToFavorites == null) {
Log.d("MainActivity", "Why the fuck the star is not here?!");
return;
}
// check if there is a last Stop
String stopID = getLastSuccessfullySearchedBusStopID();
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (isStopInFavorites(stopID)) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
}
/**
* Check if the last Bus Stop is in the favorites
*
* @return
*/
public boolean isStopInFavorites(String busStopId) {
boolean found = false;
// no stop no party
if (busStopId != null) {
SQLiteDatabase userDB = new UserDB(getApplicationContext()).getReadableDatabase();
found = UserDB.isStopInFavorites(userDB, busStopId);
}
return found;
}
/**
* Add the last Stop to favorites
*/
@Override
public void toggleLastStopToFavorites() {
Stop stop = getLastSuccessfullySearchedBusStop();
if (stop != null) {
+ // THIS MAKES NO FUCKING SENSE, it's even showing a MEMORY LEAK WARNING
// toggle the status in background
new AsyncStopFavoriteAction(getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE) {
/**
* Callback fired when the Stop is saved in the favorites
* @param result
*/
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
// update the star icon
updateStarIconFromLastBusStop();
}
}.execute(stop);
} else {
// this case have no sense, but just immediately update the favorite icon
updateStarIconFromLastBusStop();
}
}
@Override
public void showFloatingActionButton(boolean yes) {
if (yes) floatingActionButton.show();
else floatingActionButton.hide();
}
@Override
public void enableRefreshLayout(boolean yes) {
swipeRefreshLayout.setEnabled(yes);
}
////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
public void showKeyboard() {
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText;
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
private void setSearchModeBusStopID() {
searchMode = SEARCH_BY_ID;
busStopSearchByNameEditText.setVisibility(View.GONE);
busStopSearchByNameEditText.setText("");
busStopSearchByIDEditText.setVisibility(View.VISIBLE);
floatingActionButton.setImageResource(R.drawable.alphabetical);
}
private void setSearchModeBusStopName() {
searchMode = SEARCH_BY_NAME;
busStopSearchByIDEditText.setVisibility(View.GONE);
busStopSearchByIDEditText.setText("");
busStopSearchByNameEditText.setVisibility(View.VISIBLE);
floatingActionButton.setImageResource(R.drawable.numeric);
}
/**
* Having that cursor at the left of the edit text makes me cancer.
*
* @param busStopID bus stop ID
*/
private void setBusStopSearchByIDEditText(String busStopID) {
busStopSearchByIDEditText.setText(busStopID);
busStopSearchByIDEditText.setSelection(busStopID.length());
}
private void showHints() {
howDoesItWorkTextView.setVisibility(View.VISIBLE);
hideHintButton.setVisibility(View.VISIBLE);
actionHelpMenuItem.setVisible(false);
}
private void hideHints() {
howDoesItWorkTextView.setVisibility(View.GONE);
hideHintButton.setVisibility(View.GONE);
actionHelpMenuItem.setVisible(true);
}
//TODO: toggle spinner from mainActivity
@Override
public void toggleSpinner(boolean enable) {
if (enable) {
//already set by the RefreshListener when needed
//swipeRefreshLayout.setRefreshing(true);
progressBar.setVisibility(View.VISIBLE);
} else {
swipeRefreshLayout.setRefreshing(false);
progressBar.setVisibility(View.GONE);
}
}
private void prepareGUIForBusLines() {
swipeRefreshLayout.setEnabled(true);
swipeRefreshLayout.setVisibility(View.VISIBLE);
actionHelpMenuItem.setVisible(true);
}
private void prepareGUIForBusStops() {
swipeRefreshLayout.setEnabled(false);
swipeRefreshLayout.setVisibility(View.VISIBLE);
actionHelpMenuItem.setVisible(false);
}
/**
* This provides a temporary fix to make the transition
* to a single asynctask go smoother
*
* @param fragmentType the type of fragment created
*/
@Override
public void readyGUIfor(FragmentKind fragmentType) {
hideKeyboard();
//if we are getting results, already, stop waiting for nearbyStops
if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) {
locmgr.removeUpdates(locListener);
pendingNearbyStopsRequest = false;
}
if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
else
switch (fragmentType) {
case ARRIVALS:
prepareGUIForBusLines();
if (getOption(OPTION_SHOW_LEGEND, true)) {
showHints();
}
break;
case STOPS:
prepareGUIForBusStops();
break;
default:
Log.e("BusTO Activity", "Called readyGUI with unsupported type of Fragment");
return;
}
// Shows hints
}
/**
* Open an URL in the default browser.
*
* @param url URL
*/
public void openIceweasel(String url) {
Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(browserIntent1);
}
///////////////////// INTENT HELPER ////////////////////////////////////////////////////////////
/**
* Try to extract the bus stop ID from a URi
*
* @param uri The URL
* @return bus stop ID or null
*/
public static String getBusStopIDFromUri(Uri uri) {
String busStopID;
// everithing catches fire when passing null to a switch.
String host = uri.getHost();
if (host == null) {
Log.e("ActivityMain", "Not an URL: " + uri);
return null;
}
switch (host) {
case "m.gtt.to.it":
// http://m.gtt.to.it/m/it/arrivi.jsp?n=1254
busStopID = uri.getQueryParameter("n");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?n from: " + uri);
}
break;
case "www.gtt.to.it":
case "gtt.to.it":
// http://www.gtt.to.it/cms/percorari/arrivi?palina=1254
busStopID = uri.getQueryParameter("palina");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?palina from: " + uri);
}
break;
default:
Log.e("ActivityMain", "Unexpected intent URL: " + uri);
busStopID = null;
}
return busStopID;
}
- public void changeStarType(String stopID) {
- if (isStopInFavorites(stopID)) {
- changeStarFilled();
- } else {
- changeStarOutline();
- }
- }
-
- public void changeStarFilled() {
- addToFavorites.setImageResource(R.drawable.ic_star_filled);
- }
-
- public void changeStarOutline() {
- addToFavorites.setImageResource(R.drawable.ic_star_outline);
- }
-
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
index b1c23bd..fc582dc 100644
--- a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
@@ -1,273 +1,272 @@
/*
BusTO - UI components
Copyright (C) 2017 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.adapters;
import android.content.Context;
import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import com.android.volley.VolleyError;
+
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
-import it.reyboz.bustorino.fragments.FragmentListener;
+import it.reyboz.bustorino.fragments.FragmentListenerMain;
import it.reyboz.bustorino.util.RoutePositionSorter;
import it.reyboz.bustorino.util.StopSorterByDistance;
-import java.io.NotSerializableException;
import java.util.*;
public class ArrivalsStopAdapter extends RecyclerView.Adapter {
private final static int layoutRes = R.layout.arrivals_nearby_card;
//private List stops;
private @Nullable Location userPosition;
- private FragmentListener listener;
+ private FragmentListenerMain listener;
private List< Pair > routesPairList = new ArrayList<>();
- private Context context;
+ private final Context context;
//Maximum number of stops to keep
private final int MAX_STOPS = 20; //TODO: make it programmable
- public ArrivalsStopAdapter(@Nullable List< Pair > routesPairList, FragmentListener fragmentListener, Context con, @Nullable Location pos) {
+ public ArrivalsStopAdapter(@Nullable List< Pair > routesPairList, FragmentListenerMain fragmentListener, Context con, @Nullable Location pos) {
listener = fragmentListener;
userPosition = pos;
this.routesPairList = routesPairList;
context = con.getApplicationContext();
resetListAndPosition();
// if(paline!=null)
//resetRoutesPairList(paline);
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//DO THE ACTUAL WORK TO PUT THE DATA
if(routesPairList==null || routesPairList.size() == 0) return; //NO STOPS
final Pair stopRoutePair = routesPairList.get(position);
if(stopRoutePair!=null && stopRoutePair.first!=null){
final Stop stop = stopRoutePair.first;
final Route r = stopRoutePair.second;
final Double distance = stop.getDistanceFromLocation(userPosition);
if(distance!=Double.POSITIVE_INFINITY){
holder.distancetextView.setText(distance.intValue()+" m");
} else {
holder.distancetextView.setVisibility(View.GONE);
}
final String stopText = String.format(context.getResources().getString(R.string.two_strings_format),stop.getStopDisplayName(),stop.ID);
holder.stopNameView.setText(stopText);
//final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione);
if (r!=null) {
holder.lineNameTextView.setText(r.getNameForDisplay());
holder.lineDirectionTextView.setText(r.destinazione);
holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true));
} else {
holder.lineNameTextView.setVisibility(View.INVISIBLE);
holder.lineDirectionTextView.setVisibility(View.INVISIBLE);
//holder.arrivalsTextView.setVisibility(View.INVISIBLE);
}
/* EXPERIMENTS
if(r.destinazione==null || r.destinazione.trim().isEmpty()){
holder.lineDirectionTextView.setVisibility(View.GONE);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams();
params.addRule(RelativeLayout.RIGHT_OF,holder.lineNameTextView.getId());
holder.arrivalsDescriptionTextView.setLayoutParams(params);
} else {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams();
params.removeRule(RelativeLayout.RIGHT_OF);
holder.arrivalsDescriptionTextView.setLayoutParams(params);
holder.lineDirectionTextView.setVisibility(View.VISIBLE);
}
*/
holder.stopID =stop.ID;
} else {
Log.w("SquareStopAdapter","!! The selected stop is null !!");
}
}
@Override
public int getItemCount() {
return routesPairList.size();
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView lineNameTextView;
TextView lineDirectionTextView;
TextView stopNameView;
TextView arrivalsDescriptionTextView;
TextView arrivalsTextView;
TextView distancetextView;
String stopID;
ViewHolder(View holdView){
super(holdView);
holdView.setOnClickListener(this);
lineNameTextView = (TextView) holdView.findViewById(R.id.lineNameTextView);
lineDirectionTextView = (TextView) holdView.findViewById(R.id.lineDirectionTextView);
stopNameView = (TextView) holdView.findViewById(R.id.arrivalStopName);
arrivalsTextView = (TextView) holdView.findViewById(R.id.arrivalsTimeTextView);
arrivalsDescriptionTextView = (TextView) holdView.findViewById(R.id.arrivalsDescriptionTextView);
distancetextView = (TextView) holdView.findViewById(R.id.arrivalsDistanceTextView);
}
@Override
public void onClick(View v) {
- listener.createFragmentForStop(stopID);
+ listener.requestArrivalsForStopID(stopID);
}
}
public void resetRoutesPairList(List stopList){
Collections.sort(stopList,new StopSorterByDistance(userPosition));
this.routesPairList = new ArrayList<>(stopList.size());
int maxNum = Math.min(MAX_STOPS, stopList.size());
for(Palina p: stopList.subList(0,maxNum)){
//if there are no routes available, skip stop
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
routesPairList.add(new Pair<>(p,r));
}
}
}
public void setUserPosition(@Nullable Location userPosition) {
this.userPosition = userPosition;
}
public void setRoutesPairListAndPosition(List> mRoutesPairList, @Nullable Location pos) {
if(pos!=null){
this.userPosition = pos;
}
if(mRoutesPairList!=null){
//this.routesPairList = routesPairList;
//remove duplicates
sortAndRemoveDuplicates(mRoutesPairList, this.userPosition);
//routesPairList = mRoutesPairList;
//STUPID CODE
if (this.routesPairList == null || routesPairList.size() == 0){
routesPairList = mRoutesPairList;
notifyDataSetChanged();
} else{
final HashMap, Integer> indexMapIn = getRouteIndexMap(mRoutesPairList);
final HashMap, Integer> indexMapExisting = getRouteIndexMap(routesPairList);
//List> oldList = routesPairList;
routesPairList = mRoutesPairList;
/*
for (Pair pair: indexMapIn.keySet()){
final Integer posIn = indexMapIn.get(pair);
if (posIn == null) continue;
if (indexMapExisting.containsKey(pair)){
final Integer posExisting = indexMapExisting.get(pair);
//THERE IS ALREADY
//routesPairList.remove(posExisting.intValue());
//routesPairList.add(posIn,mRoutesPairList.get(posIn));
notifyItemMoved(posExisting, posIn);
indexMapExisting.remove(pair);
} else{
//INSERT IT
//routesPairList.add(posIn,mRoutesPairList.get(posIn));
notifyItemInserted(posIn);
}
}//
//REMOVE OLD STOPS
for (Pair pair: indexMapExisting.keySet()) {
final Integer posExisting = indexMapExisting.get(pair);
if (posExisting == null) continue;
//routesPairList.remove(posExisting.intValue());
notifyItemRemoved(posExisting);
}
//*/notifyDataSetChanged();
}
//remove and join the
}
}
/**
* Sort and remove the repetitions for the routesPairList
*/
private void resetListAndPosition(){
Collections.sort(this.routesPairList,new RoutePositionSorter(userPosition));
//All of this to get only the first occurrences of a line (name & direction)
ListIterator> iterator = routesPairList.listIterator();
Set> allRoutesDirections = new HashSet<>();
while(iterator.hasNext()){
final Pair stopRoutePair = iterator.next();
if (stopRoutePair.second != null) {
final Pair routeNameDirection = new Pair<>(stopRoutePair.second.getName(), stopRoutePair.second.destinazione);
if (allRoutesDirections.contains(routeNameDirection)) {
iterator.remove();
} else {
allRoutesDirections.add(routeNameDirection);
}
}
}
}
/**
* Sort and remove the repetitions in the list
*/
private static void sortAndRemoveDuplicates(List< Pair > routesPairList, Location positionToSort ){
Collections.sort(routesPairList,new RoutePositionSorter(positionToSort));
//All of this to get only the first occurrences of a line (name & direction)
ListIterator> iterator = routesPairList.listIterator();
Set> allRoutesDirections = new HashSet<>();
while(iterator.hasNext()){
final Pair stopRoutePair = iterator.next();
if (stopRoutePair.second != null) {
final Pair routeNameDirection = new Pair<>(stopRoutePair.second.getName(), stopRoutePair.second.destinazione);
if (allRoutesDirections.contains(routeNameDirection)) {
iterator.remove();
} else {
allRoutesDirections.add(routeNameDirection);
}
}
}
}
private static HashMap, Integer> getRouteIndexMap(List> routesPairList){
final HashMap, Integer> myMap = new HashMap<>();
for (int i=0; i(name.toLowerCase(Locale.ROOT).trim(),destination.toLowerCase(Locale.ROOT).trim()), i);
}
return myMap;
}
}
diff --git a/src/it/reyboz/bustorino/adapters/SquareStopAdapter.java b/src/it/reyboz/bustorino/adapters/SquareStopAdapter.java
index e4ab98b..0892a9e 100644
--- a/src/it/reyboz/bustorino/adapters/SquareStopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/SquareStopAdapter.java
@@ -1,128 +1,128 @@
/*
BusTO - UI components
Copyright (C) 2017 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.adapters;
import android.location.Location;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.util.StopSorterByDistance;
-import it.reyboz.bustorino.fragments.FragmentListener;
+import it.reyboz.bustorino.fragments.FragmentListenerMain;
import java.util.Collections;
import java.util.List;
public class SquareStopAdapter extends RecyclerView.Adapter {
private final static int layoutRes = R.layout.stop_card;
//private List stops;
private @Nullable Location userPosition;
- private FragmentListener listener;
+ private FragmentListenerMain listener;
private List stops;
- public SquareStopAdapter(@Nullable List stopList, FragmentListener fragmentListener, @Nullable Location pos) {
+ public SquareStopAdapter(@Nullable List stopList, FragmentListenerMain fragmentListener, @Nullable Location pos) {
listener = fragmentListener;
userPosition = pos;
stops = stopList;
}
@Override
public SquareViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
//sort the stops by distance
if(stops != null && stops.size() > 0)
Collections.sort(stops,new StopSorterByDistance(userPosition));
return new SquareViewHolder(view);
}
@Override
public void onBindViewHolder(SquareViewHolder holder, int position) {
//DO THE ACTUAL WORK TO PUT THE DATA
if(stops==null || stops.size() == 0) return; //NO STOPS
final Stop stop = stops.get(position);
if(stop!=null){
if(stop.getDistanceFromLocation(userPosition)!=Double.POSITIVE_INFINITY){
Double distance = stop.getDistanceFromLocation(userPosition);
holder.distancetextView.setText(distance.intValue()+" m");
} else {
holder.distancetextView.setVisibility(View.GONE);
}
holder.stopNameView.setText(stop.getStopDisplayName());
holder.stopIDView.setText(stop.ID);
String whatStopsHere = stop.routesThatStopHereToString();
if(whatStopsHere == null) {
holder.routesView.setVisibility(View.GONE);
} else {
holder.routesView.setText(whatStopsHere);
holder.routesView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
}
holder.stopID =stop.ID;
} else {
Log.w("SquareStopAdapter","!! The selected stop is null !!");
}
}
@Override
public int getItemCount() {
return stops.size();
}
class SquareViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView stopIDView;
TextView stopNameView;
TextView routesView;
TextView distancetextView;
String stopID;
SquareViewHolder(View holdView){
super(holdView);
holdView.setOnClickListener(this);
stopIDView = (TextView) holdView.findViewById(R.id.stop_numberText);
stopNameView = (TextView) holdView.findViewById(R.id.stop_nameText);
routesView = (TextView) holdView.findViewById(R.id.stop_linesText);
distancetextView = (TextView) holdView.findViewById(R.id.stop_distanceTextView);
}
@Override
public void onClick(View v) {
- listener.createFragmentForStop(stopID);
+ listener.requestArrivalsForStopID(stopID);
}
}
public void setStops(List stops) {
this.stops = stops;
}
public void setUserPosition(@Nullable Location userPosition) {
this.userPosition = userPosition;
}
/*
@Override
public Stop getItem(int position) {
return stops.get(position);
}
*/
}
diff --git a/src/it/reyboz/bustorino/data/UserDB.java b/src/it/reyboz/bustorino/data/UserDB.java
index 9dbeb31..476cc9f 100644
--- a/src/it/reyboz/bustorino/data/UserDB.java
+++ b/src/it/reyboz/bustorino/data/UserDB.java
@@ -1,281 +1,291 @@
/*
BusTO ("backend" components)
Copyright (C) 2016 Ludovico Pavesi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.data;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.StopsDBInterface;
public class UserDB extends SQLiteOpenHelper {
public static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "user.db";
static final String TABLE_NAME = "favorites";
private final Context c; // needed during upgrade
private final static String[] usernameColumnNameAsArray = {"username"};
public final static String[] getFavoritesColumnNamesAsArray = {"ID", "username"};
public UserDB(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.c = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
// exception intentionally left unhandled
db.execSQL("CREATE TABLE favorites (ID TEXT PRIMARY KEY NOT NULL, username TEXT)");
if(OldDB.doesItExist(this.c)) {
upgradeFromOldDatabase(db);
}
}
private void upgradeFromOldDatabase(SQLiteDatabase newdb) {
OldDB old;
try {
old = new OldDB(this.c);
} catch(IllegalStateException e) {
// can't create database => it doesn't really exist, no matter what doesItExist() says
return;
}
int ver = old.getOldVersion();
/* version 8 was the previous version, OldDB "upgrades" itself to 1337 but unless the app
* has crashed midway through the upgrade and the user is retrying, that should never show
* up here. And if it does, try to recover favorites anyway.
* Versions < 8 already got dropped during the update process, so let's do the same.
*
* Edit: Android runs getOldVersion() then, after a while, onUpgrade(). Just to make it
* more complicated. Workaround added in OldDB.
*/
if(ver >= 8) {
ArrayList ID = new ArrayList<>();
ArrayList username = new ArrayList<>();
int len;
int len2;
try {
Cursor c = old.getReadableDatabase().rawQuery("SELECT busstop_ID, busstop_username FROM busstop WHERE busstop_isfavorite = 1 ORDER BY busstop_name ASC", new String[] {});
int zero = c.getColumnIndex("busstop_ID");
int one = c.getColumnIndex("busstop_username");
while(c.moveToNext()) {
try {
ID.add(c.getString(zero));
} catch(Exception e) {
// no ID = can't add this
continue;
}
if(c.getString(one) == null || c.getString(one).length() <= 0) {
username.add(null);
} else {
username.add(c.getString(one));
}
}
c.close();
old.close();
} catch(Exception ignored) {
// there's no hope, go ahead and nuke old database.
}
len = ID.size();
len2 = username.size();
if(len2 < len) {
len = len2;
}
if (len > 0) {
try {
Stop stopStopStopStopStop;
for (int i = 0; i < len; i++) {
stopStopStopStopStop = new Stop(ID.get(i));
stopStopStopStopStop.setStopUserName(username.get(i));
addOrUpdateStop(stopStopStopStopStop, newdb);
}
} catch(Exception ignored) {
// partial data is better than no data at all, no transactions here
}
}
}
if(!OldDB.destroy(this.c)) {
// TODO: notify user somehow?
Log.e("UserDB", "Failed to delete old database, you should really uninstall and reinstall the app. Unfortunately I have no way to tell the user.");
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// nothing to do yet
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// nothing to do yet
}
/**
* Check if a stop ID is in the favorites
*
* @param db readable database
* @param stopId stop ID
* @return boolean
*/
public static boolean isStopInFavorites(SQLiteDatabase db, String stopId) {
boolean found = false;
try {
Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopId}, null, null, null);
if(c.moveToNext()) {
found = true;
}
c.close();
} catch(SQLiteException ignored) {
// don't care
}
return found;
}
/**
* Gets stop name set by the user.
*
* @param db readable database
* @param stopID stop ID
* @return name set by user, or null if not set\not found
*/
public static String getStopUserName(SQLiteDatabase db, String stopID) {
String username = null;
try {
Cursor c = db.query(TABLE_NAME, usernameColumnNameAsArray, "ID = ?", new String[] {stopID}, null, null, null);
if(c.moveToNext()) {
username = c.getString(c.getColumnIndex("username"));
}
c.close();
} catch(SQLiteException ignored) {}
return username;
}
/**
* Get all the bus stops marked as favorites
*
* @param db
* @param dbi
* @return
*/
public static List getFavorites(SQLiteDatabase db, StopsDBInterface dbi) {
List l = new ArrayList<>();
Stop s;
String stopID, stopUserName;
try {
Cursor c = db.query(TABLE_NAME, getFavoritesColumnNamesAsArray, null, null, null, null, null, null);
int colID = c.getColumnIndex("ID");
int colUser = c.getColumnIndex("username");
while(c.moveToNext()) {
stopUserName = c.getString(colUser);
stopID = c.getString(colID);
s = dbi.getAllFromID(stopID);
if(s == null) {
// can't find it in database
l.add(new Stop(stopUserName, stopID, null, null, null));
} else {
// setStopName() already does sanity checks
s.setStopUserName(stopUserName);
l.add(s);
}
}
c.close();
} catch(SQLiteException ignored) {}
// comparison rules are too complicated to let SQLite do this (e.g. it outputs: 3234, 34, 576, 67, 8222) and stop name is in another database
Collections.sort(l);
return l;
}
public static boolean addOrUpdateStop(Stop s, SQLiteDatabase db) {
ContentValues cv = new ContentValues();
long result = -1;
String un = s.getStopUserName();
cv.put("ID", s.ID);
// is there an username?
if(un == null) {
// no: see if it's in the database
cv.put("username", getStopUserName(db, s.ID));
} else {
// yes: use it
cv.put("username", un);
}
try {
//ignore and throw -1 if the row is already in the DB
result = db.insertWithOnConflict(TABLE_NAME, null, cv,SQLiteDatabase.CONFLICT_IGNORE);
} catch (SQLiteException ignored) {}
// Android Studio suggested this unreadable replacement: return true if insert succeeded (!= -1), or try to update and return
return (result != -1) || updateStop(s, db);
}
public static boolean updateStop(Stop s, SQLiteDatabase db) {
try {
ContentValues cv = new ContentValues();
cv.put("username", s.getStopUserName());
db.update(TABLE_NAME, cv, "ID = ?", new String[]{s.ID});
return true;
} catch(SQLiteException e) {
return false;
}
}
public static boolean deleteStop(Stop s, SQLiteDatabase db) {
try {
db.delete(TABLE_NAME, "ID = ?", new String[]{s.ID});
return true;
} catch(SQLiteException e) {
return false;
}
}
+ public static boolean checkStopInFavorites(String stopID, Context con){
+ boolean found = false;
+ // no stop no party
+ if (stopID != null) {
+ SQLiteDatabase userDB = new UserDB(con).getReadableDatabase();
+ found = UserDB.isStopInFavorites(userDB, stopID);
+ }
+
+ return found;
+ }
}
diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
index 64fc35a..48df060 100644
--- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -1,405 +1,405 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.data.UserDB;
public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
private final static String KEY_STOP_ID = "stopid";
private final static String KEY_STOP_NAME = "stopname";
private final static String DEBUG_TAG = "BUSTOArrivalsFragment";
private final static int loaderFavId = 2;
private final static int loaderStopId = 1;
private final static ArrivalsFetcher[] defaultFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
static final String STOP_TITLE = "messageExtra";
private @Nullable String stopID,stopName;
private DBStatusManager prefs;
private DBStatusManager.OnDBUpdateStatusChangeListener listener;
private boolean justCreated = false;
private Palina lastUpdatedPalina = null;
private boolean needUpdateOnAttach = false;
private boolean fetchersChangeRequestPending = false;
//Views
protected ImageButton addToFavorites;
protected TextView timesSourceTextView;
private List fetchers = new ArrayList<>(Arrays.asList(defaultFetchers));
public static ArrivalsFragment newInstance(String stopID){
return newInstance(stopID, null);
}
public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){
ArrivalsFragment fragment = new ArrivalsFragment();
Bundle args = new Bundle();
args.putString(KEY_STOP_ID,stopID);
//parameter for ResultListFragment
args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
if (stopName != null){
args.putString(KEY_STOP_NAME,stopName);
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stopID = getArguments().getString(KEY_STOP_ID);
//this might really be null
stopName = getArguments().getString(KEY_STOP_NAME);
final ArrivalsFragment arrivalsFragment = this;
listener = new DBStatusManager.OnDBUpdateStatusChangeListener() {
@Override
public void onDBStatusChanged(boolean updating) {
if(!updating){
getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment);
} else {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(loaderFavId);
lm.destroyLoader(loaderStopId);
}
}
@Override
public boolean defaultStatusValue() {
return true;
}
};
prefs = new DBStatusManager(getContext().getApplicationContext(),listener);
justCreated = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_arrivals, container, false);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites);
resultsListView = (ListView) root.findViewById(R.id.resultsListView);
timesSourceTextView = (TextView) root.findViewById(R.id.timesSourceTextView);
timesSourceTextView.setOnLongClickListener(view -> {
if(!fetchersChangeRequestPending){
rotateFetchers();
//Show we are changing provider
timesSourceTextView.setText(R.string.arrival_source_changing);
- //request new arrival times
- mListener.createFragmentForStop(stopID);
+
+ mListener.requestArrivalsForStopID(stopID);
fetchersChangeRequestPending = true;
return true;
}
return false;
});
timesSourceTextView.setOnClickListener(view -> {
Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT)
.show();
});
//Button
addToFavorites.setClickable(true);
addToFavorites.setOnClickListener(v -> {
// add/remove the stop in the favorites
mListener.toggleLastStopToFavorites();
});
resultsListView.setOnItemClickListener((parent, view, position, id) -> {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
});
String displayName = getArguments().getString(STOP_TITLE);
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
return root;
}
@Override
public void onResume() {
super.onResume();
LoaderManager loaderManager = getLoaderManager();
if(stopID!=null){
//refresh the arrivals
if(!justCreated)
- mListener.createFragmentForStop(stopID);
+ mListener.requestArrivalsForStopID(stopID);
else justCreated = false;
//start the loader
if(prefs.isDBUpdating(true)){
prefs.registerListener();
} else {
loaderManager.restartLoader(loaderFavId, getArguments(), this);
}
updateMessage();
}
}
@Override
public void onStart() {
super.onStart();
if (needUpdateOnAttach){
updateFragmentData(null);
}
}
@Nullable
public String getStopID() {
return stopID;
}
/**
* Give the fetchers
* @return the list of the fetchers
*/
public ArrayList getCurrentFetchers(){
ArrayList v = new ArrayList();
for (ArrivalsFetcher fetcher: fetchers){
v.add(fetcher);
}
return v;
}
public Fetcher[] getCurrentFetchersAsArray(){
Fetcher[] arr = new Fetcher[fetchers.size()];
fetchers.toArray(arr);
return arr;
}
private void rotateFetchers(){
Collections.rotate(fetchers, -1);
}
/**
* Update the UI with the new data
* @param p the full Palina
*/
public void updateFragmentData(@Nullable Palina p){
if (p!=null)
lastUpdatedPalina = p;
if (!isAdded()){
//defer update at next show
if (p==null)
Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null");
else needUpdateOnAttach = true;
} else {
final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina);
showArrivalsSources(lastUpdatedPalina);
super.resetListAdapter(adapter);
}
}
/**
* Set the message of the arrival times source
* @param p Palina with the arrival times
*/
protected void showArrivalsSources(Palina p){
final Passaggio.Source source = p.getPassaggiSourceIfAny();
String source_txt;
switch (source){
case GTTJSON:
source_txt = getString(R.string.gttjsonfetcher);
break;
case FiveTAPI:
source_txt = getString(R.string.fivetapifetcher);
break;
case FiveTScraper:
source_txt = getString(R.string.fivetscraper);
break;
case UNDETERMINED:
//Don't show the view
timesSourceTextView.setVisibility(View.GONE);
return;
default:
throw new IllegalStateException("Unexpected value: " + source);
}
int count = 0;
while (source != fetchers.get(0).getSourceForFetcher() && count < 100){
//we need to update the fetcher that is requested
rotateFetchers();
count++;
}
if (count>10)
Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work");
final String base_message = getString(R.string.times_source_fmt, source_txt);
timesSourceTextView.setVisibility(View.VISIBLE);
timesSourceTextView.setText(base_message);
fetchersChangeRequestPending = false;
}
@Override
public void setNewListAdapter(ListAdapter adapter) {
throw new UnsupportedOperationException();
}
/**
* Update the message in the fragment
*
* It may eventually change the "Add to Favorite" icon
*/
private void updateMessage(){
String message = null;
if (stopName != null && stopID != null && stopName.length() > 0) {
message = (stopID.concat(" - ").concat(stopName));
} else if(stopID!=null) {
message = stopID;
} else {
Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong");
}
if(message!=null) {
setTextViewMessage(getString(R.string.passages,message));
}
// whatever is the case, update the star icon
mListener.updateStarIconFromLastBusStop();
}
@Override
public Loader onCreateLoader(int id, Bundle args) {
if(args.getString(KEY_STOP_ID)==null) return null;
final String stopID = args.getString(KEY_STOP_ID);
final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete();
CursorLoader cl;
switch (id){
case loaderFavId:
builder.appendPath("favorites").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null);
break;
case loaderStopId:
builder.appendPath("stop").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME},
null,null,null);
break;
default:
return null;
}
cl.setUpdateThrottle(500);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
switch (loader.getId()){
case loaderFavId:
final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]);
if(data.getCount()>0){
data.moveToFirst();
final String probableName = data.getString(colUserName);
if(probableName!=null && !probableName.isEmpty()){
stopName = probableName;
updateMessage();
}
}
if(stopName == null){
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB");
getLoaderManager().restartLoader(loaderStopId,getArguments(),this);
}
break;
case loaderStopId:
if(data.getCount()>0){
data.moveToFirst();
stopName = data.getString(data.getColumnIndex(
NextGenDB.Contract.StopsTable.COL_NAME
));
updateMessage();
} else {
Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
}
}
}
@Override
public void onPause() {
if(listener!=null)
prefs.unregisterListener();
super.onPause();
}
@Override
public void onLoaderReset(Loader loader) {
//NOTHING TO DO
}
}
diff --git a/src/it/reyboz/bustorino/fragments/BaseFragment.java b/src/it/reyboz/bustorino/fragments/BaseFragment.java
new file mode 100644
index 0000000..c1359ab
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/BaseFragment.java
@@ -0,0 +1,45 @@
+package it.reyboz.bustorino.fragments;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+
+import androidx.fragment.app.Fragment;
+
+import it.reyboz.bustorino.BuildConfig;
+
+import static android.content.Context.MODE_PRIVATE;
+
+public abstract class BaseFragment extends Fragment {
+
+ protected String PREF_FILE= BuildConfig.APPLICATION_ID+".fragment_prefs";
+
+ protected void setOption(String optionName, boolean value) {
+ Context mContext = getContext();
+ SharedPreferences.Editor editor = mContext.getSharedPreferences(PREF_FILE, MODE_PRIVATE).edit();
+ editor.putBoolean(optionName, value);
+ editor.commit();
+ }
+
+ protected boolean getOption(String optionName, boolean optDefault) {
+ Context mContext = getContext();
+ SharedPreferences preferences = mContext.getSharedPreferences(PREF_FILE, MODE_PRIVATE);
+ return preferences.getBoolean(optionName, optDefault);
+ }
+
+ protected void showToastMessage(int messageID, boolean short_lenght) {
+ final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
+ Toast.makeText(getContext(), messageID, length).show();
+ }
+
+ public void hideKeyboard() {
+ View view = getActivity().getCurrentFocus();
+ if (view != null) {
+ ((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE))
+ .hideSoftInputFromWindow(view.getWindowToken(),
+ InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ }
+}
diff --git a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
new file mode 100644
index 0000000..439b22f
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -0,0 +1,24 @@
+package it.reyboz.bustorino.fragments;
+
+public interface CommonFragmentListener {
+
+
+ /**
+ * Tell the activity that we need to disable/enable its floatingActionButton
+ * @param yes or no
+ */
+ void showFloatingActionButton(boolean yes);
+
+ /**
+ * Sends the message to the activity to adapt the GUI
+ * to the fragment that has been attached
+ * @param fragmentType the type of fragment attached
+ */
+ void readyGUIfor(FragmentKind fragmentType);
+ /**
+ * Houston, we need another fragment!
+ *
+ * @param ID the Stop ID
+ */
+ void requestArrivalsForStopID(String ID);
+}
diff --git a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java
index b81abc4..f72703a 100644
--- a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java
+++ b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java
@@ -1,85 +1,85 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.widget.AbsListView;
import java.lang.ref.WeakReference;
public class CommonScrollListener extends RecyclerView.OnScrollListener implements AbsListView.OnScrollListener{
- WeakReference listenerWeakReference;
+ WeakReference listenerWeakReference;
//enable swipeRefreshLayout when scrolling down or not
boolean enableRefreshLayout;
int lastvisibleitem;
- public CommonScrollListener(FragmentListener lis,boolean enableRefreshLayout){
+ public CommonScrollListener(FragmentListenerMain lis, boolean enableRefreshLayout){
listenerWeakReference = new WeakReference<>(lis);
this.enableRefreshLayout = enableRefreshLayout;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
/*
* This seems to be a totally useless method
*/
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- FragmentListener listener = listenerWeakReference.get();
+ FragmentListenerMain listener = listenerWeakReference.get();
if(listener==null){
//can't do anything, sorry
Log.i(this.getClass().getName(),"called onScroll but FragmentListener is null");
return;
}
if (firstVisibleItem>=0) {
if (lastvisibleitem < firstVisibleItem) {
Log.i("Busto", "Scrolling DOWN");
listener.showFloatingActionButton(false);
//lastScrollUp = true;
} else if (lastvisibleitem > firstVisibleItem) {
Log.i("Busto", "Scrolling UP");
listener.showFloatingActionButton(true);
//lastScrollUp = false;
}
lastvisibleitem = firstVisibleItem;
}
if(enableRefreshLayout){
boolean enable = false;
if(view != null && view.getChildCount() > 0){
// check if the first item of the list is visible
boolean firstItemVisible = view.getFirstVisiblePosition() == 0;
// check if the top of the first item is visible
boolean topOfFirstItemVisible = view.getChildAt(0).getTop() == 0;
// enabling or disabling the refresh layout
enable = firstItemVisible && topOfFirstItemVisible;
}
listener.enableRefreshLayout(enable);
//Log.d(getString(R.string.list_fragment_debug),"onScroll active, first item visible: "+firstVisibleItem+", refreshlayout enabled: "+enable);
}}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- FragmentListener listener = listenerWeakReference.get();
+ FragmentListenerMain listener = listenerWeakReference.get();
if(newState!=SCROLL_STATE_IDLE) listener.showFloatingActionButton(false);
else listener.showFloatingActionButton(true);
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index c46563b..9836f48 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,237 +1,237 @@
/*
BusTO (fragments)
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.sqlite.SQLiteException;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.middleware.*;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Helper class to manage the fragments and their needs
*/
public class FragmentHelper {
GeneralActivity act;
private Stop lastSuccessfullySearchedBusStop;
//support for multiple frames
private final int secondaryFrameLayout;
private final int swipeRefID;
private final int primaryFrameLayout;
public static final int NO_FRAME = -3;
private WeakReference lastTaskRef;
private NextGenDB newDBHelper;
private boolean shouldHaltAllActivities=false;
public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) {
this(act,swipeRefID,mainFrame,NO_FRAME);
}
public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) {
this.act = act;
this.swipeRefID = swipeRefID;
this.primaryFrameLayout = primaryFrameLayout;
this.secondaryFrameLayout = secondaryFrameLayout;
newDBHelper = new NextGenDB(act.getApplicationContext());
}
/**
* Get the last successfully searched bus stop or NULL
*
* @return the stop
*/
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
this.lastSuccessfullySearchedBusStop = stop;
}
public void setLastTaskRef(WeakReference lastTaskRef) {
this.lastTaskRef = lastTaskRef;
}
/**
* Called when you need to create a fragment for a specified Palina
* @param p the Stop that needs to be displayed
*/
public void createOrUpdateStopFragment(Palina p){
boolean sameFragment;
ArrivalsFragment arrivalsFragment;
if(act==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
return;
}
FragmentManager fm = act.getSupportFragmentManager();
if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) {
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
} else
sameFragment = false;
setLastSuccessfullySearchedBusStop(p);
if(!sameFragment) {
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
String displayStuff;
if (displayName != null && displayName.length() > 0) {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
} else {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p));
} else {
Log.d("BusTO", "Same bus stop, accessing existing fragment");
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
}
// DO NOT CALL `setListAdapter` ever on arrivals fragment
arrivalsFragment.updateFragmentData(p);
act.hideKeyboard();
toggleSpinner(false);
}
/**
* Called when you need to display the results of a search of stops
* @param resultList the List of stops found
* @param query String queried
*/
public void createFragmentFor(List resultList,String query){
act.hideKeyboard();
StopListFragment listfragment = StopListFragment.newInstance(query);
attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query);
listfragment.setStopList(resultList);
toggleSpinner(false);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
- if (act instanceof FragmentListener)
- ((FragmentListener) act).toggleSpinner(on);
+ if (act instanceof FragmentListenerMain)
+ ((FragmentListenerMain) act).toggleSpinner(on);
else {
SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID);
srl.setRefreshing(false);
}
}
/**
* Attach a new fragment to a cointainer
* @param fm the FragmentManager
* @param fragment the Fragment
* @param sendToSecondaryFrame needs to be displayed in secondary frame or not
* @param tag tag for the fragment
*/
public void attachFragmentToContainer(FragmentManager fm,Fragment fragment, boolean sendToSecondaryFrame, String tag){
FragmentTransaction ft = fm.beginTransaction();
if(sendToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
ft.replace(secondaryFrameLayout,fragment,tag);
else ft.replace(primaryFrameLayout,fragment,tag);
ft.addToBackStack("state_"+tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
//fm.executePendingTransactions();
}
synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){
if(newDBHelper !=null)
try {
return newDBHelper.insertBatchContent(valuesArr, tableName);
} catch (SQLiteException exc){
Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace());
return -2;
}
else return -1;
}
synchronized public ContentResolver getContentResolver(){
return act.getContentResolver();
}
public void setBlockAllActivities(boolean shouldI) {
this.shouldHaltAllActivities = shouldI;
}
public void stopLastRequestIfNeeded(){
if(lastTaskRef == null) return;
AsyncDataDownload task = lastTaskRef.get();
if(task!=null){
task.cancel(true);
}
}
/**
* Wrapper to show the errors/status that happened
* @param res result from Fetcher
*/
public void showErrorMessage(Fetcher.result res){
//TODO: implement a common set of errors for all fragments
switch (res){
case OK:
break;
case CLIENT_OFFLINE:
act.showToastMessage(R.string.network_error, true);
break;
case SERVER_ERROR:
if (act.isConnected()) {
act.showToastMessage(R.string.parsing_error, true);
} else {
act.showToastMessage(R.string.network_error, true);
}
case PARSER_ERROR:
default:
showShortToast(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
showShortToast(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
showShortToast(R.string.no_bus_stop_have_this_name);
break;
}
}
public void showShortToast(int message){
if (act!=null)
act.showToastMessage(message,true);
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentListener.java b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
similarity index 73%
rename from src/it/reyboz/bustorino/fragments/FragmentListener.java
rename to src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
index feb738f..dd16709 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentListener.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
@@ -1,72 +1,58 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import it.reyboz.bustorino.backend.Stop;
-public interface FragmentListener {
+public interface FragmentListenerMain extends CommonFragmentListener {
void toggleSpinner(boolean state);
- /**
- * Sends the message to the activity to adapt the GUI
- * to the fragment that has been attached
- * @param fragmentType the type of fragment attached
- */
- void readyGUIfor(FragmentKind fragmentType);
- /**
- * Houston, we need another fragment!
- *
- * @param ID the Stop ID
- */
- void createFragmentForStop(String ID);
+
/**
* Add the last successfully searched stop to the favorites
*/
void toggleLastStopToFavorites();
/**
* Get the last successfully searched bus stop or NULL
*
+ * TODO: Figure out why it's in this interface, then move it
* @return
*/
Stop getLastSuccessfullySearchedBusStop();
/**
* Get the last successfully searched bus stop ID or NULL
*
+ * TODO: Figure out why it's in this interface, then move it
+ * WHY IS IT HERE, AND WHY DO WE KEEP HAVE TO USE NULL???
* @return
*/
String getLastSuccessfullySearchedBusStopID();
/**
* Automatically update the "Add to favorite" star icon
*/
void updateStarIconFromLastBusStop();
- /**
- * Tell the activity that we need to disable/enable its floatingActionButton
- * @param yes or no
- */
- void showFloatingActionButton(boolean yes);
-
/**
* Tell activity that we need to enable/disable the refreshLayout
* @param yes or no
*/
void enableRefreshLayout(boolean yes);
}
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index 5d42247..5a56cf1 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,111 +1,356 @@
package it.reyboz.bustorino.fragments;
+import android.content.Context;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
+import android.util.Log;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.integration.android.IntentIntegrator;
import it.reyboz.bustorino.R;
-
+import it.reyboz.bustorino.backend.ArrivalsFetcher;
+import it.reyboz.bustorino.backend.FiveTAPIFetcher;
+import it.reyboz.bustorino.backend.FiveTScraperFetcher;
+import it.reyboz.bustorino.backend.FiveTStopsFetcher;
+import it.reyboz.bustorino.backend.GTTJSONFetcher;
+import it.reyboz.bustorino.backend.GTTStopsFetcher;
+import it.reyboz.bustorino.backend.Stop;
+import it.reyboz.bustorino.backend.StopsFinderByName;
+import it.reyboz.bustorino.data.UserDB;
+import it.reyboz.bustorino.middleware.AsyncDataDownload;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
-public class MainScreenFragment extends Fragment {
+public class MainScreenFragment extends BaseFragment implements FragmentListenerMain{
+
+
+ private final String OPTION_SHOW_LEGEND = "show_legend";
- // TODO: Rename parameter arguments, choose names that match
- // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String ARG_PARAM1 = "param1";
- private static final String ARG_PARAM2 = "param2";
+ private static final String DEBUG_TAG = "BusTO - MainFragment";
+
+ private CommonFragmentListener mListener;
+ /// UI ELEMENTS //
+ private ImageButton addToFavorites;
+ private FragmentHelper fragmentHelper;
+ private SwipeRefreshLayout swipeRefreshLayout;
+ private EditText busStopSearchByIDEditText;
+ private EditText busStopSearchByNameEditText;
+ private ProgressBar progressBar;
+ private TextView howDoesItWorkTextView;
+ private Button hideHintButton;
+ private MenuItem actionHelpMenuItem;
+ private FloatingActionButton floatingActionButton;
+ //private Snackbar snackbar;
+ /*
+ * Search mode
+ */
+ private static final int SEARCH_BY_NAME = 0;
+ private static final int SEARCH_BY_ID = 1;
+ private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
+ private int searchMode;
+ //private ImageButton addToFavorites;
+ private final ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()};
- // TODO: Rename and change types of parameters
- private String mParam1;
- private String mParam2;
public MainScreenFragment() {
// Required empty public constructor
}
- /**
- * Use this factory method to create a new instance of
- * this fragment using the provided parameters.
- *
- * @param param1 Parameter 1.
- * @param param2 Parameter 2.
- * @return A new instance of fragment MainScreenFragment.
- */
- // TODO: Rename and change types and number of parameters
- public static MainScreenFragment newInstance(String param1, String param2) {
+
+ public static MainScreenFragment newInstance() {
MainScreenFragment fragment = new MainScreenFragment();
Bundle args = new Bundle();
- args.putString(ARG_PARAM1, param1);
- args.putString(ARG_PARAM2, param2);
+ //args.putString(ARG_PARAM1, param1);
+ //args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
- mParam1 = getArguments().getString(ARG_PARAM1);
- mParam2 = getArguments().getString(ARG_PARAM2);
+ //do nothing
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
- return inflater.inflate(R.layout.fragment_main_screen, container, false);
+ View root = inflater.inflate(R.layout.fragment_main_screen, container, false);
+ addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites);
+ busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText);
+ busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText);
+ progressBar = root.findViewById(R.id.progressBar);
+ howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView);
+ hideHintButton = root.findViewById(R.id.hideHintButton);
+ swipeRefreshLayout = root.findViewById(R.id.listRefreshLayout);
+ floatingActionButton = root.findViewById(R.id.floatingActionButton);
+ return root;
}
+
+
/*
GUI METHODS
*/
/**
* QR scan button clicked
*
* @param v View QRButton clicked
*/
public void onQRButtonClick(View v) {
IntentIntegrator integrator = new IntentIntegrator(getActivity());
integrator.initiateScan();
}
public void onHideHint(View v) {
- /*
+
hideHints();
setOption(OPTION_SHOW_LEGEND, false);
-
- */
}
+ /**
+ * OK this is pure shit
+ *
+ * @param v View clicked
+ */
+ public void onSearchClick(View v) {
+ final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
+ if (searchMode == SEARCH_BY_ID) {
+ String busStopID = busStopSearchByIDEditText.getText().toString();
+ requestArrivalsForStopID(busStopID);
+ } else { // searchMode == SEARCH_BY_NAME
+ String query = busStopSearchByNameEditText.getText().toString();
+ //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
+ new AsyncDataDownload(fragmentHelper, stopsFinderByNames).execute(query);
+ }
+ }
+
public void onToggleKeyboardLayout(View v) {
- /*
+
if (searchMode == SEARCH_BY_NAME) {
setSearchModeBusStopID();
if (busStopSearchByIDEditText.requestFocus()) {
showKeyboard();
}
} else { // searchMode == SEARCH_BY_ID
setSearchModeBusStopName();
if (busStopSearchByNameEditText.requestFocus()) {
showKeyboard();
}
}
- */
+
+ }
+ @Override
+ public void enableRefreshLayout(boolean yes) {
+ swipeRefreshLayout.setEnabled(yes);
}
+
+ ////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
+
/**
- * OK this is pure shit
+ * Get the last successfully searched bus stop or NULL
*
- * @param v View clicked
+ * @return
*/
- public void onSearchClick(View v) {
- //TODO
+ @Override
+ public Stop getLastSuccessfullySearchedBusStop() {
+ return fragmentHelper.getLastSuccessfullySearchedBusStop();
}
+
+ /**
+ * Get the last successfully searched bus stop ID or NULL
+ *
+ * @return
+ */
+ @Override
+ public String getLastSuccessfullySearchedBusStopID() {
+ Stop stop = getLastSuccessfullySearchedBusStop();
+ return stop == null ? null : stop.ID;
+ }
+
+ /**
+ * Update the star "Add to favorite" icon
+ */
+ @Override
+ public void updateStarIconFromLastBusStop() {
+
+ // no favorites no party!
+
+ if (addToFavorites == null) {
+ Log.d("MainActivity", "Why the fuck the star is not here?!");
+ return;
+ }
+
+ // check if there is a last Stop
+ String stopID = getLastSuccessfullySearchedBusStopID();
+ if (stopID == null) {
+ addToFavorites.setVisibility(View.INVISIBLE);
+ } else {
+ // filled or outline?
+ if (UserDB.checkStopInFavorites(stopID, getContext().getApplicationContext())) {
+ addToFavorites.setImageResource(R.drawable.ic_star_filled);
+ } else {
+ addToFavorites.setImageResource(R.drawable.ic_star_outline);
+ }
+
+ addToFavorites.setVisibility(View.VISIBLE);
+ }
+ }
+ public void showKeyboard() {
+ InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ View view = searchMode == SEARCH_BY_ID ? busStopSearchByIDEditText : busStopSearchByNameEditText;
+ imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
+ }
+
+ private void setSearchModeBusStopID() {
+ searchMode = SEARCH_BY_ID;
+ busStopSearchByNameEditText.setVisibility(View.GONE);
+ busStopSearchByNameEditText.setText("");
+ busStopSearchByIDEditText.setVisibility(View.VISIBLE);
+ floatingActionButton.setImageResource(R.drawable.alphabetical);
+ }
+
+ private void setSearchModeBusStopName() {
+ searchMode = SEARCH_BY_NAME;
+ busStopSearchByIDEditText.setVisibility(View.GONE);
+ busStopSearchByIDEditText.setText("");
+ busStopSearchByNameEditText.setVisibility(View.VISIBLE);
+ floatingActionButton.setImageResource(R.drawable.numeric);
+ }
+
+ /**
+ * Having that cursor at the left of the edit text makes me cancer.
+ *
+ * @param busStopID bus stop ID
+ */
+ private void setBusStopSearchByIDEditText(String busStopID) {
+ busStopSearchByIDEditText.setText(busStopID);
+ busStopSearchByIDEditText.setSelection(busStopID.length());
+ }
+
+ private void showHints() {
+ howDoesItWorkTextView.setVisibility(View.VISIBLE);
+ hideHintButton.setVisibility(View.VISIBLE);
+ actionHelpMenuItem.setVisible(false);
+ }
+
+ private void hideHints() {
+ howDoesItWorkTextView.setVisibility(View.GONE);
+ hideHintButton.setVisibility(View.GONE);
+ actionHelpMenuItem.setVisible(true);
+ }
+
+ //TODO: toggle spinner from mainActivity
+ @Override
+ public void toggleSpinner(boolean enable) {
+ if (enable) {
+ //already set by the RefreshListener when needed
+ //swipeRefreshLayout.setRefreshing(true);
+ progressBar.setVisibility(View.VISIBLE);
+ } else {
+ swipeRefreshLayout.setRefreshing(false);
+ progressBar.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public void toggleLastStopToFavorites() {
+
+ }
+
+ private void prepareGUIForBusLines() {
+ swipeRefreshLayout.setEnabled(true);
+ swipeRefreshLayout.setVisibility(View.VISIBLE);
+ actionHelpMenuItem.setVisible(true);
+ }
+
+ private void prepareGUIForBusStops() {
+ swipeRefreshLayout.setEnabled(false);
+ swipeRefreshLayout.setVisibility(View.VISIBLE);
+ actionHelpMenuItem.setVisible(false);
+ }
+
+
+ @Override
+ public void showFloatingActionButton(boolean yes) {
+ mListener.showFloatingActionButton(yes);
+ }
+
+ /**
+ * This provides a temporary fix to make the transition
+ * to a single asynctask go smoother
+ *
+ * @param fragmentType the type of fragment created
+ */
+ @Override
+ public void readyGUIfor(FragmentKind fragmentType) {
+ hideKeyboard();
+ //if we are getting results, already, stop waiting for nearbyStops
+ if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) {
+ locmgr.removeUpdates(locListener);
+ pendingNearbyStopsRequest = false;
+ }
+ if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
+ else
+ switch (fragmentType) {
+ case ARRIVALS:
+ prepareGUIForBusLines();
+ if (getOption(OPTION_SHOW_LEGEND, true)) {
+ showHints();
+ }
+ break;
+ case STOPS:
+ prepareGUIForBusStops();
+ break;
+ default:
+ Log.e("BusTO Activity", "Called readyGUI with unsupported type of Fragment");
+ return;
+ }
+ // Shows hints
+
+ }
+
+ @Override
+ public void requestArrivalsForStopID(String ID) {
+ final FragmentManager framan = getChildFragmentManager();
+ if (ID == null || ID.length() <= 0) {
+ // we're still in UI thread, no need to mess with Progress
+ showToastMessage(R.string.insert_bus_stop_number_error, true);
+ toggleSpinner(false);
+ } else if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
+ ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
+ if (fragment.getStopID() != null && fragment.getStopID().equals(ID)){
+ // Run with previous fetchers
+ //fragment.getCurrentFetchers().toArray()
+ new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray()).execute(ID);
+ } else{
+ new AsyncDataDownload(fragmentHelper, arrivalsFetchers).execute(ID);
+ }
+ }
+ else {
+ new AsyncDataDownload(fragmentHelper,arrivalsFetchers).execute(ID);
+ Log.d("MainActiv", "Started search for arrivals of stop " + ID);
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
index ab24fd8..c18bb22 100644
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -1,648 +1,648 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import androidx.core.util.Pair;
import androidx.preference.PreferenceManager;
import androidx.appcompat.widget.AppCompatButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.volley.*;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import it.reyboz.bustorino.adapters.SquareStopAdapter;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.StopSorterByDistance;
import java.util.*;
public class NearbyStopsFragment extends Fragment implements LoaderManager.LoaderCallbacks {
- private FragmentListener mListener;
+ private FragmentListenerMain mListener;
private FragmentLocationListener fragmentLocationListener;
private final String[] PROJECTION = {StopsTable.COL_ID,StopsTable.COL_LAT,StopsTable.COL_LONG,
StopsTable.COL_NAME,StopsTable.COL_TYPE,StopsTable.COL_LINES_STOPPING};
private final static String DEBUG_TAG = "NearbyStopsFragment";
private final static String FRAGMENT_TYPE_KEY = "FragmentType";
public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
private int fragment_type;
//data Bundle
private final String BUNDLE_LOCATION = "location";
private final int LOADER_ID = 0;
private RecyclerView gridRecyclerView;
private SquareStopAdapter dataAdapter;
private AutoFitGridLayoutManager gridLayoutManager;
boolean canStartDBQuery = true;
private Location lastReceivedLocation = null;
private ProgressBar circlingProgressBar,flatProgressBar;
private int distance;
protected SharedPreferences globalSharedPref;
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener;
private TextView messageTextView,titleTextView;
private CommonScrollListener scrollListener;
private AppCompatButton switchButton;
private boolean firstLocForStops = true,firstLocForArrivals = true;
public static final int COLUMN_WIDTH_DP = 250;
private Integer MAX_DISTANCE = -3;
private int MIN_NUM_STOPS = -1;
private int TIME_INTERVAL_REQUESTS = -1;
private AppLocationManager locManager;
//These are useful for the case of nearby arrivals
private ArrivalsManager arrivalsManager = null;
private ArrivalsStopAdapter arrivalsStopAdapter = null;
public NearbyStopsFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
* @return A new instance of fragment NearbyStopsFragment.
*/
public static NearbyStopsFragment newInstance(int fragmentType) {
if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS )
throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED");
NearbyStopsFragment fragment = new NearbyStopsFragment();
final Bundle args = new Bundle(1);
args.putInt(FRAGMENT_TYPE_KEY,fragmentType);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
setFragmentType(getArguments().getInt(FRAGMENT_TYPE_KEY));
}
locManager = AppLocationManager.getInstance(getContext());
fragmentLocationListener = new FragmentLocationListener(this);
globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences),Context.MODE_PRIVATE);
globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false);
gridRecyclerView = (RecyclerView) root.findViewById(R.id.stopGridRecyclerView);
gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP));
gridRecyclerView.setLayoutManager(gridLayoutManager);
gridRecyclerView.setHasFixedSize(false);
circlingProgressBar = (ProgressBar) root.findViewById(R.id.loadingBar);
flatProgressBar = (ProgressBar) root.findViewById(R.id.horizontalProgressBar);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
titleTextView = (TextView) root.findViewById(R.id.titleTextView);
switchButton = (AppCompatButton) root.findViewById(R.id.switchButton);
preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Log.d(DEBUG_TAG,"Key "+key+" was changed");
if(key.equals(getString(R.string.databaseUpdatingPref))){
if(!sharedPreferences.getBoolean(getString(R.string.databaseUpdatingPref),true)){
canStartDBQuery = true;
Log.d(DEBUG_TAG,"The database has finished updating, can start update now");
}
}
}
};
scrollListener = new CommonScrollListener(mListener,false);
switchButton.setOnClickListener(v -> {
switchFragmentType();
});
return root;
}
protected ArrayList createStopListFromCursor(Cursor data){
ArrayList stopList = new ArrayList<>();
final int col_id = data.getColumnIndex(StopsTable.COL_ID);
final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
data.moveToFirst();
for(int i=0; i onCreateLoader(int id, Bundle args) {
//BUILD URI
lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
Uri.Builder builder = new Uri.Builder();
builder.scheme("content").authority(AppDataProvider.AUTHORITY)
.appendPath("stops").appendPath("location")
.appendPath(String.valueOf(lastReceivedLocation.getLatitude()))
.appendPath(String.valueOf(lastReceivedLocation.getLongitude()))
.appendPath(String.valueOf(distance)); //distance
CursorLoader cl = new CursorLoader(getContext(),builder.build(),PROJECTION,null,null,null);
cl.setUpdateThrottle(2000);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
if (0 > MAX_DISTANCE) throw new AssertionError();
//Cursor might be null
if(data==null){
Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
return;
}
if(!isDBUpdating() && (data.getCount() stopList = createStopListFromCursor(data);
if(data.getCount()>0) {
//quick trial to hopefully always get the stops in the correct order
Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation));
switch (fragment_type){
case TYPE_STOPS:
showStopsInRecycler(stopList);
break;
case TYPE_ARRIVALS:
arrivalsManager = new ArrivalsManager(stopList);
flatProgressBar.setVisibility(View.VISIBLE);
flatProgressBar.setProgress(0);
flatProgressBar.setIndeterminate(false);
//for the moment, be satisfied with only one location
//AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
break;
default:
}
} else {
setNoStopsLayout();
}
}
@Override
public void onLoaderReset(Loader loader) {
}
/**
* To enable targeting from the Button
*/
public void switchFragmentType(View v){
switchFragmentType();
}
/**
* Call when you need to switch the type of fragment
*/
private void switchFragmentType(){
if(fragment_type==TYPE_ARRIVALS){
setFragmentType(TYPE_STOPS);
switchButton.setText(getString(R.string.show_arrivals));
titleTextView.setText(getString(R.string.nearby_stops_message));
if(arrivalsManager!=null)
arrivalsManager.cancelAllRequests();
if(dataAdapter!=null)
gridRecyclerView.setAdapter(dataAdapter);
} else if (fragment_type==TYPE_STOPS){
setFragmentType(TYPE_ARRIVALS);
titleTextView.setText(getString(R.string.nearby_arrivals_message));
switchButton.setText(getString(R.string.show_stops));
if(arrivalsStopAdapter!=null)
gridRecyclerView.setAdapter(arrivalsStopAdapter);
}
fragmentLocationListener.lastUpdateTime = -1;
locManager.removeLocationRequestFor(fragmentLocationListener);
locManager.addLocationRequestFor(fragmentLocationListener);
}
//useful methods
protected boolean isDBUpdating(){
return globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
}
/////// GUI METHODS ////////
private void showStopsInRecycler(List stops){
if(firstLocForStops) {
dataAdapter = new SquareStopAdapter(stops, mListener, lastReceivedLocation);
gridRecyclerView.setAdapter(dataAdapter);
firstLocForStops = false;
}else {
dataAdapter.setStops(stops);
dataAdapter.setUserPosition(lastReceivedLocation);
}
dataAdapter.notifyDataSetChanged();
//showRecyclerHidingLoadMessage();
if (gridRecyclerView.getVisibility() != View.VISIBLE) {
circlingProgressBar.setVisibility(View.GONE);
gridRecyclerView.setVisibility(View.VISIBLE);
}
messageTextView.setVisibility(View.GONE);
}
private void showArrivalsInRecycler(List palinas){
Collections.sort(palinas,new StopSorterByDistance(lastReceivedLocation));
final ArrayList> routesPairList = new ArrayList<>(10);
//int maxNum = Math.min(MAX_STOPS, stopList.size());
for(Palina p: palinas){
//if there are no routes available, skip stop
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
routesPairList.add(new Pair<>(p,r));
}
}
if(firstLocForArrivals){
arrivalsStopAdapter = new ArrivalsStopAdapter(routesPairList,mListener,getContext(),lastReceivedLocation);
gridRecyclerView.setAdapter(arrivalsStopAdapter);
firstLocForArrivals = false;
} else {
arrivalsStopAdapter.setRoutesPairListAndPosition(routesPairList,lastReceivedLocation);
}
//arrivalsStopAdapter.notifyDataSetChanged();
showRecyclerHidingLoadMessage();
}
private void setNoStopsLayout(){
messageTextView.setVisibility(View.VISIBLE);
messageTextView.setText(R.string.no_stops_nearby);
circlingProgressBar.setVisibility(View.GONE);
}
/**
* Does exactly what is says on the tin
*/
private void showRecyclerHidingLoadMessage(){
if (gridRecyclerView.getVisibility() != View.VISIBLE) {
circlingProgressBar.setVisibility(View.GONE);
gridRecyclerView.setVisibility(View.VISIBLE);
}
messageTextView.setVisibility(View.GONE);
}
class ArrivalsManager implements FiveTAPIVolleyRequest.ResponseListener, Response.ErrorListener{
final HashMap mStops;
final Map> routesToAdd = new HashMap<>();
final static String REQUEST_TAG = "NearbyArrivals";
private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS};
final NetworkVolleyManager volleyManager;
private final int MAX_ARRIVAL_STOPS =35;
int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
ArrivalsManager(List stops){
mStops = new HashMap<>();
volleyManager = NetworkVolleyManager.getInstance(getContext());
for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
mStops.put(s.ID,new Palina(s));
for(QueryType t: types) {
final FiveTAPIVolleyRequest req = FiveTAPIVolleyRequest.getNewRequest(t, s.ID, this, this);
if (req != null) {
req.setTag(REQUEST_TAG);
volleyManager.addToRequestQueue(req);
activeRequestCount++;
}
}
}
flatProgressBar.setMax(activeRequestCount);
}
@Override
public void onErrorResponse(VolleyError error) {
if(error instanceof ParseError){
//TODO
Log.w(DEBUG_TAG,"Parsing error for stop request");
} else if (error instanceof NetworkError){
String s;
if(error.networkResponse!=null)
s = new String(error.networkResponse.data);
else s="";
Log.w(DEBUG_TAG,"Network error: "+s);
}else {
Log.w(DEBUG_TAG,"Volley Error: "+error.getMessage());
}
if(error.networkResponse!=null){
Log.w(DEBUG_TAG, "Error status code: "+error.networkResponse.statusCode);
}
//counters
activeRequestCount--;
reqErrorCount++;
flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
}
@Override
public void onResponse(Palina result, QueryType type) {
//counter for requests
activeRequestCount--;
reqSuccessCount++;
final Palina palinaInMap = mStops.get(result.ID);
//palina cannot be null here
//sorry for the brutal crash when it happens
if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
//necessary to split the Arrivals and Details cases
switch (type){
case ARRIVALS:
palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
final List possibleRoutes = routesToAdd.get(result.ID);
if(possibleRoutes!=null) {
palinaInMap.addInfoFromRoutes(possibleRoutes);
routesToAdd.remove(result.ID);
}
break;
case DETAILS:
if(palinaInMap.queryAllRoutes().size()>0){
//merge the branches
palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
} else {
routesToAdd.put(result.ID,result.queryAllRoutes());
}
break;
default:
throw new IllegalArgumentException("Wrong QueryType in onResponse");
}
final ArrayList outList = new ArrayList<>();
for(Palina p: mStops.values()){
final List routes = p.queryAllRoutes();
if(routes!=null && routes.size()>0) outList.add(p);
}
showArrivalsInRecycler(outList);
flatProgressBar.setProgress(reqErrorCount+reqSuccessCount);
if(activeRequestCount==0) {
flatProgressBar.setIndeterminate(true);
flatProgressBar.setVisibility(View.GONE);
}
}
void cancelAllRequests(){
volleyManager.getRequestQueue().cancelAll(REQUEST_TAG);
flatProgressBar.setVisibility(View.GONE);
}
}
/**
* Local locationListener, to use for the GPS
*/
class FragmentLocationListener implements AppLocationManager.LocationRequester{
LoaderManager.LoaderCallbacks callbacks;
private int oldLocStatus = -2;
private LocationCriteria cr;
private long lastUpdateTime = -1;
public FragmentLocationListener(LoaderManager.LoaderCallbacks callbacks) {
this.callbacks = callbacks;
}
@Override
public void onLocationChanged(Location location) {
//set adapter
float accuracy = location.getAccuracy();
if(accuracy<60 && canStartDBQuery) {
distance = 20;
final Bundle msgBundle = new Bundle();
msgBundle.putParcelable(BUNDLE_LOCATION,location);
getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
}
lastUpdateTime = System.currentTimeMillis();
Log.d("BusTO:NearPositListen","can start loader "+ canStartDBQuery);
}
@Override
public void onLocationStatusChanged(int status) {
switch(status){
case AppLocationManager.LOCATION_GPS_AVAILABLE:
messageTextView.setVisibility(View.GONE);
break;
case AppLocationManager.LOCATION_UNAVAILABLE:
messageTextView.setText(R.string.enableGpsText);
messageTextView.setVisibility(View.VISIBLE);
break;
default:
Log.e(DEBUG_TAG,"Location status not recognized");
}
}
@Override
public LocationCriteria getLocationCriteria() {
return new LocationCriteria(60,TIME_INTERVAL_REQUESTS);
}
@Override
public long getLastUpdateTimeMillis() {
return lastUpdateTime;
}
void resetUpdateTime(){
lastUpdateTime = -1;
}
}
/**
* Simple trick to get an automatic number of columns (from https://www.journaldev.com/13792/android-gridlayoutmanager-example)
*
*/
class AutoFitGridLayoutManager extends GridLayoutManager {
private int columnWidth;
private boolean columnWidthChanged = true;
public AutoFitGridLayoutManager(Context context, int columnWidth) {
super(context, 1);
setColumnWidth(columnWidth);
}
public void setColumnWidth(int newColumnWidth) {
if (newColumnWidth > 0 && newColumnWidth != columnWidth) {
columnWidth = newColumnWidth;
columnWidthChanged = true;
}
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (columnWidthChanged && columnWidth > 0) {
int totalSpace;
if (getOrientation() == VERTICAL) {
totalSpace = getWidth() - getPaddingRight() - getPaddingLeft();
} else {
totalSpace = getHeight() - getPaddingTop() - getPaddingBottom();
}
int spanCount = Math.max(1, totalSpace / columnWidth);
setSpanCount(spanCount);
columnWidthChanged = false;
}
super.onLayoutChildren(recycler, state);
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/ResultListFragment.java b/src/it/reyboz/bustorino/fragments/ResultListFragment.java
index a9db50d..edf4ea9 100644
--- a/src/it/reyboz/bustorino/fragments/ResultListFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ResultListFragment.java
@@ -1,298 +1,298 @@
/*
BusTO - Fragments components
Copyright (C) 2016 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.UserDB;
/**
* This is a generalized fragment that can be used both for
*
*
*/
public class ResultListFragment extends Fragment{
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
static final String LIST_TYPE = "list-type";
protected static final String LIST_STATE = "list_state";
protected static final String MESSAGE_TEXT_VIEW = "message_text_view";
private FragmentKind adapterKind;
private boolean adapterSet = false;
- protected FragmentListener mListener;
+ protected FragmentListenerMain mListener;
protected TextView messageTextView;
protected ListView resultsListView;
private FloatingActionButton fabutton;
private ListAdapter mListAdapter = null;
boolean listShown;
private Parcelable mListInstanceState = null;
public ResultListFragment() {
// Required empty public constructor
}
public ListView getResultsListView() {
return resultsListView;
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param listType whether the list is used for STOPS or LINES (Orari)
* @return A new instance of fragment ResultListFragment.
*/
public static ResultListFragment newInstance(FragmentKind listType, String eventualStopTitle) {
ResultListFragment fragment = new ResultListFragment();
Bundle args = new Bundle();
args.putSerializable(LIST_TYPE, listType);
if (eventualStopTitle != null) {
args.putString(ArrivalsFragment.STOP_TITLE, eventualStopTitle);
}
fragment.setArguments(args);
return fragment;
}
public static ResultListFragment newInstance(FragmentKind listType) {
return newInstance(listType, null);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
adapterKind = (FragmentKind) getArguments().getSerializable(LIST_TYPE);
}
}
/**
* Check if the last Bus Stop is in the favorites
* @return true if it iss
*/
public boolean isStopInFavorites(String busStopId) {
boolean found = false;
// no stop no party
if(busStopId != null) {
SQLiteDatabase userDB = new UserDB(getContext()).getReadableDatabase();
found = UserDB.isStopInFavorites(userDB, busStopId);
}
return found;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_list_view, container, false);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
if (adapterKind != null) {
resultsListView = (ListView) root.findViewById(R.id.resultsListView);
switch (adapterKind) {
case STOPS:
resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
/*
* Casting because of Javamerda
* @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener
*/
Stop busStop = (Stop) parent.getItemAtPosition(position);
- mListener.createFragmentForStop(busStop.ID);
+ mListener.requestArrivalsForStopID(busStop.ID);
}
});
// set the textviewMessage
setTextViewMessage(getString(R.string.results));
break;
case ARRIVALS:
resultsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
}
});
String displayName = getArguments().getString(ArrivalsFragment.STOP_TITLE);
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
break;
default:
throw new IllegalStateException("Argument passed was not of a supported type");
}
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
} else
Log.d(getString(R.string.list_fragment_debug), "No content root for fragment");
return root;
}
public boolean isFragmentForTheSameStop(Palina p) {
return adapterKind.equals(FragmentKind.ARRIVALS) && getTag().equals(getFragmentTag(p));
}
public static String getFragmentTag(Palina p) {
return p.ID;
}
@Override
public void onResume() {
super.onResume();
//Log.d(getString(R.string.list_fragment_debug),"Fragment restored, saved listAdapter is "+(mListAdapter));
if (mListAdapter != null) {
ListAdapter adapter = mListAdapter;
mListAdapter = null;
resetListAdapter(adapter);
}
if (mListInstanceState != null) {
Log.d("resultsListView", "trying to restore instance state");
resultsListView.onRestoreInstanceState(mListInstanceState);
}
switch (adapterKind) {
case ARRIVALS:
resultsListView.setOnScrollListener(new CommonScrollListener(mListener, true));
fabutton.show();
break;
case STOPS:
resultsListView.setOnScrollListener(new CommonScrollListener(mListener, false));
break;
default:
//NONE
}
mListener.readyGUIfor(adapterKind);
}
@Override
public void onPause() {
if (adapterKind.equals(FragmentKind.ARRIVALS)) {
SwipeRefreshLayout reflay = getActivity().findViewById(R.id.listRefreshLayout);
reflay.setEnabled(false);
Log.d("BusTO Fragment " + this.getTag(), "RefreshLayout disabled");
}
super.onPause();
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
- if (context instanceof FragmentListener) {
- mListener = (FragmentListener) context;
+ if (context instanceof FragmentListenerMain) {
+ mListener = (FragmentListenerMain) context;
fabutton = (FloatingActionButton) getActivity().findViewById(R.id.floatingActionButton);
} else {
throw new RuntimeException(context.toString()
+ " must implement ResultFragmentListener");
}
}
@Override
public void onDetach() {
mListener = null;
if (fabutton != null)
fabutton.show();
super.onDetach();
}
@Override
public void onDestroyView() {
resultsListView = null;
//Log.d(getString(R.string.list_fragment_debug), "called onDestroyView");
getArguments().putString(MESSAGE_TEXT_VIEW, messageTextView.getText().toString());
super.onDestroyView();
}
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
Log.d("ResultListFragment", "onViewStateRestored");
if (savedInstanceState != null) {
mListInstanceState = savedInstanceState.getParcelable(LIST_STATE);
Log.d("ResultListFragment", "listInstanceStatePresent :" + mListInstanceState);
}
}
protected void resetListAdapter(ListAdapter adapter) {
boolean hadAdapter = mListAdapter != null;
mListAdapter = adapter;
if (resultsListView != null) {
resultsListView.setAdapter(adapter);
resultsListView.setVisibility(View.VISIBLE);
}
}
public void setNewListAdapter(ListAdapter adapter){
resetListAdapter(adapter);
}
/**
* Set the message textView
* @param message the whole message to write in the textView
*/
public void setTextViewMessage(String message) {
messageTextView.setText(message);
messageTextView.setVisibility(View.VISIBLE);
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java b/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java
index ec2f0a6..959f5d8 100644
--- a/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncStopFavoriteAction.java
@@ -1,121 +1,123 @@
/*
BusTO - Middleware components
Copyright (C) 2016 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.middleware;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.widget.Toast;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.UserDB;
/**
* Handler to add or remove or toggle a Stop in your favorites
*/
public class AsyncStopFavoriteAction extends AsyncTask {
private Context context;
/**
* Kind of actions available
*/
public enum Action { ADD, REMOVE, TOGGLE };
/**
* Action chosen
*
* Note that TOGGLE is not converted to ADD or REMOVE.
*/
private Action action;
+ // extra stuff to do after we've done it
+ private Runnable mrunner;
/**
* Constructor
*
* @param context
* @param action
*/
public AsyncStopFavoriteAction(Context context, Action action) {
this.context = context.getApplicationContext();
this.action = action;
}
@Override
protected Boolean doInBackground(Stop... stops) {
boolean result = false;
Stop stop = stops[0];
// check if the request has sense
if(stop != null) {
// get a writable database
UserDB userDatabase = new UserDB(context);
SQLiteDatabase db = userDatabase.getWritableDatabase();
// eventually toggle the status
if(Action.TOGGLE.equals(action)) {
if(UserDB.isStopInFavorites(db, stop.ID)) {
action = Action.REMOVE;
} else {
action = Action.ADD;
}
}
// at this point the action is just ADD or REMOVE
// add or remove?
if(Action.ADD.equals(action)) {
// add
result = UserDB.addOrUpdateStop(stop, db);
} else {
// remove
result = UserDB.deleteStop(stop, db);
}
// please sir, close the door
db.close();
}
return result;
}
/**
* Callback fired when everything was done
*
* @param result
*/
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if(result) {
// at this point the action should be just ADD or REMOVE
if(Action.ADD.equals(action)) {
// now added
Toast.makeText(this.context, R.string.added_in_favorites, Toast.LENGTH_SHORT).show();
} else {
// now removed
Toast.makeText(this.context, R.string.removed_from_favorites, Toast.LENGTH_SHORT).show();
}
} else {
// wtf
Toast.makeText(this.context, R.string.cant_add_to_favorites, Toast.LENGTH_SHORT).show();
}
}
}