commit dbb8486929ded49bea9bfde5e0dabe941b6312c5 Author: Fabio Mazza Date: Sat May 23 02:06:22 2026 +0200 Fix working flow to restore exactly the back navigation, update Nearby screen Summary: Move implementation to FragmentHelper Clean unused code Convert code Move data logic of NearbyStopsFragment to ViewModel, rewrite in Kotlin Reviewers: #libre_busto_hackers, valerio.bozzolan Reviewed By: #libre_busto_hackers, valerio.bozzolan Subscribers: valerio.bozzolan Project Tags: #libre_busto Differential Revision: https://gitpull.it/D246 diff --git a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java index e90e6df..f9a96d7 100644 --- a/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java +++ b/app/src/main/java/it/reyboz/bustorino/ActivityPrincipal.java @@ -49,6 +49,8 @@ import com.google.android.material.snackbar.Snackbar; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.function.Consumer; import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.data.DBUpdateCheckWorker; @@ -70,9 +72,6 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen private final static String TAG_FAVORITES="favorites_frag"; private Snackbar snackbar; - private boolean showingMainFragmentFromOther = false; - private boolean onCreateComplete = false; - private ServiceAlertsViewModel serviceAlertsViewModel; private FragmentKind showingFragmentKind; @@ -85,8 +84,7 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen R.id.nav_favorites_item, () -> checkAndShowFavoritesFragment(getSupportFragmentManager(), true), - R.id.nav_home, () -> - showHomeMainFragmentFromClick(true), + R.id.nav_home, this::showHomeMainFragmentFromClick, R.id.nav_map_item, () -> requestMapFragment(true), @@ -99,6 +97,7 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen R.id.nav_nearby, this::openNearbyStopsFragment ); + private long lastClosingAttempt = -1L; private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) { @Override @@ -117,7 +116,6 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen } }; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -279,10 +277,10 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen case "favorites" -> checkAndShowFavoritesFragment(framan, false); case "lines" -> showLinesFragment(framan, false, null); case "nearby" -> createShowMainFragment(framan, MainScreenFragment.makeArgsNearby(), false); - default -> showHomeMainFragmentFromClick(false); + default -> createShowMainFragment(framan, MainScreenFragment.makeArgsButtonsScreen(), false); } } - onCreateComplete = true; + //boolean onCreateComplete = true; //last but not least, set the good default values checkApplyDefaultSettingsValues(); @@ -475,20 +473,30 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen */ private boolean activityCustomBackPressed(){ - boolean resolved = true; var mainFragManager = getSupportFragmentManager(); + boolean resolved = mainFragManager.getBackStackEntryCount() > 0; + Fragment shownFrag = mainFragManager.findFragmentById(R.id.mainActContentFrame); - if (mDrawer.isDrawerOpen(GravityCompat.START)) + if (mDrawer.isDrawerOpen(GravityCompat.START)) { mDrawer.closeDrawer(GravityCompat.START); - else if(shownFrag != null && shownFrag.isVisible() && shownFrag.getChildFragmentManager().getBackStackEntryCount() > 0){ - if(shownFrag instanceof MainScreenFragment){ - //we have to stop the arrivals reload - ((MainScreenFragment) shownFrag).cancelReloadArrivalsIfNeeded(); + resolved = true; + } + else if (shownFrag instanceof MainScreenFragment mainFrag) { //the only case when we have to pop both stacks + mainFrag.cancelReloadArrivalsIfNeeded(); + + var chManager = mainFrag.getChildFragmentManager(); + + if(chManager.getBackStackEntryCount() > 0){ + chManager.popBackStack(); + Log.d(DEBUG_TAG, "Child back stack popped"); + resolved = true; + } else{ + Log.d(DEBUG_TAG, "No back stack on child fragment manager"); } - shownFrag.getChildFragmentManager().popBackStack(); - if(showingMainFragmentFromOther && mainFragManager.getBackStackEntryCount() > 0){ - getSupportFragmentManager().popBackStack(); - Log.d(DEBUG_TAG, "Popping main back stack also"); + boolean haveToPopMainFromFragment = mainFrag.needToPopMainStackOnBack(); + //mainFrag.getChildFragmentManager().popBackStack(); + if(haveToPopMainFromFragment){ // pops the stack + mainFragManager.popBackStack(); } } else if (getSupportFragmentManager().getBackStackEntryCount() > 0) { @@ -536,6 +544,7 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen private void updateShowingFragmentKindInternal(@NonNull FragmentKind newKind){ if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Updating fragment kind, new: "+newKind+", current: "+showingFragmentKind); + boolean showingMainFragmentFromOther = false; if(showingFragmentKind == null){ showingFragmentKind = newKind; showingMainFragmentFromOther = false; @@ -562,7 +571,9 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen R.anim.slide_out // popExit )*/ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); - if (addToBackStack) ft.addToBackStack(null); + if (addToBackStack) { + ft.addToBackStack(null); + } ft.commit(); } /** @@ -571,7 +582,6 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen * @param arguments args for the fragment */ private static void createShowMainFragment(FragmentManager fraMan,@Nullable Bundle arguments, boolean addToBackStack){ - //var frag = MainScreenFragment.newInstance(); FragmentTransaction ft = fraMan.beginTransaction() .replace(R.id.mainActContentFrame, MainScreenFragment.class, arguments, MainScreenFragment.FRAGMENT_TAG) .setReorderingAllowed(false) @@ -585,34 +595,18 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen if (addToBackStack) ft.addToBackStack(null); ft.commit(); } - private void showMainFragmentFromClick(@Nullable Bundle argsToCreate, boolean addToBackStack){ - FragmentManager fraMan = getSupportFragmentManager(); - Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); - final MainScreenFragment mainScreenFragment; - if (fragment==null | !(fragment instanceof MainScreenFragment)){ - createShowMainFragment(fraMan, argsToCreate, addToBackStack); - } - else if(!fragment.isVisible()){ - - mainScreenFragment = (MainScreenFragment) fragment; - showMainFragment(fraMan, mainScreenFragment, addToBackStack); - Log.d(DEBUG_TAG, "Found the main fragment"); - } else{ - mainScreenFragment = (MainScreenFragment) fragment; - } - } - - private void showHomeMainFragmentFromClick(boolean addToBackStack){ + private void showHomeMainFragmentFromClick(){ FragmentManager fraMan = getSupportFragmentManager(); var fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); if(fragment instanceof MainScreenFragment mainFrag){ + var visible = mainFrag.isVisible(); if(!mainFrag.isVisible()){ - showMainFragment(fraMan, mainFrag, addToBackStack); + showMainFragment(fraMan, mainFrag, true); } - mainFrag.showButtonsFragmentIfNotNearby(addToBackStack); + mainFrag.showButtonsFragmentIfNotNearby(true); } else{ - createShowMainFragment(fraMan, MainScreenFragment.makeArgsButtonsScreen(), addToBackStack); + createShowMainFragment(fraMan, MainScreenFragment.makeArgsButtonsScreen(), true); } } @@ -714,10 +708,7 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen @Override public void readyGUIfor(FragmentKind fragmentType) { - MainScreenFragment mainFragmentIfVisible = getMainFragmentIfVisible(); - if (mainFragmentIfVisible!=null){ - mainFragmentIfVisible.readyGUIfor(fragmentType); - } + updateShowingFragmentKindInternal(fragmentType); Integer titleResId = null; @@ -758,38 +749,24 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen } if(getSupportActionBar()!=null && titleResId!=null) getSupportActionBar().setTitle(titleResId); + MainScreenFragment mainFragmentIfVisible = getMainFragmentIfVisible(); + if (mainFragmentIfVisible!=null){ + mainFragmentIfVisible.readyGUIfor(fragmentType); + } } @Override public void requestArrivalsForStopID(String ID) { - //register if the request came from the main fragment or not - MainScreenFragment probableFragment = getMainFragmentIfVisible(); - - // this has some contorted logic, but it works - if (probableFragment == null){ - FragmentManager fraMan = getSupportFragmentManager(); - Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); - Log.d(DEBUG_TAG, "Requested main fragment, not visible. Search by TAG returned: "+fragment); - if(fragment!=null){ - //the fragment is there but not shown - probableFragment = (MainScreenFragment) fragment; - // set the flag - probableFragment.setSuppressArrivalsReload(true); - showMainFragment(fraMan, probableFragment, true); - probableFragment.requestArrivalsForStopID(ID); - } else { - // we have no fragment - //if onCreate is complete, then we are not asking for the first showing fragment - final Bundle args = MainScreenFragment.makeArgsArrivals(ID); - boolean addtobackstack = onCreateComplete; - createShowMainFragment(fraMan, args ,addtobackstack); - } - } else { - //the MainScreeFragment is shown, nothing to do - probableFragment.requestArrivalsForStopID(ID); + Consumer consumer = fragment -> { + fragment.setSuppressArrivalsReload(true); + fragment.requestArrivalsForStopID(ID); + }; + boolean done = getMainFragmentAndDoStuff(consumer); + if(!done){ + //create the fragment + final Bundle args = MainScreenFragment.makeArgsArrivals(ID); + createShowMainFragment(getSupportFragmentManager(), args ,true); } - - mNavView.setCheckedItem(R.id.nav_home); } @Override public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom){ @@ -802,6 +779,23 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen } + private boolean getMainFragmentAndDoStuff(Consumer consumer){ + FragmentManager fraMan = getSupportFragmentManager(); + var frag = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); + if( frag instanceof MainScreenFragment mainFrag){ + if(!mainFrag.isVisible()) { + showMainFragment(fraMan, mainFrag, true); + mainFrag.setMainFragmentManagerTransition(true); + } else{ + mainFrag.setMainFragmentManagerTransition(false); + } + consumer.accept(mainFrag); + return true; + } else{ + return false; + } + } + @Override public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) { @@ -814,17 +808,9 @@ public class ActivityPrincipal extends GeneralActivity implements FragmentListen @Override public void openNearbyStopsFragment() { - FragmentManager fraMan = getSupportFragmentManager(); - var fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); - if(fragment instanceof MainScreenFragment mainFrag){ - if(!mainFrag.isVisible()){ - showMainFragment(fraMan, mainFrag, true); - } - mainFrag.openNearbyStopsFragment(); - } else{ - // there is no fragment and it is not visible - // add to back stack the main fragment, as the NearbyStopsFragment will not be added - createShowMainFragment(fraMan, MainScreenFragment.makeArgsNearby(), true); + boolean done = getMainFragmentAndDoStuff(MainScreenFragment::openNearbyStopsFragment); + if(!done){ + createShowMainFragment(getSupportFragmentManager(), MainScreenFragment.makeArgsNearby(), true); } } diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java index dc192c6..7102c78 100644 --- a/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java @@ -24,6 +24,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; import androidx.preference.PreferenceManager; +import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; @@ -34,8 +35,6 @@ import android.widget.TextView; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.fragments.FragmentListenerMain; -import it.reyboz.bustorino.util.RoutePositionSorter; -import it.reyboz.bustorino.util.StopSorterByDistance; import java.util.*; @@ -44,7 +43,7 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter stops; private @NonNull GPSPoint userPosition; private FragmentListenerMain listener; - private List< Pair > routesPairList; + private List< RouteWithStop > routesPairList; private final Context context; //Maximum number of stops to keep private final int MAX_STOPS = 20; //TODO: make it programmable @@ -52,7 +51,7 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter > routesPairList, FragmentListenerMain fragmentListener, Context con, @NonNull GPSPoint pos) { + public ArrivalsStopAdapter(@Nullable List< RouteWithStop > routesPairList, FragmentListenerMain fragmentListener, Context con, @NonNull GPSPoint pos) { listener = fragmentListener; userPosition = pos; this.routesPairList = routesPairList; @@ -81,10 +80,10 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter stopRoutePair = routesPairList.get(position); - if(stopRoutePair!=null && stopRoutePair.first!=null){ - final Stop stop = stopRoutePair.first; - final Route r = stopRoutePair.second; + final var stopRoutePair = routesPairList.get(position); + if(stopRoutePair != null){ + final Stop stop = stopRoutePair.getStop(); + final Route r = stopRoutePair.getRoute(); final Double distance = stop.getDistanceFromLocation(userPosition.getLatitude(), userPosition.longitude); if(distance!=Double.POSITIVE_INFINITY){ holder.distancetextView.setText(distance.intValue()+" m"); @@ -165,7 +164,7 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter stopList){ Collections.sort(stopList,new StopSorterByDistance(userPosition)); @@ -181,59 +180,92 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter> mRoutesPairList, @Nullable GPSPoint pos) { - if(pos!=null){ - this.userPosition = pos; + public void setRoutesPairListAndPosition(@NonNull List newList) { + if (routesPairList == null) { + routesPairList = new ArrayList<>(newList); + notifyItemRangeInserted(0, newList.size()); + return; } - if(mRoutesPairList!=null){ - //this.routesPairList = routesPairList; - //remove duplicates - sortAndRemoveDuplicates(mRoutesPairList, this.userPosition); - //routesPairList = mRoutesPairList; - //STUPID CODE - if (this.routesPairList == null || routesPairList.size() == 0){ - routesPairList = mRoutesPairList; - notifyDataSetChanged(); - } else{ - - final HashMap, Integer> indexMapIn = getRouteIndexMap(mRoutesPairList); - final HashMap, Integer> indexMapExisting = getRouteIndexMap(routesPairList); - //List> oldList = routesPairList; - routesPairList = mRoutesPairList; - /* - for (Pair pair: indexMapIn.keySet()){ - final Integer posIn = indexMapIn.get(pair); - if (posIn == null) continue; - if (indexMapExisting.containsKey(pair)){ - final Integer posExisting = indexMapExisting.get(pair); - //THERE IS ALREADY - //routesPairList.remove(posExisting.intValue()); - //routesPairList.add(posIn,mRoutesPairList.get(posIn)); - - notifyItemMoved(posExisting, posIn); - indexMapExisting.remove(pair); - } else{ - //INSERT IT - //routesPairList.add(posIn,mRoutesPairList.get(posIn)); - notifyItemInserted(posIn); + + DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return routesPairList.size(); + } + + @Override + public int getNewListSize() { + return newList.size(); } - }// - //REMOVE OLD STOPS - for (Pair pair: indexMapExisting.keySet()) { + + @Override + public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { + + RouteWithStop oldItem = routesPairList.get(oldItemPosition); + RouteWithStop newItem = newList.get(newItemPosition); + + // usa un ID univoco + return oldItem.getId().equals(newItem.getId()); + } + + @Override + public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { + + RouteWithStop oldItem = routesPairList.get(oldItemPosition); + RouteWithStop newItem = newList.get(newItemPosition); + + // confronta il contenuto + return oldItem.equals(newItem); + } + } + ); + + routesPairList.clear(); + routesPairList.addAll(newList); + + diffResult.dispatchUpdatesTo(this); + + + /*if (this.routesPairList == null || routesPairList.size() == 0){ + routesPairList = mRoutesPairList; + notifyDataSetChanged(); + } else{ + + final var indexMapIn = getRouteIndexMap(mRoutesPairList); + final var indexMapExisting = getRouteIndexMap(routesPairList); + //List> oldList = routesPairList; + routesPairList = mRoutesPairList; + /* + for (Pair pair: indexMapIn.keySet()){ + final Integer posIn = indexMapIn.get(pair); + if (posIn == null) continue; + if (indexMapExisting.containsKey(pair)){ final Integer posExisting = indexMapExisting.get(pair); - if (posExisting == null) continue; + //THERE IS ALREADY //routesPairList.remove(posExisting.intValue()); - notifyItemRemoved(posExisting); + //routesPairList.add(posIn,mRoutesPairList.get(posIn)); + + notifyItemMoved(posExisting, posIn); + indexMapExisting.remove(pair); + } else{ + //INSERT IT + //routesPairList.add(posIn,mRoutesPairList.get(posIn)); + notifyItemInserted(posIn); } - //*/notifyDataSetChanged(); - + }// + //REMOVE OLD STOPS + for (Pair pair: indexMapExisting.keySet()) { + final Integer posExisting = indexMapExisting.get(pair); + if (posExisting == null) continue; + //routesPairList.remove(posExisting.intValue()); + notifyItemRemoved(posExisting); } - //remove and join the - } + + */ } @@ -241,52 +273,27 @@ public class ArrivalsStopAdapter extends RecyclerView.Adapter> iterator = routesPairList.listIterator(); - Set> allRoutesDirections = new HashSet<>(); - while(iterator.hasNext()){ - final Pair stopRoutePair = iterator.next(); - if (stopRoutePair.second != null) { - final Pair routeNameDirection = new Pair<>(stopRoutePair.second.getName(), stopRoutePair.second.destinazione); - if (allRoutesDirections.contains(routeNameDirection)) { - iterator.remove(); - } else { - allRoutesDirections.add(routeNameDirection); - } - } - } - } - /** - * Sort and remove the repetitions in the list - */ - private static void sortAndRemoveDuplicates(List< Pair > routesPairList, GPSPoint positionToSort ){ - Collections.sort(routesPairList,new RoutePositionSorter(positionToSort)); + //Collections.sort(this.routesPairList,new RoutePositionSorter(userPosition)); //All of this to get only the first occurrences of a line (name & direction) - ListIterator> iterator = routesPairList.listIterator(); + var iterator = routesPairList.listIterator(); Set> allRoutesDirections = new HashSet<>(); while(iterator.hasNext()){ - final Pair stopRoutePair = iterator.next(); - if (stopRoutePair.second != null) { - final Pair routeNameDirection = new Pair<>(stopRoutePair.second.getName(), stopRoutePair.second.destinazione); - if (allRoutesDirections.contains(routeNameDirection)) { - iterator.remove(); - } else { - allRoutesDirections.add(routeNameDirection); - } + final var stopRoutePair = iterator.next(); + stopRoutePair.getRoute(); + final Pair routeNameDirection = new Pair<>(stopRoutePair.getRoute().getName(), stopRoutePair.getRoute().destinazione); + if (allRoutesDirections.contains(routeNameDirection)) { + iterator.remove(); + } else { + allRoutesDirections.add(routeNameDirection); } } } - private static HashMap, Integer> getRouteIndexMap(List> routesPairList){ - final HashMap, Integer> myMap = new HashMap<>(); + + private static HashMap getRouteIndexMap(List routesPairList){ + final HashMap myMap = new HashMap<>(); for (int i=0; i(name.toLowerCase(Locale.ROOT).trim(),destination.toLowerCase(Locale.ROOT).trim()), i); + myMap.put(routesPairList.get(i), i); } return myMap; } diff --git a/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java b/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java index 059b9ff..f1757e8 100644 --- a/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java +++ b/app/src/main/java/it/reyboz/bustorino/backend/FiveTNormalizer.java @@ -19,6 +19,7 @@ package it.reyboz.bustorino.backend; import android.util.Log; +import androidx.annotation.NonNull; /** * Converts some weird stop IDs found on the 5T website to the form used everywhere else (including GTT website). @@ -254,6 +255,7 @@ public abstract class FiveTNormalizer { return null; } } + @NonNull public static String fixShortNameForDisplay(String routeID, boolean withBarratoSpace) { /*if (routeID.length() == 3 && routeID.charAt(2) == 'B') { return routeID.substring(0, 2).concat("/"); diff --git a/app/src/main/java/it/reyboz/bustorino/backend/RouteWithStop.kt b/app/src/main/java/it/reyboz/bustorino/backend/RouteWithStop.kt new file mode 100644 index 0000000..50e0baa --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/backend/RouteWithStop.kt @@ -0,0 +1,10 @@ +package it.reyboz.bustorino.backend + +data class RouteWithStop( + val stop: Stop, + val route: Route, +) { + val id by lazy { + "stop${stop.ID}route${route.displayCode}" + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java index 5dc985f..b29e6f4 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -35,13 +35,14 @@ import it.reyboz.bustorino.middleware.*; import java.lang.ref.WeakReference; import java.util.List; +import java.util.concurrent.LinkedBlockingDeque; /** * Helper class to manage the fragments and their needs */ public class FragmentHelper { //GeneralActivity act; - private final FragmentListenerMain listenerMain; + private final FragmentListenerMain mainFragment; private final WeakReference managerWeakRef; private Stop lastSuccessfullySearchedBusStop; //support for multiple frames @@ -52,13 +53,16 @@ public class FragmentHelper { private static final String DEBUG_TAG = "BusTO FragmHelper"; private final StopSearcher stopSearcher; + //this is for deciding whether to popMain Fragment stack with children + private final LinkedBlockingDeque popMainQueueOnMainFragStack = new LinkedBlockingDeque<>(); + public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) { this(listener,framan, context,mainFrame,NO_FRAME); } public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) { - this.listenerMain = listener; + this.mainFragment = listener; this.managerWeakRef = new WeakReference<>(fraMan); this.primaryFrameLayout = primaryFrameLayout; this.secondaryFrameLayout = secondaryFrameLayout; @@ -79,7 +83,16 @@ public class FragmentHelper { this.lastSuccessfullySearchedBusStop = stop; } - + public boolean needToPopMainStackOnBack(){ + if(popMainQueueOnMainFragStack.isEmpty()){ + return true; + } + return popMainQueueOnMainFragStack.pop(); + } + public void setMainFragmentManagerTransition(boolean popMain){ + Log.d(DEBUG_TAG, "Adding child fragment pop for main screen: " + popMain); + popMainQueueOnMainFragStack.addFirst(popMain); + } /** * Called when you need to create a fragment for a specified Palina * @param p the Stop that needs to be displayed @@ -124,7 +137,7 @@ public class FragmentHelper { // enable fragment auto refresh arrivalsFragment.setReloadOnResume(true); - listenerMain.hideKeyboard(); + mainFragment.hideKeyboard(); toggleSpinner(false); } @@ -134,7 +147,7 @@ public class FragmentHelper { * @param query String queried */ public void createStopListFragment(List resultList, String query, boolean addToBackStack){ - listenerMain.hideKeyboard(); + mainFragment.hideKeyboard(); StopListFragment listfragment = StopListFragment.newInstance(query); if(managerWeakRef.get()==null) { //SOMETHING WENT VERY WRONG @@ -143,6 +156,8 @@ public class FragmentHelper { } attachFragmentToContainer(managerWeakRef.get(), listfragment, "search_"+query, false, addToBackStack); + //DO NOT DO THE SAME ON THE ARRIVALS (the call goes through MainActivity) + setMainFragmentManagerTransition(false); listfragment.setStopList(resultList); //listenerMain.readyGUIfor(FragmentKind.STOPS); toggleSpinner(false); @@ -154,7 +169,7 @@ public class FragmentHelper { * @param on new status of spinner system */ public void toggleSpinner(boolean on){ - listenerMain.toggleSpinner(on); + mainFragment.toggleSpinner(on); } /** diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java index 1282296..b4724b3 100644 --- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -49,8 +49,10 @@ import android.widget.Toast; import com.google.android.material.floatingactionbutton.FloatingActionButton; import java.util.Map; +import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; +import it.reyboz.bustorino.BuildConfig; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.util.Permissions; @@ -65,7 +67,7 @@ import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS; * Use the {@link MainScreenFragment#newInstance} factory method to * create an instance of this fragment. */ -public class MainScreenFragment extends BarcodeFragment implements FragmentListenerMain{ +public class MainScreenFragment extends BarcodeFragment implements FragmentListenerMain, ParentFragmentManagerFromChild{ private static final String SAVED_FRAGMENT="saved_fragment"; @@ -140,7 +142,8 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList private CoordinatorLayout coordLayout; //this is really a hackish thing, but it works - private LinkedBlockingQueue thingsToDoOnStart = new LinkedBlockingQueue<>(); + private final LinkedBlockingQueue thingsToDoOnStart = new LinkedBlockingQueue<>(); + private void refreshStop() { if(getContext() == null){ @@ -243,7 +246,6 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList } - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -261,6 +263,18 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList pendingSearchQuery = args.getString(ARG_SEARCH_QUERY); } + fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame); + + } + + @Override + public boolean needToPopMainStackOnBack() { + return fragmentHelper.needToPopMainStackOnBack(); + } + + @Override + public void setMainFragmentManagerTransition(boolean yes) { + fragmentHelper.setMainFragmentManagerTransition(yes); } @Override @@ -325,8 +339,6 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList childFragMan = getChildFragmentManager(); childFragMan.addOnBackStackChangedListener(() -> Log.d("BusTO Main Fragment", "BACK STACK CHANGED")); - fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame); - /* cr.setAccuracy(Criteria.ACCURACY_FINE); cr.setAltitudeRequired(false); @@ -793,8 +805,7 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList */ @Override public void readyGUIfor(FragmentKind fragmentType) { - - + if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Readying main fragment for type "+fragmentType); //if we are getting results, already, stop waiting for nearbyStops if (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS) { hideKeyboard(); @@ -815,7 +826,7 @@ public class MainScreenFragment extends BarcodeFragment implements FragmentList prepareGUIForBusStops(); break; default: - Log.d(DEBUG_TAG, "Fragment type is unknown"); + //Log.d(DEBUG_TAG, "Fragment type is unknown"); return; } // Shows hints diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.java deleted file mode 100644 index 0f2c81e..0000000 --- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.java +++ /dev/null @@ -1,122 +0,0 @@ -package it.reyboz.bustorino.fragments; - -import android.content.Context; -import android.util.Log; -import com.android.volley.NetworkError; -import com.android.volley.ParseError; -import com.android.volley.Response; -import com.android.volley.VolleyError; -import it.reyboz.bustorino.backend.NetworkVolleyManager; -import it.reyboz.bustorino.backend.Palina; -import it.reyboz.bustorino.backend.Route; -import it.reyboz.bustorino.backend.Stop; -import it.reyboz.bustorino.backend.mato.MapiArrivalRequest; - -import java.util.ArrayList; -import java.util.Date; -import java.util.HashMap; -import java.util.List; - -class NearbyArrivalsDownloader implements Response.Listener, Response.ErrorListener { - final static String DEBUG_TAG = "BusTO-NearbyArrivDowns"; - //final Map> routesToAdd = new HashMap<>(); - final ArrayList nonEmptyPalinas = new ArrayList<>(); - final HashMap completedRequests = new HashMap<>(); - final static String REQUEST_TAG = "NearbyArrivals"; - final NetworkVolleyManager volleyManager; - int activeRequestCount = 0, reqErrorCount = 0, reqSuccessCount = 0; - - final ArrivalsListener listener; - - NearbyArrivalsDownloader(Context context, ArrivalsListener arrivalsListener) { - volleyManager = NetworkVolleyManager.getInstance(context); - - listener = arrivalsListener; - //flatProgressBar.setMax(numreq); - } - - public int requestArrivalsForStops(List stops){ - int MAX_ARRIVAL_STOPS = 35; - Date currentDate = new Date(); - int timeRange = 3600; - int departures = 10; - int numreq = 0; - activeRequestCount = 0; - reqErrorCount = 0; - reqSuccessCount = 0; - nonEmptyPalinas.clear(); - completedRequests.clear(); - - for (Stop s : stops.subList(0, Math.min(stops.size(), MAX_ARRIVAL_STOPS))) { - - final MapiArrivalRequest req = new MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this); - req.setTag(REQUEST_TAG); - volleyManager.addToRequestQueue(req); - activeRequestCount++; - numreq++; - completedRequests.put(s.ID, false); - } - listener.setProgress(reqErrorCount+reqSuccessCount, activeRequestCount); - return numreq; - } - - private int totalRequests(){ - return activeRequestCount + reqSuccessCount + reqErrorCount; - } - - - @Override - public void onErrorResponse(VolleyError error) { - if (error instanceof ParseError) { - //TODO - Log.w(DEBUG_TAG, "Parsing error for stop request"); - } else if (error instanceof NetworkError) { - String s; - if (error.networkResponse != null) - s = new String(error.networkResponse.data); - else s = ""; - Log.w(DEBUG_TAG, "Network error: " + s); - } else { - Log.w(DEBUG_TAG, "Volley Error: " + error.getMessage()); - } - if (error.networkResponse != null) { - Log.w(DEBUG_TAG, "Error status code: " + error.networkResponse.statusCode); - } - //counters - activeRequestCount--; - reqErrorCount++; - //flatProgressBar.setProgress(reqErrorCount + reqSuccessCount); - listener.setProgress(reqErrorCount + reqSuccessCount, activeRequestCount); - } - - @Override - public void onResponse(Palina palinaResult) { - //counter for requests - activeRequestCount--; - reqSuccessCount++; - listener.setProgress(reqErrorCount + reqSuccessCount, activeRequestCount); - - //add the palina to the successful one - if(palinaResult!=null) { - final List routes = palinaResult.queryAllRoutes(); - if (routes != null && !routes.isEmpty()) { - nonEmptyPalinas.add(palinaResult); - listener.showCompletedArrivals(nonEmptyPalinas); - } - } - } - - void cancelAllRequests() { - volleyManager.getRequestQueue().cancelAll(REQUEST_TAG); - //flatProgressBar.setVisibility(View.GONE); - listener.onAllRequestsCancelled(); - } - - public interface ArrivalsListener{ - void setProgress(int completedRequests, int pendingRequests); - - void onAllRequestsCancelled(); - - void showCompletedArrivals(ArrayList completedPalinas); - } -} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.kt b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.kt new file mode 100644 index 0000000..8e7007a --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyArrivalsDownloader.kt @@ -0,0 +1,113 @@ +package it.reyboz.bustorino.fragments + +import android.content.Context +import android.util.Log +import com.android.volley.NetworkError +import com.android.volley.ParseError +import com.android.volley.Response +import com.android.volley.VolleyError +import it.reyboz.bustorino.backend.NetworkVolleyManager +import it.reyboz.bustorino.backend.Palina +import it.reyboz.bustorino.backend.Stop +import it.reyboz.bustorino.backend.mato.MapiArrivalRequest +import java.util.* +import kotlin.math.min + +internal class NearbyArrivalsDownloader(context: Context, val listener: ArrivalsListener) : Response.Listener, + Response.ErrorListener { + //final Map> routesToAdd = new HashMap<>(); + val nonEmptyPalinas = ArrayList() + val completedRequests: HashMap = HashMap() + val volleyManager: NetworkVolleyManager = NetworkVolleyManager.getInstance(context) + var activeRequestCount: Int = 0 + var reqErrorCount: Int = 0 + var reqSuccessCount: Int = 0 + + + fun requestArrivalsForStops(stops: List): Int { + val currentDate = Date() + val timeRange = 3600 + val departures = 10 + var numreq = 0 + activeRequestCount = 0 + reqErrorCount = 0 + reqSuccessCount = 0 + nonEmptyPalinas.clear() + completedRequests.clear() + + for (s in stops.subList(0, min(stops.size, MAX_ARRIVAL_STOPS))) { + val req = MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this) + req.setTag(REQUEST_TAG) + volleyManager.addToRequestQueue(req) + activeRequestCount++ + numreq++ + completedRequests[s.ID] = false + } + listener.setProgress(reqErrorCount + reqSuccessCount, activeRequestCount) + return numreq + } + + private fun totalRequests(): Int { + return activeRequestCount + reqSuccessCount + reqErrorCount + } + + + override fun onErrorResponse(error: VolleyError) { + if (error is ParseError) { + //TODO + Log.w(DEBUG_TAG, "Parsing error for stop request") + } else if (error is NetworkError) { + val s: String? + if (error.networkResponse != null) s = String(error.networkResponse.data) + else s = "" + Log.w(DEBUG_TAG, "Network error: " + s) + } else { + Log.w(DEBUG_TAG, "Volley Error: " + error.message) + } + if (error.networkResponse != null) { + Log.w(DEBUG_TAG, "Error status code: " + error.networkResponse.statusCode) + } + //counters + activeRequestCount-- + reqErrorCount++ + //flatProgressBar.setProgress(reqErrorCount + reqSuccessCount); + listener.setProgress(reqErrorCount + reqSuccessCount, activeRequestCount) + } + + override fun onResponse(palinaResult: Palina?) { + //counter for requests + activeRequestCount-- + reqSuccessCount++ + listener.setProgress(reqErrorCount + reqSuccessCount, activeRequestCount) + + //add the palina to the successful one + if (palinaResult != null) { + val routes = palinaResult.queryAllRoutes() + if (routes != null && !routes.isEmpty()) { + nonEmptyPalinas.add(palinaResult) + listener.showCompletedArrivals(nonEmptyPalinas) + } + } + } + + fun cancelAllRequests() { + volleyManager.getRequestQueue().cancelAll(REQUEST_TAG) + //flatProgressBar.setVisibility(View.GONE); + listener.onAllRequestsCancelled() + } + + interface ArrivalsListener { + fun setProgress(completedRequests: Int, pendingRequests: Int) + + fun onAllRequestsCancelled() + + fun showCompletedArrivals(completedPalinas: ArrayList) + } + + companion object { + private const val DEBUG_TAG: String = "BusTO-NearbyArrivDowns" + const val REQUEST_TAG: String = "NearbyArrivals" + private const val MAX_ARRIVAL_STOPS = 35 + + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java deleted file mode 100644 index cc0eca8..0000000 --- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ /dev/null @@ -1,716 +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.annotation.SuppressLint; -import android.content.Context; - -import android.content.SharedPreferences; -import android.location.Location; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModelProvider; -import androidx.core.util.Pair; -import androidx.preference.PreferenceManager; -import androidx.appcompat.widget.AppCompatButton; -import androidx.recyclerview.widget.RecyclerView; -import androidx.work.WorkInfo; - -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import android.widget.ProgressBar; -import android.widget.TextView; -import it.reyboz.bustorino.BuildConfig; -import it.reyboz.bustorino.R; -import it.reyboz.bustorino.adapters.ArrivalsStopAdapter; -import it.reyboz.bustorino.backend.*; -import it.reyboz.bustorino.data.DatabaseUpdate; -import it.reyboz.bustorino.adapters.SquareStopAdapter; -import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager; -import it.reyboz.bustorino.middleware.FusedNativeLocationProvider; -import it.reyboz.bustorino.util.Permissions; -import it.reyboz.bustorino.util.StopSorterByDistance; -import it.reyboz.bustorino.viewmodels.NearbyStopsViewModel; -import org.jetbrains.annotations.NotNull; - -import java.util.*; - -public class NearbyStopsFragment extends ScreenBaseFragment { - - @Nullable - @Override - public View getBaseViewForSnackBar() { - return null; - } - - public enum FragType{ - STOPS(1), ARRIVALS(2); - private final int num; - FragType(int num){ - this.num = num; - } - public static FragType fromNum(int i){ - switch (i){ - case 1: return STOPS; - case 2: return ARRIVALS; - default: - throw new IllegalArgumentException("type not recognized"); - } - } - } - private enum LocationShowingStatus {SEARCHING, FIRST_FIX, DISABLED, NO_PERMISSION} - - private FragmentListenerMain mListener; - - private final static String DEBUG_TAG = "NearbyStopsFragment"; - private final static String FRAGMENT_TYPE_KEY = "FragmentType"; - //public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20; - private FragType fragment_type = FragType.STOPS; - - public final static String FRAGMENT_TAG="NearbyStopsFrag"; - - private RecyclerView gridRecyclerView; - - private SquareStopAdapter dataAdapter; - private AutoFitGridLayoutManager gridLayoutManager; - private GPSPoint lastPosition = null; - private ProgressBar circlingProgressBar,flatProgressBar; - //protected SharedPreferences globalSharedPref; - //private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; - private TextView messageTextView,titleTextView, loadingTextView; - private CommonScrollListener scrollListener; - private AppCompatButton switchButton; - private boolean firstLocForStops = true,firstLocForArrivals = true; - public static final int COLUMN_WIDTH_DP = 250; - - - private Integer MAX_DISTANCE = -3; - private int MIN_NUM_STOPS = -1; - - //These are useful for the case of nearby arrivals - private NearbyArrivalsDownloader arrivalsManager = null; - private ArrivalsStopAdapter arrivalsStopAdapter = null; - - private ArrayList currentNearbyStops = new ArrayList<>(); - - private LocationShowingStatus showingStatus = LocationShowingStatus.NO_PERMISSION; - private boolean isLocationEnabled = false; - - private final FusedNativeLocationProvider.LocationUpdateListener locationUpdateListener = new FusedNativeLocationProvider.LocationUpdateListener() { - @Override - public void onLocationUpdate(@NotNull Location location) { - updateLocationViewModel(location); - } - - @Override - public void onFusedStatusChanged(boolean isEnabled) { - Log.d(DEBUG_TAG, "Location provider is enabled: " + isEnabled); - isLocationEnabled = isEnabled; - if(isEnabled){ - setShowingStatus(LocationShowingStatus.SEARCHING); - } else{ - setShowingStatus(LocationShowingStatus.DISABLED); - } - } - }; - private final FusedNativeLocationProvider.Options locationOptionsArrivals = new FusedNativeLocationProvider.Options(5*1000L, 50f), - locationOptionsStops = new FusedNativeLocationProvider.Options(1000L, 5f);; - - - - /* - TODO: we do not request the permission in this fragment, only showing it when we have the location. Request position if this changes. - private final ActivityResultLauncher permissionsResultLauncher = getPositionRequestLauncher( - granted ->{ - - } - ); - */ - private FusedNativeLocationProvider locationProvider = null; - - - private final NearbyArrivalsDownloader.ArrivalsListener arrivalsListener = new NearbyArrivalsDownloader.ArrivalsListener() { - @Override - public void setProgress(int completedRequests, int pendingRequests) { - if(flatProgressBar!=null) { - if (pendingRequests == 0) { - flatProgressBar.setIndeterminate(true); - flatProgressBar.setVisibility(View.GONE); - } else { - flatProgressBar.setIndeterminate(false); - flatProgressBar.setProgress(completedRequests); - } - } - } - - @Override - public void onAllRequestsCancelled() { - if(flatProgressBar!=null) flatProgressBar.setVisibility(View.GONE); - } - - @Override - public void showCompletedArrivals(ArrayList completedPalinas) { - showArrivalsInRecycler(completedPalinas); - } - }; - - //ViewModel - private NearbyStopsViewModel viewModel; - - public NearbyStopsFragment() { - // Required empty public constructor - } - - /** - * Use this factory method to create a new instance of - * this fragment using the provided parameters. - * @return A new instance of fragment NearbyStopsFragment. - */ - public static NearbyStopsFragment newInstance(FragType type) { - //if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS ) - // throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED"); - NearbyStopsFragment fragment = new NearbyStopsFragment(); - final Bundle args = new Bundle(1); - args.putInt(FRAGMENT_TYPE_KEY,type.num); - fragment.setArguments(args); - return fragment; - } - - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - if (getArguments() != null) { - setFragmentType(FragType.fromNum(getArguments().getInt(FRAGMENT_TYPE_KEY))); - } - //locManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); - //fragmentLocationListener = new FragmentLocationListener(); - if (getContext()!=null) { - //globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); - //globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener); - } - - //NearbyArrivalsDownloader nearbyArrivalsDownloader = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); - locationProvider = new FusedNativeLocationProvider(requireContext()); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - // Inflate the layout for this fragment - if (getContext() == null) throw new RuntimeException(); - View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false); - gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView); - gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), Float.valueOf(utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP)).intValue()); - gridRecyclerView.setLayoutManager(gridLayoutManager); - gridRecyclerView.setHasFixedSize(false); - circlingProgressBar = root.findViewById(R.id.circularProgressBar); - flatProgressBar = root.findViewById(R.id.horizontalProgressBar); - messageTextView = root.findViewById(R.id.messageTextView); - titleTextView = root.findViewById(R.id.titleTextView); - loadingTextView = root.findViewById(R.id.positionLoadingTextView); - switchButton = root.findViewById(R.id.switchButton); - - scrollListener = new CommonScrollListener(mListener,false); - switchButton.setOnClickListener(v -> switchFragmentType()); - if(BuildConfig.DEBUG) - Log.d(DEBUG_TAG, "onCreateView"); - - final Context appContext =requireContext().getApplicationContext(); - DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer>() { - @SuppressLint("MissingPermission") - @Override - public void onChanged(List workInfos) { - if(workInfos.isEmpty()) { - viewModel.setDBUpdateRunning(false); - return; - } - - WorkInfo wi = workInfos.get(0); - if (wi.getState() == WorkInfo.State.RUNNING && locationProvider.isRunning()) { - locationProvider.stopUpdates(); - viewModel.setDBUpdateRunning(true); - } else{ - //start the request - if(Permissions.bothLocationPermissionsGranted(requireContext())) { - if(!locationProvider.isRunning()){ - startLocationUpdatesByType(); - } - } else{ - setShowingStatus(LocationShowingStatus.NO_PERMISSION); - } - - viewModel.setDBUpdateRunning(false); - //actually restart request - } - } - }); - - //observe the livedata - viewModel.getStopsAtDistance().observe(getViewLifecycleOwner(), stops -> { - Log.d(DEBUG_TAG, "Received "+stops.size()+" stops nearby"); - Integer distance = viewModel.getDistanceMtLiveData().getValue(); - if(distance == null){ - distance = 40; - } - if ((stops.size() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) { - viewModel.setDistance(distance + 40); - //viewModel.requestStopsAtDistance(distance, true); - //Log.d(DEBUG_TAG, "Doubling distance now!"); - return; // THIS WORKS AS AN `else` - } - if(!stops.isEmpty()) { - currentNearbyStops =stops; - showStopsInViews(currentNearbyStops, lastPosition); - } - }); - if(Permissions.anyLocationPermissionsGranted(appContext)){ - setShowingStatus(LocationShowingStatus.SEARCHING); - } else { - setShowingStatus(LocationShowingStatus.NO_PERMISSION); - - } - //add location listener - locationProvider.addListener(locationUpdateListener); - - return root; - } - - //because linter is stupid and cannot look inside *anyLocationPermissionGranted* - @SuppressLint("MissingPermission") - private boolean requestLocationUpdates(){ - if(Permissions.anyLocationPermissionsGranted(requireContext())) { - startLocationUpdatesByType(); - return true; - } else return false; - } - - /** - * Internal bit used to start location updates - */ - private void startLocationUpdatesByType(){ - switch (fragment_type) { - case STOPS: locationProvider.startUpdates(locationOptionsStops); break; - case ARRIVALS: locationProvider.startUpdates(locationOptionsArrivals); break; - } - } - - - - /** - * Use this method to set the fragment type - * @param type the type, TYPE_ARRIVALS or TYPE_STOPS - */ - private void setFragmentType(FragType type){ - boolean isChanged = fragment_type != type; - this.fragment_type = type; - /*switch(type){ - case ARRIVALS: - TIME_INTERVAL_REQUESTS = 5*1000; - break; - case STOPS: - TIME_INTERVAL_REQUESTS = 1000; - - } - - */ - if(isChanged){ - startLocationUpdatesByType(); - setShowingStatus(LocationShowingStatus.SEARCHING); - } - } - /** - * Set the location in the view model if it is good - * @param location new location - */ - private void updateLocationViewModel(@NonNull Location location, float accuracy){ - if(viewModel==null) { - return; - } - if(location.getAccuracy() stops, GPSPoint location){ - if (stops.isEmpty()) { - setNoStopsLayout(); - return; - } - if (location == null){ - // we could do something better, but it's better to do this for now - return; - } - - double minDistance = Double.POSITIVE_INFINITY; - for(Stop s: stops){ - minDistance = Math.min(minDistance, s.getDistanceFromLocation(location.getLatitude(), location.getLongitude())); - } - - - //quick trial to hopefully always get the stops in the correct order - Collections.sort(stops,new StopSorterByDistance(location)); - switch (fragment_type){ - case STOPS: - showStopsInRecycler(stops); - break; - case ARRIVALS: - if(getContext()==null) break; //don't do anything if we're not attached - if(arrivalsManager==null) - arrivalsManager = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); - arrivalsManager.requestArrivalsForStops(stops); - /*flatProgressBar.setVisibility(View.VISIBLE); - flatProgressBar.setProgress(0); - flatProgressBar.setIndeterminate(false); - */ - //for the moment, be satisfied with only one location - //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener); - break; - default: - } - - } - - /** - * To enable targeting from the Button - */ - public void switchFragmentType(View v){ - switchFragmentType(); - } - - /** - * Call when you need to switch the type of fragment - */ - private void switchFragmentType(){ - switch (fragment_type){ - case ARRIVALS: - setFragmentType(FragType.STOPS); - break; - case STOPS: - setFragmentType(FragType.ARRIVALS); - break; - default: - } - prepareForFragmentType(); - //locManager.removeLocationRequestFor(fragmentLocationListener); - //locManager.addLocationRequestFor(fragmentLocationListener); - if(lastPosition!=null) { - // we have at least one fix on the position - showStopsInViews(currentNearbyStops, lastPosition); - } - } - - /** - * Prepare the views for the set fragment type - */ - private void prepareForFragmentType(){ - if(fragment_type==FragType.STOPS){ - switchButton.setText(getString(R.string.show_arrivals)); - titleTextView.setText(getString(R.string.nearby_stops_message)); - if(arrivalsManager!=null) - arrivalsManager.cancelAllRequests(); - if(dataAdapter!=null) - gridRecyclerView.setAdapter(dataAdapter); - - } else if (fragment_type==FragType.ARRIVALS){ - titleTextView.setText(getString(R.string.nearby_arrivals_message)); - switchButton.setText(getString(R.string.show_stops)); - if(arrivalsStopAdapter!=null) - gridRecyclerView.setAdapter(arrivalsStopAdapter); - } - } - - //useful methods - - /////// GUI METHODS //////// - private void showStopsInRecycler(List stops){ - - if(firstLocForStops) { - dataAdapter = new SquareStopAdapter(stops, mListener, lastPosition); - gridRecyclerView.setAdapter(dataAdapter); - firstLocForStops = false; - }else { - dataAdapter.setStops(stops); - dataAdapter.setUserPosition(lastPosition); - } - dataAdapter.notifyDataSetChanged(); - - //showRecyclerHidingLoadMessage(); - if (gridRecyclerView.getVisibility() != View.VISIBLE) { - circlingProgressBar.setVisibility(View.GONE); - loadingTextView.setVisibility(View.GONE); - gridRecyclerView.setVisibility(View.VISIBLE); - } - messageTextView.setVisibility(View.GONE); - - if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_STOPS); - } - - private void showArrivalsInRecycler(List palinas){ - Collections.sort(palinas,new StopSorterByDistance(lastPosition)); - - final ArrayList> routesPairList = new ArrayList<>(10); - //int maxNum = Math.min(MAX_STOPS, stopList.size()); - for(Palina p: palinas){ - //if there are no routes available, skip stop - if(p.queryAllRoutes().isEmpty()) continue; - for(Route r: p.queryAllRoutes()){ - //if there are no routes, should not do anything - if (r.passaggi != null && !r.passaggi.isEmpty()) - routesPairList.add(new Pair<>(p,r)); - } - } - if (getContext()==null){ - Log.e(DEBUG_TAG, "Trying to show arrivals in Recycler but we're not attached"); - return; - } - if(firstLocForArrivals){ - arrivalsStopAdapter = new ArrivalsStopAdapter(routesPairList,mListener,getContext(),lastPosition); - gridRecyclerView.setAdapter(arrivalsStopAdapter); - firstLocForArrivals = false; - } else { - arrivalsStopAdapter.setRoutesPairListAndPosition(routesPairList,lastPosition); - } - - //arrivalsStopAdapter.notifyDataSetChanged(); - - showRecyclerHidingLoadMessage(); - if(mListener!=null) mListener.readyGUIfor(FragmentKind.NEARBY_ARRIVALS); - - } - - private void setNoStopsLayout(){ - messageTextView.setVisibility(View.VISIBLE); - messageTextView.setText(R.string.no_stops_nearby); - circlingProgressBar.setVisibility(View.GONE); - loadingTextView.setVisibility(View.GONE); - } - - /** - * Does exactly what is says on the tin - */ - private void showRecyclerHidingLoadMessage(){ - if (gridRecyclerView.getVisibility() != View.VISIBLE) { - circlingProgressBar.setVisibility(View.GONE); - loadingTextView.setVisibility(View.GONE); - gridRecyclerView.setVisibility(View.VISIBLE); - } - messageTextView.setVisibility(View.GONE); - } - - /* - * Local locationListener, to use for the GPS - */ - /* - class FragmentLocationListener implements LocationListenerCompat { - - private long lastUpdateTime = -1; - public boolean isRegistered = false; - - @Override - public void onLocationChanged(@NonNull Location location) { - if(viewModel==null){ - return; - } - if(location.getAccuracy()<200) { - - lastPosition = new GPSPoint(location.getLatitude(), location.getLongitude()); - //viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true); - viewModel.setLastLocation(location); - } - lastUpdateTime = System.currentTimeMillis(); - //Log.d("BusTO:NearPositListen","can start request for stops: "+ !dbUpdateRunning); - } - - @Override - public void onProviderEnabled(@NonNull String provider) { - Log.d(DEBUG_TAG, "Location provider "+provider+" enabled"); - if(provider.equals(LocationManager.GPS_PROVIDER)){ - setShowingStatus(LocationShowingStatus.SEARCHING); - } - } - - @Override - public void onProviderDisabled(@NonNull String provider) { - Log.d(DEBUG_TAG, "Location provider "+provider+" disabled"); - if(provider.equals(LocationManager.GPS_PROVIDER)) { - setShowingStatus(LocationShowingStatus.DISABLED); - } - } - - @Override - public void onStatusChanged(@NonNull String provider, int status, @Nullable Bundle extras) { - LocationListenerCompat.super.onStatusChanged(provider, status, extras); - } - } - - */ - -} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt new file mode 100644 index 0000000..1478be5 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt @@ -0,0 +1,681 @@ +/* + 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.annotation.SuppressLint +import android.content.Context +import android.location.Location +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ProgressBar +import android.widget.TextView +import androidx.appcompat.widget.AppCompatButton +import androidx.core.util.Pair +import androidx.fragment.app.viewModels +import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.RecyclerView +import androidx.work.WorkInfo +import it.reyboz.bustorino.BuildConfig +import it.reyboz.bustorino.R +import it.reyboz.bustorino.adapters.ArrivalsStopAdapter +import it.reyboz.bustorino.adapters.SquareStopAdapter +import it.reyboz.bustorino.backend.* +import it.reyboz.bustorino.data.DatabaseUpdate +import it.reyboz.bustorino.fragments.NearbyArrivalsDownloader.ArrivalsListener +import it.reyboz.bustorino.middleware.AutoFitGridLayoutManager +import it.reyboz.bustorino.middleware.FusedNativeLocationProvider +import it.reyboz.bustorino.middleware.FusedNativeLocationProvider.LocationUpdateListener +import it.reyboz.bustorino.util.Permissions.Companion.anyLocationPermissionsGranted +import it.reyboz.bustorino.util.Permissions.Companion.bothLocationPermissionsGranted +import it.reyboz.bustorino.util.StopSorterByDistance +import it.reyboz.bustorino.viewmodels.NearbyStopsViewModel +import java.util.* +import kotlin.math.min + +class NearbyStopsFragment : ScreenBaseFragment() { + override fun getBaseViewForSnackBar(): View? { + return null + } + + enum class FragType(val num: Int) { + STOPS(1), ARRIVALS(2); + + companion object { + @JvmStatic + fun fromNum(i: Int): FragType { + when (i) { + 1 -> return STOPS + 2 -> return ARRIVALS + else -> throw IllegalArgumentException("type not recognized") + } + } + } + } + + private enum class LocationShowingStatus { + SEARCHING, FIRST_FIX, DISABLED, NO_PERMISSION + } + + private var mListener: FragmentListenerMain? = null + + private var fragment_type = FragType.STOPS + + private lateinit var gridRecyclerView: RecyclerView + + private var dataAdapter: SquareStopAdapter? = null + private var gridLayoutManager: AutoFitGridLayoutManager? = null + private var lastPosition: GPSPoint? = null + private var circlingProgressBar: ProgressBar? = null + private lateinit var flatProgressBar: ProgressBar + + //protected SharedPreferences globalSharedPref; + //private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener; + private var messageTextView: TextView? = null + private var titleTextView: TextView? = null + private var loadingTextView: TextView? = null + private var scrollListener: CommonScrollListener? = null + private var switchButton: AppCompatButton? = null + private var firstLocForStops = true + private var firstLocForArrivals = true + private var stopsMaxDistance = -3 + private var stopsMinNumber = -1 + + //These are useful for the case of nearby arrivals + private var arrivalsManager: NearbyArrivalsDownloader? = null + private var arrivalsStopAdapter: ArrivalsStopAdapter? = null + + private var currentNearbyStops = ArrayList() + + private var showingStatus = LocationShowingStatus.NO_PERMISSION + private var isLocationEnabled = false + + private val locationUpdateListener: LocationUpdateListener = object : LocationUpdateListener { + override fun onLocationUpdate(location: Location) { + updateLocationViewModel(location) + } + + override fun onFusedStatusChanged(isEnabled: Boolean) { + Log.d(DEBUG_TAG, "Location provider is enabled: " + isEnabled) + isLocationEnabled = isEnabled + if (isEnabled) { + setShowingStatus(LocationShowingStatus.SEARCHING) + } else { + setShowingStatus(LocationShowingStatus.DISABLED) + } + } + } + private val locationOptionsArrivals = FusedNativeLocationProvider.Options(5 * 1000L, 50f) + private val locationOptionsStops = FusedNativeLocationProvider.Options(1000L, 5f) + + + /* + TODO: we do not request the permission in this fragment, only showing it when we have the location. Request position if this changes. + private final ActivityResultLauncher permissionsResultLauncher = getPositionRequestLauncher( + granted ->{ + + } + ); + */ + private var locationProvider: FusedNativeLocationProvider? = null + + + /*private val arrivalsListener: ArrivalsListener = object : ArrivalsListener { + override fun setProgress(completedRequests: Int, pendingRequests: Int) { + if (pendingRequests == 0) { + flatProgressBar.setIndeterminate(true) + flatProgressBar.setVisibility(View.GONE) + } else { + flatProgressBar.setIndeterminate(false) + flatProgressBar.progress = completedRequests + } + } + + /*override fun onAllRequestsCancelled() { + if (flatProgressBar != null) flatProgressBar!!.setVisibility(View.GONE) + } + + */ + + override fun showCompletedArrivals(completedPalinas: ArrayList) { + showArrivalsInRecycler(completedPalinas) + } + } + + */ + + //ViewModel + private val viewModel : NearbyStopsViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + arguments?.let{ + + setFragmentType(FragType.fromNum(it.getInt(FRAGMENT_TYPE_KEY))) + } + //locManager = (LocationManager) requireContext().getSystemService(Context.LOCATION_SERVICE); + //fragmentLocationListener = new FragmentLocationListener(); + if (getContext() != null) { + //globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE); + //globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener); + } + + //NearbyArrivalsDownloader nearbyArrivalsDownloader = new NearbyArrivalsDownloader(getContext().getApplicationContext(), arrivalsListener); + locationProvider = FusedNativeLocationProvider(requireContext()) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + // Inflate the layout for this fragment + if (getContext() == null) throw RuntimeException() + val root = inflater.inflate(R.layout.fragment_nearby_stops, container, false) + gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView) + gridLayoutManager = AutoFitGridLayoutManager( + requireContext().getApplicationContext(), + utils.convertDipToPixels(getContext(), COLUMN_WIDTH_DP.toFloat()).toInt() + ) + gridRecyclerView.setLayoutManager(gridLayoutManager) + gridRecyclerView.setHasFixedSize(false) + circlingProgressBar = root.findViewById(R.id.circularProgressBar) + flatProgressBar = root.findViewById(R.id.horizontalProgressBar) + messageTextView = root.findViewById(R.id.messageTextView) + titleTextView = root.findViewById(R.id.titleTextView) + loadingTextView = root.findViewById(R.id.positionLoadingTextView) + switchButton = root.findViewById(R.id.switchButton) + + scrollListener = CommonScrollListener(mListener, false) + switchButton!!.setOnClickListener(View.OnClickListener { v: View? -> switchFragmentType() }) + if (BuildConfig.DEBUG) Log.d(DEBUG_TAG, "onCreateView") + + val appContext = requireContext().applicationContext + DatabaseUpdate.watchUpdateWorkStatus(context, this){ workInfos -> + if (workInfos.isEmpty()) { + viewModel.setDBUpdateRunning(false) + return@watchUpdateWorkStatus + } + + val wi = workInfos.get(0) + if (wi.state == WorkInfo.State.RUNNING && locationProvider!!.isRunning()) { + locationProvider!!.stopUpdates() + viewModel.setDBUpdateRunning(true) + } else { + //start the request + if (bothLocationPermissionsGranted(requireContext())) { + if (!locationProvider!!.isRunning()) { + startLocationUpdatesByType() + } + } else { + setShowingStatus(LocationShowingStatus.NO_PERMISSION) + } + + viewModel.setDBUpdateRunning(false) + //actually restart request + } + } + + + if (anyLocationPermissionsGranted(appContext)) { + setShowingStatus(LocationShowingStatus.SEARCHING) + } else { + setShowingStatus(LocationShowingStatus.NO_PERMISSION) + } + //add location listener + locationProvider!!.addListener(locationUpdateListener) + + return root + } + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + gridRecyclerView.setVisibility(View.INVISIBLE) + gridRecyclerView.addOnScrollListener(scrollListener!!) + mListener?.readyGUIfor(FragmentKind.NEARBY_STOPS) + + //observe the livedata + viewModel.stopsAtDistance.observe(getViewLifecycleOwner()) {stops -> + Log.d(DEBUG_TAG, "Received " + stops.size + " stops nearby") + var distance = viewModel.distanceMtLiveData.getValue() + if (distance == null) { + distance = 40 + } + if ((stops.size < stopsMinNumber && distance <= stopsMaxDistance)) { + viewModel.setDistance(distance + 40) + //viewModel.requestStopsAtDistance(distance, true); + //Log.d(DEBUG_TAG, "Doubling distance now!"); + return@observe // THIS WORKS AS AN `else` + } + if (!stops.isEmpty()) { + currentNearbyStops = stops + showStopsInViews(currentNearbyStops, lastPosition) + } + } + + viewModel.downloadingArrivals.observe(viewLifecycleOwner){ running -> + if(!running) flatProgressBar.visibility = View.GONE + else flatProgressBar.visibility = View.VISIBLE + } + viewModel.progressPerc.observe(viewLifecycleOwner){ progress -> + flatProgressBar.isIndeterminate = false + flatProgressBar.progress = progress + flatProgressBar.max = 100 + + if (progress<100){ + flatProgressBar.visibility = View.VISIBLE + } + + } + + viewModel.arrivalsDecoupled.observe(viewLifecycleOwner){ stoprouteList -> + if (getContext() == null) { + Log.e(DEBUG_TAG, "Trying to show arrivals in Recycler but we're not attached") + return@observe + } + if (firstLocForArrivals) { + arrivalsStopAdapter = ArrivalsStopAdapter(stoprouteList, mListener, getContext(), lastPosition!!) + gridRecyclerView.setAdapter(arrivalsStopAdapter) + firstLocForArrivals = false + } else { + arrivalsStopAdapter!!.setRoutesPairListAndPosition(stoprouteList) + } + + //arrivalsStopAdapter.notifyDataSetChanged(); + showRecyclerHidingLoadMessage() + if (mListener != null) mListener!!.readyGUIfor(FragmentKind.NEARBY_ARRIVALS) + } + } + + + /** + * Internal bit used to start location updates + */ + private fun startLocationUpdatesByType() { + when (fragment_type) { + FragType.STOPS -> locationProvider!!.startUpdates(locationOptionsStops) + FragType.ARRIVALS -> locationProvider!!.startUpdates(locationOptionsArrivals) + } + } + + + /** + * Use this method to set the fragment type + * @param type the type, TYPE_ARRIVALS or TYPE_STOPS + */ + private fun setFragmentType(type: FragType) { + val isChanged = fragment_type != type + this.fragment_type = type + /*switch(type){ + case ARRIVALS: + TIME_INTERVAL_REQUESTS = 5*1000; + break; + case STOPS: + TIME_INTERVAL_REQUESTS = 1000; + + } + + */ + if (isChanged) { + startLocationUpdatesByType() + setShowingStatus(LocationShowingStatus.SEARCHING) + } + } + + /** + * Set the location in the view model if it is good + * @param location new location + */ + private fun updateLocationViewModel(location: Location, accuracy: Float = 150f) { + if (location.getAccuracy() < accuracy) { + lastPosition = GPSPoint(location.getLatitude(), location.getLongitude()) + //viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true); + viewModel.setLastLocation(location) + } + } + + private fun setShowingStatus(newStatus: LocationShowingStatus) { + var newStatus = newStatus + if (BuildConfig.DEBUG) Log.d(DEBUG_TAG, "Asked to set showing status : $newStatus") + if (newStatus == showingStatus) { + return + } + if (!isLocationEnabled && newStatus != LocationShowingStatus.NO_PERMISSION) { + Log.d(DEBUG_TAG, "asked to show status: $newStatus but the position is disabled") + newStatus = LocationShowingStatus.DISABLED + } + + when (newStatus) { + LocationShowingStatus.FIRST_FIX -> { + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + gridRecyclerView.setVisibility(View.VISIBLE) + messageTextView!!.setVisibility(View.GONE) + } + + LocationShowingStatus.NO_PERMISSION -> { + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + messageTextView!!.setText(R.string.enable_position_message_nearby) + messageTextView!!.setVisibility(View.VISIBLE) + } + + LocationShowingStatus.DISABLED -> { + //if (showingStatus== LocationShowingStatus.SEARCHING){ + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + //} + messageTextView!!.setText(R.string.enable_location_message) + messageTextView!!.setVisibility(View.VISIBLE) + } + + LocationShowingStatus.SEARCHING -> { + circlingProgressBar!!.setVisibility(View.VISIBLE) + loadingTextView!!.setVisibility(View.VISIBLE) + gridRecyclerView.setVisibility(View.GONE) + messageTextView!!.setVisibility(View.GONE) + } + } + showingStatus = newStatus + } + + + override fun onAttach(context: Context) { + super.onAttach(context) + if (context is FragmentListenerMain) { + mListener = context as FragmentListenerMain + } else { + throw RuntimeException( + context + .toString() + " must implement OnFragmentInteractionListener" + ) + } + Log.d(DEBUG_TAG, "OnAttach called") + //viewModel = ViewModelProvider(this).get(NearbyStopsViewModel::class.java) + } + + override fun onPause() { + super.onPause() + + //gridRecyclerView.setAdapter(null) + Log.d(DEBUG_TAG, "On paused called") + + locationProvider!!.stopUpdates() + } + + override fun onResume() { + super.onResume() + //fix view if we were showing the stops or the arrivals + prepareForFragmentType() + when (fragment_type) { + FragType.STOPS -> if (dataAdapter != null) { + //gridRecyclerView.setAdapter(dataAdapter); + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + } + + FragType.ARRIVALS -> if (arrivalsStopAdapter != null) { + //gridRecyclerView.setAdapter(arrivalsStopAdapter); + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + } + } + + mListener!!.enableRefreshLayout(false) + Log.d(DEBUG_TAG, "OnResume called") + if (getContext() == null) { + Log.e(DEBUG_TAG, "NULL CONTEXT, everything is going to crash now") + stopsMinNumber = 5 + stopsMaxDistance = 600 + return + } + //Re-read preferences + val shpr = PreferenceManager.getDefaultSharedPreferences(requireContext().getApplicationContext()) + //For some reason, they are all saved as strings + stopsMaxDistance = shpr.getInt(getString(R.string.pref_key_radius_recents), 600) + var isMinStopInt = true + try { + stopsMinNumber = shpr.getInt(getString(R.string.pref_key_num_recents), 5) + } catch (ex: ClassCastException) { + isMinStopInt = false + } + if (!isMinStopInt) try { + stopsMinNumber = shpr.getString(getString(R.string.pref_key_num_recents), "5")!!.toInt() + } catch (ex: NumberFormatException) { + stopsMinNumber = 5 + } + if (BuildConfig.DEBUG) Log.d( + DEBUG_TAG, + "Max distance for stops: $stopsMaxDistance, Min number of stops: $stopsMinNumber" + ) + + if (!locationProvider!!.isRunning()) { + startLocationUpdatesByType() + } + } + + + + + override fun onDetach() { + super.onDetach() + mListener = null + if (arrivalsManager != null) arrivalsManager!!.cancelAllRequests() + } + + /** + * Display the stops, or run new set of requests for arrivals + */ + private fun showStopsInViews(stops: ArrayList, location: GPSPoint?) { + if (stops.isEmpty()) { + setNoStopsLayout() + return + } + if (location == null) { + // we could do something better, but it's better to do this for now + return + } + + /*var minDistance = Double.POSITIVE_INFINITY + for (s in stops) { + minDistance = min(minDistance, s.getDistanceFromLocation(location.getLatitude(), location.getLongitude())) + } + + */ + + + //quick trial to hopefully always get the stops in the correct order + Collections.sort(stops, StopSorterByDistance(location)) + when (fragment_type) { + FragType.STOPS -> showStopsInRecycler(stops) + FragType.ARRIVALS -> { + //don't do anything if we're not attached + /*context?.let{ + if (arrivalsManager == null) arrivalsManager = + NearbyArrivalsDownloader(it.applicationContext, arrivalsListener) + arrivalsManager!!.requestArrivalsForStops(stops) + } + + */ + viewModel.requestArrivalsForStops(stops) + } + } + } + + /** + * To enable targeting from the Button + */ + fun switchFragmentType(v: View?) { + switchFragmentType() + } + + /** + * Call when you need to switch the type of fragment + */ + private fun switchFragmentType() { + when (fragment_type) { + FragType.ARRIVALS -> setFragmentType(FragType.STOPS) + FragType.STOPS -> setFragmentType(FragType.ARRIVALS) + else -> {} + } + prepareForFragmentType() + //locManager.removeLocationRequestFor(fragmentLocationListener); + //locManager.addLocationRequestFor(fragmentLocationListener); + if (lastPosition != null) { + // we have at least one fix on the position + showStopsInViews(currentNearbyStops, lastPosition) + } + } + + /** + * Prepare the views for the set fragment type + */ + private fun prepareForFragmentType() { + if (fragment_type == FragType.STOPS) { + switchButton!!.setText(getString(R.string.show_arrivals)) + titleTextView!!.setText(getString(R.string.nearby_stops_message)) + if (arrivalsManager != null) arrivalsManager!!.cancelAllRequests() + if (dataAdapter != null) gridRecyclerView!!.setAdapter(dataAdapter) + } else if (fragment_type == FragType.ARRIVALS) { + titleTextView!!.setText(getString(R.string.nearby_arrivals_message)) + switchButton!!.setText(getString(R.string.show_stops)) + if (arrivalsStopAdapter != null) gridRecyclerView!!.setAdapter(arrivalsStopAdapter) + } + } + + //useful methods + /**//// GUI METHODS //////// */ + private fun showStopsInRecycler(stops: MutableList?) { + if (firstLocForStops) { + dataAdapter = SquareStopAdapter(stops, mListener, lastPosition) + gridRecyclerView!!.setAdapter(dataAdapter) + firstLocForStops = false + } else { + dataAdapter!!.setStops(stops) + dataAdapter!!.setUserPosition(lastPosition) + } + dataAdapter!!.notifyDataSetChanged() + + //showRecyclerHidingLoadMessage(); + if (gridRecyclerView!!.getVisibility() != View.VISIBLE) { + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + gridRecyclerView!!.setVisibility(View.VISIBLE) + } + messageTextView!!.setVisibility(View.GONE) + + if (mListener != null) mListener!!.readyGUIfor(FragmentKind.NEARBY_STOPS) + } + + private fun showArrivalsInRecycler(routesPairList: List>) { + + + } + + private fun setNoStopsLayout() { + messageTextView!!.setVisibility(View.VISIBLE) + messageTextView!!.setText(R.string.no_stops_nearby) + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + } + + /** + * Does exactly what is says on the tin + */ + private fun showRecyclerHidingLoadMessage() { + if (gridRecyclerView.getVisibility() != View.VISIBLE) { + circlingProgressBar!!.setVisibility(View.GONE) + loadingTextView!!.setVisibility(View.GONE) + gridRecyclerView.setVisibility(View.VISIBLE) + } + messageTextView!!.setVisibility(View.GONE) + } /* + * Local locationListener, to use for the GPS + */ + /* + class FragmentLocationListener implements LocationListenerCompat { + + private long lastUpdateTime = -1; + public boolean isRegistered = false; + + @Override + public void onLocationChanged(@NonNull Location location) { + if(viewModel==null){ + return; + } + if(location.getAccuracy()<200) { + + lastPosition = new GPSPoint(location.getLatitude(), location.getLongitude()); + //viewModel.requestStopsAtDistance(location.getLatitude(), location.getLongitude(), distance, true); + viewModel.setLastLocation(location); + } + lastUpdateTime = System.currentTimeMillis(); + //Log.d("BusTO:NearPositListen","can start request for stops: "+ !dbUpdateRunning); + } + + @Override + public void onProviderEnabled(@NonNull String provider) { + Log.d(DEBUG_TAG, "Location provider "+provider+" enabled"); + if(provider.equals(LocationManager.GPS_PROVIDER)){ + setShowingStatus(LocationShowingStatus.SEARCHING); + } + } + + @Override + public void onProviderDisabled(@NonNull String provider) { + Log.d(DEBUG_TAG, "Location provider "+provider+" disabled"); + if(provider.equals(LocationManager.GPS_PROVIDER)) { + setShowingStatus(LocationShowingStatus.DISABLED); + } + } + + @Override + public void onStatusChanged(@NonNull String provider, int status, @Nullable Bundle extras) { + LocationListenerCompat.super.onStatusChanged(provider, status, extras); + } + } + + */ + + companion object { + private const val DEBUG_TAG = "NearbyStopsFragment" + private const val FRAGMENT_TYPE_KEY = "FragmentType" + const val FRAGMENT_TAG: String = "NearbyStopsFrag" + + const val COLUMN_WIDTH_DP: Int = 250 + + + /** + * Use this factory method to create a new instance of + * this fragment using the provided parameters. + * @return A new instance of fragment NearbyStopsFragment. + */ + @JvmStatic + fun newInstance(type: FragType): NearbyStopsFragment { + //if(fragmentType != TYPE_STOPS && fragmentType != TYPE_ARRIVALS ) + // throw new IllegalArgumentException("WRONG KIND OF FRAGMENT USED"); + val fragment = NearbyStopsFragment() + val args = Bundle(1) + args.putInt(FRAGMENT_TYPE_KEY, type.num) + fragment.setArguments(args) + return fragment + } + } +} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ParentFragmentManagerFromChild.kt b/app/src/main/java/it/reyboz/bustorino/fragments/ParentFragmentManagerFromChild.kt new file mode 100644 index 0000000..f1a2697 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ParentFragmentManagerFromChild.kt @@ -0,0 +1,9 @@ +package it.reyboz.bustorino.fragments + + +interface ParentFragmentManagerFromChild { + + fun needToPopMainStackOnBack() : Boolean + + fun setMainFragmentManagerTransition(yes: Boolean) +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java b/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java index 7012d7d..678f663 100644 --- a/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java +++ b/app/src/main/java/it/reyboz/bustorino/util/RoutePositionSorter.java @@ -17,21 +17,19 @@ */ package it.reyboz.bustorino.util; -import android.location.Location; import androidx.core.util.Pair; import android.util.Log; import it.reyboz.bustorino.backend.*; -import java.time.ZonedDateTime; import java.util.Collections; import java.util.Comparator; import java.util.List; -public class RoutePositionSorter implements Comparator> { +public class RoutePositionSorter implements Comparator { private final double latPos, longPos; - private final double minutialmetro = 6.0/100; //v = 5km/h - private final double distancemultiplier = 2./3; + public final double MINUTI_PER_METRO = 6.0/100; //v = 5km/h + public final double DISTANCE_MULTIPLIER = 2./3; public RoutePositionSorter(double latitude, double longitude){ latPos = latitude; longPos = longitude; @@ -41,16 +39,16 @@ public class RoutePositionSorter implements Comparator> { } @Override - public int compare(Pair pair1, Pair pair2) throws NullPointerException{ + public int compare(RouteWithStop pair1, RouteWithStop pair2) throws NullPointerException{ int delta = 0; - final Stop stop1 = pair1.first, stop2 = pair2.first; + final Stop stop1 = pair1.getStop(), stop2 = pair2.getStop(); double dist1 = utils.measuredistanceBetween(latPos,longPos, stop1.getLatitude(),stop1.getLongitude()); double dist2 = utils.measuredistanceBetween(latPos,longPos, stop2.getLatitude(),stop2.getLongitude()); - final List passaggi1 = pair1.second.passaggi, - passaggi2 = pair2.second.passaggi; - if(passaggi1.size()<=0 || passaggi2.size()<=0){ + final List passaggi1 = pair1.getRoute().passaggi, + passaggi2 = pair2.getRoute().passaggi; + if(passaggi1.isEmpty() || passaggi2.isEmpty()){ Log.e("ArrivalsStopAdapter","Cannot compare: No arrivals in one of the stops"); } else { Collections.sort(passaggi1); @@ -66,7 +64,7 @@ public class RoutePositionSorter implements Comparator> { delta = (int) passaggi1.get(0).getDifferenceMinutes(passaggi2.get(0)); } - delta += (int)((dist1 -dist2)*minutialmetro*distancemultiplier); + delta += (int)((dist1 -dist2)* MINUTI_PER_METRO * DISTANCE_MULTIPLIER); return delta; } diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt index fc182cb..4eb4a5e 100644 --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/NearbyStopsViewModel.kt @@ -23,11 +23,13 @@ import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map import it.reyboz.bustorino.BuildConfig -import it.reyboz.bustorino.backend.GPSPoint -import it.reyboz.bustorino.backend.Stop +import it.reyboz.bustorino.backend.* import it.reyboz.bustorino.data.OldDataRepository -import java.util.ArrayList +import it.reyboz.bustorino.fragments.NearbyArrivalsDownloader +import it.reyboz.bustorino.util.StopSorterByDistance +import java.util.* import java.util.concurrent.Executors class NearbyStopsViewModel(application: Application): AndroidViewModel(application) { @@ -35,6 +37,35 @@ class NearbyStopsViewModel(application: Application): AndroidViewModel(applicati private val executor = Executors.newFixedThreadPool(2) private val oldRepo = OldDataRepository(executor, application) + val arrivalsNearby = MutableLiveData>() + + val progressPerc = MutableLiveData() + + val downloadingArrivals = MutableLiveData() + private val arrivalsListener = object : NearbyArrivalsDownloader.ArrivalsListener { + override fun setProgress(completedRequests: Int, pendingRequests: Int) { + val totalReq = completedRequests + pendingRequests + progressPerc.postValue( (completedRequests * 100) / totalReq ) + + if(pendingRequests == 0) + downloadingArrivals.postValue(false) + } + + override fun onAllRequestsCancelled() { + downloadingArrivals.postValue(false) + } + + override fun showCompletedArrivals(completedPalinas: ArrayList) { + arrivalsNearby.postValue(completedPalinas) + } + + } + + private val nearbyArrivalsDownloader = NearbyArrivalsDownloader(application,arrivalsListener ) + + fun requestArrivalsForStops(stops: List) { + nearbyArrivalsDownloader.requestArrivalsForStops(stops) + } val locationLiveData = MutableLiveData() val distanceMtLiveData = MutableLiveData(40) @@ -150,7 +181,72 @@ class NearbyStopsViewModel(application: Application): AndroidViewModel(applicati } + val arrivalsDecoupled = arrivalsNearby.map { palinas -> + locationLiveData.value?.let { loc -> + Collections.sort(palinas, StopSorterByDistance(loc)) + } + + var routesPairList = ArrayList(10) + //int maxNum = Math.min(MAX_STOPS, stopList.size()); + for (p in palinas) { + //if there are no routes available, skip stop + if (p.queryAllRoutes().isEmpty()) continue + for (r in p.queryAllRoutes()) { + //if there are no routes, should not do anything + if (r.passaggi != null && !r.passaggi.isEmpty()) routesPairList.add(RouteWithStop(p, r)) + } + } + + val pos = locationLiveData.value + if(pos != null) { + routesPairList.sortWith { p1, p2 -> + comparePairsRoutesArrivals(p1,p2,pos) + } + } + routesPairList + } + + fun comparePairsRoutesArrivals(pair1: RouteWithStop, pair2: RouteWithStop, pos: GPSPoint) : Int{ + var delta = 0 + val stop1: Stop = pair1.stop + val stop2: Stop = pair2.stop + + val dist1 = utils.measuredistanceBetween( + pos.latitude, pos.longitude, + stop1.getLatitude()!!, stop1.getLongitude()!! + ) + val dist2 = utils.measuredistanceBetween( + pos.latitude, pos.longitude, + stop2.getLatitude()!!, stop2.getLongitude()!! + ) + val passaggi1 = pair1.route.passaggi + val passaggi2 = pair2.route.passaggi + if (passaggi1.size <= 0 || passaggi2.size <= 0) { + Log.e("ArrivalsStopAdapter", "Cannot compare: No arrivals in one of the stops") + } else { + Collections.sort(passaggi1) + Collections.sort(passaggi2) + + /*int deltaOre = passaggi1.get(0).hh-passaggi2.get(0).hh; + if(deltaOre>12) + deltaOre -= 24; + else if (deltaOre<-12) + deltaOre += 24; + delta+=deltaOre*60 + passaggi1.get(0).mm-passaggi2.get(0).mm; + + */ + delta = passaggi1[0]!!.getDifferenceMinutes(passaggi2[0]!!).toInt() + } + delta += ((dist1 - dist2) * MINUTI_PER_METRO * DISTANCE_MULTIPLIER).toInt() + + return delta + + } + companion object{ private const val DEBUG_TAG = "BusTO-NearbyStopVwModel" + + const val MINUTI_PER_METRO: Double = 6.0 / 100 //v = 5km/h + const val DISTANCE_MULTIPLIER: Double = 2.0 / 3 } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_screen.xml b/app/src/main/res/layout/fragment_main_screen.xml index 33c749c..4c96a87 100644 --- a/app/src/main/res/layout/fragment_main_screen.xml +++ b/app/src/main/res/layout/fragment_main_screen.xml @@ -41,7 +41,7 @@ android:singleLine="true" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="@id/leftGuide" - app:layout_constraintEnd_toEndOf="@id/rightGuide" + app:layout_constraintEnd_toStartOf="@id/searchButton" > @@ -59,17 +59,12 @@ android:imeOptions="actionSearch" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="@id/leftGuide" - app:layout_constraintEnd_toEndOf="@id/rightGuide" + app:layout_constraintEnd_toStartOf="@id/searchButton" android:singleLine="true" android:visibility="gone"> - + + - - + - - + android:layout_height="match_parent" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:animateLayoutChanges="true" + android:visibility="gone"> + + + @@ -71,7 +71,8 @@ android:progressDrawable="@color/blue_620" android:indeterminate="true" android:indeterminateTint="@color/blue_620" - android:layout_below="@+id/titleTextView" android:layout_centerHorizontal="true"/> + android:layout_below="@+id/titleTextView" + android:layout_centerHorizontal="true"/>