diff --git a/.bzrignore b/.bzrignore index 9516879..1ad263f 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,33 +1,34 @@ # Built application files *.apk *.ap_ # Files for the dex VM *.dex # Java class files *.class # Generated files bin/ gen/ # Local configuration file (SDK path, etc) local.properties # Eclipse project files .classpath .project # Android Studio *.iml .idea/ .gradle/ build/ +captures/ #NDK obj/ # I don't want to see this shit. Thumbs.db .DS_Store diff --git a/res/layout/fragment_nearby_stops.xml b/res/layout/fragment_nearby_stops.xml index 3119b0c..f7b673e 100644 --- a/res/layout/fragment_nearby_stops.xml +++ b/res/layout/fragment_nearby_stops.xml @@ -1,37 +1,35 @@ - - - + android:layout_marginTop="10dp" + android:gravity="center_horizontal" android:layout_margin="10dp"/> + android:indeterminateTint="@color/blue_700" + android:layout_below="@+id/titleTextView" android:layout_centerHorizontal="true"/> diff --git a/res/layout/square_stop_element.xml b/res/layout/square_stop_element.xml index fd2be01..537175f 100644 --- a/res/layout/square_stop_element.xml +++ b/res/layout/square_stop_element.xml @@ -1,39 +1,39 @@ + android:textAlignment="viewEnd" android:layout_alignParentTop="true" + android:layout_toRightOf="@+id/busStopIDView" android:layout_toEndOf="@+id/busStopIDView"/> \ No newline at end of file diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index fca94b0..9d3bb90 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -1,83 +1,85 @@ Stai utilizzando l\'ultimo ritrovato in materia di rispetto della tua privacy. Cerca QR Code Numero fermata Nome fermata Inserisci il numero della fermata Inserisci il nome della fermata Verifica l\'accesso ad Internet! Sembra che nessuna fermata abbia questo nome Errore di lettura del sito 5T/GTT (dannato sito!) Fermata: %1$s Linee: %1$s Scegli la fermata… Nessun passaggio Nessun QR code Preferiti Aiuto Informazioni Più informazioni News Invia bug Codice sorgente Licenza Incontra l\'autore Fermata aggiunta ai preferiti Impossibile aggiungere ai preferiti (memoria piena o database corrotto?)! Preferiti Tocca il nome di una fermata per aggiungerla in questa lista Rimuovi Rinomina Rinomina fermata Reset Informazioni Tocca il nome della fermata per aggiungerla ai preferiti\n\nCome leggere gli orari:\n   12:56* Orario in tempo reale\n   12:56   Orario programmato\n\nTrascina giù per aggiornare l\'orario. OK ! Benvenuto!

Grazie per aver scelto BusTO, un\'app \"politicamente\" indipendente da GTT/5T, per spostarsi a Torino attraverso software libero:

Perché usare BusTO?

- Non sei monitorato
- Non ci sono pubblicità
- La tua privacy è al sicuro
- Inoltre l\'app è molto leggera!

Come Funziona?

Quest\'app ottiene i passaggi dei bus in tempo reale filtrando i dati forniti pubblicamente sul sito www.gtt.to.it o www.5t.torino.it "per uso personale".

Ingredienti:
- Valerio Bozzolan sviluppatore principale.
- Marco Gagino intenso collaboratore, creatore dell\'icona, responsabile della grafica.
- Paolo Alloatti per l\'idea dell\'icona.
- JSoup libreria per "web scaping".
- makovkastar bottone fluttuante.
- Google icone Material Design.
- Tutti i contributori!

Licenze

L\'app e il relativo codice sorgente sono distribuiti sotto la licenza GNU General Public License v3+. Ciò significa che puoi usare, studiare, migliorare e ricondividere quest\'app con qualunque mezzo e per qualsiasi scopo: a patto di mantenere sempre questi diritti a tua volta e di dare credito a Valerio Bozzolan.

Note

Quest\'applicazione è rilasciata nella speranza che sia utile a tutti ma senza NESSUNA garanzia.

