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 refreshStop = new Runnable() {
public void run() {
if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame);
if (fragment == null){
new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).execute();
} else{
String stopName = fragment.getStopID();
new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray(), getApplicationContext()).execute(stopName);
}
} else //we create a new fragment, which is WRONG
new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).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(refreshStop));
/**
* @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, framan, getApplicationContext(),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);
*/
requestArrivalsForStopID(busStopID);
}
//Try (hopefully) database update
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(this));
//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(this));
ActionBar bar = getSupportActionBar();
if(bar!=null) bar.show();
else Log.w(DEBUG_TAG, "ACTION BAR IS NULL");
//check if we can display the experiments or not
SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
//Log.w(DEBUG_TAG, "Preference experimental is "+exper_On);
//MenuItem experimentsItem =
if (experimentsMenuItem != null)
experimentsMenuItem.setVisible(exper_On);
}
@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);
experimentsMenuItem = menu.findItem(R.id.action_experiments);
SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
experimentsMenuItem.setVisible(exper_On);
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_donate:
openIceweasel("https://www.liberapay.com/Libre_BusTO/");
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();
requestArrivalsForStopID(busStopID);
} else { // searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
//new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
new AsyncDataDownload(fh, stopsFinderByNames, getApplicationContext()).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(this));
}
} 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 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(), this).execute(ID);
} else{
new AsyncDataDownload(fh, arrivalsFetchers, this).execute(ID);
}
}
else {
new AsyncDataDownload(fh, arrivalsFetchers, this).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);
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(this));
}
}
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 {
Activity runningAct;
public NearbyStopsRequester(Activity runningAct) {
this.runningAct = runningAct;
}
@Override
public void run() {
//final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false);
final boolean noPermission = ContextCompat.checkSelfPermission(getApplicationContext(),
Manifest.permission.ACCESS_FINE_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) {
pendingNearbyStopsRequest = true;
Permissions.assertLocationPermissions(getApplicationContext(),runningAct);
Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission);
return;
} 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 //////////////////////////////////////////////////
@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);
}
}
+ @Override
+ public void showMapCenteredOnStop(Stop stop) {
+ //nothing to do
+ //TODO: delete this activity altogether
+ Log.w(DEBUG_TAG,"Asked to show the stop on the map");
+ }
+
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
}
private void openIceweasel(String url){
utils.openIceweasel(url, this);
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
index f05929e..c20ece9 100644
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -1,568 +1,573 @@
package it.reyboz.bustorino;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.work.BackoffPolicy;
import androidx.work.Constraints;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.NetworkType;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.DatabaseUpdate;
import it.reyboz.bustorino.fragments.FavoritesFragment;
import it.reyboz.bustorino.fragments.FragmentKind;
import it.reyboz.bustorino.fragments.FragmentListenerMain;
import it.reyboz.bustorino.fragments.MainScreenFragment;
import it.reyboz.bustorino.fragments.MapFragment;
import it.reyboz.bustorino.middleware.GeneralActivity;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
import static it.reyboz.bustorino.backend.utils.openIceweasel;
public class ActivityPrincipal extends GeneralActivity implements FragmentListenerMain {
private DrawerLayout mDrawer;
private NavigationView mNavView;
private ActionBarDrawerToggle drawerToggle;
private final static String DEBUG_TAG="BusTO Act Principal";
private final static String TAG_FAVORITES="favorites_frag";
private Snackbar snackbar;
private boolean showingMainFragmentFromOther = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_principal);
final SharedPreferences theShPr = getMainSharedPreferences();
Toolbar mToolbar = findViewById(R.id.default_toolbar);
setSupportActionBar(mToolbar);
if (getSupportActionBar()!=null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
else Log.w(DEBUG_TAG, "NO ACTION BAR");
mToolbar.setOnMenuItemClickListener(new ToolbarItemClickListener(this));
mDrawer = findViewById(R.id.drawer_layout);
drawerToggle = setupDrawerToggle(mToolbar);
// Setup toggle to display hamburger icon with nice animation
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.syncState();
mDrawer.addDrawerListener(drawerToggle);
mDrawer.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
hideKeyboard();
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
}
@Override
public void onDrawerStateChanged(int newState) {
}
});
mNavView = findViewById(R.id.nvView);
setupDrawerContent(mNavView);
/*View header = mNavView.getHeaderView(0);
*/
//mNavView.getMenu().findItem(R.id.versionFooter).
/// LEGACY CODE
//---------------------------- START INTENT CHECK QUEUE ------------------------------------
// Intercept calls from URL intent
boolean tryedFromIntent = false;
String busStopID = 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");
/**
* 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);
requestArrivalsForStopID(busStopID);
}
//Try (hopefully) database update
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;
break;
}
}
if (showProgress) {
createDefaultSnackbar();
} else {
if(snackbar!=null) {
snackbar.dismiss();
snackbar = null;
}
}
});
// show the main fragment
showMainFragment();
}
private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) {
// NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it
// and will not render the hamburger icon without it.
return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close);
}
/**
* Setup drawer actions
* @param navigationView the navigation view on which to set the callbacks
*/
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
menuItem -> {
if (menuItem.getItemId() == R.id.drawer_action_settings) {
Log.d("MAINBusTO", "Pressed button preferences");
closeDrawerIfOpen();
startActivity(new Intent(ActivityPrincipal.this, ActivitySettings.class));
return true;
} else if(menuItem.getItemId() == R.id.nav_favorites_item){
closeDrawerIfOpen();
//get Fragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
FavoritesFragment fragment = FavoritesFragment.newInstance();
ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
return true;
} else if(menuItem.getItemId() == R.id.nav_arrivals){
closeDrawerIfOpen();
showMainFragment();
return true;
} else if(menuItem.getItemId() == R.id.nav_map_item){
closeDrawerIfOpen();
final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ);
switch (result) {
case PERMISSION_OK:
createAndShowMapFragment(null);
break;
case PERMISSION_ASKING:
permissionDoneRunnables.put(permission,
() -> createAndShowMapFragment(null));
break;
case PERMISSION_NEG_CANNOT_ASK:
String storage_perm = getString(R.string.storage_permission);
String text = getString(R.string.too_many_permission_asks, storage_perm);
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
return true;
}
//selectDrawerItem(menuItem);
Log.d(DEBUG_TAG, "pressed item "+menuItem.toString());
return true;
});
}
private void closeDrawerIfOpen(){
if (mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(GravityCompat.START);
}
// `onPostCreate` called when activity start-up is complete after `onStart()`
// NOTE 1: Make sure to override the method with only a single `Bundle` argument
// Note 2: Make sure you implement the correct `onPostCreate(Bundle savedInstanceState)` method.
// There are 2 signatures and only `onPostCreate(Bundle state)` shows the hamburger icon.
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggles
drawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.extra_menu_items, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==STORAGE_PERMISSION_REQ){
final String storagePerm = 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(storagePerm)) {
Runnable toRun = permissionDoneRunnables.get(storagePerm);
if (toRun != null)
toRun.run();
permissionDoneRunnables.remove(storagePerm);
}
} else {
//permission denied
showToastMessage(R.string.permission_storage_maps_msg, false);
}
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
int[] cases = {R.id.nav_arrivals, R.id.nav_favorites_item};
Log.d(DEBUG_TAG, "Item pressed");
if (item.getItemId() == android.R.id.home) {
mDrawer.openDrawer(GravityCompat.START);
return true;
}
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onBackPressed() {
boolean foundFragment = false;
Fragment shownFrag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
if (mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(GravityCompat.START);
else if(shownFrag != null && shownFrag.isVisible() && shownFrag.getChildFragmentManager().getBackStackEntryCount() > 0){
//if we have been asked to show a stop from another fragment, we should go back even in the main
shownFrag.getChildFragmentManager().popBackStackImmediate();
if(showingMainFragmentFromOther && getSupportFragmentManager().getBackStackEntryCount() > 0){
getSupportFragmentManager().popBackStack();
}
}
else if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
else
super.onBackPressed();
}
private void createDefaultSnackbar() {
if (snackbar == null) {
snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
}
snackbar.show();
}
private MainScreenFragment createAndShowMainFragment(){
FragmentManager fraMan = getSupportFragmentManager();
MainScreenFragment fragment = MainScreenFragment.newInstance();
FragmentTransaction transaction = fraMan.beginTransaction();
transaction.replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG);
transaction.commit();
return fragment;
}
/**
* Show the fragment by adding it to the backstack
* @param fraMan the fragmentManager
* @param fragment the fragment
*/
private static void showMainFragment(FragmentManager fraMan, MainScreenFragment fragment){
fraMan.beginTransaction().replace(R.id.mainActContentFrame, fragment)
.setReorderingAllowed(true)
.addToBackStack(null)
/*.setCustomAnimations(
R.anim.slide_in, // enter
R.anim.fade_out, // exit
R.anim.fade_in, // popEnter
R.anim.slide_out // popExit
)*/
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
private MainScreenFragment showMainFragment(){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
MainScreenFragment mainScreenFragment = null;
if (fragment==null | !(fragment instanceof MainScreenFragment)){
mainScreenFragment = createAndShowMainFragment();
}
else if(!fragment.isVisible()){
mainScreenFragment = (MainScreenFragment) fragment;
showMainFragment(fraMan, mainScreenFragment);
Log.d(DEBUG_TAG, "Found the main fragment");
} else{
mainScreenFragment = (MainScreenFragment) fragment;
}
return mainScreenFragment;
}
@Nullable
private MainScreenFragment getMainFragmentIfVisible(){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
if (fragment!= null && fragment.isVisible()) return (MainScreenFragment) fragment;
else return null;
}
@Override
public void showFloatingActionButton(boolean yes) {
//TODO
}
/*
public void setDrawerSelectedItem(String fragmentTag){
switch (fragmentTag){
case MainScreenFragment.FRAGMENT_TAG:
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case MapFragment.FRAGMENT_TAG:
break;
case FavoritesFragment.FRAGMENT_TAG:
mNavView.setCheckedItem(R.id.nav_favorites_item);
break;
}
}*/
@Override
public void readyGUIfor(FragmentKind fragmentType) {
MainScreenFragment probableFragment = getMainFragmentIfVisible();
if (probableFragment!=null){
probableFragment.readyGUIfor(fragmentType);
}
int titleResId;
switch (fragmentType){
case MAP:
mNavView.setCheckedItem(R.id.nav_map_item);
titleResId = R.string.map;
break;
case FAVORITES:
mNavView.setCheckedItem(R.id.nav_favorites_item);
titleResId = R.string.nav_favorites_text;
break;
case ARRIVALS:
titleResId = R.string.nav_arrivals_text;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case STOPS:
titleResId = R.string.stop_search_view_title;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case MAIN_SCREEN_FRAGMENT:
case NEARBY_STOPS:
case NEARBY_ARRIVALS:
titleResId=R.string.app_name_full;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
default:
titleResId = 0;
}
if(getSupportActionBar()!=null && titleResId!=0)
getSupportActionBar().setTitle(titleResId);
}
@Override
public void requestArrivalsForStopID(String ID) {
//register if the request came from the main fragment or not
MainScreenFragment probableFragment = getMainFragmentIfVisible();
showingMainFragmentFromOther = (probableFragment==null);
if (showingMainFragmentFromOther){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
if(fragment!=null){
//the fragment is there but not shown
probableFragment = (MainScreenFragment) fragment;
// set the flag
probableFragment.setSuppressArrivalsReload(true);
showMainFragment(fraMan, probableFragment);
} else {
// we have no fragment
probableFragment = createAndShowMainFragment();
}
}
probableFragment.requestArrivalsForStopID(ID);
mNavView.setCheckedItem(R.id.nav_arrivals);
}
@Override
public void toggleSpinner(boolean state) {
MainScreenFragment probableFragment = getMainFragmentIfVisible();
if (probableFragment!=null){
probableFragment.toggleSpinner(state);
}
}
@Override
public void enableRefreshLayout(boolean yes) {
MainScreenFragment probableFragment = getMainFragmentIfVisible();
if (probableFragment!=null){
probableFragment.enableRefreshLayout(yes);
}
}
+ @Override
+ public void showMapCenteredOnStop(Stop stop) {
+ createAndShowMapFragment(stop);
+ }
+
//Map Fragment stuff
void createAndShowMapFragment(@Nullable Stop stop){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MapFragment fragment = stop == null? MapFragment.getInstance(): MapFragment.getInstance(stop);
ft.replace(R.id.mainActContentFrame, fragment, MapFragment.FRAGMENT_TAG);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{
private final Context activityContext;
public ToolbarItemClickListener(Context activityContext) {
this.activityContext = activityContext;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_about:
startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class));
return true;
case R.id.action_hack:
openIceweasel(getString(R.string.hack_url), activityContext);
return true;
case R.id.action_source:
openIceweasel("https://gitpull.it/source/libre-busto/", activityContext);
return true;
case R.id.action_licence:
openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext);
return true;
default:
}
return false;
}
}
}
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
index 62fe6e6..94eb7eb 100644
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -1,153 +1,156 @@
package it.reyboz.bustorino.backend;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
public abstract class utils {
private static final double EarthRadius = 6371e3;
public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
final double phi1 = Math.toRadians(lat1);
final double phi2 = Math.toRadians(lat2);
final double deltaPhi = Math.toRadians(lat2-lat1);
final double deltaTheta = Math.toRadians(long2-long1);
final double a = Math.sin(deltaPhi/2)*Math.sin(deltaPhi/2)+
Math.cos(phi1)*Math.cos(phi2)*Math.sin(deltaTheta/2)*Math.sin(deltaTheta/2);
final double c = 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
return Math.abs(EarthRadius*c);
}
+ public static Double angleRawDifferenceFromMeters(double distanceInMeters){
+ return Math.toDegrees(distanceInMeters/EarthRadius);
+ }
/*
public static int convertDipToPixels(Context con,float dips)
{
return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f);
}
*/
public static float convertDipToPixels(Context con, float dp){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics());
}
/*
public static int calculateNumColumnsFromSize(View containerView, int pixelsize){
int width = containerView.getWidth();
float ncols = ((float)width)/pixelsize;
return (int) Math.floor(ncols);
}
*/
/**
* Check if there is an internet connection
* @param con context object to get the system service
* @return true if we are
*/
public static boolean isConnected(Context con) {
ConnectivityManager connMgr = (ConnectivityManager) con.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
///////////////////// 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 static String toTitleCase(String givenString) {
String[] arr = givenString.split(" ");
StringBuffer sb = new StringBuffer();
//Log.d("BusTO chars", "String parsing: "+givenString+" in array: "+ Arrays.toString(arr));
for (int i = 0; i < arr.length; i++) {
if (arr[i].length() > 1)
sb.append(Character.toUpperCase(arr[i].charAt(0)))
.append(arr[i].substring(1)).append(" ");
else sb.append(arr[i]);
}
return sb.toString().trim();
}
/**
* Open an URL in the default browser.
*
* @param url URL
*/
public static void openIceweasel(String url, Context context) {
Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (browserIntent1.resolveActivity(context.getPackageManager()) != null) {
//check we have an activity ready to receive intents (otherwise, there will be a crash)
context.startActivity(browserIntent1);
}
}
/**
* Print the first i lines of the the trace of an exception
* https://stackoverflow.com/questions/21706722/fetch-only-first-n-lines-of-a-stack-trace
*/
/*
public static String traceCaller(Exception ex, int i) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StringBuilder sb = new StringBuilder();
ex.printStackTrace(pw);
String ss = sw.toString();
String[] splitted = ss.split("\n");
sb.append("\n");
if(splitted.length > 2 + i) {
for(int x = 2; x < i+2; x++) {
sb.append(splitted[x].trim());
sb.append("\n");
}
return sb.toString();
}
return "Trace too Short.";
}
*/
}
diff --git a/src/it/reyboz/bustorino/data/AppDataProvider.java b/src/it/reyboz/bustorino/data/AppDataProvider.java
index 99e7322..d4ea545 100644
--- a/src/it/reyboz/bustorino/data/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/data/AppDataProvider.java
@@ -1,283 +1,283 @@
/*
BusTO (middleware)
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.data;
import android.content.*;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import it.reyboz.bustorino.BuildConfig;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
import java.util.List;
import static it.reyboz.bustorino.data.UserDB.getFavoritesColumnNamesAsArray;
public class AppDataProvider extends ContentProvider {
public static final String AUTHORITY = BuildConfig.APPLICATION_ID +".provider";
private static final int STOP_OP = 1;
private static final int LINE_OP = 2;
private static final int BRANCH_OP = 3;
private static final int FAVORITES_OP =4;
private static final int MANY_STOPS = 5;
private static final int ADD_UPDATE_BRANCHES = 6;
private static final int LINE_INSERT_OP = 7;
private static final int CONNECTIONS = 8;
private static final int LOCATION_SEARCH = 9;
private static final int GET_ALL_FAVORITES =10;
public static final String FAVORITES = "favorites";
private static final String DEBUG_TAG="AppDataProvider";
private Context con;
private NextGenDB appDBHelper;
private UserDB userDBHelper;
private SQLiteDatabase db;
private DBStatusManager preferences;
public AppDataProvider() {
}
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
/*
* The calls to addURI() go here, for all of the content URI patterns that the provider
* should recognize.
*/
sUriMatcher.addURI(AUTHORITY, "stop/#", STOP_OP);
sUriMatcher.addURI(AUTHORITY,"stops",MANY_STOPS);
sUriMatcher.addURI(AUTHORITY,"stops/location/*/*/*",LOCATION_SEARCH);
/*
* Sets the code for a single row to 2. In this case, the "#" wildcard is
* used. "content://com.example.app.provider/table3/3" matches, but
* "content://com.example.app.provider/table3 doesn't.
*/
sUriMatcher.addURI(AUTHORITY, "line/#", LINE_OP);
sUriMatcher.addURI(AUTHORITY,"branch/#",BRANCH_OP);
sUriMatcher.addURI(AUTHORITY,"line/insert",LINE_INSERT_OP);
sUriMatcher.addURI(AUTHORITY,"branches",ADD_UPDATE_BRANCHES);
sUriMatcher.addURI(AUTHORITY,"connections",CONNECTIONS);
sUriMatcher.addURI(AUTHORITY,"favorites/#",FAVORITES_OP);
sUriMatcher.addURI(AUTHORITY,FAVORITES,GET_ALL_FAVORITES);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// Implement this to handle requests to delete one or more rows.
db = appDBHelper.getWritableDatabase();
int rows;
switch (sUriMatcher.match(uri)){
case MANY_STOPS:
rows = db.delete(NextGenDB.Contract.StopsTable.TABLE_NAME,null,null);
break;
default:
throw new UnsupportedOperationException("Not yet implemented");
}
return rows;
}
@Override
public String getType(Uri uri) {
// TODO: Implement this to handle requests for the MIME type of the data
// at the given URI.
int match = sUriMatcher.match(uri);
String baseTypedir = "vnd.android.cursor.dir/";
String baseTypeitem = "vnd.android.cursor.item/";
switch (match){
case LOCATION_SEARCH:
return baseTypedir+"stop";
case LINE_OP:
return baseTypeitem+"line";
case CONNECTIONS:
return baseTypedir+"stops";
}
return baseTypedir+"/item";
}
@Override
public Uri insert(Uri uri, ContentValues values) throws IllegalArgumentException{
//AVOID OPENING A DB CONNECTION, WILL THROW VERY NASTY ERRORS
if(preferences.isDBUpdating(true))
return null;
db = appDBHelper.getWritableDatabase();
Uri finalUri;
long last_rowid = -1;
switch (sUriMatcher.match(uri)){
case ADD_UPDATE_BRANCHES:
Log.d("InsBranchWithProvider","new Insert request");
String line_name = values.getAsString(NextGenDB.Contract.LinesTable.COLUMN_NAME);
if(line_name==null) throw new IllegalArgumentException("No line name given");
long lineid = -1;
Cursor c = db.query(LinesTable.TABLE_NAME,
new String[]{LinesTable._ID,LinesTable.COLUMN_NAME,LinesTable.COLUMN_DESCRIPTION},NextGenDB.Contract.LinesTable.COLUMN_NAME +" =?",
new String[]{line_name},null,null,null);
Log.d("InsBranchWithProvider","finding line in the database: "+c.getCount()+" matches");
if(c.getCount() == 0){
//There are no lines, insert?
//NOPE
/*
c.close();
ContentValues cv = new ContentValues();
cv.put(LinesTable.COLUMN_NAME,line_name);
lineid = db.insert(LinesTable.TABLE_NAME,null,cv);
*/
break;
}else {
c.moveToFirst();
/*
while(c.moveToNext()){
Log.d("InsBranchWithProvider","line: "+c.getString(c.getColumnIndex(LinesTable.COLUMN_NAME))+"\n"
+c.getString(c.getColumnIndex(LinesTable.COLUMN_DESCRIPTION)));
}*/
lineid = c.getInt(c.getColumnIndex(NextGenDB.Contract.LinesTable._ID));
c.close();
}
values.remove(NextGenDB.Contract.LinesTable.COLUMN_NAME);
values.put(BranchesTable.COL_LINE,lineid);
last_rowid = db.insertWithOnConflict(NextGenDB.Contract.BranchesTable.TABLE_NAME,null,values,SQLiteDatabase.CONFLICT_REPLACE);
break;
case MANY_STOPS:
//Log.d("AppDataProvider_busTO","New stop insert request");
try{
last_rowid = db.insertOrThrow(NextGenDB.Contract.StopsTable.TABLE_NAME,null,values);
} catch (SQLiteConstraintException e){
Log.w("AppDataProvider_busTO","Insert failed because of constraint");
last_rowid = -1;
e.printStackTrace();
}
break;
case CONNECTIONS:
try{
last_rowid = db.insertOrThrow(NextGenDB.Contract.ConnectionsTable.TABLE_NAME,null,values);
} catch (SQLiteConstraintException e){
Log.w("AppDataProvider_busTO","Insert failed because of constraint");
last_rowid = -1;
e.printStackTrace();
}
break;
default:
throw new IllegalArgumentException("Invalid parameters");
}
finalUri = ContentUris.withAppendedId(uri,last_rowid);
return finalUri;
}
@Override
public boolean onCreate() {
con = getContext();
appDBHelper = new NextGenDB(getContext());
userDBHelper = new UserDB(getContext());
if(con!=null) {
preferences = new DBStatusManager(con,null);
} else {
preferences = null;
Log.e(DEBUG_TAG,"Cannot get shared preferences");
}
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws UnsupportedOperationException,IllegalArgumentException {
//IMPORTANT
//The app should not query when the DB is updating, but apparently, it does
if(preferences.isDBUpdating(true))
//throw new UnsupportedOperationException("DB is updating");
return null;
SQLiteDatabase db = appDBHelper.getReadableDatabase();
List parts = uri.getPathSegments();
switch (sUriMatcher.match(uri)){
case LOCATION_SEARCH:
//authority/stops/location/"Lat"/"Lon"/"distance"
//distance in metres (integer)
if(parts.size()>=4 && "location".equals(parts.get(1))){
Double latitude = Double.parseDouble(parts.get(2));
Double longitude = Double.parseDouble(parts.get(3));
//converting distance to a float to not lose precision
- float distance = parts.size()>=5 ? Float.parseFloat(parts.get(4))/1000 : 0.1f;
- if(parts.size()>=5)
- Log.d("LocationSearch"," given distance to search is "+parts.get(4)+" m");
+ float distance = parts.size()>=5 ? Float.parseFloat(parts.get(4))/1000 : 0.02f;
+ //if(parts.size()>=5)
+ //Log.d("LocationSearch"," given distance to search is "+parts.get(4)+" m");
Double distasAngle = (distance/6371)*180/Math.PI; //small angles approximation, still valid for about 500 metres
String whereClause = StopsTable.COL_LAT+ "< "+(latitude+distasAngle)+" AND "
+StopsTable.COL_LAT +" > "+(latitude-distasAngle)+" AND "+
StopsTable.COL_LONG+" < "+(longitude+distasAngle)+" AND "+StopsTable.COL_LONG+" > "+(longitude-distasAngle);
//Log.d("Provider-LOCSearch","Querying stops by position, query args: \n"+whereClause);
return db.query(StopsTable.TABLE_NAME,projection,whereClause,null,null,null,null);
}
else {
Log.w(DEBUG_TAG,"Not enough parameters");
if(parts.size()>=5) for(String s:parts) Log.d(DEBUG_TAG,"\t element "+parts.indexOf(s)+" is: "+s);
return null;
}
case FAVORITES_OP:
final String stopFavSelection = getFavoritesColumnNamesAsArray[0]+" = ?";
db = userDBHelper.getReadableDatabase();
Log.d(DEBUG_TAG,"Asked information on Favorites about stop with id "+uri.getLastPathSegment());
return db.query(UserDB.TABLE_NAME,projection,stopFavSelection,new String[]{uri.getLastPathSegment()},null,null,sortOrder);
case STOP_OP:
//Let's try this plain and simple
final String[] selectionValues = {uri.getLastPathSegment()};
final String stopSelection = StopsTable.COL_ID+" = ?";
Log.d(DEBUG_TAG,"Asked information about stop with id "+selectionValues[0]);
return db.query(StopsTable.TABLE_NAME,projection,stopSelection,selectionValues,null,null,sortOrder);
case MANY_STOPS:
return db.query(StopsTable.TABLE_NAME, projection, selection, selectionArgs, null, null, sortOrder);
case GET_ALL_FAVORITES:
db = userDBHelper.getReadableDatabase();
return db.query(UserDB.TABLE_NAME, projection, selection, selectionArgs, null, null,sortOrder);
default:
Log.e("DataProvider","got request "+uri.getPath()+" which doesn't match anything");
}
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO: Implement this to handle requests to update one or more rows.
throw new UnsupportedOperationException("Not yet implemented");
}
// public static Uri getBaseUriGivenOp(int operationType);
public static Uri.Builder getUriBuilderToComplete(){
final Uri.Builder b = new Uri.Builder();
b.scheme("content").authority(AUTHORITY);
return b;
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
}
diff --git a/src/it/reyboz/bustorino/data/FavoritesLiveData.java b/src/it/reyboz/bustorino/data/FavoritesLiveData.java
index 82b38c9..863c961 100644
--- a/src/it/reyboz/bustorino/data/FavoritesLiveData.java
+++ b/src/it/reyboz/bustorino/data/FavoritesLiveData.java
@@ -1,201 +1,197 @@
package it.reyboz.bustorino.data;
-import android.annotation.SuppressLint;
-import android.content.AsyncQueryHandler;
+
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.content.ContentResolverCompat;
-import androidx.core.os.CancellationSignal;
-import androidx.core.os.OperationCanceledException;
+
import androidx.lifecycle.LiveData;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
-import it.reyboz.bustorino.backend.Route;
+import it.reyboz.bustorino.BuildConfig;
import it.reyboz.bustorino.backend.Stop;
-import it.reyboz.bustorino.data.NextGenDB.Contract.*;
-
public class FavoritesLiveData extends LiveData> implements CustomAsyncQueryHandler.AsyncQueryListener {
private static final String TAG = "FavoritesLiveData";
private final boolean notifyChangesDescendants;
@NonNull
private final Context mContext;
@NonNull
private final FavoritesLiveData.ForceLoadContentObserver mObserver;
private final CustomAsyncQueryHandler queryHandler;
private final Uri FAVORITES_URI = AppDataProvider.getUriBuilderToComplete().appendPath(
AppDataProvider.FAVORITES).build();
- private final int FAV_TOKEN = 23, STOPS_TOKEN_BASE=90;
+ private final int FAV_TOKEN = 23, STOPS_TOKEN_BASE=220;
@Nullable
private List stopsFromFavorites, stopsDone;
private boolean isQueryRunning = false;
private int stopNeededCount = 0;
public FavoritesLiveData(@NonNull Context context, boolean notifyDescendantsChanges) {
super();
mContext = context.getApplicationContext();
mObserver = new FavoritesLiveData.ForceLoadContentObserver();
notifyChangesDescendants = notifyDescendantsChanges;
queryHandler = new CustomAsyncQueryHandler(mContext.getContentResolver(),this);
}
private void loadData() {
loadData(false);
}
private static Uri.Builder getStopsBuilder(){
return AppDataProvider.getUriBuilderToComplete().appendPath("stop");
}
private void loadData(boolean forceQuery) {
Log.d(TAG, "loadData()");
if (!forceQuery){
if (getValue()!= null){
//Data already loaded
return;
}
}
if (isQueryRunning){
//we are waiting for data, we will get an update soon
return;
}
isQueryRunning = true;
queryHandler.startQuery(FAV_TOKEN,null, FAVORITES_URI, UserDB.getFavoritesColumnNamesAsArray, null, null, null);
}
@Override
protected void onActive() {
- Log.d(TAG, "onActive()");
+ //Log.d(TAG, "onActive()");
loadData();
}
- @Override
- protected void onInactive() {
- Log.d(TAG, "onInactive()");
-
- }
-
/**
* Clear the data for the cursor
*/
public void onClear(){
ContentResolver resolver = mContext.getContentResolver();
resolver.unregisterContentObserver(mObserver);
}
@Override
protected void setValue(List stops) {
+ //Log.d("BusTO-FavoritesLiveData","Setting the new values for the stops, have "+
+ // stops.size()+" stops");
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(FAVORITES_URI, notifyChangesDescendants,mObserver);
super.setValue(stops);
}
@Override
public void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (token == FAV_TOKEN) {
stopsFromFavorites = UserDB.getFavoritesFromCursor(cursor, UserDB.getFavoritesColumnNamesAsArray);
cursor.close();
-
- for (int i = 0; i < stopsFromFavorites.size(); i++) {
- Stop s = stopsFromFavorites.get(i);
- queryHandler.startQuery(STOPS_TOKEN_BASE + i, null, getStopsBuilder().appendPath(s.ID).build(),
- NextGenDB.QUERY_COLUMN_stops_all, null, null, null);
- }
+ //reset counters
stopNeededCount = stopsFromFavorites.size();
stopsDone = new ArrayList<>();
+ if(stopsFromFavorites.size() == 0){
+ //we don't need to call the other query
+ setValue(stopsDone);
+ } else
+ for (int i = 0; i < stopsFromFavorites.size(); i++) {
+ Stop s = stopsFromFavorites.get(i);
+ queryHandler.startQuery(STOPS_TOKEN_BASE + i, null,
+ getStopsBuilder().appendPath(s.ID).build(),
+ NextGenDB.QUERY_COLUMN_stops_all, null, null, null);
+ }
+
} else if(token >= STOPS_TOKEN_BASE){
final int index = token - STOPS_TOKEN_BASE;
assert stopsFromFavorites != null;
Stop stopUpdate = stopsFromFavorites.get(index);
Stop finalStop;
List result = Arrays.asList(NextGenDB.getStopsFromCursorAllFields(cursor));
cursor.close();
if (result.size() < 1){
// stop is not in the DB
finalStop = stopUpdate;
- } else{
+ } else {
finalStop = result.get(0);
- assert (finalStop.ID.equals(stopUpdate.ID));
+ if (BuildConfig.DEBUG && !(finalStop.ID.equals(stopUpdate.ID))) {
+ throw new AssertionError("Assertion failed");
+ }
finalStop.setStopUserName(stopUpdate.getStopUserName());
}
if (stopsDone!=null)
stopsDone.add(finalStop);
stopNeededCount--;
if (stopNeededCount == 0) {
// we have finished the queries
isQueryRunning = false;
Collections.sort(stopsDone);
setValue(stopsDone);
}
}
}
/**
* Content Observer that forces reload of cursor when data changes
* On different thread (new Handler)
*/
public final class ForceLoadContentObserver
extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
Log.d(TAG, "ForceLoadContentObserver.onChange()");
loadData(true);
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
index b849b0b..1b60a2b 100644
--- a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
+++ b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java
@@ -1,31 +1,40 @@
package it.reyboz.bustorino.fragments;
+import android.location.Location;
import android.view.View;
+import it.reyboz.bustorino.backend.Stop;
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);
/**
* Method to call when we want to hide the keyboard
*/
void hideKeyboard();
+
+ /**
+ * We want to open the map on the specified stop
+ * @param stop needs to have location data (latitude, longitude)
+ */
+ void showMapCenteredOnStop(Stop stop);
+
}
diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
index 6435af2..56885aa 100644
--- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
+++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
@@ -1,284 +1,262 @@
package it.reyboz.bustorino.fragments;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
-import android.content.Intent;
import android.os.Bundle;
-import android.os.Handler;
+import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
import java.util.List;
-import it.reyboz.bustorino.ActivityFavorites;
-import it.reyboz.bustorino.ActivityMain;
-import it.reyboz.bustorino.ActivityMap;
-import it.reyboz.bustorino.R;
+import it.reyboz.bustorino.*;
import it.reyboz.bustorino.adapters.StopAdapter;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.FavoritesViewModel;
-import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
public class FavoritesFragment extends BaseFragment {
private ListView favoriteListView;
private EditText busStopNameText;
private TextView favoriteTipTextView;
private ImageView angeryBusImageView;
@Nullable
private CommonFragmentListener mListener;
public static final String FRAGMENT_TAG = "BusTOFavFragment";
public static FavoritesFragment newInstance() {
FavoritesFragment fragment = new FavoritesFragment();
Bundle args = new Bundle();
//args.putString(ARG_PARAM1, param1);
//args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
public FavoritesFragment(){
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
//do nothing
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_favorites, container, false);
favoriteListView = root.findViewById(R.id.favoriteListView);
favoriteListView.setOnItemClickListener((parent, view, position, 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);
if(mListener!=null){
mListener.requestArrivalsForStopID(busStop.ID);
}
});
angeryBusImageView = root.findViewById(R.id.angeryBusImageView);
favoriteTipTextView = root.findViewById(R.id.favoriteTipTextView);
registerForContextMenu(favoriteListView);
FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class);
model.getFavorites().observe(getViewLifecycleOwner(), this::showStops);
showStops(new ArrayList<>());
return root;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof CommonFragmentListener) {
mListener = (CommonFragmentListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement CommonFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
if (v.getId() == R.id.favoriteListView) {
// if we aren't attached to activity, return null
if (getActivity()==null) return;
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.menu_favourites_entry, menu);
}
}
@Override
public void onResume() {
super.onResume();
if (mListener!=null) mListener.readyGUIfor(FragmentKind.FAVORITES);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
.getMenuInfo();
Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position);
switch (item.getItemId()) {
case R.id.action_favourite_entry_delete:
if (getContext()!=null)
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE,
result -> {
}).execute(busStop);
return true;
case R.id.action_rename_bus_stop_username:
showBusStopUsernameInputDialog(busStop);
return true;
case R.id.action_view_on_map:
- final String theGeoUrl = busStop.getGeoURL();
- /*
- if(theGeoUrl==null){
- //doesn't have a position
- Toast.makeText(getContext(),R.string.cannot_show_on_map_no_position,Toast.LENGTH_SHORT).show();
+ if (busStop.getLatitude() == null | busStop.getLongitude() == null |
+ mListener==null
+ ) {
+ Toast.makeText(getContext(), R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show();
return true;
}
- // start ActivityMap with these extras in intent
- Intent intent = new Intent(getContext(), ActivityMap.class);
- Bundle b = new Bundle();
- double lat, lon;
- if (busStop.getLatitude()!=null)
- lat = busStop.getLatitude();
- else lat = 200;
- if (busStop.getLongitude()!=null)
- lon = busStop.getLongitude();
- else lon = 200;
- b.putDouble("lat", lat);
- b.putDouble("lon",lon);
- b.putString("name", busStop.getStopDefaultName());
- b.putString("ID", busStop.ID);
- intent.putExtras(b);
-
- startActivity(intent);
- TODO: start map on button press
- */
+ //GeoPoint point = new GeoPoint(busStop.getLatitude(), busStop.getLongitude());
+
+ mListener.showMapCenteredOnStop(busStop);
return true;
default:
return super.onContextItemSelected(item);
}
}
void showStops(List busStops){
// If no data is found show a friendly message
-
+ if(BuildConfig.DEBUG)
+ Log.d("BusTO - Favorites", "We have "+busStops.size()+" favorites in the list");
if (busStops.size() == 0) {
favoriteListView.setVisibility(View.INVISIBLE);
// TextView favoriteTipTextView = (TextView) findViewById(R.id.favoriteTipTextView);
//assert favoriteTipTextView != null;
favoriteTipTextView.setVisibility(View.VISIBLE);
//ImageView angeryBusImageView = (ImageView) findViewById(R.id.angeryBusImageView);
angeryBusImageView.setVisibility(View.VISIBLE);
} else {
favoriteListView.setVisibility(View.VISIBLE);
favoriteTipTextView.setVisibility(View.INVISIBLE);
angeryBusImageView.setVisibility(View.INVISIBLE);
}
/* There's a nice method called notifyDataSetChanged() to avoid building the ListView
* all over again. This method exists in a billion answers on Stack Overflow, but
* it's nowhere to be seen around here, Android Studio can't find it no matter what.
* Anyway, it only works from Android 2.3 onward (which is why it refuses to appear, I
* guess) and requires to modify the list with .add() and .clear() and some other
* methods, so to update a single stop we need to completely rebuild the list for no
* reason. It would probably end up as "slow" as throwing away the old ListView and
* redrwaing everything.
*/
-
// Show results
favoriteListView.setAdapter(new StopAdapter(getContext(), busStops));
}
public void showBusStopUsernameInputDialog(final Stop busStop) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
LayoutInflater inflater = this.getLayoutInflater();
View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null);
busStopNameText = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name);
busStopNameText.setText(busStop.getStopDisplayName());
busStopNameText.setHint(busStop.getStopDefaultName());
builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title));
builder.setView(renameDialogLayout);
builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String busStopUsername = busStopNameText.getText().toString();
String oldUserName = busStop.getStopUserName();
// changed to none
if(busStopUsername.length() == 0) {
// unless it was already empty, set new
if(oldUserName != null) {
busStop.setStopUserName(null);
}
} else { // changed to something
// something different?
if(!busStopUsername.equals(oldUserName)) {
busStop.setStopUserName(busStopUsername);
}
}
launchUpdate(busStop);
}
});
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// delete user name from database
busStop.setStopUserName(null);
launchUpdate(busStop);
}
});
builder.show();
}
private void launchUpdate(Stop busStop){
if (getContext()!=null)
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.UPDATE,
new AsyncStopFavoriteAction.ResultListener() {
@Override
public void doStuffWithResult(Boolean result) {
//Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show();
}
}).execute(busStop);
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index 3f265a4..f7e21cd 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,268 +1,270 @@
/*
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.Context;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.util.Log;
import android.widget.Toast;
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.backend.utils;
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 final FragmentListenerMain listenerMain;
private final WeakReference managerWeakRef;
private Stop lastSuccessfullySearchedBusStop;
//support for multiple frames
private final int secondaryFrameLayout;
private final int primaryFrameLayout;
private final Context context;
public static final int NO_FRAME = -3;
private static final String DEBUG_TAG = "BusTO FragmHelper";
private WeakReference lastTaskRef;
private boolean shouldHaltAllActivities=false;
public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) {
this(listener,framan, context,mainFrame,NO_FRAME);
}
public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) {
this.listenerMain = listener;
this.managerWeakRef = new WeakReference<>(fraMan);
this.primaryFrameLayout = primaryFrameLayout;
this.secondaryFrameLayout = secondaryFrameLayout;
this.context = context.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 addToBackStack){
boolean sameFragment;
ArrivalsFragment arrivalsFragment;
if(managerWeakRef.get()==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
FragmentManager fm = managerWeakRef.get();
if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) {
arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout);
//Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?");
assert arrivalsFragment != null;
sameFragment = arrivalsFragment.isFragmentForTheSameStop(p);
} else {
sameFragment = false;
Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment");
}
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);
}
String probableTag = ResultListFragment.getFragmentTag(p);
attachFragmentToContainer(fm,arrivalsFragment,new AttachParameters(probableTag, true, addToBackStack));
} 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);
// enable fragment auto refresh
arrivalsFragment.setReloadOnResume(true);
listenerMain.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 createStopListFragment(List resultList, String query, boolean addToBackStack){
listenerMain.hideKeyboard();
StopListFragment listfragment = StopListFragment.newInstance(query);
if(managerWeakRef.get()==null || shouldHaltAllActivities) {
//SOMETHING WENT VERY WRONG
Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
attachFragmentToContainer(managerWeakRef.get(),listfragment,
new AttachParameters("search_"+query, false,addToBackStack));
listfragment.setStopList(resultList);
toggleSpinner(false);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
listenerMain.toggleSpinner(on);
}
/**
* Attach a new fragment to a cointainer
* @param fm the FragmentManager
* @param fragment the Fragment
* @param parameters attach parameters
*/
protected void attachFragmentToContainer(FragmentManager fm,Fragment fragment, AttachParameters parameters){
+ if(shouldHaltAllActivities) //nothing to do
+ return;
FragmentTransaction ft = fm.beginTransaction();
int frameID;
if(parameters.attachToSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
// ft.replace(secondaryFrameLayout,fragment,tag);
frameID = secondaryFrameLayout;
else frameID = primaryFrameLayout;
switch (parameters.transaction){
case REPLACE:
ft.replace(frameID,fragment,parameters.tag);
}
if (parameters.addToBackStack)
ft.addToBackStack("state_"+parameters.tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
- if(!fm.isDestroyed())
+ if(!fm.isDestroyed() && !shouldHaltAllActivities)
ft.commit();
//fm.executePendingTransactions();
}
- public void setBlockAllActivities(boolean shouldI) {
+ public synchronized void setBlockAllActivities(boolean shouldI) {
this.shouldHaltAllActivities = shouldI;
}
public void stopLastRequestIfNeeded(){
if(lastTaskRef == null) return;
AsyncDataDownload task = lastTaskRef.get();
if(task!=null){
- task.cancel(true);
+ task.cancel(false);
}
}
/**
* 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:
showToastMessage(R.string.network_error, true);
break;
case SERVER_ERROR:
if (utils.isConnected(context)) {
showToastMessage(R.string.parsing_error, true);
} else {
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 showToastMessage(int messageID, boolean short_lenght) {
final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
if (context != null)
Toast.makeText(context, messageID, length).show();
}
private void showShortToast(int messageID){
showToastMessage(messageID, true);
}
enum Transaction{
REPLACE,
}
static final class AttachParameters {
String tag;
boolean attachToSecondaryFrame;
Transaction transaction;
boolean addToBackStack;
public AttachParameters(String tag, boolean attachToSecondaryFrame, Transaction transaction, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.transaction = transaction;
this.addToBackStack = addToBackStack;
}
public AttachParameters(String tag, boolean attachToSecondaryFrame, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.addToBackStack = addToBackStack;
this.transaction = Transaction.REPLACE;
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
index 5f8a6b1..c37740e 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
@@ -1,33 +1,29 @@
/*
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 FragmentListenerMain extends CommonFragmentListener {
void toggleSpinner(boolean state);
- //TODO: implement void showStopOnMap()
-
/**
* 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 a055cff..bbf9f2e 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,700 +1,703 @@
package it.reyboz.bustorino.fragments;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.os.Build;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
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 android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.zxing.integration.android.IntentIntegrator;
import java.util.Map;
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.Palina;
-import it.reyboz.bustorino.backend.StopsFinderByName;
+import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.middleware.AsyncDataDownload;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MainScreenFragment extends BaseFragment implements FragmentListenerMain{
private static final String OPTION_SHOW_LEGEND = "show_legend";
private static final String SAVED_FRAGMENT="saved_fragment";
private static final String DEBUG_TAG = "BusTO - MainFragment";
public final static String FRAGMENT_TAG = "MainScreenFragment";
/// 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 boolean setupOnAttached = true;
private boolean suppressArrivalsReload = false;
//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()};
//// HIDDEN BUT IMPORTANT ELEMENTS ////
FragmentManager fragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
if (fragment == null){
//we create a new fragment, which is WRONG
new AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
} else{
String stopName = fragment.getStopID();
new AsyncDataDownload(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
}
} else //we create a new fragment, which is WRONG
new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute();
}
};
/// LOCATION STUFF ///
boolean pendingNearbyStopsRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
AppLocationManager locationManager;
private final LocationCriteria cr = new LocationCriteria(2000, 10000);
//Location
private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
@Override
public void onLocationChanged(Location loc) {
}
@Override
public void onLocationStatusChanged(int status) {
if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){
//request Stops
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public long getLastUpdateTimeMillis() {
return 50;
}
@Override
public LocationCriteria getLocationCriteria() {
return cr;
}
@Override
public void onLocationProviderAvailable() {
//Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest);
if(!isNearbyFragmentShown()){
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public void onLocationDisabled() {
}
};
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback