diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
index 9283f44..b11a2da 100644
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -1,342 +1,341 @@
/*
BusTO (backend components)
Copyright (C) 2019 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.backend;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
import it.reyboz.bustorino.fragments.SettingsFragment;
public abstract class utils {
private static final double EarthRadius = 6371e3;
public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
final double phi1 = Math.toRadians(lat1);
final double phi2 = Math.toRadians(lat2);
final double deltaPhi = Math.toRadians(lat2-lat1);
final double deltaTheta = Math.toRadians(long2-long1);
final double a = Math.sin(deltaPhi/2)*Math.sin(deltaPhi/2)+
Math.cos(phi1)*Math.cos(phi2)*Math.sin(deltaTheta/2)*Math.sin(deltaTheta/2);
final double c = 2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a));
return Math.abs(EarthRadius*c);
}
public static Double angleRawDifferenceFromMeters(double distanceInMeters){
return Math.toDegrees(distanceInMeters/EarthRadius);
}
/*
public static int convertDipToPixels(Context con,float dips)
{
return (int) (dips * con.getResources().getDisplayMetrics().density + 0.5f);
}
*/
public static float convertDipToPixels(Context con, float dp){
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp,con.getResources().getDisplayMetrics());
}
/*
public static int calculateNumColumnsFromSize(View containerView, int pixelsize){
int width = containerView.getWidth();
float ncols = ((float)width)/pixelsize;
return (int) Math.floor(ncols);
}
*/
/**
* Check if there is an internet connection
* @param con context object to get the system service
* @return true if we are
*/
public static boolean isConnected(Context con) {
ConnectivityManager connMgr = (ConnectivityManager) con.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
///////////////////// INTENT HELPER ////////////////////////////////////////////////////////////
/**
* Try to extract the bus stop ID from a URi
*
* @param uri The URL
* @return bus stop ID or null
*/
public static String getBusStopIDFromUri(Uri uri) {
String busStopID;
// everithing catches fire when passing null to a switch.
String host = uri.getHost();
if (host == null) {
Log.e("ActivityMain", "Not an URL: " + uri);
return null;
}
switch (host) {
case "m.gtt.to.it":
// http://m.gtt.to.it/m/it/arrivi.jsp?n=1254
busStopID = uri.getQueryParameter("n");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?n from: " + uri);
}
break;
case "www.gtt.to.it":
case "gtt.to.it":
// http://www.gtt.to.it/cms/percorari/arrivi?palina=1254
busStopID = uri.getQueryParameter("palina");
if (busStopID == null) {
Log.e("ActivityMain", "Expected ?palina from: " + uri);
}
break;
default:
Log.e("ActivityMain", "Unexpected intent URL: " + uri);
busStopID = null;
}
return busStopID;
}
final static Pattern ROMAN_PATTERN = Pattern.compile(
"^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$");
private static boolean isRomanNumber(String str){
if(str.isEmpty()) return false;
final Matcher matcher = ROMAN_PATTERN.matcher(str);
return matcher.find();
}
public static String toTitleCase(String givenString, boolean lowercaseRest) {
String[] arr = givenString.trim().split(" ");
StringBuilder sb = new StringBuilder();
//Log.d("BusTO chars", "String parsing: "+givenString+" in array: "+ Arrays.toString(arr));
for (String s : arr) {
if (s.length() > 0) {
String[] allsubs = s.split("\\.");
boolean addPoint = s.contains(".");
/*if (s.contains(".lli")|| s.contains(".LLI")) //Fratelli
{
DOESN'T ALWAYS WORK
addPoint = false;
allsubs = new String[]{s};
}*/
boolean first = true;
for (String subs : allsubs) {
if(first) first=false;
else {
if (addPoint) sb.append(".");
sb.append(" ");
}
if(isRomanNumber(subs)){
//add and skip the rest
sb.append(subs);
continue;
}
//SPLIT ON ', check if contains "D'"
if(subs.toLowerCase(Locale.ROOT).startsWith("d'")){
sb.append("D'");
subs = subs.substring(2);
}
int index = 0;
char c = subs.charAt(index);
if(subs.length() > 1 && c=='('){
sb.append(c);
index += 1;
c = subs.charAt(index);
}
sb.append(Character.toUpperCase(c));
if (lowercaseRest)
sb.append(subs.substring(index+1).toLowerCase(Locale.ROOT));
else
sb.append(subs.substring(index+1));
}
if(addPoint && allsubs.length == 1) sb.append('.');
sb.append(" ");
/*sb.append(Character.toUpperCase(arr[i].charAt(0)));
if (lowercaseRest)
sb.append(arr[i].substring(1).toLowerCase(Locale.ROOT));
else
sb.append(arr[i].substring(1));
sb.append(" ");
*/
} else sb.append(s);
}
return sb.toString().trim();
}
/**
* Open an URL in the default browser.
*
* @param url URL
*/
public static void openIceweasel(String url, Context context) {
Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (browserIntent1.resolveActivity(context.getPackageManager()) != null) {
//check we have an activity ready to receive intents (otherwise, there will be a crash)
context.startActivity(browserIntent1);
} else{
Log.e("BusTO","openIceweasel can't find a browser");
}
}
/**
* Get the default list of fetchers for arrival times
* @return array of ArrivalsFetchers to use
*/
public static ArrivalsFetcher[] getDefaultArrivalsFetchers(){
return new ArrivalsFetcher[]{ new MatoAPIFetcher(),
new GTTJSONFetcher(), new FiveTScraperFetcher()};
}
/**
* Get the default list of fetchers for arrival times
* @return array of ArrivalsFetchers to use
*/
public static List getDefaultArrivalsFetchers(Context context){
SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
- final Set setSelected = new HashSet<>();
- setSelected.addAll(defSharPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE,
+ final Set setSelected = new HashSet<>(defSharPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE,
new HashSet<>()));
if (setSelected.isEmpty()) {
return Arrays.asList(new MatoAPIFetcher(),
new GTTJSONFetcher(), new FiveTScraperFetcher());
}else{
ArrayList outFetchers = new ArrayList<>(4);
/*for(String s: setSelected){
switch (s){
case "matofetcher":
outFetchers.add(new MatoAPIFetcher());
break;
case "fivetapifetcher":
outFetchers.add(new FiveTAPIFetcher());
break;
case "gttjsonfetcher":
outFetchers.add(new GTTJSONFetcher());
break;
case "fivetscraper":
outFetchers.add(new FiveTScraperFetcher());
break;
default:
throw new IllegalArgumentException();
}
}*/
if (setSelected.contains("matofetcher")) {
outFetchers.add(new MatoAPIFetcher());
setSelected.remove("matofetcher");
}
if (setSelected.contains("fivetapifetcher")) {
outFetchers.add(new FiveTAPIFetcher());
setSelected.remove("fivetapifetcher");
}
if (setSelected.contains("gttjsonfetcher")){
outFetchers.add(new GTTJSONFetcher());
setSelected.remove("gttjsonfetcher");
}
if (setSelected.contains("fivetscraper")) {
outFetchers.add(new FiveTScraperFetcher());
setSelected.remove("fivetscraper");
}
if(!setSelected.isEmpty()){
- Log.e("BusTO-Utils","Getting some fetchers values which are not contemplated");
+ Log.e("BusTO-Utils","Getting some fetchers values which are not contemplated: "+setSelected);
}
return outFetchers;
}
}
/**
* Print the first i lines of the the trace of an exception
* https://stackoverflow.com/questions/21706722/fetch-only-first-n-lines-of-a-stack-trace
*/
/*
public static String traceCaller(Exception ex, int i) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
StringBuilder sb = new StringBuilder();
ex.printStackTrace(pw);
String ss = sw.toString();
String[] splitted = ss.split("\n");
sb.append("\n");
if(splitted.length > 2 + i) {
for(int x = 2; x < i+2; x++) {
sb.append(splitted[x].trim());
sb.append("\n");
}
return sb.toString();
}
return "Trace too Short.";
}
*/
public static String joinList(@Nullable List dat, String separator){
StringBuilder sb = new StringBuilder();
if(dat==null || dat.size()==0)
return "";
else if(dat.size()==1)
return dat.get(0);
sb.append(dat.get(0));
for (int i=1; i Set convertArrayToSet(T[] array)
{
// Create an empty Set
Set set = new HashSet<>();
// Add each element into the set
set.addAll(Arrays.asList(array));
// Return the converted Set
return set;
}
public static String giveClassesForArray(T[] array){
StringBuilder sb = new StringBuilder();
for (T f: array){
sb.append("");
sb.append(f.getClass().getSimpleName());
sb.append("; ");
}
return sb.toString();
}
}
diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
index 86b83ed..30126cc 100644
--- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -1,532 +1,550 @@
/*
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.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.FiveTScraperFetcher;
import it.reyboz.bustorino.backend.GTTJSONFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
private final static String KEY_STOP_ID = "stopid";
private final static String KEY_STOP_NAME = "stopname";
private final static String DEBUG_TAG_ALL = "BUSTOArrivalsFragment";
private String DEBUG_TAG = DEBUG_TAG_ALL;
private final static int loaderFavId = 2;
private final static int loaderStopId = 1;
static final String STOP_TITLE = "messageExtra";
private @Nullable String stopID,stopName;
private DBStatusManager prefs;
private DBStatusManager.OnDBUpdateStatusChangeListener listener;
private boolean justCreated = false;
private Palina lastUpdatedPalina = null;
private boolean needUpdateOnAttach = false;
private boolean fetchersChangeRequestPending = false;
private boolean stopIsInFavorites = false;
//Views
protected ImageButton addToFavorites;
protected TextView timesSourceTextView;
private List fetchers = null; //new ArrayList<>(Arrays.asList(utils.getDefaultArrivalsFetchers()));
private boolean reloadOnResume = true;
public static ArrivalsFragment newInstance(String stopID){
return newInstance(stopID, null);
}
public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){
ArrivalsFragment fragment = new ArrivalsFragment();
Bundle args = new Bundle();
args.putString(KEY_STOP_ID,stopID);
//parameter for ResultListFragmentrequestArrivalsForStopID
args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
if (stopName != null){
args.putString(KEY_STOP_NAME,stopName);
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stopID = getArguments().getString(KEY_STOP_ID);
DEBUG_TAG = DEBUG_TAG_ALL+" "+stopID;
//this might really be null
stopName = getArguments().getString(KEY_STOP_NAME);
final ArrivalsFragment arrivalsFragment = this;
listener = new DBStatusManager.OnDBUpdateStatusChangeListener() {
@Override
public void onDBStatusChanged(boolean updating) {
if(!updating){
getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment);
} else {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(loaderFavId);
lm.destroyLoader(loaderStopId);
}
}
@Override
public boolean defaultStatusValue() {
return true;
}
};
prefs = new DBStatusManager(getContext().getApplicationContext(),listener);
justCreated = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_arrivals, container, false);
messageTextView = root.findViewById(R.id.messageTextView);
addToFavorites = root.findViewById(R.id.addToFavorites);
resultsListView = root.findViewById(R.id.resultsListView);
timesSourceTextView = root.findViewById(R.id.timesSourceTextView);
timesSourceTextView.setOnLongClickListener(view -> {
if(!fetchersChangeRequestPending){
rotateFetchers();
//Show we are changing provider
timesSourceTextView.setText(R.string.arrival_source_changing);
mListener.requestArrivalsForStopID(stopID);
fetchersChangeRequestPending = true;
return true;
}
return false;
});
timesSourceTextView.setOnClickListener(view -> {
Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT)
.show();
});
//Button
addToFavorites.setClickable(true);
addToFavorites.setOnClickListener(v -> {
// add/remove the stop in the favorites
toggleLastStopToFavorites();
});
resultsListView.setOnItemClickListener((parent, view, position, id) -> {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
});
String displayName = getArguments().getString(STOP_TITLE);
if(displayName!=null)
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
return root;
}
@Override
public void onResume() {
super.onResume();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "OnResume, justCreated "+justCreated);
/*if(needUpdateOnAttach){
updateFragmentData(null);
needUpdateOnAttach=false;
}*/
if(stopID!=null){
//refresh the arrivals
if(!justCreated){
+ fetchers = utils.getDefaultArrivalsFetchers(getContext());
+ adjustFetchersToSource();
+
if (reloadOnResume)
mListener.requestArrivalsForStopID(stopID);
}
else justCreated = false;
//start the loader
if(prefs.isDBUpdating(true)){
prefs.registerListener();
} else {
Log.d(DEBUG_TAG, "Restarting loader for stop");
loaderManager.restartLoader(loaderFavId, getArguments(), this);
}
updateMessage();
}
}
@Override
public void onStart() {
super.onStart();
if (needUpdateOnAttach){
updateFragmentData(null);
needUpdateOnAttach = false;
}
}
@Override
public void onPause() {
if(listener!=null)
prefs.unregisterListener();
super.onPause();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "onPause, have running loaders: "+loaderManager.hasRunningLoaders());
loaderManager.destroyLoader(loaderFavId);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
//get fetchers
fetchers = utils.getDefaultArrivalsFetchers(context);
}
@Nullable
public String getStopID() {
return stopID;
}
public boolean reloadsOnResume() {
return reloadOnResume;
}
public void setReloadOnResume(boolean reloadOnResume) {
this.reloadOnResume = reloadOnResume;
}
/**
* Give the fetchers
* @return the list of the fetchers
*/
public ArrayList getCurrentFetchers(){
return new ArrayList<>(this.fetchers);
}
public ArrivalsFetcher[] getCurrentFetchersAsArray(){
ArrivalsFetcher[] arr = new ArrivalsFetcher[fetchers.size()];
fetchers.toArray(arr);
return arr;
}
private void rotateFetchers(){
Log.d(DEBUG_TAG, "Rotating fetchers, before: "+fetchers);
Collections.rotate(fetchers, -1);
Log.d(DEBUG_TAG, "Rotating fetchers, afterwards: "+fetchers);
}
/**
* Update the UI with the new data
* @param p the full Palina
*/
public void updateFragmentData(@Nullable Palina p){
if (p!=null)
lastUpdatedPalina = p;
if (!isAdded()){
//defer update at next show
if (p==null)
Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null");
else needUpdateOnAttach = true;
} else {
final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina);
showArrivalsSources(lastUpdatedPalina);
super.resetListAdapter(adapter);
}
}
/**
* Set the message of the arrival times source
* @param p Palina with the arrival times
*/
protected void showArrivalsSources(Palina p){
final Passaggio.Source source = p.getPassaggiSourceIfAny();
if (source == null){
Log.e(DEBUG_TAG, "NULL SOURCE");
return;
}
String source_txt;
switch (source){
case GTTJSON:
source_txt = getString(R.string.gttjsonfetcher);
break;
case FiveTAPI:
source_txt = getString(R.string.fivetapifetcher);
break;
case FiveTScraper:
source_txt = getString(R.string.fivetscraper);
break;
case MatoAPI:
source_txt = getString(R.string.source_mato);
break;
case UNDETERMINED:
//Don't show the view
source_txt = getString(R.string.undetermined_source);
break;
default:
throw new IllegalStateException("Unexpected value: " + source);
}
- int count = 0;
- if (source!= Passaggio.Source.UNDETERMINED)
- while (source != fetchers.get(0).getSourceForFetcher() && count < 10){
- //we need to update the fetcher that is requested
- rotateFetchers();
- count++;
- }
- if (count>10)
+ //
+ final boolean updatedFetchers = adjustFetchersToSource(source);
+ if(!updatedFetchers)
Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work");
final String base_message = getString(R.string.times_source_fmt, source_txt);
timesSourceTextView.setText(base_message);
if (p.getTotalNumberOfPassages() > 0) {
timesSourceTextView.setVisibility(View.VISIBLE);
} else {
timesSourceTextView.setVisibility(View.INVISIBLE);
}
fetchersChangeRequestPending = false;
}
+ protected boolean adjustFetchersToSource(Passaggio.Source source){
+ if (source == null) return false;
+ int count = 0;
+ if (source!= Passaggio.Source.UNDETERMINED)
+ while (source != fetchers.get(0).getSourceForFetcher() && count < 200){
+ //we need to update the fetcher that is requested
+ rotateFetchers();
+ count++;
+ }
+ if (count>=200){
+ return false;
+ }
+ else return true;
+ }
+ protected boolean adjustFetchersToSource(){
+ if (lastUpdatedPalina == null) return false;
+ final Passaggio.Source source = lastUpdatedPalina.getPassaggiSourceIfAny();
+ return adjustFetchersToSource(source);
+ }
+
@Override
public void setNewListAdapter(ListAdapter adapter) {
throw new UnsupportedOperationException();
}
/**
* Update the message in the fragment
*
* It may eventually change the "Add to Favorite" icon
*/
private void updateMessage(){
String message = null;
if (stopName != null && stopID != null && stopName.length() > 0) {
message = (stopID.concat(" - ").concat(stopName));
} else if(stopID!=null) {
message = stopID;
} else {
Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong");
}
if(message!=null) {
setTextViewMessage(getString(R.string.passages,message));
}
// whatever is the case, update the star icon
//updateStarIconFromLastBusStop();
}
@NonNull
@Override
public Loader onCreateLoader(int id, Bundle args) {
if(args.getString(KEY_STOP_ID)==null) return null;
final String stopID = args.getString(KEY_STOP_ID);
final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete();
CursorLoader cl;
switch (id){
case loaderFavId:
builder.appendPath("favorites").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null);
break;
case loaderStopId:
builder.appendPath("stop").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME},
null,null,null);
break;
default:
return null;
}
cl.setUpdateThrottle(500);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
switch (loader.getId()){
case loaderFavId:
final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]);
if(data.getCount()>0){
// IT'S IN FAVORITES
data.moveToFirst();
final String probableName = data.getString(colUserName);
stopIsInFavorites = true;
stopName = probableName;
//update the message in the textview
updateMessage();
} else {
stopIsInFavorites =false;
}
updateStarIcon();
if(stopName == null){
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB");
getLoaderManager().restartLoader(loaderStopId,getArguments(),this);
}
break;
case loaderStopId:
if(data.getCount()>0){
data.moveToFirst();
int index = data.getColumnIndex(
NextGenDB.Contract.StopsTable.COL_NAME
);
if (index == -1){
Log.e(DEBUG_TAG, "Index is -1, column not present. App may explode now...");
}
stopName = data.getString(index);
updateMessage();
} else {
Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
}
}
}
@Override
public void onLoaderReset(Loader loader) {
//NOTHING TO DO
}
public void toggleLastStopToFavorites() {
Stop stop = lastUpdatedPalina;
if (stop != null) {
// toggle the status in background
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE,
v->updateStarIconFromLastBusStop(v)).execute(stop);
} else {
// this case have no sense, but just immediately update the favorite icon
updateStarIconFromLastBusStop(true);
}
}
/**
* Update the star "Add to favorite" icon
*/
public void updateStarIconFromLastBusStop(Boolean toggleDone) {
if (stopIsInFavorites)
stopIsInFavorites = !toggleDone;
else stopIsInFavorites = toggleDone;
updateStarIcon();
// check if there is a last Stop
/*
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (isStopInFavorites(stopID)) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
*/
}
/**
* Update the star icon according to `stopIsInFavorites`
*/
public void updateStarIcon() {
// no favorites no party!
// check if there is a last Stop
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (stopIsInFavorites) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
}
}
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index 8897cc3..2b711f5 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,808 +1,808 @@
package it.reyboz.bustorino.fragments;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import java.util.Map;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher;
import it.reyboz.bustorino.middleware.AsyncStopsSearcher;
import it.reyboz.bustorino.middleware.BarcodeScanContract;
import it.reyboz.bustorino.middleware.BarcodeScanOptions;
import it.reyboz.bustorino.middleware.BarcodeScanUtils;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MainScreenFragment extends ScreenBaseFragment implements FragmentListenerMain{
private static final String OPTION_SHOW_LEGEND = "show_legend";
private static final String SAVED_FRAGMENT="saved_fragment";
private static final String DEBUG_TAG = "BusTO - MainFragment";
public static final String PENDING_STOP_SEARCH="PendingStopSearch";
public final static String FRAGMENT_TAG = "MainScreenFragment";
/// UI ELEMENTS //
private ImageButton addToFavorites;
private FragmentHelper fragmentHelper;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem;
private FloatingActionButton floatingActionButton;
private boolean setupOnResume = true;
private boolean suppressArrivalsReload = false;
private boolean instanceStateSaved = false;
//private Snackbar snackbar;
/*
* Search mode
*/
private static final int SEARCH_BY_NAME = 0;
private static final int SEARCH_BY_ID = 1;
private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
private int searchMode;
//private ImageButton addToFavorites;
- private final ArrivalsFetcher[] arrivalsFetchers = utils.getDefaultArrivalsFetchers();
//// HIDDEN BUT IMPORTANT ELEMENTS ////
FragmentManager fragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
if(getContext() == null) return;
List fetcherList = utils.getDefaultArrivalsFetchers(getContext());
ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[fetcherList.size()];
arrivalsFetchers = fetcherList.toArray(arrivalsFetchers);
if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
if (fragment == null){
//we create a new fragment, which is WRONG
Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment");
// AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
} else{
String stopName = fragment.getStopID();
new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
}
} else //we create a new fragment, which is WRONG
new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute();
}
};
//
private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new BarcodeScanContract(),
result -> {
if(result!=null && result.getContents()!=null) {
//Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
Uri uri;
try {
uri = Uri.parse(result.getContents()); // this apparently prevents NullPointerException. Somehow.
} catch (NullPointerException e) {
if (getContext()!=null)
Toast.makeText(getContext().getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
return;
}
String busStopID = getBusStopIDFromUri(uri);
busStopSearchByIDEditText.setText(busStopID);
requestArrivalsForStopID(busStopID);
} else {
//Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
if (getContext()!=null)
Toast.makeText(getContext().getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
}
});
/// LOCATION STUFF ///
boolean pendingNearbyStopsRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
AppLocationManager locationManager;
private final LocationCriteria cr = new LocationCriteria(2000, 10000);
//Location
private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
@Override
public void onLocationChanged(Location loc) {
}
@Override
public void onLocationStatusChanged(int status) {
if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){
//request Stops
pendingNearbyStopsRequest = false;
if (getContext()!= null)
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public long getLastUpdateTimeMillis() {
return 50;
}
@Override
public LocationCriteria getLocationCriteria() {
return cr;
}
@Override
public void onLocationProviderAvailable() {
//Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest);
if(!isNearbyFragmentShown() && getContext()!=null){
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public void onLocationDisabled() {
}
};
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback