diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Palina.java b/app/src/main/java/it/reyboz/bustorino/backend/Palina.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Palina.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Palina.java
@@ -18,11 +18,14 @@
package it.reyboz.bustorino.backend;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
@@ -37,7 +40,7 @@
* 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 {
+public class Palina extends Stop implements Parcelable {
private ArrayList routes = new ArrayList<>();
private boolean routesModified = false;
private Passaggio.Source allSource = null;
@@ -414,4 +417,59 @@
return mList;
}
//private void mergeRoute
+
+ /// ------- Parcelable stuff ---
+ protected Palina(Parcel in) {
+ super(in);
+ routes = in.createTypedArrayList(Route.CREATOR);
+ routesModified = in.readByte() != 0;
+ allSource = in.readByte() == 0 ? null : Passaggio.Source.valueOf(in.readString());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeTypedList(routes);
+ dest.writeByte((byte) (routesModified ? 1 : 0));
+ if (allSource == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeString(allSource.name());
+ }
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Palina createFromParcel(Parcel in) {
+ return new Palina(in);
+ }
+
+ @Override
+ public Palina[] newArray(int size) {
+ return new Palina[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ // Methods using the parcelable
+ public byte[] asByteArray(){
+ final Parcel p = Parcel.obtain();
+ writeToParcel(p,0);
+ final byte[] b = p.marshall();
+ p.recycle();
+ return b;
+ }
+
+ public static Palina fromByteArray(byte[] data){
+ final Parcel p = Parcel.obtain();
+ p.unmarshall(data, 0, data.length);
+ p.setDataPosition(0);
+ final Palina palina = Palina.CREATOR.createFromParcel(p);
+ p.recycle();
+ return palina;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Passaggio.java b/app/src/main/java/it/reyboz/bustorino/backend/Passaggio.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Passaggio.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Passaggio.java
@@ -18,6 +18,8 @@
package it.reyboz.bustorino.backend;
+import android.os.Parcel;
+import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -25,7 +27,7 @@
import java.util.Locale;
-public final class Passaggio implements Comparable {
+public final class Passaggio implements Comparable, Parcelable {
private static final int UNKNOWN_TIME = -3;
private static final String DEBUG_TAG = "BusTO-Passaggio";
@@ -173,6 +175,51 @@
}
+ protected Passaggio(Parcel in) {
+ passaggioGTT = in.readString();
+ hh = in.readInt();
+ mm = in.readInt();
+ if (in.readByte() == 0) {
+ realtimeDifference = null;
+ } else {
+ realtimeDifference = in.readInt();
+ }
+ isInRealTime = in.readByte() != 0;
+ source = Source.valueOf(in.readString());
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(passaggioGTT);
+ dest.writeInt(hh);
+ dest.writeInt(mm);
+ if (realtimeDifference == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeInt(realtimeDifference);
+ }
+ dest.writeByte((byte) (isInRealTime ? 1 : 0));
+ dest.writeString(source.name());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator() {
+ @Override
+ public Passaggio createFromParcel(Parcel in) {
+ return new Passaggio(in);
+ }
+
+ @Override
+ public Passaggio[] newArray(int size) {
+ return new Passaggio[size];
+ }
+ };
+
//
// @Override
// public String toString() {
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Route.java b/app/src/main/java/it/reyboz/bustorino/backend/Route.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Route.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Route.java
@@ -18,6 +18,8 @@
package it.reyboz.bustorino.backend;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -28,9 +30,9 @@
import java.util.Collections;
import java.util.List;
-public class Route implements Comparable {
+public class Route implements Comparable, Parcelable {
final static int[] reduced_week = {Calendar.MONDAY,Calendar.TUESDAY,Calendar.WEDNESDAY,Calendar.THURSDAY,Calendar.FRIDAY};
- final static int[] feriali = {Calendar.MONDAY,Calendar.TUESDAY,Calendar.WEDNESDAY,Calendar.THURSDAY,Calendar.FRIDAY,Calendar.SATURDAY};
+ //final static int[] feriali = {Calendar.MONDAY,Calendar.TUESDAY,Calendar.WEDNESDAY,Calendar.THURSDAY,Calendar.FRIDAY,Calendar.SATURDAY};
final static int[] weekend = {Calendar.SUNDAY,Calendar.SATURDAY};
private final static int BRANCHID_MISSING = -1;
@@ -63,22 +65,15 @@
}
@Nullable
public static Type fromCode(int i){
- switch (i){
- case 1:
- return BUS;
- case 2:
- return LONG_DISTANCE_BUS;
- case 3:
- return METRO;
- case 4:
- return RAILWAY;
- case 5:
- return TRAM;
- case -2:
- return UNKNOWN;
- default:
- return null;
- }
+ return switch (i) {
+ case 1 -> BUS;
+ case 2 -> LONG_DISTANCE_BUS;
+ case 3 -> METRO;
+ case 4 -> RAILWAY;
+ case 5 -> TRAM;
+ case -2 -> UNKNOWN;
+ default -> null;
+ };
}
}
public enum FestiveInfo{
@@ -93,16 +88,12 @@
return code;
}
public static FestiveInfo fromCode(int i){
- switch (i){
- case -2:
- return UNKNOWN;
- case 0:
- return FERIALE;
- case 1:
- return FESTIVO;
- default:
- return UNKNOWN;
- }
+ return switch (i) {
+ case -2 -> UNKNOWN;
+ case 0 -> FERIALE;
+ case 1 -> FESTIVO;
+ default -> UNKNOWN;
+ };
}
}
/**
@@ -360,8 +351,7 @@
@Override
public boolean equals(Object obj) {
- if(obj instanceof Route){
- Route r = (Route) obj;
+ if(obj instanceof Route r){
boolean result = false;
if(this.name.equals(r.name) && this.branchid == r.branchid){
if(description!=null && r.description!=null)
@@ -443,4 +433,69 @@
return adjusted;
}
+ // ---- Parcelable implem ---
+ protected Route(Parcel in) {
+ name = in.readString();
+ displayCode = in.readByte() == 0 ? null : in.readString();
+ destinazione = in.readString();
+ passaggi = in.createTypedArrayList(Passaggio.CREATOR);
+ type = Type.valueOf(in.readString());
+ description = in.readString();
+ if (in.readByte() == 0) {
+ stopsList = null;
+ } else {
+ stopsList = in.createStringArrayList();
+ }
+ branchid = in.readInt();
+ serviceDays = in.createIntArray();
+ festivo = FestiveInfo.valueOf(in.readString());
+ gtfsId = in.readByte() == 0 ? null : in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(name);
+ if (displayCode == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeString(displayCode);
+ }
+ dest.writeString(destinazione);
+ dest.writeTypedList(passaggi);
+ dest.writeString(type.name());
+ dest.writeString(description);
+ if (stopsList == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeStringList(stopsList);
+ }
+ dest.writeInt(branchid);
+ dest.writeIntArray(serviceDays);
+ dest.writeString(festivo.name());
+ if (gtfsId == null) {
+ dest.writeByte((byte) 0);
+ } else {
+ dest.writeByte((byte) 1);
+ dest.writeString(gtfsId);
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator CREATOR = new Creator<>() {
+ @Override
+ public Route createFromParcel(Parcel in) {
+ return new Route(in);
+ }
+
+ @Override
+ public Route[] newArray(int size) {
+ return new Route[size];
+ }
+ };
}
diff --git a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
--- a/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
+++ b/app/src/main/java/it/reyboz/bustorino/backend/Stop.java
@@ -20,6 +20,8 @@
import android.location.Location;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -32,7 +34,7 @@
import java.util.List;
import java.util.Locale;
-public class Stop implements Comparable {
+public class Stop implements Comparable, Parcelable {
// remove "final" in case you need to set these from outside the parser\scrapers\fetchers
@@ -357,4 +359,71 @@
return new Stop(ID, name, username, location, type, routesThatStopHere, lat, lon, gtfsId);
}
+
+ /// ----- Parcelable implementation ----
+ protected Stop(Parcel in) {
+ ID = in.readString();
+ name = in.readByte() == 0 ? null : in.readString();
+ username = in.readByte() == 0 ? null : in.readString();
+ location = in.readByte() == 0 ? null : in.readString();
+ type = in.readByte() == 0 ? null : Route.Type.valueOf(in.readString());
+ routesThatStopHere = in.readByte() == 0 ? null : in.createStringArrayList();
+ lat = in.readByte() == 0 ? null : in.readDouble();
+ lon = in.readByte() == 0 ? null : in.readDouble();
+ routesThatStopHereString = in.readByte() == 0 ? null : in.readString();
+ absurdGTTPlaceName = in.readByte() == 0 ? null : in.readString();
+ gtfsID = in.readByte() == 0 ? null : in.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(ID);
+ dest.writeByte((byte) (name == null ? 0 : 1));
+ if (name != null) dest.writeString(name);
+
+ dest.writeByte((byte) (username == null ? 0 : 1));
+ if (username != null) dest.writeString(username);
+
+ dest.writeByte((byte) (location == null ? 0 : 1));
+ if (location != null) dest.writeString(location);
+
+ dest.writeByte((byte) (type == null ? 0 : 1));
+ if (type != null) dest.writeString(type.name());
+
+ dest.writeByte((byte) (routesThatStopHere == null ? 0 : 1));
+ if (routesThatStopHere != null) dest.writeStringList(routesThatStopHere);
+
+ dest.writeByte((byte) (lat == null ? 0 : 1));
+ if (lat != null) dest.writeDouble(lat);
+
+ dest.writeByte((byte) (lon == null ? 0 : 1));
+ if (lon != null) dest.writeDouble(lon);
+
+ dest.writeByte((byte) (routesThatStopHereString == null ? 0 : 1));
+ if (routesThatStopHereString != null) dest.writeString(routesThatStopHereString);
+
+ dest.writeByte((byte) (absurdGTTPlaceName == null ? 0 : 1));
+ if (absurdGTTPlaceName != null) dest.writeString(absurdGTTPlaceName);
+
+ dest.writeByte((byte) (gtfsID == null ? 0 : 1));
+ if (gtfsID != null) dest.writeString(gtfsID);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Creator CREATOR = new Creator<>() {
+ @Override
+ public Stop createFromParcel(Parcel in) {
+ return new Stop(in);
+ }
+
+ @Override
+ public Stop[] newArray(int size) {
+ return new Stop[size];
+ }
+ };
+
}
diff --git a/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java b/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
--- a/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/FavoritesLiveData.java
@@ -24,6 +24,7 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
+import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
@@ -171,7 +172,7 @@
List result = NextGenDB.getStopsFromCursorAllFields(cursor);
cursor.close();
- if (result.size() < 1){
+ if (result.isEmpty()){
// stop is not in the DB
finalStop = stopUpdate;
} else {
@@ -205,7 +206,7 @@
extends ContentObserver {
public ForceLoadContentObserver() {
- super(new Handler());
+ super(new Handler(Looper.myLooper()));
}
@Override
diff --git a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
--- a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java
@@ -17,16 +17,20 @@
*/
package it.reyboz.bustorino.data;
+import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
+import android.database.SQLException;
import android.database.sqlite.SQLiteConstraintException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Log;
+import androidx.annotation.NonNull;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
@@ -332,6 +336,87 @@
}
return rowsUpdated;
}
+
+ public static boolean insertBranchesIntoDB(@NonNull Context context, @NonNull List routesToInsert){
+ final NextGenDB nextGenDB = NextGenDB.getInstance(context);
+ //ContentValues[] values = new ContentValues[routesToInsert.size()];
+ ArrayList branchesValues = new ArrayList<>(routesToInsert.size());
+ ArrayList connectionsVals = new ArrayList<>(routesToInsert.size());
+ long starttime,endtime;
+ for (Route r:routesToInsert){
+ //if it has received an interrupt, stop
+ if(Thread.interrupted()) return false;
+ //otherwise, build contentValues
+ final ContentValues cv = new ContentValues();
+ cv.put(BranchesTable.COL_BRANCHID,r.branchid);
+ cv.put(LinesTable.COLUMN_NAME,r.getName());
+ 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;
+ branchesValues.add(cv);
+ if(r.getStopsList() != null)
+ for(int i=0; i createStopListFromCursor(Cursor data){
ArrayList stopList = new ArrayList<>();
diff --git a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
--- a/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
+++ b/app/src/main/java/it/reyboz/bustorino/data/OldDataRepository.kt
@@ -24,7 +24,9 @@
import java.util.ArrayList
import java.util.concurrent.Executor
-class OldDataRepository(private val executor: Executor, private val nextGenDB: NextGenDB) {
+class OldDataRepository(private val executor: Executor,
+ private val nextGenDB: NextGenDB,
+ ) {
constructor(executor: Executor, context: Context): this(executor, NextGenDB.getInstance(context))
fun requestStopsWithGtfsIDs(
diff --git a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
--- a/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
+++ b/app/src/main/java/it/reyboz/bustorino/data/UserDB.java
@@ -30,6 +30,7 @@
import java.io.IOException;
import java.util.*;
+import androidx.annotation.Nullable;
import de.siegmar.fastcsv.reader.CloseableIterator;
import de.siegmar.fastcsv.reader.CsvReader;
import de.siegmar.fastcsv.reader.CsvRow;
@@ -189,7 +190,7 @@
* @param stopID stop ID
* @return name set by user, or null if not set\not found
*/
- public static String getStopUserName(SQLiteDatabase db, String stopID) {
+ public static @Nullable String getStopUserName(SQLiteDatabase db, String stopID) {
String username = null;
try {
@@ -201,7 +202,9 @@
username = c.getString(userNameIndex);
}
c.close();
- } catch(SQLiteException ignored) {}
+ } catch(SQLiteException e) {
+ Log.e("BusTO-UserDB","Cannot get stop User name for stop "+stopID+":\n"+e);
+ }
return username;
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java
deleted file mode 100644
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ /dev/null
@@ -1,710 +0,0 @@
-/*
- 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 android.widget.*;
-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 java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import androidx.recyclerview.widget.DividerItemDecoration;
-import androidx.recyclerview.widget.GridLayoutManager;
-import androidx.recyclerview.widget.LinearLayoutManager;
-import androidx.recyclerview.widget.RecyclerView;
-import it.reyboz.bustorino.R;
-import it.reyboz.bustorino.adapters.PalinaAdapter;
-import it.reyboz.bustorino.adapters.RouteOnlyLineAdapter;
-import it.reyboz.bustorino.backend.ArrivalsFetcher;
-import it.reyboz.bustorino.backend.DBStatusManager;
-import it.reyboz.bustorino.backend.Fetcher;
-import it.reyboz.bustorino.backend.FiveTNormalizer;
-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;
-import it.reyboz.bustorino.util.LinesNameSorter;
-
-import static it.reyboz.bustorino.fragments.ScreenBaseFragment.setOption;
-
-public class ArrivalsFragment extends ResultBaseFragment implements LoaderManager.LoaderCallbacks {
-
- private static final String OPTION_SHOW_LEGEND = "show_legend";
- 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 final static String SOURCES_TEXT="sources_textview_message";
-
- 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;
- protected TextView messageTextView;
- protected RecyclerView arrivalsRecyclerView;
- private PalinaAdapter mListAdapter = null;
-
- private TextView howDoesItWorkTextView;
- private Button hideHintButton;
-
-
- //private NestedScrollView theScrollView;
- protected RecyclerView noArrivalsRecyclerView;
- private RouteOnlyLineAdapter noArrivalsAdapter;
- private TextView noArrivalsTitleView;
- private GridLayoutManager layoutManager;
-
- //private View canaryEndView;
- private List fetchers = null; //new ArrayList<>(Arrays.asList(utils.getDefaultArrivalsFetchers()));
-
- private boolean reloadOnResume = true;
-
- private final PalinaAdapter.PalinaClickListener palinaClickListener = new PalinaAdapter.PalinaClickListener() {
- @Override
- public void showRouteFullDirection(Route route) {
- String routeName;
- Log.d(DEBUG_TAG, "Make toast for line "+route.getName());
-
-
- routeName = FiveTNormalizer.routeInternalToDisplay(route.getName());
- if (routeName == null) {
- routeName = route.getDisplayCode();
- }
- if(getContext()==null)
- Log.e(DEBUG_TAG, "Touched on a route but Context is null");
- else if (route.destinazione == null || route.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, route.destinazione), Toast.LENGTH_SHORT).show();
- }
- }
-
- @Override
- public void requestShowingRoute(Route route) {
- Log.d(DEBUG_TAG, "Need to show line for route:\ngtfsID "+route.getGtfsId()+ " name "+route.getName());
- if(route.getGtfsId()!=null){
- mListener.showLineOnMap(route.getGtfsId(), stopID);
- } else {
- String gtfsID = FiveTNormalizer.getGtfsRouteID(route);
- Log.d(DEBUG_TAG, "GtfsID for route is: " + gtfsID);
- mListener.showLineOnMap(gtfsID, stopID);
- }
- }
- };
-
-
- 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;
- }
-
- public static String getFragmentTag(Palina p) {
- return "palina_"+p.ID;
- }
-
- @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);
- // "How does it work part"
- howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView);
- hideHintButton = root.findViewById(R.id.hideHintButton);
- hideHintButton.setOnClickListener(this::onHideHint);
-
- //theScrollView = root.findViewById(R.id.arrivalsScrollView);
- // recyclerview holding the arrival times
- arrivalsRecyclerView = root.findViewById(R.id.arrivalsRecyclerView);
- final LinearLayoutManager manager = new LinearLayoutManager(getContext());
- arrivalsRecyclerView.setLayoutManager(manager);
- final DividerItemDecoration mDividerItemDecoration = new DividerItemDecoration(arrivalsRecyclerView.getContext(),
- manager.getOrientation());
- arrivalsRecyclerView.addItemDecoration(mDividerItemDecoration);
- 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();
- });
-
- 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);
- }
- //no arrivals stuff
- noArrivalsRecyclerView = root.findViewById(R.id.noArrivalsRecyclerView);
- layoutManager = new GridLayoutManager(getContext(),60);
- layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- return 12;
- }
- });
- noArrivalsRecyclerView.setLayoutManager(layoutManager);
- noArrivalsTitleView = root.findViewById(R.id.noArrivalsMessageTextView);
-
- //canaryEndView = root.findViewById(R.id.canaryEndView);
-
- /*String sourcesTextViewData = getArguments().getString(SOURCES_TEXT);
- if (sourcesTextViewData!=null){
- timesSourceTextView.setText(sourcesTextViewData);
- }*/
- //need to do this when we recreate the fragment but we haven't updated the arrival times
- if (lastUpdatedPalina!=null)
- showArrivalsSources(lastUpdatedPalina);
- return root;
- }
-
- @Override
- public void onResume() {
- super.onResume();
- LoaderManager loaderManager = getLoaderManager();
- Log.d(DEBUG_TAG, "OnResume, justCreated "+justCreated+", lastUpdatedPalina is: "+lastUpdatedPalina);
- /*if(needUpdateOnAttach){
- updateFragmentData(null);
- needUpdateOnAttach=false;
- }*/
- /*if(lastUpdatedPalina!=null){
- updateFragmentData(null);
- showArrivalsSources(lastUpdatedPalina);
- }*/
- mListener.readyGUIfor(FragmentKind.ARRIVALS);
-
- if (mListAdapter!=null)
- resetListAdapter(mListAdapter);
- if(noArrivalsAdapter!=null){
- noArrivalsRecyclerView.setAdapter(noArrivalsAdapter);
-
- }
-
- if(stopID!=null){
- 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();
- }
-
- if (ScreenBaseFragment.getOption(requireContext(),OPTION_SHOW_LEGEND, true)) {
- showHints();
- }
-
-
- }
-
-
- @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;
- }
-
- // HINT "HOW TO USE"
- 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);
- }
-
- public void onHideHint(View v) {
- hideHints();
- setOption(requireContext(),OPTION_SHOW_LEGEND, false);
- }
- /**
- * 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, palinaClickListener, true);
- showArrivalsSources(lastUpdatedPalina);
- resetListAdapter(adapter);
-
- final ArrayList routesWithNoPassages = lastUpdatedPalina.getRoutesNamesWithNoPassages();
- if(routesWithNoPassages.isEmpty()){
- //hide the views if there are no empty routes
- noArrivalsRecyclerView.setVisibility(View.GONE);
- noArrivalsTitleView.setVisibility(View.GONE);
- }else{
- Collections.sort(routesWithNoPassages, new LinesNameSorter());
- noArrivalsAdapter = new RouteOnlyLineAdapter(routesWithNoPassages, null);
- if(noArrivalsRecyclerView!=null){
- noArrivalsRecyclerView.setAdapter(noArrivalsAdapter);
-
- noArrivalsRecyclerView.setVisibility(View.VISIBLE);
- noArrivalsTitleView.setVisibility(View.VISIBLE);
-
- }
- }
-
-
- //canaryEndView.setVisibility(View.VISIBLE);
- //check if canaryEndView is visible
- //boolean isCanaryVisibile = ViewUtils.Companion.isViewPartiallyVisibleInScroll(canaryEndView, theScrollView);
- //Log.d(DEBUG_TAG, "Canary view fully visibile: "+isCanaryVisibile);
-
- }
- }
-
-
-
- /**
- * 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);
- }
- //
- 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);
- timesSourceTextView.setVisibility(View.VISIBLE);
-
- 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++;
- }
- return count < 200;
-
- }
- protected boolean adjustFetchersToSource(){
- if (lastUpdatedPalina == null) return false;
- final Passaggio.Source source = lastUpdatedPalina.getPassaggiSourceIfAny();
- return adjustFetchersToSource(source);
- }
-
- /**
- * 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.isEmpty()) {
- 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;
- if (probableName != null && !probableName.isEmpty())
- stopName = probableName; //set the stop
- //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
- }
- protected void resetListAdapter(PalinaAdapter adapter) {
- mListAdapter = adapter;
- if (arrivalsRecyclerView != null) {
- arrivalsRecyclerView.setAdapter(adapter);
- arrivalsRecyclerView.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Set the message textView
- * @param message the whole message to write in the textView
- */
- public void setTextViewMessage(String message) {
- messageTextView.setText(message);
- messageTextView.setVisibility(View.VISIBLE);
- }
-
- 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);
- }
-
-
- }
-
- @Override
- public void onDestroyView() {
- arrivalsRecyclerView = null;
- if(getArguments()!=null) {
- getArguments().putString(SOURCES_TEXT, timesSourceTextView.getText().toString());
- getArguments().putString(MESSAGE_TEXT_VIEW, messageTextView.getText().toString());
- }
- super.onDestroyView();
- }
-
- public boolean isFragmentForTheSameStop(Palina p) {
- if (getTag() != null)
- return getTag().equals(getFragmentTag(p));
- else return false;
- }
-}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ArrivalsFragment.kt
@@ -0,0 +1,796 @@
+/*
+ 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.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.*
+import androidx.fragment.app.viewModels
+import androidx.loader.app.LoaderManager
+import androidx.loader.content.CursorLoader
+import androidx.loader.content.Loader
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.adapters.PalinaAdapter
+import it.reyboz.bustorino.adapters.PalinaAdapter.PalinaClickListener
+import it.reyboz.bustorino.adapters.RouteOnlyLineAdapter
+import it.reyboz.bustorino.backend.*
+import it.reyboz.bustorino.backend.DBStatusManager.OnDBUpdateStatusChangeListener
+import it.reyboz.bustorino.backend.Passaggio.Source
+import it.reyboz.bustorino.data.AppDataProvider
+import it.reyboz.bustorino.data.NextGenDB
+import it.reyboz.bustorino.data.UserDB
+import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction
+import it.reyboz.bustorino.middleware.SearchRequestType
+import it.reyboz.bustorino.util.LinesNameSorter
+import it.reyboz.bustorino.viewmodels.ArrivalsViewModel
+import java.util.*
+
+
+class ArrivalsFragment : ResultBaseFragment(), LoaderManager.LoaderCallbacks {
+ private var DEBUG_TAG = DEBUG_TAG_ALL
+ private lateinit var stopID: String
+ //private set
+ private var stopName: String? = null
+ private var prefs: DBStatusManager? = null
+ private var listener: OnDBUpdateStatusChangeListener? = null
+ private var justCreated = false
+ private var lastUpdatedPalina: Palina? = null
+ private var needUpdateOnAttach = false
+ private var fetchersChangeRequestPending = false
+ private var stopIsInFavorites = false
+
+ //Views
+ protected lateinit var addToFavorites: ImageButton
+ protected lateinit var timesSourceTextView: TextView
+ protected lateinit var messageTextView: TextView
+ protected lateinit var arrivalsRecyclerView: RecyclerView
+ private lateinit var mListAdapter: PalinaAdapter
+
+ private lateinit var resultsLayout : LinearLayout
+ private lateinit var loadingMessageTextView: TextView
+ private lateinit var progressBar: ProgressBar
+
+ private lateinit var howDoesItWorkTextView: TextView
+ private lateinit var hideHintButton: Button
+
+
+ //private NestedScrollView theScrollView;
+ protected lateinit var noArrivalsRecyclerView: RecyclerView
+ private var noArrivalsAdapter: RouteOnlyLineAdapter? = null
+ private var noArrivalsTitleView: TextView? = null
+ private var layoutManager: GridLayoutManager? = null
+
+ //private View canaryEndView;
+ private var fetchers: List = ArrayList()
+ private val arrivalsViewModel : ArrivalsViewModel by viewModels()
+
+
+ private var reloadOnResume = true
+
+ fun getStopID() = stopID
+
+ private val palinaClickListener: PalinaClickListener = object : PalinaClickListener {
+ override fun showRouteFullDirection(route: Route) {
+ var routeName: String?
+ Log.d(DEBUG_TAG, "Make toast for line " + route.name)
+
+
+ routeName = FiveTNormalizer.routeInternalToDisplay(route.name)
+ if (routeName == null) {
+ routeName = route.displayCode
+ }
+ if (context == null) Log.e(DEBUG_TAG, "Touched on a route but Context is null")
+ else if (route.destinazione == null || route.destinazione.length == 0) {
+ Toast.makeText(
+ context,
+ getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ Toast.makeText(
+ context,
+ getString(R.string.route_towards_destination, routeName, route.destinazione), Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ override fun requestShowingRoute(route: Route) {
+ Log.d(
+ DEBUG_TAG, """Need to show line for route: gtfsID ${route.gtfsId} name ${route.name}"""
+ )
+ if (route.gtfsId != null) {
+ mListener.showLineOnMap(route.gtfsId, stopID)
+ } else {
+ val gtfsID = FiveTNormalizer.getGtfsRouteID(route)
+ Log.d(DEBUG_TAG, "GtfsID for route is: $gtfsID")
+ mListener.showLineOnMap(gtfsID, stopID)
+ }
+ }
+ }
+
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ stopID = arguments!!.getString(KEY_STOP_ID) ?: ""
+ DEBUG_TAG = DEBUG_TAG_ALL + " " + stopID
+
+ //this might really be null
+ stopName = arguments!!.getString(KEY_STOP_NAME)
+ val arrivalsFragment = this
+ listener = object : OnDBUpdateStatusChangeListener {
+ override fun onDBStatusChanged(updating: Boolean) {
+ if (!updating) {
+ loaderManager.restartLoader(
+ loaderFavId,
+ arguments, arrivalsFragment
+ )
+ } else {
+ val lm = loaderManager
+ lm.destroyLoader(loaderFavId)
+ lm.destroyLoader(loaderStopId)
+ }
+ }
+
+ override fun defaultStatusValue(): Boolean {
+ return true
+ }
+ }
+ prefs = DBStatusManager(context!!.applicationContext, listener)
+ justCreated = true
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ val root = inflater.inflate(R.layout.fragment_arrivals, container, false)
+ messageTextView = root.findViewById(R.id.messageTextView)
+ addToFavorites = root.findViewById(R.id.addToFavorites)
+ // "How does it work part"
+ howDoesItWorkTextView = root.findViewById(R.id.howDoesItWorkTextView)
+ hideHintButton = root.findViewById(R.id.hideHintButton)
+ //TODO: Hide this layout at the beginning, show it later
+ resultsLayout = root.findViewById(R.id.resultsLayout)
+ loadingMessageTextView = root.findViewById(R.id.loadingMessageTextView)
+ progressBar = root.findViewById(R.id.circularProgressBar)
+
+ hideHintButton.setOnClickListener(View.OnClickListener { v: View? -> this.onHideHint(v) })
+
+ //theScrollView = root.findViewById(R.id.arrivalsScrollView);
+ // recyclerview holding the arrival times
+ arrivalsRecyclerView = root.findViewById(R.id.arrivalsRecyclerView)
+ val manager = LinearLayoutManager(context)
+ arrivalsRecyclerView.setLayoutManager(manager)
+ val mDividerItemDecoration = DividerItemDecoration(
+ arrivalsRecyclerView.context,
+ manager.orientation
+ )
+ arrivalsRecyclerView.addItemDecoration(mDividerItemDecoration)
+ timesSourceTextView = root.findViewById(R.id.timesSourceTextView)
+ timesSourceTextView.setOnLongClickListener { view: View? ->
+ if (!fetchersChangeRequestPending) {
+ rotateFetchers()
+ //Show we are changing provider
+ timesSourceTextView.setText(R.string.arrival_source_changing)
+
+ //mListener.requestArrivalsForStopID(stopID)
+ requestArrivalsForTheFragment()
+ fetchersChangeRequestPending = true
+ return@setOnLongClickListener true
+ }
+ false
+ }
+ timesSourceTextView.setOnClickListener(View.OnClickListener { view: View? ->
+ Toast.makeText(
+ context, R.string.change_arrivals_source_message, Toast.LENGTH_SHORT
+ )
+ .show()
+ })
+ //Button
+ addToFavorites.setClickable(true)
+ addToFavorites.setOnClickListener(View.OnClickListener { v: View? ->
+ // add/remove the stop in the favorites
+ toggleLastStopToFavorites()
+ })
+
+ val displayName = arguments!!.getString(STOP_TITLE)
+ if (displayName != null) setTextViewMessage(
+ String.format(
+ getString(R.string.passages), displayName
+ )
+ )
+
+
+ val probablemessage = arguments!!.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)
+ }
+ //no arrivals stuff
+ noArrivalsRecyclerView = root.findViewById(R.id.noArrivalsRecyclerView)
+ layoutManager = GridLayoutManager(context, 60)
+ layoutManager!!.spanSizeLookup = object : SpanSizeLookup() {
+ override fun getSpanSize(position: Int): Int {
+ return 12
+ }
+ }
+ noArrivalsRecyclerView.setLayoutManager(layoutManager)
+ noArrivalsTitleView = root.findViewById(R.id.noArrivalsMessageTextView)
+
+ //canaryEndView = root.findViewById(R.id.canaryEndView);
+
+ /*String sourcesTextViewData = getArguments().getString(SOURCES_TEXT);
+ if (sourcesTextViewData!=null){
+ timesSourceTextView.setText(sourcesTextViewData);
+ }*/
+ //need to do this when we recreate the fragment but we haven't updated the arrival times
+ lastUpdatedPalina?.let { showArrivalsSources(it) }
+ /*if (lastUpdatedPalina?.queryAllRoutes() != null && lastUpdatedPalina!!.queryAllRoutes()!!.size >0){
+ showArrivalsSources(lastUpdatedPalina!!)
+ } else{
+ Log.d(DEBUG_TAG, "No routes names")
+ }
+
+ */
+
+
+
+ arrivalsViewModel.palinaLiveData.observe(viewLifecycleOwner){
+ mListener.toggleSpinner(false)
+ if(arrivalsViewModel.resultLiveData.value==Fetcher.Result.OK){
+ //the result is true
+ changeUIFirstSearchActive(false)
+ updateFragmentData(it)
+ } else{
+ progressBar.visibility=View.INVISIBLE
+ loadingMessageTextView.text = getString(R.string.no_bus_stop_have_this_name)
+ }
+
+ }
+
+ arrivalsViewModel.sourcesLiveData.observe(viewLifecycleOwner){
+ Log.d(DEBUG_TAG, "Using arrivals source: $it")
+ val srcString = getDisplayArrivalsSource(it,requireContext())
+ loadingMessageTextView.text = getString(R.string.searching_arrivals_fmt, srcString)
+ }
+
+ arrivalsViewModel.resultLiveData.observe(viewLifecycleOwner){res ->
+ when (res) {
+ Fetcher.Result.OK -> {}
+ Fetcher.Result.CLIENT_OFFLINE -> showToastMessage(R.string.network_error, true)
+ Fetcher.Result.SERVER_ERROR -> {
+ if (utils.isConnected(context)) {
+ showToastMessage(R.string.parsing_error, true)
+ } else {
+ showToastMessage(R.string.network_error, true)
+ }
+ showToastMessage(R.string.internal_error,true)
+ }
+
+ Fetcher.Result.PARSER_ERROR -> showShortToast(R.string.internal_error)
+ Fetcher.Result.QUERY_TOO_SHORT -> showShortToast(R.string.query_too_short)
+ Fetcher.Result.EMPTY_RESULT_SET -> showShortToast(R.string.no_arrivals_stop)
+
+ Fetcher.Result.NOT_FOUND -> showShortToast(R.string.no_bus_stop_have_this_name)
+ else -> showShortToast(R.string.internal_error)
+ }
+ }
+ return root
+ }
+
+
+ private fun showShortToast(id: Int) = showToastMessage(id,true)
+
+
+ private fun changeUIFirstSearchActive(yes: Boolean){
+ if(yes){
+ resultsLayout.visibility = View.GONE
+ progressBar.visibility = View.VISIBLE
+ loadingMessageTextView.visibility = View.VISIBLE
+ } else{
+ resultsLayout.visibility = View.VISIBLE
+ progressBar.visibility = View.GONE
+ loadingMessageTextView.visibility = View.GONE
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val loaderManager = loaderManager
+ Log.d(DEBUG_TAG, "OnResume, justCreated $justCreated, lastUpdatedPalina is: $lastUpdatedPalina")
+ /*if(needUpdateOnAttach){
+ updateFragmentData(null);
+ needUpdateOnAttach=false;
+ }*/
+ /*if(lastUpdatedPalina!=null){
+ updateFragmentData(null);
+ showArrivalsSources(lastUpdatedPalina);
+ }*/
+ mListener.readyGUIfor(FragmentKind.ARRIVALS)
+
+ resetListAdapter(mListAdapter)
+ if (noArrivalsAdapter != null) {
+ noArrivalsRecyclerView.adapter = noArrivalsAdapter
+ }
+
+ if (stopID.isNotEmpty()) {
+ if (!justCreated) {
+ fetchers = utils.getDefaultArrivalsFetchers(context)
+ adjustFetchersToSource()
+
+ if (reloadOnResume) requestArrivalsForTheFragment() //mListener.requestArrivalsForStopID(stopID)
+ } else {
+ //start first search
+ requestArrivalsForTheFragment()
+ changeUIFirstSearchActive(true)
+ justCreated = false
+ }
+ //start the loader
+ if (prefs!!.isDBUpdating(true)) {
+ prefs!!.registerListener()
+ } else {
+ Log.d(DEBUG_TAG, "Restarting loader for stop")
+ loaderManager.restartLoader(
+ loaderFavId,
+ arguments, this
+ )
+ }
+ updateMessage()
+ }
+
+ if (ScreenBaseFragment.getOption(requireContext(), OPTION_SHOW_LEGEND, true)) {
+ showHints()
+ }
+ }
+
+
+ override fun onStart() {
+ super.onStart()
+ if (needUpdateOnAttach) {
+ updateFragmentData(null)
+ needUpdateOnAttach = false
+ }
+ }
+
+ override fun onPause() {
+ if (listener != null) prefs!!.unregisterListener()
+ super.onPause()
+ val loaderManager = loaderManager
+ Log.d(DEBUG_TAG, "onPause, have running loaders: " + loaderManager.hasRunningLoaders())
+ loaderManager.destroyLoader(loaderFavId)
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+
+ //get fetchers
+ fetchers = utils.getDefaultArrivalsFetchers(context)
+ }
+
+ fun reloadsOnResume(): Boolean {
+ return reloadOnResume
+ }
+
+ fun setReloadOnResume(reloadOnResume: Boolean) {
+ this.reloadOnResume = reloadOnResume
+ }
+
+ // HINT "HOW TO USE"
+ private fun showHints() {
+ howDoesItWorkTextView!!.visibility = View.VISIBLE
+ hideHintButton!!.visibility = View.VISIBLE
+ //actionHelpMenuItem.setVisible(false);
+ }
+
+ private fun hideHints() {
+ howDoesItWorkTextView!!.visibility = View.GONE
+ hideHintButton!!.visibility = View.GONE
+ //actionHelpMenuItem.setVisible(true);
+ }
+
+ fun onHideHint(v: View?) {
+ hideHints()
+ ScreenBaseFragment.setOption(requireContext(), OPTION_SHOW_LEGEND, false)
+ }
+
+ val currentFetchers: ArrayList
+ /**
+ * Give the fetchers
+ * @return the list of the fetchers
+ */
+ get() = ArrayList(this.fetchers)
+ /*val currentFetchersAsArray: Array
+ get() {
+ val arr = arrayOfNulls(fetchers!!.size)
+ fetchers!!.toArray(arr)
+ return arr
+ }
+
+ */
+
+ fun getCurrentFetchersAsArray(): Array {
+ val r= fetchers.toTypedArray() ?: emptyArray()
+ //?: emptyArray()
+ return r
+ }
+
+ private fun rotateFetchers() {
+ Log.d(DEBUG_TAG, "Rotating fetchers, before: $fetchers")
+ fetchers?.let { Collections.rotate(it, -1) }
+ Log.d(DEBUG_TAG, "Rotating fetchers, afterwards: $fetchers")
+ }
+
+
+ /**
+ * Update the UI with the new data
+ * @param p the full Palina
+ */
+ fun updateFragmentData(p: Palina?) {
+ 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 {
+ val adapter = PalinaAdapter(context, lastUpdatedPalina, palinaClickListener, true)
+ showArrivalsSources(lastUpdatedPalina!!)
+ resetListAdapter(adapter)
+
+ val routesWithNoPassages = lastUpdatedPalina!!.routesNamesWithNoPassages
+ if (routesWithNoPassages.isEmpty()) {
+ //hide the views if there are no empty routes
+ noArrivalsRecyclerView!!.visibility = View.GONE
+ noArrivalsTitleView!!.visibility = View.GONE
+ } else {
+ Collections.sort(routesWithNoPassages, LinesNameSorter())
+ noArrivalsAdapter = RouteOnlyLineAdapter(routesWithNoPassages, null)
+ if (noArrivalsRecyclerView != null) {
+ noArrivalsRecyclerView!!.adapter = noArrivalsAdapter
+
+ noArrivalsRecyclerView!!.visibility = View.VISIBLE
+ noArrivalsTitleView!!.visibility = View.VISIBLE
+ }
+ }
+
+
+ //canaryEndView.setVisibility(View.VISIBLE);
+ //check if canaryEndView is visible
+ //boolean isCanaryVisibile = ViewUtils.Companion.isViewPartiallyVisibleInScroll(canaryEndView, theScrollView);
+ //Log.d(DEBUG_TAG, "Canary view fully visibile: "+isCanaryVisibile);
+ }
+ }
+
+
+
+
+ /**
+ * Set the message of the arrival times source
+ * @param p Palina with the arrival times
+ */
+ protected fun showArrivalsSources(p: Palina) {
+ val source = p.passaggiSourceIfAny
+ val source_txt = getDisplayArrivalsSource(source, requireContext())
+ //
+ val updatedFetchers = adjustFetchersToSource(source)
+ if (!updatedFetchers) Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work")
+ val base_message = getString(R.string.times_source_fmt, source_txt)
+ timesSourceTextView!!.text = base_message
+ timesSourceTextView!!.visibility = View.VISIBLE
+
+ if (p.totalNumberOfPassages > 0) {
+ timesSourceTextView!!.visibility = View.VISIBLE
+ } else {
+ timesSourceTextView!!.visibility = View.INVISIBLE
+ }
+ fetchersChangeRequestPending = false
+ }
+
+ protected fun adjustFetchersToSource(source: Passaggio.Source?): Boolean {
+ if (source == null) return false
+ var count = 0
+ if (source != Passaggio.Source.UNDETERMINED) while (source != fetchers!![0]!!.sourceForFetcher && count < 200) {
+ //we need to update the fetcher that is requested
+ rotateFetchers()
+ count++
+ }
+ return count < 200
+ }
+
+ protected fun adjustFetchersToSource(): Boolean {
+ if (lastUpdatedPalina == null) return false
+ val source = lastUpdatedPalina!!.passaggiSourceIfAny
+ return adjustFetchersToSource(source)
+ }
+
+ /**
+ * Update the message in the fragment
+ *
+ * It may eventually change the "Add to Favorite" icon
+ */
+ private fun updateMessage() {
+ var message: String? = null
+ if (stopName != null && stopID != null && !stopName!!.isEmpty()) {
+ message = ("$stopID - $stopName")
+ } else if (stopID != null) {
+ message = stopID
+ } else {
+ Log.e("ArrivalsFragm$tag", "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();
+ }
+
+ override fun onCreateLoader(id: Int, p1: Bundle?): Loader {
+ val args = arguments
+ //if (args?.getString(KEY_STOP_ID) == null) throw
+ val stopID = args?.getString(KEY_STOP_ID) ?: ""
+ val builder = AppDataProvider.getUriBuilderToComplete()
+ val cl: CursorLoader
+ when (id) {
+ loaderFavId -> {
+ builder.appendPath("favorites").appendPath(stopID)
+ cl = CursorLoader(context!!, builder.build(), UserDB.getFavoritesColumnNamesAsArray, null, null, null)
+ }
+
+ loaderStopId -> {
+ builder.appendPath("stop").appendPath(stopID)
+ cl = CursorLoader(
+ context!!, builder.build(), arrayOf(NextGenDB.Contract.StopsTable.COL_NAME),
+ null, null, null
+ )
+ }
+
+ else -> {
+ cl = CursorLoader(context!!, builder.build(), null, null,null,null)
+ Log.d(DEBUG_TAG, "This is probably going to crash")
+ }
+ }
+ cl.setUpdateThrottle(500)
+ return cl
+ }
+
+ override fun onLoadFinished(loader: Loader, data: Cursor) {
+ when (loader.id) {
+ loaderFavId -> {
+ val colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1])
+ if (data.count > 0) {
+ // IT'S IN FAVORITES
+ data.moveToFirst()
+ val probableName = data.getString(colUserName)
+ stopIsInFavorites = true
+ if (probableName != null && !probableName.isEmpty()) stopName = probableName //set the stop
+
+ //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$tag", "Stop wasn't in the favorites and has no name, looking in the DB")
+ loaderManager.restartLoader(
+ loaderStopId,
+ arguments, this
+ )
+ }
+ }
+
+ loaderStopId -> if (data.count > 0) {
+ data.moveToFirst()
+ val 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$tag", "Stop is not inside the database... CLOISTER BELL")
+ }
+ }
+ }
+
+ override fun onLoaderReset(loader: Loader) {
+ //NOTHING TO DO
+ }
+
+ protected fun resetListAdapter(adapter: PalinaAdapter) {
+ mListAdapter = adapter
+ arrivalsRecyclerView.adapter = adapter
+ arrivalsRecyclerView.visibility = View.VISIBLE
+ }
+
+ /**
+ * Set the message textView
+ * @param message the whole message to write in the textView
+ */
+ fun setTextViewMessage(message: String?) {
+ messageTextView!!.text = message
+ messageTextView!!.visibility = View.VISIBLE
+ }
+
+ fun toggleLastStopToFavorites() {
+ val stop: Stop? = lastUpdatedPalina
+ if (stop != null) {
+ // toggle the status in background
+
+ AsyncStopFavoriteAction(
+ context!!.applicationContext, AsyncStopFavoriteAction.Action.TOGGLE
+ ) { v: Boolean -> 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
+ */
+ fun updateStarIconFromLastBusStop(toggleDone: Boolean) {
+ stopIsInFavorites = if (stopIsInFavorites) !toggleDone
+ else 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`
+ */
+ fun updateStarIcon() {
+ // no favorites no party!
+
+ // check if there is a last Stop
+
+ if (stopID == null) {
+ addToFavorites!!.visibility = View.INVISIBLE
+ } else {
+ // filled or outline?
+ if (stopIsInFavorites) {
+ addToFavorites!!.setImageResource(R.drawable.ic_star_filled)
+ } else {
+ addToFavorites!!.setImageResource(R.drawable.ic_star_outline)
+ }
+
+ addToFavorites!!.visibility = View.VISIBLE
+ }
+ }
+
+ override fun onDestroyView() {
+ //arrivalsRecyclerView = null
+ if (arguments != null) {
+ arguments!!.putString(SOURCES_TEXT, timesSourceTextView!!.text.toString())
+ arguments!!.putString(MESSAGE_TEXT_VIEW, messageTextView!!.text.toString())
+ }
+ super.onDestroyView()
+ }
+
+ override fun getBaseViewForSnackBar(): View? {
+ return null
+ }
+
+ fun isFragmentForTheSameStop(p: Palina): Boolean {
+ return if (tag != null) tag == getFragmentTag(p)
+ else false
+ }
+
+
+ /**
+ * Request arrivals in the fragment
+ */
+ fun requestArrivalsForTheFragment(){
+
+ // Run with previous fetchers
+ //fragment.getCurrentFetchers().toArray()
+ //AsyncArrivalsSearcher(, getCurrentFetchersAsArray(), context).execute(stopID)
+ context?.let {
+ mListener.toggleSpinner(true)
+ val fetcherSources = fetchers.map { f-> f?.sourceForFetcher?.name ?: "" }
+ //val workRequest = ArrivalsWorker.buildWorkRequest(stopID, fetcherSources.toTypedArray())
+ //val workManager = WorkManager.getInstance(it)
+
+ //workManager.enqueueUniqueWork(getArrivalsWorkID(stopID), ExistingWorkPolicy.REPLACE, workRequest)
+
+ arrivalsViewModel.requestArrivalsForStop(stopID,fetcherSources.toTypedArray())
+
+ //prepareGUIForArrivals();
+ //new AsyncArrivalsSearcher(fragmentHelper,fetchers, getContext()).execute(ID);
+ Log.d(DEBUG_TAG, "Started search for arrivals of stop $stopID")
+ }
+ }
+
+ companion object {
+ private const val OPTION_SHOW_LEGEND = "show_legend"
+ private const val KEY_STOP_ID = "stopid"
+ private const val KEY_STOP_NAME = "stopname"
+ private const val DEBUG_TAG_ALL = "BUSTOArrivalsFragment"
+ private const val loaderFavId = 2
+ private const val loaderStopId = 1
+ const val STOP_TITLE: String = "messageExtra"
+ private const val SOURCES_TEXT = "sources_textview_message"
+
+ @JvmStatic
+ @JvmOverloads
+ fun newInstance(stopID: String, stopName: String? = null): ArrivalsFragment {
+ val fragment = ArrivalsFragment()
+ val args = 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.arguments = args
+ return fragment
+ }
+
+ @JvmStatic
+ fun getFragmentTag(p: Palina): String {
+ return "palina_" + p.ID
+ }
+
+ @JvmStatic
+ fun getArrivalsWorkID(stopID: String) = "arrivals_search_$stopID"
+
+ @JvmStatic
+ fun getDisplayArrivalsSource(source: Source, context: Context): String{
+ return when (source) {
+ Passaggio.Source.GTTJSON -> context.getString(R.string.gttjsonfetcher)
+ Passaggio.Source.FiveTAPI -> context.getString(R.string.fivetapifetcher)
+ Passaggio.Source.FiveTScraper -> context.getString(R.string.fivetscraper)
+ Passaggio.Source.MatoAPI -> context.getString(R.string.source_mato)
+ Passaggio.Source.UNDETERMINED -> //Don't show the view
+ context.getString(R.string.undetermined_source)
+
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -53,7 +53,6 @@
import it.reyboz.bustorino.middleware.BarcodeScanUtils;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
-import org.jetbrains.annotations.NotNull;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
@@ -113,9 +112,10 @@
Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment");
// AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
} else{
- String stopName = fragment.getStopID();
+ //String stopName = fragment.getStopID();
- new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
+ //new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
+ fragment.requestArrivalsForTheFragment();
}
} else //we create a new fragment, which is WRONG
new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute();
@@ -650,7 +650,7 @@
}
- private void prepareGUIForBusLines() {
+ private void prepareGUIForArrivals() {
swipeRefreshLayout.setEnabled(true);
swipeRefreshLayout.setVisibility(View.VISIBLE);
//actionHelpMenuItem.setVisible(true);
@@ -710,7 +710,7 @@
else
switch (fragmentType) {
case ARRIVALS:
- prepareGUIForBusLines();
+ prepareGUIForArrivals();
break;
case STOPS:
prepareGUIForBusStops();
@@ -763,16 +763,19 @@
if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){
// Run with previous fetchers
//fragment.getCurrentFetchers().toArray()
- new AsyncArrivalsSearcher(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID);
+ fragment.requestArrivalsForTheFragment();
} else{
- new AsyncArrivalsSearcher(fragmentHelper, fetchers, getContext()).execute(ID);
+ //SHOW NEW ARRIVALS FRAGMENT
+ //new AsyncArrivalsSearcher(fragmentHelper, fetchers, getContext()).execute(ID);
+ fragmentHelper.createOrUpdateStopFragment(new Palina(ID), true);
}
}
else {
Log.d(DEBUG_TAG, "This is probably the first arrivals search, preparing GUI");
- prepareGUIForBusLines();
- new AsyncArrivalsSearcher(fragmentHelper,fetchers, getContext()).execute(ID);
- Log.d(DEBUG_TAG, "Started search for arrivals of stop " + ID);
+ //prepareGUIForArrivals();
+ //new AsyncArrivalsSearcher(fragmentHelper,fetchers, getContext()).execute(ID);
+ fragmentHelper.createOrUpdateStopFragment(new Palina(ID), true);
+
}
}
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ResultBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ResultBaseFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ResultBaseFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ResultBaseFragment.java
@@ -4,7 +4,7 @@
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
-public abstract class ResultBaseFragment extends Fragment {
+public abstract class ResultBaseFragment extends ScreenBaseFragment {
protected FragmentListenerMain mListener;
protected static final String MESSAGE_TEXT_VIEW = "message_text_view";
diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
--- a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
+++ b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java
@@ -41,7 +41,9 @@
protected void showToastMessage(int messageID, boolean short_lenght) {
final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
- Toast.makeText(getContext(), messageID, length).show();
+ final Context context = getContext();
+ if(context!=null)
+ Toast.makeText(context, messageID, length).show();
}
public void hideKeyboard() {
@@ -101,4 +103,5 @@
public interface LocationRequestListener{
void onPermissionResult(boolean isCoarseGranted, boolean isFineGranted);
}
+
}
diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
new file mode 100644
--- /dev/null
+++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/ArrivalsViewModel.kt
@@ -0,0 +1,184 @@
+package it.reyboz.bustorino.viewmodels
+
+import android.app.Application
+import android.content.Context
+import android.util.Log
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.viewModelScope
+import it.reyboz.bustorino.backend.*
+import it.reyboz.bustorino.backend.mato.MatoAPIFetcher
+import it.reyboz.bustorino.data.NextGenDB
+import it.reyboz.bustorino.middleware.RecursionHelper
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import java.util.concurrent.atomic.AtomicReference
+
+class ArrivalsViewModel(application: Application): AndroidViewModel(application) {
+
+ // Arrivals of palina
+ val appContext: Context
+ init {
+ appContext = application.applicationContext
+ }
+
+ val palinaLiveData = MediatorLiveData()
+ val sourcesLiveData = MediatorLiveData()
+
+ val resultLiveData = MediatorLiveData()
+
+ val currentFetchers = MediatorLiveData>()
+
+ fun requestArrivalsForStop(stopId: String, fetchers: List){
+ val context = appContext //application.applicationContext
+ currentFetchers.value = fetchers
+ viewModelScope.launch(Dispatchers.IO){
+ runArrivalsFetching(stopId, fetchers, context)
+ }
+ }
+
+ fun requestArrivalsForStop(stopId: String, fetchersSources: Array){
+ val fetchers = constructFetchersFromStrList(fetchersSources)
+ requestArrivalsForStop(stopId, fetchers)
+ }
+
+ private suspend fun runArrivalsFetching(stopId: String, fetchers: List, appContext: Context) {
+
+ if (fetchers.isEmpty()) {
+ //do nothing
+ return
+ }
+
+ // Equivalente del doInBackground nell'AsyncTask
+ val recursionHelper = RecursionHelper(fetchers.toTypedArray())
+ var resultPalina = Palina(stopId)
+
+ val stringBuilder = StringBuilder()
+ for (f in fetchers) {
+ stringBuilder.append("")
+ stringBuilder.append(f.javaClass.simpleName)
+ stringBuilder.append("; ")
+ }
+ Log.d(DEBUG_TAG, "Using fetchers: $stringBuilder")
+
+ val resultRef = AtomicReference()
+
+ while (recursionHelper.valid()) {
+
+ val fetcher = recursionHelper.getAndMoveForward()
+
+ sourcesLiveData.postValue(fetcher.sourceForFetcher)
+
+
+ if (fetcher is MatoAPIFetcher) {
+ fetcher.appContext = appContext
+ }
+ Log.d(DEBUG_TAG, "Using the ArrivalsFetcher: ${fetcher.javaClass}")
+
+ // Verifica se è un fetcher per MetroStop da saltare
+ try {
+ if (fetcher is FiveTAPIFetcher && stopId.toInt() >= 8200) {
+ continue
+ }
+ } catch (ex: NumberFormatException) {
+ Log.e(DEBUG_TAG, "The stop number is not a valid integer, expect failures")
+ }
+
+ // Legge i tempi di arrivo
+ val palina = fetcher.ReadArrivalTimesAll(stopId, resultRef)
+
+ Log.d(DEBUG_TAG, "Arrivals fetcher: $fetcher\n\tProgress: ${resultRef.get()}")
+
+
+ // Gestione del FiveTAPIFetcher per ottenere le direzioni
+ if (fetcher is FiveTAPIFetcher) {
+ val branchResultRef = AtomicReference()
+ val branches = fetcher.getDirectionsForStop(stopId, branchResultRef)
+ Log.d(DEBUG_TAG, "FiveTArrivals fetcher: $fetcher\n\tDetails req: ${branchResultRef.get()}")
+
+ if (branchResultRef.get() == Fetcher.Result.OK) {
+ palina.addInfoFromRoutes(branches)
+
+ // Inserisce i dati nel database
+ viewModelScope.launch(Dispatchers.IO) {
+ //modify the DB in another coroutine in the background
+ NextGenDB.insertBranchesIntoDB(appContext,branches)
+ }
+
+ } else {
+ resultRef.set(Fetcher.Result.NOT_FOUND)
+ }
+ }
+
+ // Unisce percorsi duplicati
+ palina.mergeDuplicateRoutes(0)
+
+ if (resultRef.get() == Fetcher.Result.OK && palina.getTotalNumberOfPassages() == 0) {
+ resultRef.set(Fetcher.Result.EMPTY_RESULT_SET)
+ Log.d(DEBUG_TAG, "Setting empty results")
+ }
+ //reportProgress
+ resultLiveData.postValue(resultRef.get())
+
+ // Se è un MatoAPIFetcher con risultati validi, salviamo i dati
+ if (resultPalina == null && fetcher is MatoAPIFetcher && palina.queryAllRoutes().size > 0) {
+ resultPalina = palina
+ }
+
+ // Se abbiamo un risultato OK, restituiamo la palina
+ if (resultRef.get() == Fetcher.Result.OK) {
+ //set data
+ resultLiveData.postValue(Fetcher.Result.OK)
+ palinaLiveData.postValue(palina)
+ //TODO: Rotate the fetchers appropriately
+ return
+ }
+ //end Fetchers loop
+ }
+
+ // Se arriviamo qui, tutti i fetcher hanno fallito
+ //failedAll = true
+
+ // Se abbiamo comunque una palina, la restituiamo
+ if (resultPalina != null) {
+ resultLiveData.postValue(resultRef.get())
+ palinaLiveData.postValue(resultPalina)
+ }
+
+ }
+
+ companion object{
+ const val DEBUG_TAG="BusTO-ArrivalsViMo"
+
+ @JvmStatic
+ fun getFetcherFromStrSource(src:String): ArrivalsFetcher?{
+ val srcEnum = Passaggio.Source.valueOf(src)
+
+ val fe: ArrivalsFetcher? = when(srcEnum){
+ Passaggio.Source.FiveTAPI -> FiveTAPIFetcher()
+ Passaggio.Source.GTTJSON -> GTTJSONFetcher()
+ Passaggio.Source.FiveTScraper -> FiveTScraperFetcher()
+ Passaggio.Source.MatoAPI -> MatoAPIFetcher()
+ Passaggio.Source.UNDETERMINED -> null
+ null -> null
+ }
+ return fe
+ }
+
+ @JvmStatic
+ fun constructFetchersFromStrList(sources: Array): List{
+ val fetchers = mutableListOf()
+ for(s in sources){
+ val fe = getFetcherFromStrSource(s)
+ if(fe!=null){
+ fetchers.add(fe)
+ } else{
+ Log.d(DEBUG_TAG, "Cannot convert fetcher source $s to a fetcher")
+ }
+ }
+
+ return fetchers
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_arrivals.xml b/app/src/main/res/layout/fragment_arrivals.xml
--- a/app/src/main/res/layout/fragment_arrivals.xml
+++ b/app/src/main/res/layout/fragment_arrivals.xml
@@ -45,10 +45,33 @@
+
+
+
diff --git a/app/src/main/res/layout/fragment_main_screen.xml b/app/src/main/res/layout/fragment_main_screen.xml
--- a/app/src/main/res/layout/fragment_main_screen.xml
+++ b/app/src/main/res/layout/fragment_main_screen.xml
@@ -83,17 +83,15 @@
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -16,6 +16,8 @@
Verifica l\'accesso ad Internet!
Sembra che nessuna fermata abbia questo nome
Nessun passaggio trovato alla fermata
+ Ricerca arrivi da %1$s
+
Errore di lettura del sito 5T/GTT (dannato sito!)
Fermata: %1$s
Linea
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -26,7 +26,7 @@
%1$s towards %2$s
%s (unknown destination)
Verify your Internet connection!
- Seems that no bus stop have this name
+ Seems that no bus stop has this name
No arrivals found for this stop
Error parsing the 5T/GTT website (damn site!)
Name too short, type more characters and retry
@@ -197,6 +197,7 @@
Arrival times sources
!-->
Arrivals source: %1$s
+ Loading arrivals from %1$s
GTT App
GTT Website
5T Torino website
@@ -299,8 +300,6 @@
- @string/map_style_versatiles
- @string/map_style_legacy_raster
- MaTO (updated more frequently, might be offline)
- GTFS RT (more stable, less frequently updated)
Remove trips data (free up space)
All GTFS trips have been removed from the database