diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 24ba670..13fe320 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,105 +1,108 @@
+
+
diff --git a/res/layout/entry_bus_stop.xml b/res/layout/entry_bus_stop.xml
index 53c2593..ee45178 100644
--- a/res/layout/entry_bus_stop.xml
+++ b/res/layout/entry_bus_stop.xml
@@ -1,70 +1,73 @@
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java
index 9baee1f..653aff0 100644
--- a/src/it/reyboz/bustorino/ActivityMain.java
+++ b/src/it/reyboz/bustorino/ActivityMain.java
@@ -1,633 +1,630 @@
/*
BusTO - Arrival times for Turin public transports.
Copyright (C) 2014 Valerio Bozzolan
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;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.app.NavUtils;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.*;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
//import com.melnykov.fab.FloatingActionButton;
import android.support.design.widget.FloatingActionButton;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
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.StopsFinderByName;
-import it.reyboz.bustorino.fragments.FragmentHelper;
-import it.reyboz.bustorino.fragments.FragmentListener;
-import it.reyboz.bustorino.fragments.NearbyStopsFragment;
-import it.reyboz.bustorino.fragments.ResultListFragment;
+import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.*;
public class ActivityMain extends GeneralActivity implements FragmentListener {
/*
* Layout elements
*/
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem;
private SwipeRefreshLayout swipeRefreshLayout;
private FloatingActionButton floatingActionButton;
private FragmentManager framan;
/*
* 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 (bug #1512948)
private int searchMode;
/*
* Options
*/
private final String OPTION_SHOW_LEGEND = "show_legend";
private final String LOCATION_PERMISSION_GIVEN = "loc_permission";
/* // useful for testing:
public class MockFetcher implements ArrivalsFetcher {
@Override
public Palina ReadArrivalTimesAll(String routeID, AtomicReference 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 RecursionHelper ArrivalFetchersRecursionHelper = new RecursionHelper<>(new ArrivalsFetcher[] {new GTTJSONFetcher(), new FiveTScraperFetcher()});
private RecursionHelper StopsFindersByNameRecursionHelper = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()});
private StopsDB stopsDB;
private UserDB userDB;
private FragmentHelper fh;
///////////////////////////////// EVENT HANDLERS ///////////////////////////////////////////////
/*
* @see swipeRefreshLayout
*/
private Handler handler = new Handler();
private final Runnable refreshing = new Runnable() {
public void run() {
new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute();
}
};
//// MAIN METHOD ///
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
framan = getSupportFragmentManager();
this.stopsDB = new StopsDB(getApplicationContext());
this.userDB = new UserDB(getApplicationContext());
setContentView(R.layout.activity_main);
busStopSearchByIDEditText = (EditText) findViewById(R.id.busStopSearchByIDEditText);
busStopSearchByNameEditText = (EditText) findViewById(R.id.busStopSearchByNameEditText);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
howDoesItWorkTextView = (TextView) findViewById(R.id.howDoesItWorkTextView);
hideHintButton = (Button) findViewById(R.id.hideHintButton);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.listRefreshLayout);
floatingActionButton = (FloatingActionButton) findViewById(R.id.floatingActionButton);
framan.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
@Override
public void onBackStackChanged() {
Log.d("MainActivity, BusTO", "BACK STACK CHANGED");
}
});
busStopSearchByIDEditText.setSelectAllOnFocus(true);
busStopSearchByIDEditText
.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
}
});
busStopSearchByNameEditText
.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent 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(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
handler.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,FragmentHelper.NO_FRAME);
+ 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
showKeyboard();
// You haven't obtained anything... from an intent?
if (tryedFromIntent) {
// This shows a luser warning
ArrivalFetchersRecursionHelper.reset();
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);
}
//Try (hopefully) database update
//TODO: Start the service in foreground, check last time it ran before
DatabaseUpdateService.startDBUpdate(getApplicationContext());
//locationHandler = new GPSLocationAdapter(getApplicationContext());
//--------- NEARBY STOPS--------//
//Let's search stops nearby
LocationManager locManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if(locManager.isProviderEnabled(LocationManager.GPS_PROVIDER) && fh.getLastSuccessfullySearchedBusStop()==null && getOption(LOCATION_PERMISSION_GIVEN,false)) {
NearbyStopsFragment fr = NearbyStopsFragment.newInstance();
FragmentTransaction ft = framan.beginTransaction();
ft.add(R.id.resultFrame, fr, "Location");
ft.commitNow();
swipeRefreshLayout.setVisibility(View.VISIBLE);
swipeRefreshLayout.setEnabled(false);
} else if(!getOption(LOCATION_PERMISSION_GIVEN, false)){
assertLocationPermissions();
}
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 asyncWgetBusStopFromBusStopID(lastSuccessfullySearchedBusStop.ID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop);
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
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;
}
@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.
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_about:
startActivity(new Intent(ActivityMain.this, ActivityAbout.class));
return true;
case R.id.action_news:
openIceweasel("http://blog.reyboz.it/tag/busto/");
return true;
case R.id.action_bugs:
openIceweasel("https://bugs.launchpad.net/bus-torino");
return true;
case R.id.action_source:
openIceweasel("https://code.launchpad.net/bus-torino");
return true;
case R.id.action_licence:
openIceweasel("http://www.gnu.org/licenses/gpl-3.0.html");
return true;
case R.id.action_author:
openIceweasel("http://boz.reyboz.it?lovebusto");
return true;
}
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();
//OLD ASYNCTASK
//new asyncWgetBusStopFromBusStopID(busStopID, ArrivalFetchersRecursionHelper, lastSuccessfullySearchedBusStop);
if(busStopID == null || busStopID.length() <= 0) {
showMessage(R.string.insert_bus_stop_number_error);
toggleSpinner(false);
} else{
new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).execute(busStopID);
Log.d("MainActiv","Started search for arrivals of stop "+busStopID);
}
} else { // searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
//new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper);
new AsyncDataDownload(AsyncDataDownload.RequestType.STOPS,fh).execute(query);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case PERMISSION_REQUEST_POSITION:
if(grantResults.length>0 && grantResults[0]==PackageManager.PERMISSION_GRANTED){
setOption(LOCATION_PERMISSION_GIVEN,true);
handler.post(new Runnable() {
@Override
public void run() {
Log.d("mainActivity","Recreating stop fragment");
NearbyStopsFragment fragment = NearbyStopsFragment.newInstance();
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();
}
});
} else {
//permission denied
setOption(LOCATION_PERMISSION_GIVEN,false);
}
//add other cases for permissions
}
}
@Override
public void createFragmentForStop(String ID) {
//new asyncWgetBusStopFromBusStopID(ID, ArrivalFetchersRecursionHelper,lastSuccessfullySearchedBusStop);
if(ID == null || ID.length() <= 0) {
// we're still in UI thread, no need to mess with Progress
showMessage(R.string.insert_bus_stop_number_error);
toggleSpinner(false);
} else {
new AsyncDataDownload(AsyncDataDownload.RequestType.ARRIVALS,fh).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) {
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);
}
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();
}
}
}
///////////////////////////////// OTHER STUFF //////////////////////////////////////////////////
@Override
public void addLastStopToFavorites() {
if(fh.getLastSuccessfullySearchedBusStop() != null) {
new AsyncAddToFavorites(this).execute(fh.getLastSuccessfullySearchedBusStop());
}
}
@Override
public void showFloatingActionButton(boolean yes) {
if(yes) floatingActionButton.show();
else floatingActionButton.hide();
}
@Override
public void enableRefreshLayout(boolean yes) {
swipeRefreshLayout.setEnabled(yes);
}
////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
@Override
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);
}
@Override
public void showMessage(int messageID) {
Toast.makeText(getApplicationContext(), messageID, Toast.LENGTH_SHORT).show();
}
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(String fragmentType) {
+ public void readyGUIfor(FragmentKind fragmentType) {
hideKeyboard();
if(fragmentType==null) Log.e("ActivityMain","Problem with fragmentType");
else
switch (fragmentType){
- case ResultListFragment.TYPE_LINES:
+ case ARRIVALS:
prepareGUIForBusLines();
if (getOption(OPTION_SHOW_LEGEND, true)) {
showHints();
}
break;
- case ResultListFragment.TYPE_STOPS:
+ 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;
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/FiveTNormalizer.java b/src/it/reyboz/bustorino/backend/FiveTNormalizer.java
index 2c42045..ddab8be 100644
--- a/src/it/reyboz/bustorino/backend/FiveTNormalizer.java
+++ b/src/it/reyboz/bustorino/backend/FiveTNormalizer.java
@@ -1,317 +1,319 @@
/*
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.backend;
import android.util.Log;
/**
* Converts some weird stop IDs found on the 5T website to the form used everywhere else (including GTT website).
*
* A stop ID in normalized form is:
* - a string containing a number without leading zeros
* - a string beginning with "ST" and then a number
* - whatever the GTT website uses.
*
* A bus route ID in normalized form is:
* - a string containing a number and optionally: "B", "CS", "CD", "N", "S", "C" at the end
* - the string "METRO"
* - "ST1" or "ST2" (Star 1 and Star 2)
* - a string beginning with E, N, S or W, followed by two digits (with a leading zero)
* - "RV2", "OB1"
* - "CAN" or "FTC" (railway lines)
* - ...screw it, let's just hope all the websites and APIs return something sane as route IDs.
*
* This class exists because Java doesn't support traits.
*
* Note: this class also just became useless, as 5T now uses the same format as GTT website.
*/
public abstract class FiveTNormalizer {
public static String FiveTNormalizeRoute(String RouteID) {
while (RouteID.startsWith("0")) {
RouteID = RouteID.substring(1);
}
return RouteID;
}
// public static String FiveTNormalizeStop(String StopID) {
// StopID = FiveTNormalizeRoute(StopID);
// // is this faster than a regex?
// if (StopID.length() == 5 && StopID.startsWith("ST") && Character.isLetter(StopID.charAt(2)) && Character.isLetter(StopID.charAt(3)) && Character.isLetter(StopID.charAt(4))) {
// switch (StopID) {
// case "STFER":
// return "8210";
// case "STPAR":
// return "8211";
// case "STMAR":
// return "8212";
// case "STMAS":
// return "8213";
// case "STPOS":
// return "8214";
// case "STMGR":
// return "8215";
// case "STRIV":
// return "8216";
// case "STRAC":
// return "8217";
// case "STBER":
// return "8218";
// case "STPDA":
// return "8219";
// case "STDOD":
// return "8220";
// case "STPSU":
// return "8221";
// case "STVIN":
// return "8222";
// case "STREU":
// return "8223";
// case "STPNU":
// return "8224";
// case "STMCI":
// return "8225";
// case "STNIZ":
// return "8226";
// case "STDAN":
// return "8227";
// case "STCAR":
// return "8228";
// case "STSPE":
// return "8229";
// case "STLGO":
// return "8230";
// }
// }
// return StopID;
// }
//
// public static String NormalizedToFiveT(final String StopID) {
// if(StopID.startsWith("82") && StopID.length() == 4) {
// switch (StopID) {
// case "8230":
// return "STLGO";
// case "8229":
// return "STSPE";
// case "8228":
// return "STCAR";
// case "8227":
// return "STDAN";
// case "8226":
// return "STNIZ";
// case "8225":
// return "STMCI";
// case "8224":
// return "STPNU";
// case "8223":
// return "STREU";
// case "8222":
// return "STVIN";
// case "8221":
// return "STPSU";
// case "8220":
// return "STDOD";
// case "8219":
// return "STPDA";
// case "8218":
// return "STBER";
// case "8217":
// return "STRAC";
// case "8216":
// return "STRIV";
// case "8215":
// return "STMGR";
// case "8214":
// return "STPOS";
// case "8213":
// return "STMAS";
// case "8212":
// return "STMAR";
// case "8211":
// return "STPAR";
// case "8210":
// return "STFER";
// }
// }
//
// return StopID;
// }
public static Route.Type decodeType(final String routename, final String bacino) {
if(routename.equals("METRO")) {
return Route.Type.METRO;
} else if(routename.equals("79")) {
return Route.Type.RAILWAY;
}
switch (bacino) {
case "U":
return Route.Type.BUS;
case "F":
return Route.Type.RAILWAY;
case "E":
return Route.Type.LONG_DISTANCE_BUS;
default:
return Route.Type.BUS;
}
}
/**
* Converts a route ID from internal format to display format, returns null if it has the same name.
*
* @param routeID ID in "internal" and normalized format
* @return string with display name, null if unchanged
*/
public static String routeInternalToDisplay(final String routeID) {
if(routeID.length() == 3 && routeID.charAt(2) == 'B') {
return routeID.substring(0,2).concat("/");
}
switch(routeID) {
case "1C":
return "1 Chieri";
case "1N":
return "1 Nichelino";
case "OB1":
return "1 Orbassano";
case "2C":
return "2 Chieri";
case "RV2":
return "2 Rivalta";
case "CO1":
return "Circolare Collegno";
case "SE1":
// I wonder why GTT calls this "SE1" while other absurd names have a human readable name too.
return "1 Settimo";
case "16CD":
return "16 Circolare Destra";
case "16CS":
return "16 Circolare Sinistra";
case "79":
return "Cremagliera Sassi-Superga";
case "W01":
return "Night Buster 1 Arancio";
case "N10":
return "Night Buster 10 Gialla";
case "W15":
return "Night Buster 15 Rosa";
case "S18":
return "Night Buster 18 Blu";
case "S04":
return "Night Buster 4 Azzurra";
case "N4":
return "Night Buster 4 Rossa";
case "N57":
return "Night Buster 57 Oro";
case "W60":
return "Night Buster 60 Argento";
case "E68":
return "Night Buster 68 Verde";
case "S05":
return "Night Buster 5 Viola";
case "ST1":
return "Star 1";
case "ST2":
return "Star 2";
case "4N":
return "4 Navetta";
case "10N":
return "10 Navetta";
case "13N":
return "13 Navetta";
case "35N":
return "35 Navetta";
case "36N":
return "36 Navetta";
case "36S":
return "36 Speciale";
case "38S":
return "38 Speciale";
case "44S":
return "44 Scolastico";
case "46N":
return "46 Navetta";
default:
return null;
}
}
public static String routeDisplayToInternal(String displayName){
String name = displayName.trim();
if(name.charAt(displayName.length()-1)=='/'){
return displayName.replace(" ","").replace("/","B");
}
switch (name.toLowerCase()){
//DEFAULT CASES
case "star 1":
return "ST1";
case "star 2":
return "ST2";
case "night buster 1 arancio":
return "W01";
case "night buster 10 gialla":
return "N10";
case "night buster 15 rosa":
return "W15";
case "night buster 18 blu":
return "S18";
case "night buster 4 azzurra":
return "S04";
case "night buster 4 rossa":
return "N4";
case "night buster 57 oro":
return "N57";
case "night buster 60 argento":
return "W60";
case "night buster 68 verde":
return "E68";
case "night buster 5 viola":
return "S05";
case "1 nichelino":
return "1N";
case "1 chieri":
return "1C";
case "1 orbassano":
return "OB1";
case "2 chieri":
return "2C";
case "2 rivalta":
return "RV2";
default:
// return displayName.trim();
}
String[] arr = name.toLowerCase().split("\\s+");
try {
if (arr.length == 2 && arr[1].trim().equals("navetta") && Integer.decode(arr[0]) > 0)
return arr[0].trim().concat("N");
} catch (NumberFormatException e){
//It's not "# navetta"
Log.w("FivetNorm","checking number when it's not");
}
if(name.toLowerCase().contains("night buster")){
if(name.toLowerCase().contains("viola"))
return "S05";
+ else if(name.toLowerCase().contains("verde"))
+ return "E68";
}
//Everything failed, let's at least compact the the (probable) code
return name.replace(" ","");
}
}
diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java
index 7da3540..599b866 100644
--- a/src/it/reyboz/bustorino/backend/Palina.java
+++ b/src/it/reyboz/bustorino/backend/Palina.java
@@ -1,303 +1,305 @@
/*
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.backend;
import android.util.Log;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
/**
* Timetable for multiple routes.
*
* Apparently "palina" and a bunch of other terms can't really be translated into English.
* Not in a way that makes sense and keeps the code readable, at least.
*/
public class Palina extends Stop {
private ArrayList routes = new ArrayList<>();
public Palina(String stopID) {
super(stopID);
}
/**
* Adds a route to the timetable.
*
* @param routeID name
* @param type bus, underground, railway, ...
* @param destinazione end of line\terminus (underground stations have the same ID for both directions)
* @return array index for this route
*/
public int addRoute(String routeID, String destinazione, Route.Type type) {
this.routes.add(new Route(routeID, destinazione, type, new ArrayList(6)));
return this.routes.size() - 1; // last inserted element and pray that direct access to ArrayList elements really is direct
}
public int addRoute(Route r){
this.routes.add(r);
return this.routes.size()-1;
}
/**
* Adds a timetable entry to a route.
*
* @param TimeGTT time in GTT format (e.g. "11:22*")
* @param arrayIndex position in the array for this route (returned by addRoute)
*/
public void addPassaggio(String TimeGTT, int arrayIndex) {
this.routes.get(arrayIndex).addPassaggio(TimeGTT);
}
/**
* Count routes with missing directions
* @return number
*/
public int countRoutesWithMissingDirections(){
int i = 0;
for (Route r : routes){
if(r.destinazione==null||r.destinazione.equals(""))
i++;
}
return i;
}
// /**
// * Clears a route timetable (or creates an empty route) and returns its index
// *
// * @param routeID name
// * @param destinazione end of line\terminus
// * @return array index for this route
// */
// public int updateRoute(String routeID, String destinazione) {
// int s = this.routes.size();
// RouteInternal r;
//
// for(int i = 0; i < s; i++) {
// r = routes.get(i);
// if(r.name.compareTo(routeID) == 0 && r.destinazione.compareTo(destinazione) == 0) {
// // capire se è possibile che ci siano stessa linea e stessa destinazione su 2 righe diverse del sito e qui una sovrascrive l'altra (probabilmente no)
// r.updateFlag();
// r.deletePassaggio();
// return i;
// }
// }
//
// return this.addRoute(routeID, destinazione);
// }
//
// /**
// * Deletes routes marked as "not updated" (= disappeared from the GTT website\API\whatever).
// * Sets all remaining routes to "not updated" because that's how this contraption works.
// */
// public void finishUpdatingRoutes() {
// RouteInternal r;
//
// for(Iterator itr = this.routes.iterator(); itr.hasNext(); ) {
// r = itr.next();
// if(r.unupdateFlag()) {
// itr.remove();
// }
// }
// }
// /**
// * Gets the current timetable for a route. Returns null if the route doesn't exist.
// * This is slower than queryRouteByIndex.
// *
// * @return timetable (passaggi)
// */
// public List queryRoute(String routeID) {
// for(Route r : this.routes) {
// if(routeID.equals(r.name)) {
// return r.getPassaggi();
// }
// }
//
// return null;
// }
//
// /**
// * Gets the current timetable for this route, from its index in the array.
// *
// * @return timetable (passaggi)
// */
// public List queryRouteByIndex(int index) {
// return this.routes.get(index).getPassaggi();
// }
/**
* Gets every route and its timetable.
*
* @return routes and timetables.
*/
public List queryAllRoutes() {
return this.routes;
}
public void sortRoutes() {
Collections.sort(this.routes);
}
/**
* Add info about the routes already found from another source
* @param additionalRoutes ArrayList of routes to get the info from
* @return the number of routes modified
*/
public int addInfoFromRoutes(List additionalRoutes){
if(routes == null || routes.size()==0) {
this.routes = new ArrayList<>(additionalRoutes);
return routes.size();
}
int count=0;
final Calendar c = Calendar.getInstance();
final int todaysInt = c.get(Calendar.DAY_OF_WEEK);
for(Route r:routes) {
int j = 0;
boolean correct = false;
Route selected = null;
+ //TODO: rewrite this as a simple loop
+ //MADNESS begins here
while (!correct) {
//find the correct route to merge to
// scan routes and find the first which has the same name
while (j < additionalRoutes.size() && !r.name.equals(additionalRoutes.get(j).name)) {
j++;
}
if (j == additionalRoutes.size()) break; //no match has been found
//should have found the first occurrence of the line
selected = additionalRoutes.get(j);
//move forward
j++;
if (selected.serviceDays != null && selected.serviceDays.length > 0) {
//check if it is in service
for (int d : selected.serviceDays) {
if (d == todaysInt) {
correct = true;
break;
}
}
} else if (r.festivo != null) {
switch (r.festivo) {
case FERIALE:
//Domenica = 1 --> Saturday=7
if (todaysInt <= 7 && todaysInt > 1) correct = true;
break;
case FESTIVO:
if (todaysInt == 1) correct = true; //TODO: implement way to recognize all holidays
break;
case UNKNOWN:
correct = true;
}
} else {
//case a: there is no info because the line is always active
//case b: there is no info because the information is missing
correct = true;
}
}
if (!correct || selected == null) {
Log.w("Palina_mergeRoutes","Cannot match the route with name "+r.name);
continue; //we didn't find any match
}
//found the correct correspondance
//MERGE INFO
if(r.mergeRouteWithAnother(selected)) count++;
}
return count;
}
// /**
// * Route with terminus (destinazione) and timetables (passaggi), internal implementation.
// *
// * Contains mostly the same data as the Route public class, but methods are quite different and extending Route doesn't really work, here.
// */
// private final class RouteInternal {
// public final String name;
// public final String destinazione;
// private boolean updated;
// private List passaggi;
//
// /**
// * Creates a new route and marks it as "updated", since it's new.
// *
// * @param routeID name
// * @param destinazione end of line\terminus
// */
// public RouteInternal(String routeID, String destinazione) {
// this.name = routeID;
// this.destinazione = destinazione;
// this.passaggi = new LinkedList<>();
// this.updated = true;
// }
//
// /**
// * Adds a time (passaggio) to the timetable for this route
// *
// * @param TimeGTT time in GTT format (e.g. "11:22*")
// */
// public void addPassaggio(String TimeGTT) {
// this.passaggi.add(new Passaggio(TimeGTT));
// }
//
// /**
// * Deletes al times (passaggi) from the timetable.
// */
// public void deletePassaggio() {
// this.passaggi = new LinkedList<>();
// this.updated = true;
// }
//
// /**
// * Sets the "updated" flag to false.
// *
// * @return previous state
// */
// public boolean unupdateFlag() {
// if(this.updated) {
// this.updated = false;
// return true;
// } else {
// return false;
// }
// }
//
// /**
// * Sets the "updated" flag to true.
// *
// * @return previous state
// */
// public boolean updateFlag() {
// if(this.updated) {
// return true;
// } else {
// this.updated = true;
// return false;
// }
// }
//
// /**
// * Exactly what it says on the tin.
// *
// * @return times from the timetable
// */
// public List getPassaggi() {
// return this.passaggi;
// }
// }
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/Stop.java b/src/it/reyboz/bustorino/backend/Stop.java
index 89711a0..1c91b7a 100644
--- a/src/it/reyboz/bustorino/backend/Stop.java
+++ b/src/it/reyboz/bustorino/backend/Stop.java
@@ -1,286 +1,288 @@
/*
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.backend;
import android.location.Location;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.net.URLEncoder;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class Stop implements Comparable {
// remove "final" in case you need to set these from outside the parser\scrapers\fetchers
public final @NonNull String ID;
private @Nullable String name;
private @Nullable String username;
public @Nullable String location;
- public final @Nullable Route.Type type;
+ public @Nullable Route.Type type;
private @Nullable List routesThatStopHere;
private final @Nullable Double lat;
private final @Nullable Double lon;
// leave this non-final
private @Nullable String routesThatStopHereString = null;
private @Nullable String absurdGTTPlaceName = null;
/**
* Hey, look, method overloading!
*/
public Stop(final @Nullable String name, final @NonNull String ID, @Nullable final String location, @Nullable final Route.Type type, @Nullable final List routesThatStopHere) {
this.ID = ID;
this.name = name;
this.username = null;
this.location = (location != null && location.length() == 0) ? null : location;
this.type = type;
this.routesThatStopHere = routesThatStopHere;
this.lat = null;
this.lon = null;
}
/**
* Hey, look, method overloading!
*/
public Stop(final @NonNull String ID) {
this.ID = ID;
this.name = null;
this.username = null;
this.location = null;
this.type = null;
this.routesThatStopHere = null;
this.lat = null;
this.lon = null;
}
/**
* Constructor that sets EVERYTHING.
*/
public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName, @Nullable String location, @Nullable Route.Type type, @Nullable List routesThatStopHere, @Nullable Double lat, @Nullable Double lon) {
this.ID = ID;
this.name = name;
this.username = userName;
this.location = location;
this.type = type;
this.routesThatStopHere = routesThatStopHere;
this.lat = lat;
this.lon = lon;
}
public @Nullable String routesThatStopHereToString() {
// M E M O I Z A T I O N
if(this.routesThatStopHereString != null) {
return this.routesThatStopHereString;
}
// no string yet? build it!
return buildString();
}
@Nullable
public String getAbsurdGTTPlaceName() {
return absurdGTTPlaceName;
}
public void setAbsurdGTTPlaceName(@NonNull String absurdGTTPlaceName) {
this.absurdGTTPlaceName = absurdGTTPlaceName;
}
public void setRoutesThatStopHere(@Nullable List routesThatStopHere) {
this.routesThatStopHere = routesThatStopHere;
}
private @Nullable String buildString() {
// no routes => no string
if(this.routesThatStopHere == null || this.routesThatStopHere.size() == 0) {
return null;
}
StringBuilder sb = new StringBuilder();
+ Collections.sort(routesThatStopHere);
int i, lenMinusOne = routesThatStopHere.size() - 1;
for (i = 0; i < lenMinusOne; i++) {
sb.append(routesThatStopHere.get(i)).append(", ");
}
// last one:
sb.append(routesThatStopHere.get(i));
this.routesThatStopHereString = sb.toString();
return this.routesThatStopHereString;
}
@Override
public int compareTo(@NonNull Stop other) {
int res;
int thisAsInt = networkTools.failsafeParseInt(this.ID);
int otherAsInt = networkTools.failsafeParseInt(other.ID);
// numeric stop IDs
if(thisAsInt != 0 && otherAsInt != 0) {
return thisAsInt - otherAsInt;
} else {
// non-numeric
res = this.ID.compareTo(other.ID);
if (res != 0) {
return res;
}
}
// try with name, then
if(this.name != null && other.name != null) {
res = this.name.compareTo(other.name);
}
// and give up
return res;
}
/**
* Sets a name.
*
* @param name stop name as string (not null)
*/
public final void setStopName(@NonNull String name) {
this.name = name;
}
/**
* Sets user name. Empty string is converted to null.
*
* @param name a string of non-zero length, or null
*/
public final void setStopUserName(@Nullable String name) {
if(name == null) {
this.username = null;
} else if(name.length() == 0) {
this.username = null;
} else {
this.username = name;
}
}
/**
* Returns stop name or username (if set).
* - empty string means "already searched everywhere, can't find it"
* - null means "didn't search, yet. Maybe you should try."
* - string means "here's the name.", obviously.
*
* @return string if known, null if still unknown
*/
public final @Nullable String getStopDisplayName() {
if(this.username == null) {
return this.name;
} else {
return this.username;
}
}
/**
* Same as getStopDisplayName, only returns default name.
* I'd use an @see tag, but Android Studio is incapable of understanding that getStopDefaultName
* refers to the method exactly above this one and not some arcane and esoteric unknown symbol.
*/
public final @Nullable String getStopDefaultName() {
return this.name;
}
/**
* Same as getStopDisplayName, only returns user name.
* Also, never an empty string.
*/
public final @Nullable String getStopUserName() {
return this.username;
}
/**
* Gets username and name from other stop if they exist, sets itself accordingly.
*
* @param other another Stop
* @return did we actually set/change anything?
*/
public final boolean mergeNameFrom(Stop other) {
boolean ret = false;
if(other.name != null) {
if(this.name == null || !this.name.equals(other.name)) {
this.name = other.name;
ret = true;
}
}
if(other.username != null) {
if(this.username == null || !this.username.equals(other.username)) {
this.username = other.username;
ret = true;
}
}
return ret;
}
public final @Nullable String getGeoURL() {
if(this.lat == null || this.lon == null) {
return null;
}
// Android documentation suggests US for machine readable output (use dot as decimal separator)
return String.format(Locale.US, "geo:%f,%f", this.lat, this.lon);
}
public final @Nullable String getGeoURLWithAddress() {
String url = getGeoURL();
if(url == null) {
return null;
}
if(this.location != null) {
try {
String addThis = "?q=".concat(URLEncoder.encode(this.location, "utf-8"));
return url.concat(addThis);
} catch (Exception ignored) {}
}
return url;
}
@Nullable
public Double getLatitude() {
return lat;
}
@Nullable
public Double getLongitude() {
return lon;
}
public Double getDistanceFromLocation(Location loc){
if(this.lat!=null && this.lon !=null)
return utils.measuredistanceBetween(this.lat,this.lon,loc.getLatitude(),loc.getLongitude());
else return Double.POSITIVE_INFINITY;
}
}
diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
new file mode 100644
index 0000000..b9026fd
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -0,0 +1,154 @@
+/*
+ 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 android.support.annotation.Nullable;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.widget.TextView;
+import it.reyboz.bustorino.R;
+import it.reyboz.bustorino.middleware.AppDataProvider;
+import it.reyboz.bustorino.middleware.NextGenDB;
+import it.reyboz.bustorino.middleware.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 int loaderFavId = 2;
+ private final static int loaderStopId = 1;
+ private @Nullable String stopID,stopName;
+ private TextView messageTextView;
+
+ public static ArrivalsFragment newInstance(String stopID){
+ Bundle args = new Bundle();
+ args.putString(KEY_STOP_ID,stopID);
+ ArrivalsFragment fragment = new ArrivalsFragment();
+ //parameter for ResultListFragment
+ args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
+ fragment.setArguments(args);
+ return fragment;
+ }
+ public static ArrivalsFragment newInstance(String stopID,String stopName){
+ ArrivalsFragment fragment = newInstance(stopID);
+ Bundle args = fragment.getArguments();
+ 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);
+ }
+ @Override
+ public void onResume() {
+ super.onResume();
+ LoaderManager loaderManager = getLoaderManager();
+ if(stopID!=null){
+ //start the loader
+ loaderManager.restartLoader(loaderFavId,getArguments(),this);
+ updateMessage();
+ }
+ }
+
+ 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));
+ }
+
+ @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.getAlmostFinishedBuilder();
+ 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.d("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
+ }
+ }
+
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ //NOTHING TO DO
+ }
+}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
index c03bea8..8924293 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java
@@ -1,228 +1,200 @@
/*
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.SQLiteDatabase;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
-import android.support.v4.content.ContentResolverCompat;
import android.support.v4.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.middleware.*;
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 int primaryFrameLayout,secondaryFrameLayout, swipeRefID;
public static final int NO_FRAME = -3;
- UserDB userDB;
- StopsDB stopsDB;
- private NextGenDB newDB;
+
+ private NextGenDB newDBHelper;
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;
- stopsDB = new StopsDB(act);
- userDB = new UserDB(act);
- newDB = new NextGenDB(act.getApplicationContext());
+ newDBHelper = new NextGenDB(act.getApplicationContext());
}
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
this.lastSuccessfullySearchedBusStop = stop;
}
/**
* 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 refreshing;
- ResultListFragment listFragment;
+ ArrivalsFragment arrivalsFragment;
if(act==null) {
//SOMETHING WENT VERY WRONG
return;
}
SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID);
FragmentManager fm = act.getSupportFragmentManager();
if(srl.isRefreshing())
refreshing=true;
- else if(fm.findFragmentById(R.id.resultFrame) instanceof ResultListFragment) {
- listFragment = (ResultListFragment) fm.findFragmentById(R.id.resultFrame);
- refreshing = listFragment.isFragmentForTheSameStop(p);
+ else if(fm.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
+ arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame);
+ refreshing = arrivalsFragment.isFragmentForTheSameStop(p);
} else
refreshing = false;
setLastSuccessfullySearchedBusStop(p);
if(!refreshing) {
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
String displayStuff;
if (displayName != null && displayName.length() > 0) {
- displayStuff = p.ID.concat(" - ").concat(displayName);
+ arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName);
} else {
- displayStuff = p.ID;
+ arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
- listFragment = ResultListFragment.newInstance(ResultListFragment.TYPE_LINES,displayStuff);
- attachFragmentToContainer(fm,listFragment,true,ResultListFragment.getFragmentTag(p));
+ attachFragmentToContainer(fm,arrivalsFragment,true,ResultListFragment.getFragmentTag(p));
} else {
Log.d("BusTO", "Same bus stop, accessing existing fragment");
- listFragment = (ResultListFragment) fm.findFragmentById(R.id.resultFrame);
+ arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(R.id.resultFrame);
}
- listFragment.setListAdapter(new PalinaAdapter(act.getApplicationContext(),p));
+ arrivalsFragment.setListAdapter(new PalinaAdapter(act.getApplicationContext(),p));
act.hideKeyboard();
- if (act instanceof FragmentListener) ((FragmentListener) act).readyGUIfor(ResultListFragment.TYPE_LINES);
-
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();
- ResultListFragment listfragment = ResultListFragment.newInstance(ResultListFragment.TYPE_STOPS);
+ StopListFragment listfragment = StopListFragment.newInstance(query);
attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query);
- if (act instanceof FragmentListener) ((FragmentListener) act).readyGUIfor(ResultListFragment.TYPE_STOPS);
- listfragment.setListAdapter(new StopAdapter(act.getApplicationContext(), resultList));
+ 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);
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();
}
- /*
- Set of methods to "wrap" database operations
- */
- //Find a way to open databases
- public void openStopsDB(){
- stopsDB.openIfNeeded();
- }
- public String getLocationFromDB(Stop p){
- return stopsDB.getLocationFromID(p.ID);
- }
- public List getStopRoutesFromDB(String stopID){
- return stopsDB.getRoutesByStop(stopID);
- }
- public void closeDBIfNeeded(){
- stopsDB.closeIfNeeded();
- }
- public String getStopNamefromDB(String stopID){
- return stopsDB.getNameFromID(stopID);
- }
synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){
- if(newDB!=null)
- return newDB.insertBatchContent(valuesArr,tableName);
+ if(newDBHelper !=null)
+ return newDBHelper.insertBatchContent(valuesArr,tableName);
else return -1;
}
synchronized public ContentResolver getContentResolver(){
return act.getContentResolver();
}
/**
* 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.showMessage(R.string.network_error);
break;
case SERVER_ERROR:
if (act.isConnected()) {
act.showMessage(R.string.parsing_error);
} else {
act.showMessage(R.string.network_error);
}
case PARSER_ERROR:
default:
act.showMessage(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
act.showMessage(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
act.showMessage(R.string.no_bus_stop_have_this_name);
break;
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentListener.java b/src/it/reyboz/bustorino/fragments/FragmentListener.java
index 3b8b955..8830d71 100644
--- a/src/it/reyboz/bustorino/fragments/FragmentListener.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentListener.java
@@ -1,50 +1,50 @@
/*
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.Palina;
public interface FragmentListener {
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(String fragmentType);
+ void readyGUIfor(FragmentKind fragmentType);
/**
* Houston, we need another fragment!
*
* @param ID the Stop ID
*/
void createFragmentForStop(String ID);
void addLastStopToFavorites();
/**
* 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/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
index 0024af7..0932dbf 100644
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -1,269 +1,269 @@
/*
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.database.Cursor;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.GridView;
import android.widget.ProgressBar;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.StopSorterByDistance;
import it.reyboz.bustorino.middleware.AppDataProvider;
import it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
import it.reyboz.bustorino.middleware.SquareStopAdapter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class NearbyStopsFragment extends Fragment implements LoaderManager.LoaderCallbacks {
private FragmentListener mListener;
private LocationManager locManager;
private FragmentLocationListener locationListener;
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";
//data Bundle
private final String BUNDLE_LOCATION = "location";
private final int LOADER_ID = 0;
private GridView gV;
private SquareStopAdapter madapter;
boolean canStartUpdate = true;
private Location lastReceivedLocation = null;
private ProgressBar loadingProgressBar;
private int distance;
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.
*/
- // TODO: Rename and change types and number of parameters
public static NearbyStopsFragment newInstance() {
NearbyStopsFragment fragment = new NearbyStopsFragment();
Bundle args = new Bundle();
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);
*/
}
locManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE);
locationListener = new FragmentLocationListener(this);
}
@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);
gV = (GridView) root.findViewById(R.id.stopGridNearby);
loadingProgressBar = (ProgressBar) root.findViewById(R.id.loadingBar);
return root;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof FragmentListener) {
mListener = (FragmentListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
@Override
public void onPause() {
super.onPause();
canStartUpdate = false;
locManager.removeUpdates(locationListener);
gV.setAdapter(null);
Log.d(DEBUG_TAG,"On paused called");
}
@Override
public void onResume() {
super.onResume();
canStartUpdate = true;
try{
locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,2000,10,locationListener);
} catch (SecurityException ex){
//ignored
//try another location provider
}
if(madapter!=null){
gV.setAdapter(madapter);
loadingProgressBar.setVisibility(View.GONE);
}
Log.d(DEBUG_TAG,"OnResume called");
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
gV.setVisibility(View.INVISIBLE);
gV.setOnScrollListener(new CommonScrollListener(mListener,false));
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public Loader 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(1000);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
ArrayList stopList = new ArrayList<>();
data.moveToFirst();
if(data.getCount()<4){
distance = distance*2;
Bundle d = new Bundle();
d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation);
getLoaderManager().restartLoader(LOADER_ID,d,this);
return;
}
Log.d("LoadFromCursor","Number of nearby stops: "+data.getCount());
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);
for(int i=0; i loader) {
}
/**
* Local locationListener, to use for the GPS
*/
class FragmentLocationListener implements LocationListener{
LoaderManager.LoaderCallbacks callbacks;
public FragmentLocationListener(LoaderManager.LoaderCallbacks callbacks) {
this.callbacks = callbacks;
}
@Override
public void onLocationChanged(Location location) {
//set adapter
float accuracy = location.getAccuracy();
if(accuracy<60 && canStartUpdate) {
distance = 100;
final Bundle setting = new Bundle();
setting.putParcelable(BUNDLE_LOCATION,location);
getLoaderManager().restartLoader(LOADER_ID,setting,callbacks);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/ResultListFragment.java b/src/it/reyboz/bustorino/fragments/ResultListFragment.java
index 8d02fdb..b86cf87 100644
--- a/src/it/reyboz/bustorino/fragments/ResultListFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ResultListFragment.java
@@ -1,370 +1,291 @@
/*
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.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import android.support.design.widget.FloatingActionButton;
import it.reyboz.bustorino.ActivityMain;
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.middleware.AsyncAddToFavorites;
-import it.reyboz.bustorino.middleware.PalinaAdapter;
/**
* This is a generalized fragment that can be used both for
*
*
*/
-public class ResultListFragment extends Fragment {
+public class ResultListFragment extends Fragment{
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
- private static final String LIST_TYPE = "list-type";
+ static final String LIST_TYPE = "list-type";
private static final String STOP_TITLE = "messageExtra";
- private static final String LIST_STATE = "list_state";
+ protected static final String LIST_STATE = "list_state";
- public static final String TYPE_LINES ="lines";
- public static final String TYPE_STOPS = "fermate";
- private static final String MESSAGE_TEXT_VIEW ="message_text_view";
- private String adapterType;
+ private static final String MESSAGE_TEXT_VIEW = "message_text_view";
+ private FragmentKind adapterKind;
private boolean adapterSet = false;
private FragmentListener mListener;
private TextView messageTextView;
- FloatingActionButton fabutton;
+ private FloatingActionButton fabutton;
private ListView resultsListView;
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(String listType,String eventualStopTitle) {
+ public static ResultListFragment newInstance(FragmentKind listType, String eventualStopTitle) {
ResultListFragment fragment = new ResultListFragment();
Bundle args = new Bundle();
- args.putString(LIST_TYPE, listType);
- if(eventualStopTitle!=null)
- args.putString(STOP_TITLE,eventualStopTitle);
+ args.putSerializable(LIST_TYPE, listType);
+ if (eventualStopTitle != null)
+ args.putString(STOP_TITLE, eventualStopTitle);
fragment.setArguments(args);
return fragment;
}
- public static ResultListFragment newInstance(String listType){
- return newInstance(listType,null);
+
+ public static ResultListFragment newInstance(FragmentKind listType) {
+ return newInstance(listType, null);
}
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
- adapterType = getArguments().getString(LIST_TYPE);
+ adapterKind = (FragmentKind) getArguments().getSerializable(LIST_TYPE);
}
}
-
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- hideMyFAB mInterface = new hideMyFAB() {
- @Override
- public void showFloatingActionButton(boolean show) {
- if (fabutton!=null){
- if(show) fabutton.show();
- else fabutton.hide();
- }
- }
- };
View root = inflater.inflate(R.layout.fragment_list_view, container, false);
messageTextView = (TextView) root.findViewById(R.id.messageTextView);
- if(adapterType!=null) {
+ if (adapterKind != null) {
resultsListView = (ListView) root.findViewById(R.id.resultsListView);
- if(adapterType.equals(TYPE_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);
- }
- });
- resultsListView.setOnScrollListener(new ListFragmentScrollListener(false,mInterface));
-
- //set the textviewMessage
- setTextViewMessage(getString(R.string.results));
- }
- else if(adapterType.equals(TYPE_LINES)) {
- 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.name);
- if (routeName == null) {
- routeName = r.name;
+ 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);
}
- 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();
+ });
+
+ //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.name);
+ if (routeName == null) {
+ routeName = r.name;
+ }
+ 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));
- resultsListView.setOnScrollListener(new ListFragmentScrollListener(true,mInterface));
+ });
+ String displayName = getArguments().getString(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) {
+
+ 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 adapterType.equals(TYPE_LINES) && getTag().equals(getFragmentTag(p));
+
+ public boolean isFragmentForTheSameStop(Palina p) {
+ return adapterKind.equals(FragmentKind.ARRIVALS) && getTag().equals(getFragmentTag(p));
}
- public static String getFragmentTag(Palina 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){
+ if (mListAdapter != null) {
ListAdapter adapter = mListAdapter;
mListAdapter = null;
setListAdapter(adapter);
}
- if(mListInstanceState!=null){
- Log.d("resultsListView","trying to restore instance state");
+ if (mListInstanceState != null) {
+ Log.d("resultsListView", "trying to restore instance state");
resultsListView.onRestoreInstanceState(mListInstanceState);
}
- mListener.readyGUIfor(adapterType);
+ switch (adapterKind) {
+ case ARRIVALS:
+ resultsListView.setOnScrollListener(new CommonScrollListener(mListener, true));
+ break;
+ case STOPS:
+ resultsListView.setOnScrollListener(new CommonScrollListener(mListener, false));
+ break;
+ default:
+ //NONE
+ }
+ mListener.readyGUIfor(adapterKind);
}
@Override
public void onPause() {
- if(adapterType.equals(TYPE_LINES)) {
+ if (adapterKind.equals(FragmentKind.ARRIVALS)) {
SwipeRefreshLayout reflay = (SwipeRefreshLayout) 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;
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)
+ 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 onSaveInstanceState(Bundle outState) {
- outState.putParcelable(LIST_STATE,resultsListView.onSaveInstanceState());
- Log.d("ResultListFragment","onSaveInstanceState");
- super.onSaveInstanceState(outState);
- }
@Override
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
- Log.d("ResultListFragment","onViewStateRestored");
- if(savedInstanceState!=null){
+ Log.d("ResultListFragment", "onViewStateRestored");
+ if (savedInstanceState != null) {
mListInstanceState = savedInstanceState.getParcelable(LIST_STATE);
- Log.d("ResultListFragment","listInstanceStatePresent :"+mListInstanceState);
+ Log.d("ResultListFragment", "listInstanceStatePresent :" + mListInstanceState);
}
}
public void setListAdapter(ListAdapter adapter) {
boolean hadAdapter = mListAdapter != null;
mListAdapter = adapter;
if (resultsListView != null) {
resultsListView.setAdapter(adapter);
resultsListView.setVisibility(View.VISIBLE);
}
}
/**
- * This method attaches the FloatingActionButton to the current ListView
+ * Set the message textView
+ * @param message the whole message to write in the textView
*/
- /* TODO REWORKING
- public void attachFABToListView(){
- if(resultsListView!=null)
- switch(adapterType){
- case TYPE_LINES:
- fabutton.attachToListView(resultsListView, null, new ListFragmentScrollListener());
- break;
- default:
- fabutton.attachToListView(resultsListView);
- break;
- }
- }
- */
- public void setTextViewMessage(String message){
+ public void setTextViewMessage(String message) {
messageTextView.setText(message);
- switch (adapterType){
- case TYPE_LINES:
+ switch (adapterKind) {
+ case ARRIVALS:
final ActivityMain activ = (ActivityMain) getActivity();
messageTextView.setClickable(true);
messageTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mListener.addLastStopToFavorites();
}
});
break;
- case TYPE_STOPS:
+ case STOPS:
messageTextView.setClickable(false);
break;
}
messageTextView.setVisibility(View.VISIBLE);
}
-
- /**
- * This methods sets both the adapter and the textviewMessage
- * Do not call when the fragment shows a list of stops
- * @param p the palina to get the info from
- * TODO: overload it so that it can be used also by the STOPS
- */
-
-
-
-
- /**
- * Classe per gestire gli scroll
- *
- */
- class ListFragmentScrollListener implements AbsListView.OnScrollListener{
- boolean enableRefreshLayout,lastScrollUp=false;
- int mLastFirstVisibleItem;
- hideMyFAB fabInterface;
- public ListFragmentScrollListener(boolean enableRefreshLayout, hideMyFAB iface){
- this.enableRefreshLayout = enableRefreshLayout;
- this.fabInterface = iface;
- }
- @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) {
- if (firstVisibleItem!=0) {
- if (mLastFirstVisibleItem < firstVisibleItem) {
- Log.i("Busto", "Scrolling DOWN");
- fabInterface.showFloatingActionButton(false);
- //lastScrollUp = true;
- } else if (mLastFirstVisibleItem > firstVisibleItem) {
- Log.i("Busto", "Scrolling UP");
- fabInterface.showFloatingActionButton(true);
- //lastScrollUp = false;
- }
- mLastFirstVisibleItem = firstVisibleItem;
- }
- if(enableRefreshLayout){
- SwipeRefreshLayout refreshLayout = (SwipeRefreshLayout) getActivity().findViewById(R.id.listRefreshLayout);
- 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;
- }
- refreshLayout.setEnabled(enable);
- //Log.d(getString(R.string.list_fragment_debug),"onScroll active, first item visible: "+firstVisibleItem+", refreshlayout enabled: "+enable);
- }}
- }
-
-}
-interface hideMyFAB {
- void showFloatingActionButton(boolean show);
}
diff --git a/src/it/reyboz/bustorino/fragments/StopListFragment.java b/src/it/reyboz/bustorino/fragments/StopListFragment.java
new file mode 100644
index 0000000..a6f7e59
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/StopListFragment.java
@@ -0,0 +1,145 @@
+/*
+ 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 android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import it.reyboz.bustorino.backend.Route;
+import it.reyboz.bustorino.backend.Stop;
+import it.reyboz.bustorino.middleware.AppDataProvider;
+import it.reyboz.bustorino.middleware.NextGenDB.Contract.StopsTable;
+import it.reyboz.bustorino.middleware.StopAdapter;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class StopListFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
+
+ private List stopList;
+ private StopAdapter mListAdapter;
+ private static final String[] dataProjection={StopsTable.COL_LINES_STOPPING,StopsTable.COL_PLACE,StopsTable.COL_TYPE,StopsTable.COL_LOCATION};
+ private static final String KEY_STOP_ID = "stopID";
+ private static final String WORDS_SEARCHED= "query";
+ private static final int EXTRA_ID=160;
+
+ private String searchedWords;
+ public StopListFragment(){
+ //required empty constructor
+ }
+
+ public static StopListFragment newInstance(String searchQuery) {
+
+ Bundle args = new Bundle();
+ //TODO: search stops inside the DB
+ args.putString(WORDS_SEARCHED,searchQuery);
+ StopListFragment fragment = new StopListFragment();
+ args.putSerializable(LIST_TYPE,FragmentKind.STOPS);
+ fragment.setArguments(args);
+ return fragment;
+ }
+
+ public void setStopList(List stopList){
+ this.stopList = stopList;
+
+ }
+
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ LoaderManager loaderManager = getLoaderManager();
+ if(stopList!=null) {
+ mListAdapter = new StopAdapter(getContext(),stopList);
+ setListAdapter(mListAdapter);
+ for (int i = 0; i < stopList.size(); i++) {
+ final Bundle b = new Bundle();
+ b.putString(KEY_STOP_ID, stopList.get(i).ID);
+ loaderManager.restartLoader(i, b, this);
+ }
+
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ searchedWords = getArguments().getString(WORDS_SEARCHED);
+
+ }
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ //The id will be the position of the element in the list
+
+ Uri.Builder builder = new Uri.Builder();
+ String stopID = args.getString(KEY_STOP_ID);
+ //Log.d("StopListLoader","Creating loader for stop "+stopID+" in position: "+id);
+ if(stopID!=null) {
+ builder.scheme("content").authority(AppDataProvider.AUTHORITY)
+ .appendPath("stop").appendPath(stopID);
+ CursorLoader cursorLoader = new CursorLoader(getContext(),builder.build(),dataProjection,null,null,null);
+ return cursorLoader;
+ } else return null;
+
+ }
+
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor data) {
+ //check that we have valid data
+ if(data==null) return;
+ final int numRows = data.getCount();
+ final int elementIdx = loader.getId();
+
+ if (numRows==0) {
+ Log.w(this.getClass().getName(),"No info for stop in position "+elementIdx);
+ return;
+ } else if(numRows>1){
+ Log.d("StopLoading","we have "+numRows+" rows, should only have 1. Taking the first...");
+ }
+ final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
+ data.moveToFirst();
+ Stop stopToModify = stopList.get(elementIdx);
+ final String linesStopping = data.getString(linesIndex);
+ stopToModify.setRoutesThatStopHere(Arrays.asList(linesStopping.split(",")));
+ try {
+ final String possibleLocation = data.getString(data.getColumnIndexOrThrow(StopsTable.COL_LOCATION));
+
+ if (stopToModify.location == null && possibleLocation != null && !possibleLocation.isEmpty() && !possibleLocation.equals("_")) {
+ stopToModify.location = possibleLocation;
+ }
+ if (stopToModify.type == null) {
+ stopToModify.type = Route.Type.fromCode(data.getInt(data.getColumnIndex(StopsTable.COL_TYPE)));
+ }
+ }catch (IllegalArgumentException arg){
+ if(arg.getMessage().contains("'location' does not exist")) Log.w("StopLoading","stop with no location found");
+ }
+ //Log.d("StopListFragmentLoader","Finished parsing data for stop in position "+elementIdx);
+ mListAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ loader.abandon();
+ }
+}
diff --git a/src/it/reyboz/bustorino/middleware/AppDataProvider.java b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
index 878c5c3..6b85724 100644
--- a/src/it/reyboz/bustorino/middleware/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/middleware/AppDataProvider.java
@@ -1,239 +1,253 @@
/*
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.middleware;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
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.middleware.NextGenDB.Contract.*;
import java.util.List;
public class AppDataProvider extends ContentProvider {
public static final String AUTHORITY = "it.reyboz.bustorino.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 String DEBUG_TAG="AppDataProvider";
private NextGenDB appDBHelper;
+ private UserDB udbhelper;
private SQLiteDatabase db;
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/#",FAVORITES_OP);
}
@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{
db = appDBHelper.getWritableDatabase();
Uri finalUri = null;
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() {
- // TODO: Implement this to initialize your content provider on startup.
appDBHelper = new NextGenDB(getContext());
+ udbhelper = new UserDB(getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) throws UnsupportedOperationException,IllegalArgumentException{
// TODO: Implement this to handle query requests from clients.
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");
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);
//return getStopsNearby(latitude,longitude,distance);
}
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:
-
- break;
+ final String stopFavSelection = UserDB.getFavoritesColumnNamesAsArray[0]+" = ?";
+ db = udbhelper.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);
default:
Log.d("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 getAlmostFinishedBuilder(){
+ final Uri.Builder b = new Uri.Builder();
+ b.scheme("content").authority(AUTHORITY);
+ return b;
+ }
}
diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
index 4c725f6..d0fc3b0 100644
--- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java
@@ -1,369 +1,344 @@
/*
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.middleware;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.fragments.FragmentHelper;
import it.reyboz.bustorino.middleware.NextGenDB.Contract.*;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Calendar;
/**
* This should be used to download data, but not to display it
*/
public class AsyncDataDownload extends AsyncTask{
private static final String TAG = "BusTO-DataDownload";
private boolean failedAll = false;
private AtomicReference res;
private RequestType t;
private String query;
WeakReference helperRef;
private ArrayList otherActivities = new ArrayList<>();
public AsyncDataDownload(RequestType type,FragmentHelper fh) {
t = type;
helperRef = new WeakReference<>(fh);
res = new AtomicReference<>();
}
@Override
protected Object doInBackground(String... params) {
RecursionHelper r;
boolean success=false;
Object result;
switch (t){
case ARRIVALS:
r = new RecursionHelper<>(new ArrivalsFetcher[] {new FiveTAPIFetcher(),new GTTJSONFetcher(), new FiveTScraperFetcher()});
break;
case STOPS:
r = new RecursionHelper<>(new StopsFinderByName[] {new GTTStopsFetcher(), new FiveTStopsFetcher()});
break;
default:
//TODO put error message
return null;
}
FragmentHelper fh = helperRef.get();
//If the FragmentHelper is null, that means the activity doesn't exist anymore
if (fh == null){
return null;
}
//Log.d(TAG,"refresh layout reference is: "+fh.isRefreshLayoutReferenceTrue());
while(r.valid()) {
if(this.isCancelled()) {
return null;
}
//get the data from the fetcher
switch (t){
case ARRIVALS:
ArrivalsFetcher f = (ArrivalsFetcher) r.getAndMoveForward();
Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass());
Stop lastSearchedBusStop = fh.getLastSuccessfullySearchedBusStop();
Palina p;
String stopID;
if(params.length>0)
stopID=params[0]; //(it's a Palina)
else if(lastSearchedBusStop!=null)
stopID = lastSearchedBusStop.ID; //(it's a Palina)
else {
publishProgress(Fetcher.result.QUERY_TOO_SHORT);
return null;
}
p= f.ReadArrivalTimesAll(stopID,res);
publishProgress(res.get());
if(f instanceof FiveTAPIFetcher){
AtomicReference gres = new AtomicReference<>();
List branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres);
if(gres.get() == Fetcher.result.OK){
p.addInfoFromRoutes(branches);
Thread t = new Thread(new BranchInserter(branches,fh,stopID));
t.start();
otherActivities.add(t);
}
//put updated values into Database
}
- //TODO: use ContentProvider when ready
if(lastSearchedBusStop != null && res.get()== Fetcher.result.OK) {
// check that we don't have the same stop
- if(!lastSearchedBusStop.ID.equals(p.ID)) {
- // remove it, get new name
- //getNameOrGetRekt();
- //TODO
- } else {
+ if(lastSearchedBusStop.ID.equals(p.ID)) {
// searched and it's the same
String sn = lastSearchedBusStop.getStopDisplayName();
- if(sn == null) {
- // something really bad happened, start from scratch
- //getNameOrGetRekt();
- //TODO
- } else {
+ if(sn != null) {
// "merge" Stop over Palina and we're good to go
p.mergeNameFrom(lastSearchedBusStop);
}
}
- } else if(res.get()== Fetcher.result.OK) {
- // we haven't searched anything yet
- //getNameOrGetRekt();
- }
-
- //Try to find the name of the stop inside StopsDB
-
- if(p.getStopDisplayName() == null){
- fh.openStopsDB();
- p.setStopName(fh.getStopNamefromDB(p.ID));
- fh.closeDBIfNeeded();
}
result = p;
//TODO: find a way to avoid overloading the user with toasts
break;
case STOPS:
StopsFinderByName finder = (StopsFinderByName) r.getAndMoveForward();
List resultList= finder.FindByName(params[0], this.res); //it's a List
- fh.openStopsDB();
- for (Stop stop : resultList){
- if(stop.location == null) stop.location = fh.getLocationFromDB(stop);
- stop.setRoutesThatStopHere(fh.getStopRoutesFromDB(stop.ID));
- }
- fh.closeDBIfNeeded();
Log.d(TAG,"Using the StopFinderByName: "+finder.getClass());
query =params[0];
result = resultList; //dummy result
break;
default:
result = null;
}
//find if it went well
if(res.get()== Fetcher.result.OK) {
//wait for other threads to finish
for(Thread t: otherActivities){
try {
t.join();
} catch (InterruptedException e) {
//do nothing
}
}
return result;
}
}
//at this point, we are sure that the result has been negative
failedAll=true;
return null;
}
@Override
protected void onProgressUpdate(Fetcher.result... values) {
FragmentHelper fh = helperRef.get();
if (fh!=null)
for (Fetcher.result r : values){
//TODO: make Toast
fh.showErrorMessage(r);
}
else {
Log.w(TAG,"We had to show some progress but activity was destroyed");
}
}
@Override
protected void onPostExecute(Object o) {
FragmentHelper fh = helperRef.get();
if(failedAll || o == null || fh == null){
//everything went bad
if(fh!=null) fh.toggleSpinner(false);
cancel(true);
+ //TODO: send message here
return;
}
switch (t){
case ARRIVALS:
Palina palina = (Palina) o;
fh.createOrUpdateStopFragment(palina);
break;
case STOPS:
//this should never be a problem
List stopList = (List) o;
if(query!=null) {
fh.createFragmentFor(stopList,query);
} else Log.e(TAG,"QUERY NULL, COULD NOT CREATE FRAGMENT");
break;
case DBUPDATE:
break;
}
}
@Override
protected void onCancelled() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(false);
}
@Override
protected void onPreExecute() {
FragmentHelper fh = helperRef.get();
if (fh!=null) fh.toggleSpinner(true);
}
public enum RequestType {
ARRIVALS,STOPS,DBUPDATE
}
/**
* Run this in a background thread.
* Sets a stop name for this.palina, guaranteed not to be null!
**/
//TODO:Implement this
/*
private void getNameOrGetRekt(Palina p) {
String nameMaybe;
SQLiteDatabase udb = uDB.getReadableDatabase();
// does it already have a name (for fetchers that support it, or already got from favorites)?
nameMaybe = p.getStopDisplayName();
if(nameMaybe != null && nameMaybe.length() > 0) {
return;
}
// ok, let's search favorites.
String usernameMaybe = UserDB.getStopUserName(udb, this.p.ID);
if(usernameMaybe != null && usernameMaybe.length() > 0) {
p.setStopUserName(usernameMaybe);
return;
}
// let's try StopsDB, then.
db.openIfNeeded();
nameMaybe = db.getNameFromID(this.p.ID);
db.closeIfNeeded();
if(nameMaybe != null && nameMaybe.length() > 0) {
p.setStopName(nameMaybe);
return;
}
// no name to be found anywhere, don't bother searching it next time
p.setStopName("");
}*/
public class BranchInserter implements Runnable{
private List routesToInsert;
private String stopID;
private FragmentHelper fragmentHelper;
public BranchInserter(List routesToInsert,FragmentHelper fh,String stopID) {
this.routesToInsert = routesToInsert;
this.stopID = stopID;
this.fragmentHelper = fh;
}
@Override
public void run() {
ContentValues[] values = new ContentValues[routesToInsert.size()];
ArrayList connectionsVals = new ArrayList<>(routesToInsert.size()*4);
long starttime,endtime;
for (Route r:routesToInsert){
//if it has received an interrupt, stop
if(Thread.interrupted()) return;
//otherwise, build contentValues
final ContentValues cv = new ContentValues();
cv.put(BranchesTable.COL_BRANCHID,r.branchid);
cv.put(LinesTable.COLUMN_NAME,r.name);
cv.put(BranchesTable.COL_DIRECTION,r.destinazione);
cv.put(BranchesTable.COL_DESCRIPTION,r.description);
for (int day :r.serviceDays) {
switch (day){
case Calendar.MONDAY:
cv.put(BranchesTable.COL_LUN,1);
break;
case Calendar.TUESDAY:
cv.put(BranchesTable.COL_MAR,1);
break;
case Calendar.WEDNESDAY:
cv.put(BranchesTable.COL_MER,1);
break;
case Calendar.THURSDAY:
cv.put(BranchesTable.COL_GIO,1);
break;
case Calendar.FRIDAY:
cv.put(BranchesTable.COL_VEN,1);
break;
case Calendar.SATURDAY:
cv.put(BranchesTable.COL_SAB,1);
break;
case Calendar.SUNDAY:
cv.put(BranchesTable.COL_DOM,1);
break;
}
}
if(r.type!=null) cv.put(BranchesTable.COL_TYPE, r.type.getCode());
cv.put(BranchesTable.COL_FESTIVO, r.festivo.getCode());
values[routesToInsert.indexOf(r)] = cv;
for(int i=0; i.
*/
package it.reyboz.bustorino.middleware;
import android.content.Context;
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Stop;
/**
* @see PalinaAdapter
*/
public class StopAdapter extends ArrayAdapter {
private LayoutInflater li;
private static int row_layout = R.layout.entry_bus_stop;
private static final int busIcon = R.drawable.bus;
private static final int trainIcon = R.drawable.subway;
private static final int tramIcon = R.drawable.tram;
private static final int cityIcon = R.drawable.city;
+
private static class ViewHolder {
TextView busStopIDTextView;
TextView busStopNameTextView;
//TextView busLineVehicleIcon;
TextView busStopLinesTextView;
TextView busStopLocaLityTextView;
}
public StopAdapter(Context context, List stops) {
super(context, row_layout, stops);
li = LayoutInflater.from(context);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder vh;
if(convertView == null) {
convertView = li.inflate(row_layout, null);
vh = new ViewHolder();
vh.busStopIDTextView = (TextView) convertView.findViewById(R.id.busStopID);
vh.busStopNameTextView = (TextView) convertView.findViewById(R.id.busStopName);
vh.busStopLinesTextView = (TextView) convertView.findViewById(R.id.routesThatStopHere);
vh.busStopLocaLityTextView = (TextView) convertView.findViewById(R.id.busStopLocality);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
Stop stop = getItem(position);
vh.busStopIDTextView.setText(stop.ID);
// NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it?
vh.busStopNameTextView.setText(stop.getStopDisplayName());
-
String whatStopsHere = stop.routesThatStopHereToString();
if(whatStopsHere == null) {
vh.busStopLinesTextView.setVisibility(View.GONE);
} else {
vh.busStopLinesTextView.setText(whatStopsHere);
vh.busStopLinesTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
}
if(stop.type == null) {
vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
} else {
switch(stop.type) {
case BUS:
default:
vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
break;
case METRO:
case RAILWAY:
vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0);
break;
case TRAM:
vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0);
break;
case LONG_DISTANCE_BUS:
// è l'opposto della città ma va beh, dettagli.
vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(cityIcon, 0, 0, 0);
}
}
if (stop.location == null) {
vh.busStopLocaLityTextView.setVisibility(View.GONE);
} else {
vh.busStopLocaLityTextView.setText(stop.location);
vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
}
return convertView;
}
}
diff --git a/src/it/reyboz/bustorino/middleware/UserDB.java b/src/it/reyboz/bustorino/middleware/UserDB.java
index eb9b87f..8874f91 100644
--- a/src/it/reyboz/bustorino/middleware/UserDB.java
+++ b/src/it/reyboz/bustorino/middleware/UserDB.java
@@ -1,248 +1,248 @@
/*
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.middleware;
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";
- private static final String TABLE_NAME = "favorites";
+ static final String TABLE_NAME = "favorites";
private final Context c; // needed during upgrade
- private static String[] usernameColumnNameAsArray = {"username"};
- private static String[] getFavoritesColumnNamesAsArray = {"ID", "username"};
+ 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
}
/**
* 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;
}
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 {
result = db.insert(TABLE_NAME, null, cv);
} 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;
}
}
}