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