Buon utilizzo! :)

]]>
Nome troppo corto, digita più caratteri e riprova %1$s verso %2$s %s (destinazione sconosciuta) Errore interno inaspettato, impossibile estrarre dati dal sito GTT/5T Visualizza sulla mappa + Fermate vicine + Ricerca della posizione in corso...
diff --git a/res/values/strings.xml b/res/values/strings.xml index 2386fcd..7024eb7 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,93 +1,94 @@ BusTO You\'re using the latest in technology when it comes to respecting your privacy. Search Scan QR Code Bus stop number Bus stop name Insert bus stop number Insert bus stop name %1$s towards %2$s %s (unknown destination) Verify your Internet connection! Seems that no bus stop have this name Error parsing the 5T/GTT website (damn site!) Name too short, type more characters and retry Arrivals at: %1$s Choose the bus stop… Lines: %1$s No timetable found No QR code Unexpected internal error, cannot extract data from GTT/5T website Help About More about News releases Bug submission Source code Licence Meet the author Bus stop is now in your favorites Favorites Favorites Press on a bus stop name to populate this list Delete Rename Rename the bus stop Reset About Tap the bus stop name to add it to the favourites\n\nHow to read timelines:\n   12:56* Real-time arrivals\n   12:56   Scheduled arrivals\n\nPull down to refresh the timetable GOT IT! Welcome!

Thanks for using BusTO, a "politically" independent app useful to move around Torino using a Free/Libre software.

Why use this app?

- You\'ll never be tracked
- You\'ll never see boring ads
- We\'ll always respect your privacy
- Moreover, it\'s lightweight!

How does it work?

This app will show you bus timetables gathering data from www.gtt.to.it or www.5t.torino.it "for personal use".

Who worked on BusTO:
- Valerio Bozzolan main developer.
- Marco Gagino main contributor and icon creator.
- Paolo Alloatti original concept of the icon.
- JSoup web scraper library.
- makovkastar floating buttons.
- Google Material Design icons.
- All the contributors!

Licenses

The app and the related source code are released by Valerio Bozzolan under the terms of the GNU General Public License v3+). So everyone is allowed to use, to study, to improve and to share this app by any kind of means and for any purpose: under the conditions of maintaining this rights and of attributing the original work to Valerio Bozzolan.

Notes

This app has been developed hoping to be useful to everyone but without ANY warranty.

This translation is kindly provided by Riccardo Caniato and Marco Gagino.

