diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml --- a/res/layout/activity_about.xml +++ b/res/layout/activity_about.xml @@ -35,12 +35,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/versionTextView" android:textAppearance="@style/TextAppearance.AppCompat.Medium" - android:gravity="center_vertical" + android:gravity="center_horizontal|center_vertical" android:layout_margin="10dp" android:layout_marginLeft="20dp" android:layout_marginStart="20dp" - /> + android:textColor="@color/black"/> </LinearLayout> \ No newline at end of file diff --git a/res/layout/fragment_arrivals.xml b/res/layout/fragment_arrivals.xml --- a/res/layout/fragment_arrivals.xml +++ b/res/layout/fragment_arrivals.xml @@ -86,7 +86,7 @@ android:layout_height="wrap_content" android:layout_marginStart="15dp" android:layout_marginLeft="15dp" - android:text="Source of arrivals" + android:text="" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="19sp" diff --git a/res/layout/stop_card.xml b/res/layout/stop_card.xml --- a/res/layout/stop_card.xml +++ b/res/layout/stop_card.xml @@ -38,9 +38,10 @@ android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:layout_below="@id/stop_nameText" - android:textIsSelectable="true" android:layout_marginTop="6dp" - android:layout_margin="8dp" - android:textColor="@color/orange_500" android:layout_marginLeft="10dp"/> + android:textIsSelectable="true" android:layout_marginTop="8dp" + android:textColor="@color/orange_500" android:layout_marginLeft="12dp" + android:layout_marginStart="12dp" android:layout_marginRight="8dp" android:layout_marginEnd="8dp" + android:layout_marginBottom="8dp"/> <TextView android:text="TextView" android:layout_width="wrap_content" diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -62,6 +62,7 @@ - <b>Fabio Mazza</b> attuale rockstar developer anziano.<br> - <b>Andrea Ugo</b> attuale rockstar developer in formazione.<br> - <b>Silviu Chiriac</b> designer del logo 2021.<br> + - <b>Marco M</b> formidabile tester e cacciatore di bug.<br> - <b>Ludovico Pavesi</b> ex rockstar developer anziano asd.<br> - <b>Valerio Bozzolan</b> attuale manutentore.<br> - <b>Marco Gagino</b> apprezzato ex collaboratore, ideatore icona e grafica.<br> @@ -94,18 +95,22 @@ <string name="no_stops_nearby">Nessuna fermata nei dintorni</string> <string name="main_menu_pref">Preferenze</string> <string name="database_update_message">Aggiornamento del database…</string> - <string name="pref_num_elements">Numero di fermate</string> + <string name="pref_num_elements">Numero minimo di fermate</string> + <string name="num_stops_nearby_not_number">Il numero di fermate da ricercare non è valido</string> + <string name="invalid_number">Valore errato, inserisci un numero</string> <string name="title_activity_settings">Impostazioni</string> + <string name="settings_search_radius">Distanza massima di ricerca (m)</string> + <string name="settings_experimental">Funzionalità sperimentali</string> <string name="action_settings">Impostazioni</string> + <string name="general_settings">Generali</string> <string name="pref_recents_group_title">Fermate recenti</string> <string name="settings_group_general">Impostazioni generali</string> <string name="settings_group_database">Gestione del database</string> <string name="settings_reset_database">Comincia aggiornamento manuale del database</string> + <string name="enable_position_message_map">Consenti l\'accesso alla posizione per mostrarla sulla mappa</string> <string name="enableGpsText">Abilitare il GPS</string> - <string name="settings_search_radius">Raggio di ricerca</string> - <string name="settings_experimental">Funzionalità sperimentali</string> <string name="bus_arriving_at">arriva alle</string> <string name="arrivals_card_at_the_stop">alla fermata</string> <string name="show_arrivals">Mostra arrivi</string> diff --git a/res/values/colors.xml b/res/values/colors.xml --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -15,5 +15,6 @@ <color name="metro_red">#DE0908</color> <color name="blue_extraurbano">#2060DD</color> <color name="white">#FFFFFF</color> + <color name="black">#000000</color> </resources> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -72,6 +72,7 @@ - <b>Fabio Mazza</b> current senior rockstar developer.<br> - <b>Andrea Ugo</b> current junior rockstar developer.<br> - <b>Silviu Chiriac</b> designer of the 2021 logo.<br> + - <b>Marco M</b> rockstar tester and bug hunter.<br> - <b>Ludovico Pavesi</b> previous senior rockstar developer asd.<br> - <b>Valerio Bozzolan</b> maintainer and infrastructure sponsor.<br> - <b>Marco Gagino</b> contributor and icon creator.<br> @@ -91,6 +92,7 @@ <p>Get involved! :)</p> ]]> </string> + <string name="cant_add_to_favorites">Cannot add to favorites (storage full or corrupted database?)!</string> <string name="action_view_on_map">View on a map</string> <string name="cannot_show_on_map_no_activity">Cannot find any application to show it in</string> @@ -99,19 +101,22 @@ <string name="list_fragment_debug" translatable="false">ListFragment - BusTO</string> <string name="mainSharedPreferences" translatable="false">it.reyboz.bustorino.preferences</string> <string name="databaseUpdatingPref" translatable="false">db_is_updating</string> - + <!-- Settings --> <string name="nearby_stops_message">Nearby stops</string> <string name="nearby_arrivals_message">Nearby connections</string> <string name="app_version">App version</string> + <string name="num_stops_nearby_not_number">The number of stops to show in the recents is invalid</string> + <string name="invalid_number">Invalid value, put a valid number</string> <string name="position_searching_message">Finding the position…</string> <string name="no_stops_nearby">No stops nearby</string> - <string name="pref_num_elements">Number of stops</string> + <string name="pref_num_elements">Minimum number of stops</string> <string name="main_menu_pref">Preferences</string> <string name="title_activity_settings">Settings</string> <string name="action_settings">Settings</string> + <string name="general_settings">General</string> <string name="settings_experimental">Experimental features</string> - <string name="settings_search_radius">Search radius</string> + <string name="settings_search_radius">Maximum distance (meters)</string> <string name="pref_recents_group_title">Recent stops</string> <string name="settings_group_general">General settings</string> <string name="settings_group_database">Database management</string> diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -2,30 +2,44 @@ <androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.preference.PreferenceCategory - android:title="General"> + android:title="@string/general_settings"> <androidx.preference.SwitchPreferenceCompat android:defaultValue="false" android:title="@string/settings_experimental" android:key="@string/pref_key_experimental"/> </androidx.preference.PreferenceCategory> <androidx.preference.PreferenceCategory android:title="@string/pref_recents_group_title"> + <!-- <androidx.preference.EditTextPreference android:defaultValue="10" android:selectAllOnFocus="true" android:singleLine="true" - android:title="@string/pref_num_elements" android:key="@string/pref_key_num_recents" android:inputType="numberDecimal" - - android:digits="0123456789" android:ems="10" + android:inputType="number" + android:title="@string/pref_num_elements" android:key="@string/pref_key_num_recents" + app:useSimpleSummaryProvider="true" + android:digits="0123456789" /> + --> + <androidx.preference.SeekBarPreference + android:title="@string/pref_num_elements" android:key="@string/pref_key_num_recents" + android:defaultValue="4" + android:max="40" + app:min="1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:showSeekBarValue="true" + + /> <androidx.preference.SeekBarPreference android:title="@string/settings_search_radius" android:key="@string/pref_key_radius_recents" android:defaultValue="500" android:max="1000" - app:min="20" + app:min="100" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_vertical" + app:showSeekBarValue="true" + /> </androidx.preference.PreferenceCategory> diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java --- a/src/it/reyboz/bustorino/ActivityPrincipal.java +++ b/src/it/reyboz/bustorino/ActivityPrincipal.java @@ -265,7 +265,7 @@ return true; } //selectDrawerItem(menuItem); - Log.d(DEBUG_TAG, "pressed item "+menuItem.toString()); + Log.d(DEBUG_TAG, "pressed item "+menuItem); return true; @@ -354,6 +354,10 @@ mDrawer.closeDrawer(GravityCompat.START); else if(shownFrag != null && shownFrag.isVisible() && shownFrag.getChildFragmentManager().getBackStackEntryCount() > 0){ //if we have been asked to show a stop from another fragment, we should go back even in the main + if(shownFrag instanceof MainScreenFragment){ + //we have to stop the arrivals reload + ((MainScreenFragment) shownFrag).cancelReloadArrivalsIfNeeded(); + } shownFrag.getChildFragmentManager().popBackStackImmediate(); if(showingMainFragmentFromOther && getSupportFragmentManager().getBackStackEntryCount() > 0){ getSupportFragmentManager().popBackStack(); @@ -406,7 +410,7 @@ private MainScreenFragment showMainFragment(){ FragmentManager fraMan = getSupportFragmentManager(); Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); - MainScreenFragment mainScreenFragment = null; + final MainScreenFragment mainScreenFragment; if (fragment==null | !(fragment instanceof MainScreenFragment)){ mainScreenFragment = createAndShowMainFragment(); } diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java --- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java @@ -130,7 +130,8 @@ public List<Route> parseDirectionsFromResponse(String response) throws IllegalArgumentException,JSONException{ - if(response == null || response.length()==0) throw new IllegalArgumentException("Response string is null or void"); + if(response == null || response.equals("null") || response.length()==0) + throw new IllegalArgumentException("Response string is null or void"); ArrayList<Route> routes = new ArrayList<>(10); JSONArray lines =new JSONArray(response); for(int i=0; i<lines.length();i++){ diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java --- a/src/it/reyboz/bustorino/backend/Palina.java +++ b/src/it/reyboz/bustorino/backend/Palina.java @@ -351,4 +351,27 @@ // return this.passaggi; // } // } + //remove duplicates + + public void mergeDuplicateRoutes(int startidx){ + //ArrayList<Route> routesCopy = new ArrayList<>(routes); + //for + if(routes.size()<=1|| startidx >= routes.size()) //we have finished + return; + Route routeCheck = routes.get(startidx); + boolean found = false; + for(int i=startidx+1; i<routes.size(); i++){ + final Route r = routes.get(i); + if(routeCheck.equals(r)){ + //we have found a match, merge + routes.remove(routeCheck); + r.mergeRouteWithAnother(routeCheck); + found=true; + break; + } + } + if (found) mergeDuplicateRoutes(startidx); + else mergeDuplicateRoutes(startidx+1); + } + //private void mergeRoute } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/backend/Route.java b/src/it/reyboz/bustorino/backend/Route.java --- a/src/it/reyboz/bustorino/backend/Route.java +++ b/src/it/reyboz/bustorino/backend/Route.java @@ -281,10 +281,7 @@ if(this.stopsList!=null && other.stopsList!=null){ int d = this.stopsList.size()-other.stopsList.size(); if(d!=0) return d; - else { - //the two have the same number of stops - - } + //if we are here, the two routes have the same number of stops } // probably useless, but... last attempt. @@ -306,11 +303,22 @@ Route r = (Route) obj; boolean result = false; if(this.name.equals(r.name) && this.branchid == r.branchid){ + if(description!=null && r.description!=null) + if(!description.trim().equals(r.description.trim())) + return false; + if(destinazione!=null && r.destinazione!=null){ + if(!this.destinazione.trim().equals(r.destinazione.trim())) + // they are not the same + return false; + } + //check stops list if(this.stopsList!=null && r.stopsList!=null){ - int d = this.stopsList.size()-r.stopsList.size(); - if(d!=0) { - result = false; + int sizeDiff = this.stopsList.size()-r.stopsList.size(); + if(sizeDiff!=0) { + return false; + } else { + //check that the stops are the same result = true; for(int j=0; j<this.stopsList.size();j++){ if(!this.stopsList.get(j).equals(r.stopsList.get(j))) { @@ -318,7 +326,11 @@ break; } } + return result; } + } else{ + //no stopsList in one or the other + return true; } } return result; @@ -342,7 +354,7 @@ if (other.getStopsList() != null && this.getStopsList() == null) this.setStopsList(other.getStopsList()); - if(this.passaggi!=null && other.passaggi!=null && this.passaggi.size()==0 && other.passaggi.size()>0){ + if(this.passaggi!=null && other.passaggi!=null && other.passaggi.size()>0){ this.passaggi.addAll(other.passaggi); } diff --git a/src/it/reyboz/bustorino/data/StopsDB.java b/src/it/reyboz/bustorino/data/StopsDB.java deleted file mode 100644 --- a/src/it/reyboz/bustorino/data/StopsDB.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - 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 <http://www.gnu.org/licenses/>. - */ - -package it.reyboz.bustorino.data; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.readystatesoftware.sqliteasset.SQLiteAssetHelper; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -import it.reyboz.bustorino.backend.Route; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.backend.StopsDBInterface; - - -public class StopsDB extends SQLiteAssetHelper implements StopsDBInterface { - private static String QUERY_TABLE_stops = "stops"; - private static String QUERY_WHERE_ID = "ID = ?"; - private static String QUERY_WHERE_LAT_AND_LNG_IN_RANGE = "lat >= ? AND lat <= ? AND lon >= ? AND lon <= ?"; - private static String[] QUERY_COLUMN_name = {"name"}; - private static final String[] QUERY_COLUMN_location = {"location"}; - private static final String[] QUERY_COLUMN_route = {"route"}; - private static final String[] QUERY_COLUMN_everything = {"name", "location", "type", "lat", "lon"}; - private static final String[] QUERY_COLUMN_everything_and_ID = {"ID", "name", "location", "type", "lat", "lon"}; - - private static String DB_NAME = "stops.sqlite"; - private static int DB_VERSION = 1; - private SQLiteDatabase db; - private AtomicInteger openCounter = new AtomicInteger(); - - public StopsDB(Context context) { - super(context, DB_NAME, null, DB_VERSION); - // WARNING: do not remove the following line, do not save anything in this database, it will be overwritten on every update! - setForcedUpgrade(); - - // remove old database (BusTo version 1.8.5 and below) - File filename = new File(context.getFilesDir(), "busto.sqlite"); - if(filename.exists()) { - //noinspection ResultOfMethodCallIgnored - filename.delete(); - } - } - - /** - * Through the magic of an atomic counter, the database gets opened and closed without race - * conditions between threads (HOPEFULLY). - * - * @return database or null if cannot be opened - */ - @Nullable - public synchronized SQLiteDatabase openIfNeeded() { - openCounter.incrementAndGet(); - this.db = getReadableDatabase(); - return this.db; - } - - /** - * Through the magic of an atomic counter, the database gets really closed only when no thread - * is using it anymore (HOPEFULLY). - */ - public synchronized void closeIfNeeded() { - // is anybody still using the database or can we close it? - if(openCounter.decrementAndGet() <= 0) { - super.close(); - this.db = null; - } - } - - public List<String> getRoutesByStop(@NonNull String stopID) { - String[] uselessArray = {stopID}; - int count; - Cursor result; - - if(this.db == null) { - return null; - } - - try { - result = this.db.query("routemap", QUERY_COLUMN_route, "stop = ?", uselessArray, null, null, null); - } catch(SQLiteException e) { - return null; - } - - count = result.getCount(); - if(count == 0) { - return null; - } - - List<String> routes = new ArrayList<>(count); - - while(result.moveToNext()) { - routes.add(result.getString(0)); - } - - result.close(); - - return routes; - } - - public String getLocationFromID(@NonNull String stopID) { - String[] uselessArray = {stopID}; - int count; - String name; - Cursor result; - - if(this.db == null) { - return null; - } - - try { - result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_location, QUERY_WHERE_ID, uselessArray, null, null, null); - } catch(SQLiteException e) { - return null; - } - - count = result.getCount(); - if(count == 0) { - return null; - } - - result.moveToNext(); - name = result.getString(0); - - result.close(); - - return name; - } - - public Stop getAllFromID(@NonNull String stopID) { - Cursor result; - int count; - Stop s; - - if(this.db == null) { - return null; - } - - try { - result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_everything, QUERY_WHERE_ID, new String[] {stopID}, null, null, null); - int colName = result.getColumnIndex("name"); - int colLocation = result.getColumnIndex("location"); - int colType = result.getColumnIndex("type"); - int colLat = result.getColumnIndex("lat"); - int colLon = result.getColumnIndex("lon"); - - count = result.getCount(); - if(count == 0) { - return null; - } - - result.moveToNext(); - - Route.Type type = routeTypeFromSymbol(result.getString(colType)); - - String locationWhichSometimesIsAnEmptyString = result.getString(colLocation); - if(locationWhichSometimesIsAnEmptyString.length() <= 0) { - locationWhichSometimesIsAnEmptyString = null; - } - - s = new Stop(stopID, result.getString(colName), null, locationWhichSometimesIsAnEmptyString, type, getRoutesByStop(stopID), result.getDouble(colLat), result.getDouble(colLon)); - } catch(SQLiteException e) { - return null; - } - - result.close(); - - return s; - } - - /** - * Query some bus stops inside a map view - * - * You can obtain the coordinates from OSMDroid using something like this: - * BoundingBoxE6 bb = mMapView.getBoundingBox(); - * double latFrom = bb.getLatSouthE6() / 1E6; - * double latTo = bb.getLatNorthE6() / 1E6; - * double lngFrom = bb.getLonWestE6() / 1E6; - * double lngTo = bb.getLonEastE6() / 1E6; - */ - public Stop[] queryAllInsideMapView(double minLat, double maxLat, double minLng, double maxLng) { - Stop[] stops = new Stop[0]; - - Cursor result; - int count; - - // coordinates must be strings in the where condition - String minLatRaw = String.valueOf(minLat); - String maxLatRaw = String.valueOf(maxLat); - String minLngRaw = String.valueOf(minLng); - String maxLngRaw = String.valueOf(maxLng); - - String stopID; - Route.Type type; - - if(this.db == null) { - return stops; - } - - try { - result = this.db.query(QUERY_TABLE_stops, QUERY_COLUMN_everything_and_ID, QUERY_WHERE_LAT_AND_LNG_IN_RANGE, new String[] {minLatRaw, maxLatRaw, minLngRaw, maxLngRaw}, null, null, null); - - int colID = result.getColumnIndex("ID"); - int colName = result.getColumnIndex("name"); - int colLocation = result.getColumnIndex("location"); - int colType = result.getColumnIndex("type"); - int colLat = result.getColumnIndex("lat"); - int colLon = result.getColumnIndex("lon"); - - count = result.getCount(); - stops = new Stop[count]; - - int i = 0; - while(result.moveToNext()) { - - stopID = result.getString(colID); - type = routeTypeFromSymbol(result.getString(colType)); - - String locationWhichSometimesIsAnEmptyString = result.getString(colLocation); - if (locationWhichSometimesIsAnEmptyString.length() <= 0) { - locationWhichSometimesIsAnEmptyString = null; - } - - stops[i++] = new Stop(stopID, result.getString(colName), null, - locationWhichSometimesIsAnEmptyString, type, getRoutesByStop(stopID), - result.getDouble(colLat), result.getDouble(colLon)); - } - - } catch(SQLiteException e) { - // TODO: put a warning in the log - return stops; - } - - result.close(); - - return stops; - } - - /** - * Get a Route Type from its char symbol - * - * @param route The route symbol (e.g. "B") - * @return The related Route.Type (e.g. Route.Type.Bus) - */ - public static Route.Type routeTypeFromSymbol(String route) { - switch (route) { - case "M": - return Route.Type.METRO; - case "T": - return Route.Type.RAILWAY; - } - - // default with case "B" - return Route.Type.BUS; - } -} diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -96,6 +96,7 @@ Handler mainHandler; private final Runnable refreshStop = new Runnable() { public void run() { + if(getContext() == null) return; if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) { ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame); if (fragment == null){ @@ -131,8 +132,8 @@ if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){ //request Stops pendingNearbyStopsRequest = false; - - mainHandler.post(new NearbyStopsRequester(getContext(), cr)); + if (getContext()!= null) + mainHandler.post(new NearbyStopsRequester(getContext(), cr)); } } @@ -149,7 +150,7 @@ @Override public void onLocationProviderAvailable() { //Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest); - if(!isNearbyFragmentShown()){ + if(!isNearbyFragmentShown() && getContext()!=null){ pendingNearbyStopsRequest = false; mainHandler.post(new NearbyStopsRequester(getContext(), cr)); } @@ -317,6 +318,18 @@ */ } + /** + * Cancel the reload of the arrival times + * because we are going to pop the fragment + */ + public void cancelReloadArrivalsIfNeeded(){ + if(getContext()==null) return; //we are not attached + + //Fragment fr = getChildFragmentManager().findFragmentById(R.id.resultFrame); + fragmentHelper.stopLastRequestIfNeeded(); + toggleSpinner(false); + } + @Override public void onAttach(@NonNull Context context) { @@ -326,7 +339,7 @@ if (context instanceof CommonFragmentListener) { mListener = (CommonFragmentListener) context; } else { - throw new RuntimeException(context.toString() + throw new RuntimeException(context + " must implement CommonFragmentListener"); } if (setupOnAttached) { @@ -407,6 +420,7 @@ fragmentHelper.stopLastRequestIfNeeded(); } + /* GUI METHODS */ @@ -437,11 +451,11 @@ } else { // searchMode == SEARCH_BY_NAME String query = busStopSearchByNameEditText.getText().toString(); //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); - new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query); + if(getContext()!=null) + new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query); } } - public void onToggleKeyboardLayout(View v) { if (searchMode == SEARCH_BY_NAME) { diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java --- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -44,6 +44,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import com.android.volley.*; +import it.reyboz.bustorino.BuildConfig; import it.reyboz.bustorino.R; import it.reyboz.bustorino.adapters.ArrivalsStopAdapter; import it.reyboz.bustorino.backend.*; @@ -225,7 +226,7 @@ if (context instanceof FragmentListenerMain) { mListener = (FragmentListenerMain) context; } else { - throw new RuntimeException(context.toString() + throw new RuntimeException(context + " must implement OnFragmentInteractionListener"); } Log.d(DEBUG_TAG, "OnAttach called"); @@ -267,12 +268,31 @@ mListener.enableRefreshLayout(false); Log.d(DEBUG_TAG,"OnResume called"); - + if(getContext()==null){ + Log.e(DEBUG_TAG, "NULL CONTEXT, everything is going to crash now"); + MIN_NUM_STOPS = 5; + MAX_DISTANCE = 600; + return; + } //Re-read preferences SharedPreferences shpr = PreferenceManager.getDefaultSharedPreferences(getContext().getApplicationContext()); //For some reason, they are all saved as strings MAX_DISTANCE = shpr.getInt(getString(R.string.pref_key_radius_recents),600); - MIN_NUM_STOPS = Integer.parseInt(shpr.getString(getString(R.string.pref_key_num_recents),"10")); + boolean isMinStopInt = true; + try{ + MIN_NUM_STOPS = shpr.getInt(getString(R.string.pref_key_num_recents), 5); + } catch (ClassCastException ex){ + isMinStopInt = false; + } + if(!isMinStopInt) + try { + MIN_NUM_STOPS = Integer.parseInt(shpr.getString(getString(R.string.pref_key_num_recents), "5")); + } catch (NumberFormatException ex){ + MIN_NUM_STOPS = 5; + } + if(BuildConfig.DEBUG) + Log.d(DEBUG_TAG, "Max distance for stops: "+MAX_DISTANCE+ + ", Min number of stops: "+MIN_NUM_STOPS); } @@ -311,21 +331,30 @@ public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) { if (0 > MAX_DISTANCE) throw new AssertionError(); //Cursor might be null + Log.d(DEBUG_TAG, "Num stops found: "+data.getCount()+", Current distance: "+distance); if(data==null){ Log.e(DEBUG_TAG,"Null cursor, something really wrong happened"); return; } - if(!isDBUpdating() && (data.getCount()<MIN_NUM_STOPS || distance<=MAX_DISTANCE)){ + if(!isDBUpdating() && (data.getCount()<MIN_NUM_STOPS && distance<=MAX_DISTANCE)){ distance = distance*2; Bundle d = new Bundle(); d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation); getLoaderManager().restartLoader(LOADER_ID,d,this); + //Log.d(DEBUG_TAG, "Doubling distance now!"); return; } Log.d("LoadFromCursor","Number of nearby stops: "+data.getCount()); //////// - ArrayList<Stop> stopList = createStopListFromCursor(data); + if(data.getCount()>0) { + ArrayList<Stop> stopList = createStopListFromCursor(data); + double minDistance = Double.POSITIVE_INFINITY; + for(Stop s: stopList){ + minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation)); + } + + //quick trial to hopefully always get the stops in the correct order Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation)); switch (fragment_type){ @@ -350,7 +379,7 @@ } @Override - public void onLoaderReset(Loader<Cursor> loader) { + public void onLoaderReset(@NonNull Loader<Cursor> loader) { } /** @@ -427,6 +456,10 @@ routesPairList.add(new Pair<>(p,r)); } } + if (getContext()==null){ + Log.e(DEBUG_TAG, "Trying to show arrivals in Recycler but we're not attached"); + return; + } if(firstLocForArrivals){ arrivalsStopAdapter = new ArrivalsStopAdapter(routesPairList,mListener,getContext(),lastReceivedLocation); gridRecyclerView.setAdapter(arrivalsStopAdapter); @@ -465,13 +498,13 @@ final static String REQUEST_TAG = "NearbyArrivals"; private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS}; final NetworkVolleyManager volleyManager; - private final int MAX_ARRIVAL_STOPS =35; int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0; ArrivalsManager(List<Stop> stops){ mStops = new HashMap<>(); volleyManager = NetworkVolleyManager.getInstance(getContext()); + int MAX_ARRIVAL_STOPS = 35; for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){ mStops.put(s.ID,new Palina(s)); for(QueryType t: types) { @@ -594,6 +627,7 @@ switch(status){ case AppLocationManager.LOCATION_GPS_AVAILABLE: messageTextView.setVisibility(View.GONE); + break; case AppLocationManager.LOCATION_UNAVAILABLE: messageTextView.setText(R.string.enableGpsText); diff --git a/src/it/reyboz/bustorino/fragments/SettingsFragment.java b/src/it/reyboz/bustorino/fragments/SettingsFragment.java --- a/src/it/reyboz/bustorino/fragments/SettingsFragment.java +++ b/src/it/reyboz/bustorino/fragments/SettingsFragment.java @@ -3,34 +3,125 @@ import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; +import android.os.Handler; import android.util.Log; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.*; import it.reyboz.bustorino.R; +import java.lang.ref.WeakReference; + public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { private static final String TAG = SettingsFragment.class.getName(); - SharedPreferences preferences; + private static final String DIALOG_FRAGMENT_TAG = + "androidx.preference.PreferenceFragment.DIALOG"; + //private static final + Handler mHandler; + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + mHandler = new Handler(); + return super.onCreateView(inflater, container, savedInstanceState); + + } @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + //getPreferenceManager().setSharedPreferencesName(getString(R.string.mainSharedPreferences)); + convertStringPrefToIntIfNeeded(getString(R.string.pref_key_num_recents), getContext()); + + getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); setPreferencesFromResource(R.xml.preferences,rootKey); - Context con = getContext(); - if (con == null){ - Log.w("SETTINGS FRAGMENT", "context is null"); - preferences = null; - } - else - preferences = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); + /*EditTextPreference editPref = findPreference(getString(R.string.pref_key_num_recents)); + editPref.setOnBindEditTextListener(editText -> { + editText.setInputType(InputType.TYPE_CLASS_NUMBER); + editText.setSelection(0,editText.getText().length()); + }); + */ + + } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { Preference pref = findPreference(key); - //non so a cosa serva tutto questo + Log.d(TAG,"Preference key "+key+" changed"); + //sometimes this happens + if(getContext()==null) return; + /* + THIS CODE STAYS COMMENTED FOR FUTURE REFERENCES + if (key.equals(getString(R.string.pref_key_num_recents))){ + //check that is it an int + + String value = sharedPreferences.getString(key,""); + boolean valid = value.length() != 0; + try{ + Integer intValue = Integer.parseInt(value); + } catch (NumberFormatException ex){ + valid = false; + } + if (!valid){ + Toast.makeText(getContext(), R.string.invalid_number, Toast.LENGTH_SHORT).show(); + if(pref instanceof EditTextPreference){ + EditTextPreference prefEdit = (EditTextPreference) pref; + //Intent intent = prefEdit.getIntent(); + Log.d(TAG, "opening preference, dialog showing "+ + (getParentFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG)!=null) ); + //getPreferenceManager().showDialog(pref); + //onDisplayPreferenceDialog(prefEdit); + mHandler.postDelayed(new DelayedDisplay(prefEdit), 500); + } + + } + } + */ Log.d("BusTO Settings", "changed "+key+"\n "+sharedPreferences.getAll()); } + + private void convertStringPrefToIntIfNeeded(String preferenceKey, Context con){ + if (con == null) return; + SharedPreferences defaultSharedPref = PreferenceManager.getDefaultSharedPreferences(getContext()); + try{ + + Integer val = defaultSharedPref.getInt(preferenceKey, 0); + } catch (NumberFormatException | ClassCastException ex){ + //convert the preference + //final String preferenceNumRecents = getString(R.string.pref_key_num_recents); + Log.d("Preference - BusTO", "Converting to integer the string preference "+preferenceKey); + String currentValue = defaultSharedPref.getString(preferenceKey, "10"); + int newValue; + try{ + newValue = Integer.parseInt(currentValue); + } catch (NumberFormatException e){ + newValue = 10; + } + final SharedPreferences.Editor editor = defaultSharedPref.edit(); + editor.remove(preferenceKey); + editor.putInt(preferenceKey, newValue); + editor.apply(); + } + } + + class DelayedDisplay implements Runnable{ + private final WeakReference<DialogPreference> preferenceWeakReference; + + public DelayedDisplay(DialogPreference preference) { + this.preferenceWeakReference = new WeakReference<>(preference); + } + + @Override + public void run() { + if(preferenceWeakReference.get()==null) + return; + + getPreferenceManager().showDialog(preferenceWeakReference.get()); + } + } } diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java @@ -116,8 +116,12 @@ return null; } //Skip the FiveTAPIFetcher for the Metro Stops because it shows incomprehensible arrival times - if(f instanceof FiveTAPIFetcher && Integer.parseInt(stopID)>= 8200) - continue; + try { + if (f instanceof FiveTAPIFetcher && Integer.parseInt(stopID) >= 8200) + continue; + } catch (NumberFormatException ex){ + Log.e(DEBUG_TAG, "The stop number is not a valid integer, expect failures"); + } p= f.ReadArrivalTimesAll(stopID,res); publishProgress(res.get()); //if (res.get()!= Fetcher.Result.OK) @@ -149,7 +153,10 @@ } } } - + p.mergeDuplicateRoutes(0); + if(p.queryAllRoutes().size() == 0) + //skip the rest and go to the next fetcher + continue; result = p; //TODO: find a way to avoid overloading the user with toasts break;