Get involved! :)

]]>
Cannot add to favorites (storage full or corrupted database?)! View on a map ListFragment - BusTO it.reyboz.bustorino.preferences Nearby stops + Finding the position...
diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java index aecdb02..9baee1f 100644 --- a/src/it/reyboz/bustorino/ActivityMain.java +++ b/src/it/reyboz/bustorino/ActivityMain.java @@ -1,607 +1,633 @@ /* 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.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; - private GPSLocationAdapter locationHandler; ///////////////////////////////// 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); 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()); - if(getOption(LOCATION_PERMISSION_GIVEN,false)==false){ + + //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(); } - //locationHandler = new GPSLocationAdapter(getApplicationContext()); - - 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 - NearbyStopsFragment fr = NearbyStopsFragment.newInstance(); - FragmentTransaction ft = framan.beginTransaction(); - ft.add(R.id.resultFrame,fr,"Location"); - ft.commitNow(); - swipeRefreshLayout.setVisibility(View.VISIBLE); - swipeRefreshLayout.setEnabled(false); + 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); + } 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); - locationHandler.startRequestingPosition(); + 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); + } 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) { hideKeyboard(); if(fragmentType==null) Log.e("ActivityMain","Problem with fragmentType"); else switch (fragmentType){ case ResultListFragment.TYPE_LINES: prepareGUIForBusLines(); if (getOption(OPTION_SHOW_LEGEND, true)) { showHints(); } break; case ResultListFragment.TYPE_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 0936b46..2c42045 100644 --- a/src/it/reyboz/bustorino/backend/FiveTNormalizer.java +++ b/src/it/reyboz/bustorino/backend/FiveTNormalizer.java @@ -1,305 +1,317 @@ /* 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; } } - //TODO: DO THE OPPOSITE FOR FIVETAPIFETCHER /** * 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"); - else return name; + } catch (NumberFormatException e){ //It's not "# navetta" - return name; + Log.w("FivetNorm","checking number when it's not"); + } + if(name.toLowerCase().contains("night buster")){ + if(name.toLowerCase().contains("viola")) + return "S05"; } + //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 b16c07e..7da3540 100644 --- a/src/it/reyboz/bustorino/backend/Palina.java +++ b/src/it/reyboz/bustorino/backend/Palina.java @@ -1,290 +1,303 @@ /* 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; 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++; - Calendar c = Calendar.getInstance(); - int to = c.get(Calendar.DAY_OF_WEEK); if (selected.serviceDays != null && selected.serviceDays.length > 0) { - //check it is in service + //check if it is in service for (int d : selected.serviceDays) { - if (d == to) { + if (d == todaysInt) { correct = true; break; } } } else if (r.festivo != null) { switch (r.festivo) { case FERIALE: //Domenica = 1 --> Saturday=7 - if (to <= 7 && to > 1) correct = true; + if (todaysInt <= 7 && todaysInt > 1) correct = true; break; case FESTIVO: - if (to == 1) correct = true; //TODO: implement way to recognize all holidays + 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 == false || selected == null) continue; //we didn't find any match, should we point it out? + 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/fragments/FragmentKind.java b/src/it/reyboz/bustorino/fragments/FragmentKind.java index 4564972..514ddd8 100644 --- a/src/it/reyboz/bustorino/fragments/FragmentKind.java +++ b/src/it/reyboz/bustorino/fragments/FragmentKind.java @@ -1,22 +1,22 @@ /* 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; public enum FragmentKind { - STOPS,ARRIVALS,FAVORITES + STOPS,ARRIVALS,FAVORITES,NEARBY } diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java index 1c28c62..0024af7 100644 --- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -1,261 +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}; - //needed for the @SimpleCursorAdapter - private final String[] bindFrom = {StopsTable.COL_ID,StopsTable.COL_NAME,StopsTable.COL_LINES_STOPPING}; - private final int[] bindTo = {R.id.busStopIDView,R.id.stopNameView,R.id.routesStoppingTextView}; + 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 1ad5c73..8d02fdb 100644 --- a/src/it/reyboz/bustorino/fragments/ResultListFragment.java +++ b/src/it/reyboz/bustorino/fragments/ResultListFragment.java @@ -1,345 +1,370 @@ /* 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 { // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER private static final String LIST_TYPE = "list-type"; private static final String STOP_TITLE = "messageExtra"; - + private 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 boolean adapterSet = false; private FragmentListener mListener; private TextView messageTextView; FloatingActionButton fabutton; private ListView resultsListView; - ListAdapter mListAdapter = null; + 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) { ResultListFragment fragment = new ResultListFragment(); Bundle args = new Bundle(); args.putString(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); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getArguments() != null) { adapterType = getArguments().getString(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) { 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; } 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 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 static String getFragmentTag(Palina p){ return p.ID; } @Override public void onResume() { super.onResume(); //Log.d(getString(R.string.list_fragment_debug),"Fragment restored, saved listAdapter is "+(mListAdapter)); if(mListAdapter!=null){ ListAdapter adapter = mListAdapter; mListAdapter = null; setListAdapter(adapter); } + if(mListInstanceState!=null){ + Log.d("resultsListView","trying to restore instance state"); + resultsListView.onRestoreInstanceState(mListInstanceState); + } mListener.readyGUIfor(adapterType); } @Override public void onPause() { if(adapterType.equals(TYPE_LINES)) { 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) 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){ + mListInstanceState = savedInstanceState.getParcelable(LIST_STATE); + 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 */ /* 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){ messageTextView.setText(message); switch (adapterType){ case TYPE_LINES: 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: 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/middleware/AppDataProvider.java b/src/it/reyboz/bustorino/middleware/AppDataProvider.java index d64aa34..878c5c3 100644 --- a/src/it/reyboz/bustorino/middleware/AppDataProvider.java +++ b/src/it/reyboz/bustorino/middleware/AppDataProvider.java @@ -1,234 +1,239 @@ /* 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 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); } @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()); 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)+" km"); + 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; 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); } diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java index bb43dfd..4c725f6 100644 --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java @@ -1,367 +1,369 @@ /* 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 { // searched and it's the same String sn = lastSearchedBusStop.getStopDisplayName(); if(sn == null) { // something really bad happened, start from scratch //getNameOrGetRekt(); //TODO } else { // "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; - Log.d(TAG,"Using the ArrivalsFetcher: "+f.getClass()); //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); 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