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
@@ -1,928 +1,914 @@
/*
BusTO - Arrival times for Turin public transport.
Copyright (C) 2021 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;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.*;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.core.graphics.Insets;
import androidx.core.view.*;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import androidx.work.WorkInfo;
import com.google.android.material.navigation.NavigationView;
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;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.PreferencesHolder;
import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.GeneralActivity;
import it.reyboz.bustorino.viewmodels.ServiceAlertsViewModel;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
import static it.reyboz.bustorino.backend.utils.openIceweasel;
public class ActivityPrincipal extends GeneralActivity implements FragmentListenerMain {
private DrawerLayout mDrawer;
private NavigationView mNavView;
private ActionBarDrawerToggle drawerToggle;
private final static String DEBUG_TAG="BusTO Act Principal";
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;
private final Map menuActions = Map.of(
R.id.drawer_action_settings, () -> {
Log.d("MAINBusTO", "Pressed button preferences");
startActivity(new Intent(this, ActivitySettings.class));
},
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),
R.id.nav_lines_item, () ->
showLinesFragment(getSupportFragmentManager(), true, null),
R.id.drawer_action_info, () ->
startActivity(new Intent(this, ActivityAbout.class)),
R.id.nav_nearby, this::openNearbyStopsFragment
);
+
private long lastClosingAttempt = -1L;
private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
boolean isResolved = activityCustomBackPressed();
Log.d(DEBUG_TAG, "backpress resolved: " + isResolved);
if(!isResolved){
long currentTime = System.currentTimeMillis();
if(currentTime - lastClosingAttempt < 2000){
finish();
} else{
lastClosingAttempt = currentTime;
Toast.makeText(getApplicationContext(),R.string.back_again_to_close,Toast.LENGTH_SHORT).show();
}
}
}
};
-
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(DEBUG_TAG, "onCreate, savedInstanceState is: "+savedInstanceState);
setContentView(R.layout.activity_principal);
serviceAlertsViewModel = new ViewModelProvider(this).get(ServiceAlertsViewModel.class);
//Use LiveModel to sync fragment state
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
getWindow().setNavigationBarContrastEnforced(false);
}
*/
//onBackPressed solution required from Android 16
backPressedCallback.setEnabled(true);
this.getOnBackPressedDispatcher().addCallback(backPressedCallback);
boolean showingArrivalsFromIntent = false;
final Toolbar mToolbar = findViewById(R.id.default_toolbar);
setSupportActionBar(mToolbar);
if (getSupportActionBar()!=null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
else Log.w(DEBUG_TAG, "NO ACTION BAR");
mToolbar.setOnMenuItemClickListener(new ToolbarItemClickListener(this));
mDrawer = findViewById(R.id.drawer_layout);
drawerToggle = setupDrawerToggle(mToolbar);
// Setup toggle to display hamburger icon with nice animation
drawerToggle.setDrawerIndicatorEnabled(true);
drawerToggle.syncState();
mDrawer.addDrawerListener(drawerToggle);
mDrawer.addDrawerListener(new DrawerLayout.DrawerListener() {
@Override
public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {
}
@Override
public void onDrawerOpened(@NonNull View drawerView) {
hideKeyboard();
}
@Override
public void onDrawerClosed(@NonNull View drawerView) {
}
@Override
public void onDrawerStateChanged(int newState) {
}
});
mNavView = findViewById(R.id.nvView);
setupDrawerContent(mNavView);
/*View header = mNavView.getHeaderView(0);
*/
//mNavView.getMenu().findItem(R.id.versionFooter).
/// LEGACY CODE
//---------------------------- START INTENT CHECK QUEUE ------------------------------------
// Intercept calls from URL intent
boolean tryedFromIntent = false;
String busStopID = null;
Uri data = getIntent().getData();
if (data != null) {
busStopID = getBusStopIDFromUri(data);
Log.d(DEBUG_TAG, "Opening Intent: busStopID: "+busStopID);
tryedFromIntent = true;
}
// Intercept calls from other activities
if (!tryedFromIntent) {
Bundle b = getIntent().getExtras();
if (b != null) {
busStopID = b.getString("bus-stop-ID");
/*
* I'm not very sure if you are coming from an Intent.
* Some launchers work in strange ways.
*/
tryedFromIntent = busStopID != null;
}
}
//---------------------------- END INTENT CHECK QUEUE --------------------------------------
if (busStopID == null) {
// Show keyboard if can't start from intent
// JUST DON'T
// showKeyboard();
// You haven't obtained anything... from an intent?
if (tryedFromIntent) {
// This shows a luser warning
Toast.makeText(getApplicationContext(),
R.string.insert_bus_stop_number_error, Toast.LENGTH_SHORT).show();
}
} else {
// If you are here an intent has worked successfully
//setBusStopSearchByIDEditText(busStopID);
//Log.d(DEBUG_TAG, "Requesting arrivals for stop "+busStopID+" from intent");
requestArrivalsForStopID(busStopID); //this shows the fragment, too
showingArrivalsFromIntent = true;
}
//database check
// DatabaseUpdate.requestDBUpdateWithWork(this, false, false);
DBUpdateCheckWorker.Companion.schedulePeriodicCheck(this,false);
/*
Watch for database update
*/
DBUpdateWorker.getWorkInfoLiveData(this)
.observe(this, workInfoList -> {
// If there are no matching work info, do nothing
if (workInfoList == null || workInfoList.isEmpty()) {
return;
}
Log.d(DEBUG_TAG, "WorkerInfo: "+workInfoList);
boolean showProgress = false;
for (WorkInfo workInfo : workInfoList) {
if (workInfo.getState() == WorkInfo.State.RUNNING) {
showProgress = true;
break;
}
}
if (showProgress) {
createDatabaseUpdateSnackbar();
} else {
if(snackbar!=null) {
snackbar.dismiss();
snackbar = null;
}
}
});
// show the main fragment
Fragment f = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
Log.d(DEBUG_TAG, "OnCreate the fragment is "+f);
String vl = PreferenceManager.getDefaultSharedPreferences(this).getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "");
Log.d(DEBUG_TAG, "The default screen to open is: "+vl);
if (showingArrivalsFromIntent){
//do nothing but exclude a case
}else if (savedInstanceState==null) {
var framan = getSupportFragmentManager();
//we are not restarting the activity from nothing
switch (vl){
case "map" -> {requestMapFragment(false);}
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();
// handle the device "insets"
/*
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.rootRelativeLayout), (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
// Apply the insets as a margin to the view. This solution sets only the
// bottom, left, and right dimensions, but you can apply whichever insets are
// appropriate to your layout. You can also update the view padding if that's
// more appropriate.
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
mlp.leftMargin = insets.left;
mlp.bottomMargin = insets.bottom;
mlp.rightMargin = insets.right;
v.setLayoutParams(mlp);
//set for toolbar
//mlp = (ViewGroup.MarginLayoutParams) mToolbar.getLayoutParams();
//mlp.topMargin = insets.top;
//mToolbar.setLayoutParams(mlp);
mToolbar.setPadding(0, insets.top, 0, 0);
// Return CONSUMED if you don't want the window insets to keep passing
// down to descendant views.
return WindowInsetsCompat.CONSUMED;
});
//to properly handle IME
WindowInsetsControllerCompat insetsController =
WindowCompat.getInsetsController(getWindow(), getWindow().getDecorView());
if (insetsController != null) {
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
);
}
*/
// Toolbar: solo inset superiore (status bar)
ViewCompat.setOnApplyWindowInsetsListener(mToolbar, (v, windowInsets) -> {
Insets statusBar = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars());
v.setPadding(0, statusBar.top, 0, 0);
return windowInsets; // NON consumare: passa gli insets ai figli
});
// Content frame: insets laterali e inferiori (navigation bar)
// I fragment figli riceveranno gli insets e potranno gestirli a loro volta
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.mainActContentFrame), (v, windowInsets) -> {
Insets systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
// Solo left/right, bottom lo gestisce ogni fragment
v.setPadding(systemBars.left, 0, systemBars.right, 0);
return windowInsets; //WindowInsetsCompat.CONSUMED; // NON consumare: passa ai fragment
});
//check if first run activity (IntroActivity) has been started once or not
final SharedPreferences theShPr = getMainSharedPreferences();
boolean hasIntroRun = theShPr.getBoolean(PreferencesHolder.PREF_INTRO_ACTIVITY_RUN,false);
if(!hasIntroRun){
startIntroductionActivity();
}
serviceAlertsViewModel.getLastTimeRunningDownload().observe(this, (timeRunning) -> {
if (timeRunning != null) {
Log.d(DEBUG_TAG, "requested alerts download at time: "+timeRunning);
}
});
serviceAlertsViewModel.launchAlertsPeriodCheck();
}
private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) {
// NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it
// and will not render the hamburger icon without it.
return new ActionBarDrawerToggle(this, mDrawer, toolbar, R.string.drawer_open, R.string.drawer_close);
}
/**
* Setup drawer actions
* @param navigationView the navigation view on which to set the callbacks
*/
private void setupDrawerContent(NavigationView navigationView) {
navigationView.setNavigationItemSelectedListener(
menuItem -> {
int menuId = menuItem.getItemId();
if( menuActions.containsKey(menuId)){
closeDrawerIfOpen();
var runnable = menuActions.get(menuId);
if(runnable!=null) runnable.run();
return true;
} else{
return false;
}
});
}
private void closeDrawerIfOpen(){
if (mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(GravityCompat.START);
}
// `onPostCreate` called when activity start-up is complete after `onStart()`
// NOTE 1: Make sure to override the method with only a single `Bundle` argument
// Note 2: Make sure you implement the correct `onPostCreate(Bundle savedInstanceState)` method.
// There are 2 signatures and only `onPostCreate(Bundle state)` shows the hamburger icon.
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Sync the toggle state after onRestoreInstanceState has occurred.
drawerToggle.syncState();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
// Pass any configuration change to the drawer toggles
drawerToggle.onConfigurationChanged(newConfig);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.principal_menu, menu);
MenuItem experimentsMenuItem = menu.findItem(R.id.action_experiments);
SharedPreferences shPr = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
boolean exper_On = shPr.getBoolean(getString(R.string.pref_key_experimental), false);
experimentsMenuItem.setVisible(exper_On);
return super.onCreateOptionsMenu(menu);
}
//requesting permissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==STORAGE_PERMISSION_REQ){
final String storagePerm = Manifest.permission.WRITE_EXTERNAL_STORAGE;
if (permissionDoneRunnables.containsKey(storagePerm)) {
Runnable toRun = permissionDoneRunnables.get(storagePerm);
if (toRun != null)
toRun.run();
permissionDoneRunnables.remove(storagePerm);
}
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(DEBUG_TAG, "Permissions check: " + Arrays.toString(permissions));
if (permissionDoneRunnables.containsKey(storagePerm)) {
Runnable toRun = permissionDoneRunnables.get(storagePerm);
if (toRun != null)
toRun.run();
permissionDoneRunnables.remove(storagePerm);
}
} else {
//permission denied
showToastMessage(R.string.permission_storage_maps_msg, false);
}
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
Log.d(DEBUG_TAG, "Item pressed");
if (item.getItemId() == android.R.id.home) {
mDrawer.openDrawer(GravityCompat.START);
return true;
}
if (drawerToggle.onOptionsItemSelected(item)) {
return true;
}
return super.onOptionsItemSelected(item);
}
/*@Override
public void onBackPressed() {
if (!activityCustomBackPressed())
super.onBackPressed();
}
*/
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) {
mainFragManager.popBackStack();
var newFrag = mainFragManager.findFragmentById(R.id.mainActContentFrame);
if(newFrag != null) {
int backStackCount = newFrag.getChildFragmentManager().getBackStackEntryCount();
Log.d(DEBUG_TAG, "new fragment is "+newFrag.getClass().getSimpleName() +", count of the backstack: "+backStackCount);
}
Log.d(DEBUG_TAG, "Popping main backstack");
}
else{
resolved = false;
}
return resolved;
}
/**
* Create and show the SnackBar with the message
* The fragment shown points to which view to attach the snackbar
*/
private void createDatabaseUpdateSnackbar() {
View baseView = null;
boolean showSnackbar = true;
final Fragment frag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
if (frag instanceof ScreenBaseFragment){
baseView = ((ScreenBaseFragment) frag).getBaseViewForSnackBar();
showSnackbar = ((ScreenBaseFragment) frag).showSnackbarOnDBUpdate();
}
if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
//if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
if (baseView !=null && showSnackbar) {
snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
snackbar.setTextColor(getColor(android.R.color.white));
snackbar.setBackgroundTint(getColor(R.color.grey_800));
if (frag instanceof ScreenBaseFragment){
((ScreenBaseFragment) frag).setSnackbarPropertiesBeforeShowing(snackbar);
}
snackbar.show();
} else{
Log.e(DEBUG_TAG, "Asked to show the snackbar but the baseView is null");
}
}
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;
} else if(newKind != showingFragmentKind) {
showingMainFragmentFromOther = (
FragmentKind.getSuperKind(newKind) != FragmentKind.getSuperKind(showingFragmentKind));
showingFragmentKind = newKind;
}
}
/**
* Show the actual fragment by adding it to the backstack
* @param fraMan the fragmentManager
* @param fragment the fragment
*/
private static void showMainFragment(FragmentManager fraMan, MainScreenFragment fragment, boolean addToBackStack){
FragmentTransaction ft = fraMan.beginTransaction()
.replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG)
.setReorderingAllowed(false)
/*.setCustomAnimations(
R.anim.slide_in, // enter
R.anim.fade_out, // exit
R.anim.fade_in, // popEnter
R.anim.slide_out // popExit
)*/
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
- if (addToBackStack) ft.addToBackStack(null);
+ if (addToBackStack) {
+ ft.addToBackStack(null);
+ }
ft.commit();
}
/**
* Create a new MainFragment for the arguments provided and show it in the layout
* @param fraMan the fragmentManager
* @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)
/*.setCustomAnimations(
R.anim.slide_in, // enter
R.anim.fade_out, // exit
R.anim.fade_in, // popEnter
R.anim.slide_out // popExit
)*/
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
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);
}
}
private void requestMapFragment(final boolean allowReturn){
// starting from Android 11, we don't need to have the STORAGE permission anymore for the map cache
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.mainActContentFrame);
if(fragment instanceof MapLibreFragment){
Log.d(DEBUG_TAG, "Requested map fragment, but it is already open");
} else {
fragment = fm.findFragmentByTag(MapLibreFragment.FRAGMENT_TAG);
if(fragment != null){
Log.d(DEBUG_TAG, "Found map fragment, reopening it");
var ft = fm.beginTransaction();
ft.replace(R.id.mainActContentFrame,fragment, MapLibreFragment.FRAGMENT_TAG);
if(allowReturn) ft.addToBackStack(null);
ft.commit();
} else {
//create from scratch
//The permissions are handled in the MapLibreFragment instead
createAndShowMapFragment(null, allowReturn);
}
}
}
private void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){
if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof FavoritesFragment){
Log.d(DEBUG_TAG, "Requested favorites fragment, but it is already open");
return;
}
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment fragment = fragmentManager.findFragmentByTag(TAG_FAVORITES);
if(fragment!=null){
ft.replace(R.id.mainActContentFrame, fragment, TAG_FAVORITES);
}else{
//use new method
ft.replace(R.id.mainActContentFrame,FavoritesFragment.class,null,TAG_FAVORITES);
}
if (addToBackStack)
ft.addToBackStack("favorites_main");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.setReorderingAllowed(false);
ft.commit();
}
private void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){
if(getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame) instanceof LinesGridShowingFragment){
Log.d(DEBUG_TAG, "Requested lines grid fragment, but it is already open");
return;
}
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment f = fragmentManager.findFragmentByTag(LinesGridShowingFragment.FRAGMENT_TAG);
if(f!=null){
ft.replace(R.id.mainActContentFrame, f, LinesGridShowingFragment.FRAGMENT_TAG);
}else{
//use new method
ft.replace(R.id.mainActContentFrame,LinesGridShowingFragment.class,fragArgs,
LinesGridShowingFragment.FRAGMENT_TAG);
}
if (addToBackStack)
ft.addToBackStack("linesGrid");
ft.setReorderingAllowed(true)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
@Nullable
private MainScreenFragment getMainFragmentIfVisible(){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
if (fragment!= null && fragment.isVisible()) return (MainScreenFragment) fragment;
else return null;
}
@Override
public void showFloatingActionButton(boolean yes) {
var frag = getMainFragmentIfVisible();
if(frag!=null){
frag.showFloatingActionButton(yes);
}
}
/*
public void setDrawerSelectedItem(String fragmentTag){
switch (fragmentTag){
case MainScreenFragment.FRAGMENT_TAG:
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case MapFragment.FRAGMENT_TAG:
break;
case FavoritesFragment.FRAGMENT_TAG:
mNavView.setCheckedItem(R.id.nav_favorites_item);
break;
}
}*/
@Override
public void readyGUIfor(FragmentKind fragmentType) {
- MainScreenFragment mainFragmentIfVisible = getMainFragmentIfVisible();
- if (mainFragmentIfVisible!=null){
- mainFragmentIfVisible.readyGUIfor(fragmentType);
- }
+
updateShowingFragmentKindInternal(fragmentType);
Integer titleResId = null;
switch (fragmentType){
case MAP:
mNavView.setCheckedItem(R.id.nav_map_item);
titleResId = R.string.map;
break;
case FAVORITES:
mNavView.setCheckedItem(R.id.nav_favorites_item);
titleResId = R.string.nav_favorites_text;
break;
case ARRIVALS:
titleResId = R.string.nav_arrivals_text;
mNavView.setCheckedItem(R.id.nav_home);
//TODO: Figure out way to change title
//mNavView.getCheckedItem().setTitle(R.string.nav_arrivals_text);
break;
case STOPS:
titleResId = R.string.stop_search_view_title;
mNavView.setCheckedItem(R.id.nav_home);
break;
case MAIN_SCREEN_FRAGMENT:
case HOME_BUTTONS:
titleResId=R.string.app_name_full;
mNavView.setCheckedItem(R.id.nav_home);
//mNavView.getCheckedItem().setTitle(R.string.nav_home_text);
break;
case NEARBY_STOPS:
case NEARBY_ARRIVALS:
titleResId=R.string.app_name_full;
mNavView.setCheckedItem(R.id.nav_nearby);
break;
case LINES:
titleResId=R.string.lines;
mNavView.setCheckedItem(R.id.nav_lines_item);
break;
}
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){
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
LinesDetailFragment.Companion.makeArgs(routeGtfsId, stopIDFrom));
tr.addToBackStack("LineFromStop-"+routeGtfsId);
tr.commit();
}
+ 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) {
FragmentTransaction tr = getSupportFragmentManager().beginTransaction();
tr.replace(R.id.mainActContentFrame, LinesDetailFragment.class,
LinesDetailFragment.Companion.makeArgsPattern(routeGtfsId, optionalPatternId, args));
tr.addToBackStack("LineFromOther-"+routeGtfsId);
tr.commit();
}
@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);
}
}
@Override
public void openLinesFragment() {
showLinesFragment(getSupportFragmentManager(), true, null);
}
@Override
public void openFavoritesFragment() {
checkAndShowFavoritesFragment(getSupportFragmentManager(), true);
}
@Override
public void toggleSpinner(boolean state) {
MainScreenFragment probableFragment = getMainFragmentIfVisible();
if (probableFragment!=null){
probableFragment.toggleSpinner(state);
}
}
@Override
public void enableRefreshLayout(boolean yes) {
MainScreenFragment probableFragment = getMainFragmentIfVisible();
if (probableFragment!=null){
probableFragment.enableRefreshLayout(yes);
}
}
@Override
public void showMapCenteredOnStop(@Nullable Stop stop) {
createAndShowMapFragment(stop, true);
}
//Map Fragment stuff
void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){
final FragmentManager fm = getSupportFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
final MapLibreFragment fragment = MapLibreFragment.newInstance(stop);
ft.replace(R.id.mainActContentFrame, fragment, MapLibreFragment.FRAGMENT_TAG);
if (addToBackStack) ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
void startIntroductionActivity(){
Intent intent = new Intent(ActivityPrincipal.this, ActivityIntro.class);
intent.putExtra(ActivityIntro.RESTART_MAIN, false);
startActivity(intent);
}
class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{
private final Context activityContext;
public ToolbarItemClickListener(Context activityContext) {
this.activityContext = activityContext;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
final int id = item.getItemId();
if(id == R.id.action_about){
startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class));
return true;
} else if (id == R.id.action_hack) {
openIceweasel(getString(R.string.hack_url), activityContext);
return true;
} else if (id == R.id.action_source){
openIceweasel("https://gitpull.it/source/libre-busto/", activityContext);
return true;
} else if (id == R.id.action_licence){
openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext);
return true;
} else if (id == R.id.action_experiments) {
startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class));
return true;
} else if (id == R.id.action_tutorial) {
startIntroductionActivity();
return true;
}
return false;
}
}
@Override
protected void onPause() {
super.onPause();
// stop updating the alerts
serviceAlertsViewModel.setRunningDownloadRequests(false);
}
@Override
protected void onResume() {
super.onResume();
serviceAlertsViewModel.launchAlertsPeriodCheck();
}
}
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
@@ -1,294 +1,301 @@
/*
BusTO - UI components
Copyright (C) 2017 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.adapters;
import android.content.Context;
import android.content.SharedPreferences;
import android.location.Location;
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;
import android.view.View;
import android.view.ViewGroup;
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.*;
public class ArrivalsStopAdapter extends RecyclerView.Adapter implements SharedPreferences.OnSharedPreferenceChangeListener {
private final static int layoutRes = R.layout.arrivals_nearby_card;
//private List 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
private String KEY_CAPITALIZE;
private NameCapitalize capit;
- public ArrivalsStopAdapter(@Nullable List< Pair > 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;
context = con.getApplicationContext();
resetListAndPosition();
// if(paline!=null)
//resetRoutesPairList(paline);
KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit);
SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
defSharPref.registerOnSharedPreferenceChangeListener(this);
String capitalizeKey = defSharPref.getString(KEY_CAPITALIZE, "");
this.capit = NameCapitalize.getCapitalize(capitalizeKey);
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
//DO THE ACTUAL WORK TO PUT THE DATA
if(routesPairList==null || routesPairList.size() == 0) return; //NO STOPS
- final Pair 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");
} else {
holder.distancetextView.setVisibility(View.GONE);
}
final String stopText = String.format(context.getResources().getString(R.string.two_strings_format),stop.getStopDisplayName(),stop.ID);
holder.stopNameView.setText(stopText);
//final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione);
if (r!=null) {
holder.lineNameTextView.setText(r.getDisplayCode());
holder.lineDirectionTextView.setText(NameCapitalize.capitalizePass(r.destinazione, capit));
holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true));
} else {
holder.lineNameTextView.setVisibility(View.INVISIBLE);
holder.lineDirectionTextView.setVisibility(View.INVISIBLE);
//holder.arrivalsTextView.setVisibility(View.INVISIBLE);
}
/* EXPERIMENTS
if(r.destinazione==null || r.destinazione.trim().isEmpty()){
holder.lineDirectionTextView.setVisibility(View.GONE);
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams();
params.addRule(RelativeLayout.RIGHT_OF,holder.lineNameTextView.getId());
holder.arrivalsDescriptionTextView.setLayoutParams(params);
} else {
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) holder.arrivalsDescriptionTextView.getLayoutParams();
params.removeRule(RelativeLayout.RIGHT_OF);
holder.arrivalsDescriptionTextView.setLayoutParams(params);
holder.lineDirectionTextView.setVisibility(View.VISIBLE);
}
*/
holder.stopID =stop.ID;
} else {
Log.w("SquareStopAdapter","!! The selected stop is null !!");
}
}
@Override
public int getItemCount() {
return routesPairList.size();
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals(KEY_CAPITALIZE)){
String k = sharedPreferences.getString(KEY_CAPITALIZE, "");
capit = NameCapitalize.getCapitalize(k);
notifyDataSetChanged();
}
}
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView lineNameTextView;
TextView lineDirectionTextView;
TextView stopNameView;
TextView arrivalsDescriptionTextView;
TextView arrivalsTextView;
TextView distancetextView;
String stopID;
ViewHolder(View holdView){
super(holdView);
holdView.setOnClickListener(this);
lineNameTextView = (TextView) holdView.findViewById(R.id.lineNameTextView);
lineDirectionTextView = (TextView) holdView.findViewById(R.id.lineDirectionTextView);
stopNameView = (TextView) holdView.findViewById(R.id.arrivalStopName);
arrivalsTextView = (TextView) holdView.findViewById(R.id.arrivalsTimeTextView);
arrivalsDescriptionTextView = (TextView) holdView.findViewById(R.id.arrivalsDescriptionTextView);
distancetextView = (TextView) holdView.findViewById(R.id.arrivalsDistanceTextView);
}
@Override
public void onClick(View v) {
listener.requestArrivalsForStopID(stopID);
}
}
-
+ /*
public void resetRoutesPairList(List stopList){
Collections.sort(stopList,new StopSorterByDistance(userPosition));
this.routesPairList = new ArrayList<>(stopList.size());
int maxNum = Math.min(MAX_STOPS, stopList.size());
for(Palina p: stopList.subList(0,maxNum)){
//if there are no routes available, skip stop
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
routesPairList.add(new Pair<>(p,r));
}
}
}
- public void setUserPosition(@Nullable GPSPoint userPosition) {
- this.userPosition = userPosition;
- }
+ */
+
+
- public void setRoutesPairListAndPosition(List> 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
- }
+
+ */
}
/**
* Sort and remove the repetitions for the routesPairList
*/
private void resetListAndPosition(){
- 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();
- 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
@@ -1,393 +1,395 @@
/*
BusTO (backend components)
Copyright (C) 2016 Ludovico Pavesi
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
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).
*
* A stop ID in normalized form is:
* - a string containing a number without leading zeros
* - a string beginning with "ST" and then a number
* - whatever the GTT website uses.
*
* A bus route ID in normalized form is:
* - a string containing a number and optionally: "B", "CS", "CD", "N", "S", "C" at the end
* - the string "METRO"
* - "ST1" or "ST2" (Star 1 and Star 2)
* - a string beginning with E, N, S or W, followed by two digits (with a leading zero)
* - "RV2", "OB1"
* - "CAN" or "FTC" (railway lines)
* - ...screw it, let's just hope all the websites and APIs return something sane as route IDs.
*
* This class exists because Java doesn't support traits.
*
* Note: this class also just became useless, as 5T now uses the same format as GTT website.
*/
public abstract class FiveTNormalizer {
public static String FiveTNormalizeRoute(String RouteID) {
while (RouteID.startsWith("0")) {
RouteID = RouteID.substring(1);
}
return RouteID;
}
// public static String FiveTNormalizeStop(String StopID) {
// StopID = FiveTNormalizeRoute(StopID);
// // is this faster than a regex?
// if (StopID.length() == 5 && StopID.startsWith("ST") && Character.isLetter(StopID.charAt(2)) && Character.isLetter(StopID.charAt(3)) && Character.isLetter(StopID.charAt(4))) {
// switch (StopID) {
// case "STFER":
// return "8210";
// case "STPAR":
// return "8211";
// case "STMAR":
// return "8212";
// case "STMAS":
// return "8213";
// case "STPOS":
// return "8214";
// case "STMGR":
// return "8215";
// case "STRIV":
// return "8216";
// case "STRAC":
// return "8217";
// case "STBER":
// return "8218";
// case "STPDA":
// return "8219";
// case "STDOD":
// return "8220";
// case "STPSU":
// return "8221";
// case "STVIN":
// return "8222";
// case "STREU":
// return "8223";
// case "STPNU":
// return "8224";
// case "STMCI":
// return "8225";
// case "STNIZ":
// return "8226";
// case "STDAN":
// return "8227";
// case "STCAR":
// return "8228";
// case "STSPE":
// return "8229";
// case "STLGO":
// return "8230";
// }
// }
// return StopID;
// }
//
// public static String NormalizedToFiveT(final String StopID) {
// if(StopID.startsWith("82") && StopID.length() == 4) {
// switch (StopID) {
// case "8230":
// return "STLGO";
// case "8229":
// return "STSPE";
// case "8228":
// return "STCAR";
// case "8227":
// return "STDAN";
// case "8226":
// return "STNIZ";
// case "8225":
// return "STMCI";
// case "8224":
// return "STPNU";
// case "8223":
// return "STREU";
// case "8222":
// return "STVIN";
// case "8221":
// return "STPSU";
// case "8220":
// return "STDOD";
// case "8219":
// return "STPDA";
// case "8218":
// return "STBER";
// case "8217":
// return "STRAC";
// case "8216":
// return "STRIV";
// case "8215":
// return "STMGR";
// case "8214":
// return "STPOS";
// case "8213":
// return "STMAS";
// case "8212":
// return "STMAR";
// case "8211":
// return "STPAR";
// case "8210":
// return "STFER";
// }
// }
//
// return StopID;
// }
public static Route.Type decodeType(final String routename, final String bacino) {
if(routename.equals("METRO")) {
return Route.Type.METRO;
} else if(routename.equals("79")) {
return Route.Type.RAILWAY;
}
switch (bacino) {
case "U":
return Route.Type.BUS;
case "F":
return Route.Type.RAILWAY;
case "E":
return Route.Type.LONG_DISTANCE_BUS;
default:
return Route.Type.BUS;
}
}
/**
* Converts a route ID from internal format to display format, returns null if it has the same name.
*
* @param routeID ID in "internal" and normalized format
* @return string with display name, null if unchanged
*/
public static String routeInternalToDisplay(final String routeID) {
if(routeID.length() == 3 && routeID.charAt(2) == 'B') {
return routeID.substring(0,2).concat("/");
}
//TODO: Decide what to do about the "+" lines (68+, 13+)
switch(routeID) {
case "1C":
return "1 Chieri";
case "1N":
return "1 Nichelino";
case "OB1":
return "1 Orbassano";
case "2C":
return "2 Chieri";
case "RV2":
return "2 Rivalta";
case "CO1":
return "Circolare Collegno";
case "SE1":
// I wonder why GTT calls this "SE1" while other absurd names have a human readable name too.
return "1 Settimo";
case "16CD":
return "16 CD";
case "16CS":
return "16 CS";
case "79":
return "Cremagliera Sassi-Superga";
case "W01":
return "Night Buster 1 Arancio";
case "N10":
return "Night Buster 10 Gialla";
case "W15":
return "Night Buster 15 Rosa";
case "S18":
return "Night Buster 18 Blu";
case "S04":
return "Night Buster 4 Azzurra";
case "N4":
return "Night Buster 4 Rossa";
case "N57":
return "Night Buster 57 Oro";
case "W60":
return "Night Buster 60 Argento";
case "E68":
return "Night Buster 68 Verde";
case "S05":
return "Night Buster 5 Viola";
case "ST1":
return "Star 1";
case "ST2":
return "Star 2";
case "4N":
return "4 Navetta";
case "10N":
return "10 Navetta";
case "13N":
return "13 Navetta";
case "35N":
return "35 Navetta";
case "36N":
return "36 Navetta";
case "36S":
return "36 Speciale";
case "38S":
return "38 Speciale";
case "44S":
return "44 Scolastico";
case "46N":
return "46 Navetta";
case "M1S":
return "MetroBus sostitutivo";
default:
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("/");
} else if (routeID.charAt(routeID.length() - 1) == '/' && routeID.charAt(routeID.length() - 2) == ' ') {
//remove last space
return routeID.substring(0, routeID.length() - 2).concat("/");
} else return routeID;
*/
int len = routeID.length();
final boolean isBarrato = (routeID.charAt(len-1) == 'B') || (routeID.charAt(len-1) == '/');
if(isBarrato) {
String output;
if ((routeID.charAt(len - 2) == ' '))
output = routeID.substring(0, len - 2);
else output = routeID.substring(0, len - 1);
if(withBarratoSpace)
output = output.concat(" /");
else
output = output.concat("/");
return output;
} else return routeID;
}
public static String fixShortNameForDisplay(String routeID){
return fixShortNameForDisplay(routeID, false);
}
public static String routeDisplayToInternal(String displayName){
String name = displayName.trim();
if(name.charAt(displayName.length()-1)=='/'){
return displayName.replace(" ","").replace("/","B");
}
switch (name.toLowerCase()){
//DEFAULT CASES
case "star 1":
return "ST1";
case "star 2":
return "ST2";
case "night buster 1 arancio":
return "W01";
case "night buster 10 gialla":
return "N10";
case "night buster 15 rosa":
return "W15";
case "night buster 18 blu":
return "S18";
case "night buster 4 azzurra":
return "S04";
case "night buster 4 rossa":
return "N4";
case "night buster 57 oro":
return "N57";
case "night buster 60 argento":
return "W60";
case "night buster 68 verde":
return "E68";
case "night buster 5 viola":
return "S05";
case "1 nichelino":
return "1N";
case "1 chieri":
return "1C";
case "1 orbassano":
return "OB1";
case "2 chieri":
return "2C";
case "2 rivalta":
return "RV2";
default:
// return displayName.trim();
}
String[] arr = name.toLowerCase().split("\\s+");
try {
if (arr.length == 2 && arr[1].trim().equals("navetta") && Integer.decode(arr[0]) > 0)
return arr[0].trim().concat("N");
} catch (NumberFormatException e){
//It's not "# navetta"
Log.w("FivetNorm","checking number when it's not");
}
if(name.toLowerCase().contains("night buster")){
if(name.toLowerCase().contains("viola"))
return "S05";
else if(name.toLowerCase().contains("verde"))
return "E68";
}
//Everything failed, let's at least compact the the (probable) code
return name.replace(" ","");
}
/**
* Create the line name in GTFS format (e.g., "gtt:10U") from a more human readable name ("10")
* @param route the route object
* @return the code for the line in GTFS format
*/
public static String getGtfsRouteID(Route route){
String routeName = route.getName();
String cutName = routeName.replace("\\s", "");
int len = cutName.length();
StringBuilder sb = new StringBuilder("gtt:");
if (cutName.charAt(len-1) == '/'){
sb.append(cutName.substring(0, len-2));
sb.append("B");
//cutName = cutName.substring(0, len-2).concat("B");
} else {
sb.append(cutName);
}
//determine service kind
switch (route.type){
case UNKNOWN:
case BUS:
case TRAM:
//tourist lines have "U" in the routeid
sb.append("U");
break;
case RAILWAY:
sb.append("F");
break;
case LONG_DISTANCE_BUS:
sb.append("E");
}
return sb.toString();
}
public static String filterFullStarName(String name){
String outName = name;
if(name.contains("STAR ")){
//FIX FOR THE MaTO data
outName = outName.replace("STAR ","ST");
}
return outName;
}
}
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
@@ -1,283 +1,298 @@
/*
BusTO (fragments)
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 androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.util.Log;
import android.widget.Toast;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
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
private final int secondaryFrameLayout;
private final int primaryFrameLayout;
private final Context context;
public static final int NO_FRAME = -3;
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;
this.context = context.getApplicationContext();
stopSearcher = new StopSearcher(this);
}
/**
* Get the last successfully searched bus stop or NULL
*
* @return the stop
*/
public Stop getLastSuccessfullySearchedBusStop() {
return lastSuccessfullySearchedBusStop;
}
public void setLastSuccessfullySearchedBusStop(Stop stop) {
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
*/
public void showArrivalsFragmentForStop(@NonNull Palina p, boolean addToBackStack){
boolean sameFragment = false;
ArrivalsFragment arrivalsFragment = null;
final FragmentManager fm = managerWeakRef.get();
if(fm == null) return;
if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment frag) {
sameFragment = frag.isFragmentForTheSameStop(p);
if(sameFragment) {
arrivalsFragment = frag;
Log.d("BusTO", "Same bus stop, accessing existing fragment");
}
}
if(!sameFragment) {
// get old fragment
var frag = fm.findFragmentByTag(ArrivalsFragment.getFragmentTag(p));
if(frag instanceof ArrivalsFragment) {
attachFragmentToContainer(fm, frag, null, true, addToBackStack);
arrivalsFragment = (ArrivalsFragment) frag;
} else { // create new fragment
//set the String to be displayed on the fragment
String displayName = p.getStopDisplayName();
if (displayName != null && !displayName.isEmpty()) {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID, displayName);
} else {
arrivalsFragment = ArrivalsFragment.newInstance(p.ID);
}
String probableTag = ArrivalsFragment.getFragmentTag(p);
attachFragmentToContainer(fm, arrivalsFragment, probableTag, true, addToBackStack);
}
}
setLastSuccessfullySearchedBusStop(p);
// update the data only if I have information about the passaggi
if(p.getTotalNumberOfPassages() > 0)
arrivalsFragment.updateFragmentData(p);
// enable fragment auto refresh
arrivalsFragment.setReloadOnResume(true);
- listenerMain.hideKeyboard();
+ mainFragment.hideKeyboard();
toggleSpinner(false);
}
/**
* Called when you need to display the results of a search of stops
* @param resultList the List of stops found
* @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
Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything");
return;
}
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);
}
/**
* Wrapper for toggleSpinner in Activity
* @param on new status of spinner system
*/
public void toggleSpinner(boolean on){
- listenerMain.toggleSpinner(on);
+ mainFragment.toggleSpinner(on);
}
/**
* Attach a new fragment to the appropriate container
* @param fm the FragmentManager
* @param fragment the Fragment
* @param tagAttach attach tag (can be null, the fragment's own tag has preference)
* @param addToBackStack if the transaction is to be added to the stack
* @param toSecondaryFrame if the fragment goes to the secondary frame
*/
protected void attachFragmentToContainer(FragmentManager fm, Fragment fragment, @Nullable String tagAttach, boolean toSecondaryFrame, boolean addToBackStack){
FragmentTransaction ft = fm.beginTransaction();
int frameID;
if(toSecondaryFrame && secondaryFrameLayout!=NO_FRAME)
frameID = secondaryFrameLayout;
else
frameID = primaryFrameLayout;
var tag = fragment.getTag();
if(tag == null) tag = tagAttach;
// there is only one case
//switch (pars.transaction){
// case REPLACE:
ft.replace(frameID,fragment,tag);
//}
if (addToBackStack)
ft.addToBackStack("state_"+tag);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
//fm.executePendingTransactions();
}
public void stopLastRequestIfNeeded(){
/*if(lastTaskRef == null) return;
AsyncTask task = lastTaskRef.get();
if(task!=null){
task.cancel(interruptIfRunning);
}
*/
stopSearcher.cancelLastRequest();
}
public void requestStopSearch(String query){
stopSearcher.cancelLastRequest();
stopSearcher.runRequest(query, new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()}); // run with the default fetchers
}
/**
* Wrapper to show the errors/status that happened
* @param res result from Fetcher
*/
public void showErrorMessage(Fetcher.Result res, SearchRequestType type){
//TODO: implement a common set of errors for all fragments
if (res==null){
Log.e(DEBUG_TAG, "Asked to show result with null result");
return;
}
Log.d(DEBUG_TAG, "Showing result for "+res);
switch (res){
case OK:
break;
case CLIENT_OFFLINE:
showToastMessage(R.string.network_error, true);
break;
case SERVER_ERROR:
if (utils.isConnected(context)) {
showToastMessage(R.string.parsing_error, true);
} else {
showToastMessage(R.string.network_error, true);
}
case PARSER_ERROR:
default:
showShortToast(R.string.internal_error);
break;
case QUERY_TOO_SHORT:
showShortToast(R.string.query_too_short);
break;
case EMPTY_RESULT_SET:
if (type == SearchRequestType.STOPS)
showShortToast(R.string.no_bus_stop_have_this_name);
else if(type == SearchRequestType.ARRIVALS){
showShortToast(R.string.no_arrivals_stop);
}
break;
case NOT_FOUND:
showShortToast(R.string.no_bus_stop_have_this_name);
break;
}
}
public void showToastMessage(int messageID, boolean short_lenght) {
final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG;
if (context != null)
Toast.makeText(context, messageID, length).show();
}
private void showShortToast(int messageID){
showToastMessage(messageID, true);
}
/*
// 18/05/2026: Commenting, do not remove, might be useful later
enum Transaction{
REPLACE,
}
private static final class AttachParameters {
String tag;
boolean attachToSecondaryFrame;
Transaction transaction;
boolean addToBackStack;
public AttachParameters(String tag, boolean attachToSecondaryFrame, Transaction transaction, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.transaction = transaction;
this.addToBackStack = addToBackStack;
}
public AttachParameters(String tag, boolean attachToSecondaryFrame, boolean addToBackStack) {
this.tag = tag;
this.attachToSecondaryFrame = attachToSecondaryFrame;
this.addToBackStack = addToBackStack;
this.transaction = Transaction.REPLACE;
}
}
*/
}
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
@@ -1,959 +1,970 @@
/*
BusTO - Fragments components
Copyright (C) 2021 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.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ProgressBar;
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;
import it.reyboz.bustorino.viewmodels.IntroViewModel;
import org.jetbrains.annotations.NotNull;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
/**
* A simple {@link Fragment} subclass.
* 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";
private static final String DEBUG_TAG = "BusTO - MainFragment";
public static final String ARG_INITIAL_CONTENT = "initial_content";
public static final String ARG_STOP_ID = "pending_stop_id";
public static final String ARG_SEARCH_QUERY = "pending_search_query";
public final static String FRAGMENT_TAG = "MainScreenFragment";
private enum SearchMode {SEARCH_ID,SEARCH_NAME,INITIAL}
public enum InternalScreen {
HOME_BUTTONS(0),
NEARBY_STOPS(1),
ARRIVALS(2),
STOP_SEARCH(3),
NEARBY_ARRIVALS(4);
public final int code;
InternalScreen(int code) { this.code = code; }
@Nullable
public static InternalScreen fromCode(int code) {
for (InternalScreen c : values()) if (c.code == code) return c;
return null;
}
@NonNull
public static InternalScreen fromFragmentKind(@NonNull FragmentKind kind){
switch (kind){
case HOME_BUTTONS -> { return InternalScreen.HOME_BUTTONS; }
case NEARBY_STOPS -> { return InternalScreen.NEARBY_STOPS; }
case FragmentKind.ARRIVALS -> { return InternalScreen.ARRIVALS; }
case FragmentKind.STOPS -> { return InternalScreen.STOP_SEARCH; }
case FragmentKind.NEARBY_ARRIVALS -> { return InternalScreen.NEARBY_ARRIVALS; }
default -> {
throw new IllegalArgumentException("Unknown fragment kind");
}
}
}
}
private FragmentHelper fragmentHelper;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private FloatingActionButton floatingActionButton;
/// VIEW MODELS in BaseFragment
private boolean setupOnStart = true;
private boolean suppressArrivalsReload = false;
private boolean initialScreenShown = false;
private SearchMode searchMode = SearchMode.INITIAL;
private FragmentManager childFragMan;
/// LOCATION STUFF ///
boolean pendingIntroRun = false;
boolean pendingNearbyStopsFragmentRequest = false;
boolean pendingNearbyAddToBackStack = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
//// ACTIVITY ATTACHED (LISTENER ///
private CommonFragmentListener mListener;
private String pendingStopID = null;
private String pendingSearchQuery = null;
private InternalScreen internalScreen = InternalScreen.HOME_BUTTONS;
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){
Log.w(DEBUG_TAG,"Asked to refresh stop but context is null");
return;
}
if (childFragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) childFragMan.findFragmentById(R.id.resultFrame);
if (fragment == null){
//we create a new fragment, which is WRONG
Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment");
} else{
//String stopName = fragment.getStopID();
fragment.requestArrivalsForTheFragment();
}
} else { //we create a new fragment, which is WRONG
Log.w(DEBUG_TAG, "Asked to refresh stop when there is no fragment");
}
}
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback<>() {
@Override
public void onActivityResult(Map result) {
if (result == null) return;
if (result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null ||
result.get(Manifest.permission.ACCESS_FINE_LOCATION) == null)
return;
Log.d(DEBUG_TAG, "Permissions for location are: " + result);
if (Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_COARSE_LOCATION))
|| Boolean.TRUE.equals(result.get(Manifest.permission.ACCESS_FINE_LOCATION))) {
locationPermissionGranted = true;
Log.w(DEBUG_TAG, "Starting position");
/*if (mListener != null && getContext() != null) {
if (locationManager == null)
locationManager = AppLocationManager.getInstance(getContext());
locationManager.addLocationRequestFor(requester);
}
*/
// show nearby fragment
//showNearbyStopsFragment();
Log.d(DEBUG_TAG, "We have location permission");
if (pendingNearbyStopsFragmentRequest) {
showNearbyFragmentIfPossible(pendingNearbyAddToBackStack);
pendingNearbyStopsFragmentRequest = false;
}
}
if (pendingNearbyStopsFragmentRequest) pendingNearbyStopsFragmentRequest = false;
}
});
public MainScreenFragment() {
// Required empty public constructor
}
public static MainScreenFragment newInstance(@NonNull InternalScreen kind,
@Nullable String stopId,
@Nullable String query) {
MainScreenFragment f = new MainScreenFragment();
f.setArguments(makeArgs(kind, stopId, query));
return f;
}
public static MainScreenFragment newInstance(@NonNull InternalScreen kind, @Nullable Bundle args){
MainScreenFragment f = new MainScreenFragment();
if (args != null) {
f.setArguments(args);
}
return f;
}
/**
* Create the bundle for the arguments of the fragment
* @param kind the kind of initial screen
* @param stopId
* @param query
* @return
*/
public static Bundle makeArgs(@NonNull InternalScreen kind, @Nullable String stopId, @Nullable String query) {
Bundle b = new Bundle();
b.putInt(ARG_INITIAL_CONTENT, kind.code);
if (stopId != null) b.putString(ARG_STOP_ID, stopId);
if (query != null) b.putString(ARG_SEARCH_QUERY, query);
return b;
}
public static Bundle makeArgsArrivals(@NonNull String stopID){
return makeArgs(InternalScreen.ARRIVALS, stopID, null);
}
public static Bundle makeArgsStops(@NonNull String query){
return makeArgs(InternalScreen.STOP_SEARCH, query, null);
}
public static Bundle makeArgsNearby(){
return makeArgs(InternalScreen.NEARBY_STOPS, null, null);
}
public static Bundle makeArgsButtonsScreen(){
return makeArgs(InternalScreen.HOME_BUTTONS, null, null);
}
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
if (args != null) {
Log.d(DEBUG_TAG, "ARGS ARE NOT NULL: "+ args);
if (args.containsKey(ARG_INITIAL_CONTENT)) {
int code = args.getInt(ARG_INITIAL_CONTENT, InternalScreen.HOME_BUTTONS.code);
InternalScreen parsed = InternalScreen.fromCode(code);
internalScreen = (parsed != null) ? parsed : InternalScreen.HOME_BUTTONS;
}
String stopId = args.getString(ARG_STOP_ID);
if (stopId != null) pendingStopID = stopId;
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
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_main_screen, container, false);
/// UI ELEMENTS //
busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText);
busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText);
progressBar = root.findViewById(R.id.progressBar);
swipeRefreshLayout = root.findViewById(R.id.listRefreshLayout);
floatingActionButton = root.findViewById(R.id.floatingActionButton);
busStopSearchByIDEditText.setSelectAllOnFocus(true);
busStopSearchByIDEditText
.setOnEditorActionListener((v, actionId, event) -> {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
});
busStopSearchByNameEditText
.setOnEditorActionListener((v, actionId, event) -> {
// IME_ACTION_SEARCH alphabetical option
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
onSearchClick(v);
return true;
}
return false;
});
swipeRefreshLayout
.setOnRefreshListener(this::refreshStop);
swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500);
coordLayout = root.findViewById(R.id.coord_layout);
floatingActionButton.setImageResource(R.drawable.magnifying_glass_larger);
floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout));
busStopSearchByIDEditText.setOnFocusChangeListener((v, hasFocus) -> {
//Log.d(DEBUG_TAG, "stop search by ID has focus: " + hasFocus);
if(hasFocus)
setSearchModeBusStopID();
});
busStopSearchByNameEditText.setOnFocusChangeListener((v, hasFocus) -> {
//Log.d(DEBUG_TAG, "stop search by Name has focus: " + hasFocus);
if(hasFocus)
setSearchModeBusStopName();
});
AppCompatImageButton qrButton = root.findViewById(R.id.QRButton);
qrButton.setOnClickListener(this::onQRButtonClick);
AppCompatImageButton searchButton = root.findViewById(R.id.searchButton);
searchButton.setOnClickListener(this::onSearchClick);
// Fragment stuff
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);
cr.setBearingRequired(false);
cr.setCostAllowed(true);
cr.setPowerRequirement(Criteria.NO_REQUIREMENT);
*/
//locationManager = AppLocationManager.getInstance(requireContext());
IntroViewModel introViewModel = new ViewModelProvider(requireActivity()).get(IntroViewModel.class);
introViewModel.getIntroIsRunning().observe(getViewLifecycleOwner(), isRunning -> {
pendingIntroRun = isRunning;
});
// TODO: Figure out how to go back to home when pressing home in the nav side bar
/*fragShowingViewModel.getKindShowingFragment().observe(getViewLifecycleOwner(), kind -> {
Log.w(DEBUG_TAG, "showing fragment kind: " + kind);
try {
var screenType = InternalScreen.fromFragmentKind(kind);
if(screenType != internalScreen) {
showDifferentSubFragments(screenType);
internalScreen = screenType;
}
} catch (IllegalArgumentException e) {
//ignored
Log.d(DEBUG_TAG, "no update from fragment kind");
}
});
*/
Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null));
return root;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(DEBUG_TAG, "onViewCreated, SwipeRefreshLayout visible: "+(swipeRefreshLayout.getVisibility()==View.VISIBLE));
Log.d(DEBUG_TAG, "Saved instance state is: "+savedInstanceState);
//Restore instance state
/*if (savedInstanceState!=null){
Fragment fragment = getChildFragmentManager().getFragment(savedInstanceState, SAVED_FRAGMENT);
if (fragment!=null){
getChildFragmentManager().beginTransaction().add(R.id.resultFrame, fragment).commit();
setupOnStart = false;
}
}
*/
if (getChildFragmentManager().findFragmentById(R.id.resultFrame)!= null){
swipeRefreshLayout.setVisibility(View.VISIBLE);
// The child FragmentManager has restored its content — don't dispatch again
return;
}
if (savedInstanceState != null) return;
showDifferentSubFragments(internalScreen);
}
/**
* Installs the initial child fragment based on the arguments supplied as arguments
*/
private void showDifferentSubFragments(@NonNull InternalScreen screen) {
boolean firstTime = !initialScreenShown;
switch (screen) {
case NEARBY_STOPS:
case NEARBY_ARRIVALS: // TODO differentiate later
//add to back stack if it is not just created
showNearbyStopsFragmentChecking(!firstTime);
break;
case ARRIVALS:
// pendingStopID is consumed in onResume → requestArrivalsForStopID
break;
case STOP_SEARCH:
if (pendingSearchQuery != null && pendingSearchQuery.length() >= 2) {
fragmentHelper.requestStopSearch(pendingSearchQuery);
} else {
showButtonsFragment(firstTime);
}
pendingSearchQuery = null;
break;
case HOME_BUTTONS:
default:
showButtonsFragment(firstTime);
}
if(!initialScreenShown){
initialScreenShown = true;
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(DEBUG_TAG, "Saving instance state");
Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
if (fragment!=null)
getChildFragmentManager().putFragment(outState, SAVED_FRAGMENT, fragment);
//if (fragmentHelper!=null) fragmentHelper.setBlockAllActivities(true);
}
public void setSuppressArrivalsReload(boolean value){
suppressArrivalsReload = value;
// we have to suppress the reloading of the (possible) ArrivalsFragment
/*if(value) {
Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
if (fragment instanceof ArrivalsFragment) {
ArrivalsFragment frag = (ArrivalsFragment) fragment;
frag.setReloadOnResume(false);
}
}
*/
}
/**
* Cancel the reload of the arrival times
* because we are going to pop the fragment
*/
public void cancelReloadArrivalsIfNeeded(){
if(getContext()==null) return; //we are not attached
//Fragment fr = getChildFragmentManager().findFragmentById(R.id.resultFrame);
fragmentHelper.stopLastRequestIfNeeded();
toggleSpinner(false);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Log.d(DEBUG_TAG, "OnAttach called, setupOnAttach: "+ setupOnStart);
if (context instanceof CommonFragmentListener) {
mListener = (CommonFragmentListener) context;
} else {
throw new RuntimeException(context
+ " must implement CommonFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
// setupOnAttached = true;
}
@Override
public void onStart() {
super.onStart();
Log.d(DEBUG_TAG, "onStart called, setupOnStart: "+setupOnStart);
try {
while (!thingsToDoOnStart.isEmpty()) {
var task = thingsToDoOnStart.take();
task.run();
}
} catch (InterruptedException e) {
Log.w(DEBUG_TAG, "Interrupted while doing task for start");
thingsToDoOnStart.clear();
}
if (setupOnStart) {
if (pendingStopID==null){
if(!pendingIntroRun){
//show the fragment
//showButtonsFragment();
}
}
else{
///TODO: if there is a stop displayed, we need to hold the update
}
setupOnStart = false;
}
}
private void showButtonsFragment(boolean addInsteadOfReplace){
swipeRefreshLayout.setVisibility(View.VISIBLE);
var ft = childFragMan.beginTransaction();
var frag = ButtonsFragment.newInstance();
if(addInsteadOfReplace)
ft.add(R.id.resultFrame,frag, ButtonsFragment.FRAGMENT_TAG);
else{
ft.replace(R.id.resultFrame, frag, ButtonsFragment.FRAGMENT_TAG);
ft.addToBackStack(null);
}
ft.commit();
}
public void showButtonsFragmentIfNotNearby(boolean addToBackStack){
if(isAdded()) {
var framan = getChildFragmentManager();
var showingFrag = framan.findFragmentById(R.id.resultFrame);
if (showingFrag == null || showingFrag instanceof NearbyStopsFragment) {
var fragHome = ButtonsFragment.newInstance();
var ft = framan.beginTransaction();
if (showingFrag == null) {
ft.add(R.id.resultFrame, fragHome, ButtonsFragment.FRAGMENT_TAG);
} else {
ft.replace(R.id.resultFrame, fragHome, ButtonsFragment.FRAGMENT_TAG);
}
if (addToBackStack) ft.addToBackStack(null);
ft.commit();
} else {
Log.d(DEBUG_TAG, "attempting to show buttons home fragment but have other types (different than nearby)");
}
} else{
Log.d(DEBUG_TAG, "Fragment is not added, putting in queue of things to do");
try {
thingsToDoOnStart.put(() -> {
showButtonsFragmentIfNotNearby(addToBackStack);
});
} catch (InterruptedException e) {
Log.e(DEBUG_TAG,"Cannot add task");
}
}
}
private void showNearbyStopsFragmentChecking(boolean addToBackStack){
if(!checkLocationPermission()){
requestLocationPermission();
pendingNearbyStopsFragmentRequest = true;
pendingNearbyAddToBackStack = addToBackStack;
Log.d(DEBUG_TAG, "requesting location permission for nearby fragment");
}
else {
Log.d(DEBUG_TAG, "Showing nearby stops fragment");
showNearbyFragmentIfPossible(addToBackStack);
}
}
@Override
public void onResume() {
super.onResume();
final Context con = requireContext();
Log.w(DEBUG_TAG, "OnResume called, setupOnStart: "+ setupOnStart);
//recheck the introduction activity has been run
if(Permissions.bothLocationPermissionsGranted(con)){
Log.d(DEBUG_TAG, "Location permission OK");
} //don't request permission
// if we have a pending stopID request, do it
Log.d(DEBUG_TAG, "Pending stop ID for arrivals: "+pendingStopID);
//this is the second time we are attaching this fragment ->
Log.d(DEBUG_TAG, "Waiting for new stop request: "+ suppressArrivalsReload);
if(!suppressArrivalsReload && pendingStopID==null){
//none of the following cases are true
// check if we are showing any fragment
/*
//TODO: check if this is needed
final Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
if(fragment==null || swipeRefreshLayout.getVisibility() != View.VISIBLE){
//we are not showing anything
if(Permissions.anyLocationPermissionsGranted(getContext())){
showNearbyFragmentIfPossible();
}
}
*/
}
if (suppressArrivalsReload){
// we have to suppress the reloading of the (possible) ArrivalsFragment
Fragment fragment = getChildFragmentManager().findFragmentById(R.id.resultFrame);
if (fragment instanceof ArrivalsFragment){
ArrivalsFragment frag = (ArrivalsFragment) fragment;
frag.setReloadOnResume(false);
}
//deactivate
suppressArrivalsReload = false;
}
if(pendingStopID!=null){
Log.d(DEBUG_TAG, "Pending request for arrivals at stop ID: "+pendingStopID);
requestArrivalsForStopID(pendingStopID);
pendingStopID = null;
}
//mListener.readyGUIfor(FragmentKind.MAIN_SCREEN_FRAGMENT);
//fragmentHelper.setBlockAllActivities(false);
}
@Override
public void onPause() {
//mainHandler = null;
//locationManager.removeLocationRequestFor(requester);
//fragmentHelper.setBlockAllActivities(true);
fragmentHelper.stopLastRequestIfNeeded();
super.onPause();
}
/*
GUI METHODS
*/
@Override
public void onQrScanSuccess(@NotNull String busIDToSearch) {
busStopSearchByIDEditText.setText(busIDToSearch);
requestArrivalsForStopID(busIDToSearch);
}
/**
* QR scan button clicked
*
* @param v View QRButton clicked
*/
public void onQRButtonClick(View v) {
launchBarcodeScan();
}
/**
* OK this is pure shit
*
* @param v View clicked
*/
public void onSearchClick(View v) {
//final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()};
if (searchMode == SearchMode.SEARCH_ID) {
String busStopID = busStopSearchByIDEditText.getText().toString();
fragmentHelper.stopLastRequestIfNeeded();
requestArrivalsForStopID(busStopID);
} else if (searchMode == SearchMode.SEARCH_NAME) {
// searchMode == SEARCH_BY_NAME
String query = busStopSearchByNameEditText.getText().toString();
query = query.trim();
if(getContext()!=null) {
if (query.length() < 1) {
Toast.makeText(getContext(), R.string.insert_bus_stop_name_error, Toast.LENGTH_SHORT).show();
} else if(query.length()< 2){
Toast.makeText(getContext(), R.string.query_too_short, Toast.LENGTH_SHORT).show();
}
else {
fragmentHelper.requestStopSearch(query);
}
}
}
}
public void onToggleKeyboardLayout(View v) {
switch (searchMode){
case SEARCH_ID:
setSearchModeBusStopName();
if (busStopSearchByNameEditText.requestFocus()) {
showKeyboard();
}
break;
case SEARCH_NAME:
case INITIAL:
setSearchModeBusStopID();
if (busStopSearchByIDEditText.requestFocus()) {
showKeyboard();
}
}
}
@Override
public void enableRefreshLayout(boolean yes) {
swipeRefreshLayout.setEnabled(yes);
}
////////////////////////////////////// GUI HELPERS /////////////////////////////////////////////
public void showKeyboard() {
if(getActivity() == null) return;
InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
View view;
if(searchMode == SearchMode.SEARCH_ID)
view= busStopSearchByIDEditText;
else if(searchMode == SearchMode.SEARCH_NAME)
view = busStopSearchByNameEditText;
else{
Log.e(DEBUG_TAG, "Asking to show keyboard but SearchMode is "+searchMode+", ignoring");
return;
}
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
}
private void setSearchModeBusStopID() {
searchMode = SearchMode.SEARCH_ID;
busStopSearchByNameEditText.setVisibility(View.GONE);
busStopSearchByNameEditText.setText("");
busStopSearchByIDEditText.setVisibility(View.VISIBLE);
floatingActionButton.setImageResource(R.drawable.alphabetical);
}
private void setSearchModeBusStopName() {
searchMode = SearchMode.SEARCH_NAME;
busStopSearchByIDEditText.setVisibility(View.GONE);
busStopSearchByIDEditText.setText("");
busStopSearchByNameEditText.setVisibility(View.VISIBLE);
floatingActionButton.setImageResource(R.drawable.numeric);
}
protected boolean isNearbyFragmentShown(){
Fragment fragment = getChildFragmentManager().findFragmentByTag(NearbyStopsFragment.FRAGMENT_TAG);
return (fragment!= null && fragment.isResumed());
}
/**
* Having that cursor at the left of the edit text makes me cancer.
*
* @param busStopID bus stop ID
*/
private void setBusStopSearchByIDEditText(String busStopID) {
busStopSearchByIDEditText.setText(busStopID);
busStopSearchByIDEditText.setSelection(busStopID.length());
}
@Nullable
@Override
public View getBaseViewForSnackBar() {
return coordLayout;
}
@Override
public void toggleSpinner(boolean enable) {
if (enable) {
//already set by the RefreshListener when needed
//swipeRefreshLayout.setRefreshing(true);
progressBar.setVisibility(View.VISIBLE);
} else {
swipeRefreshLayout.setRefreshing(false);
progressBar.setVisibility(View.GONE);
}
}
private void prepareGUIForArrivals() {
swipeRefreshLayout.setEnabled(true);
swipeRefreshLayout.setVisibility(View.VISIBLE);
//actionHelpMenuItem.setVisible(true);
}
private void prepareGUIForBusStops() {
swipeRefreshLayout.setEnabled(false);
swipeRefreshLayout.setVisibility(View.VISIBLE);
//actionHelpMenuItem.setVisible(false);
}
@Override
public void showFloatingActionButton(boolean yes) {
//mListener.showFloatingActionButton(yes);
if(yes)
floatingActionButton.setVisibility(View.VISIBLE);
else
floatingActionButton.setVisibility(View.GONE);
}
/**
* This provides a temporary fix to make the transition
* to a single asynctask go smoother
*
* @param fragmentType the type of fragment created
*/
@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();
if (pendingNearbyStopsFragmentRequest) {
//locationManager.removeLocationRequestFor(requester);
pendingNearbyStopsFragmentRequest = false;
}
}
if (fragmentType == null) Log.e("ActivityMain", "Problem with fragmentType");
else
switch (fragmentType) {
case ARRIVALS:
prepareGUIForArrivals();
break;
case STOPS:
prepareGUIForBusStops();
break;
default:
- Log.d(DEBUG_TAG, "Fragment type is unknown");
+ //Log.d(DEBUG_TAG, "Fragment type is unknown");
return;
}
// Shows hints
}
@Override
public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom) {
//pass to activity
if(mListener!=null) mListener.openLineFromStop(routeGtfsId, stopIDFrom);
}
@Override
public void openLineFromVehicle(String routeGtfsId, @Nullable String optionalPatternId, @Nullable Bundle args) {
if(mListener!=null) mListener.openLineFromVehicle(routeGtfsId, optionalPatternId, args);
}
@Override
public void openNearbyStopsFragment() {
if(isAdded())
showNearbyStopsFragmentChecking(true);
else
try{
thingsToDoOnStart.put(() -> showNearbyStopsFragmentChecking(true));
} catch (InterruptedException e) {
Log.e(DEBUG_TAG, "trying to put open nearby in task but was interrupted");
}
}
@Override
public void openLinesFragment() {
if(mListener!=null) mListener.openLinesFragment();
}
@Override
public void openFavoritesFragment() {
if(mListener!=null) mListener.openFavoritesFragment();
}
@Override
public void showMapCenteredOnStop(Stop stop) {
if(mListener!=null) mListener.showMapCenteredOnStop(stop);
}
/**
* Main method for stops requests
* @param ID the Stop ID
*/
@Override
public void requestArrivalsForStopID(String ID) {
if (!isResumed()){
//defer request
pendingStopID = ID;
Log.d(DEBUG_TAG, "Deferring update for stop "+ID+ " saved: "+pendingStopID);
return;
}
final boolean delayedRequest = !(pendingStopID==null);
final FragmentManager framan = getChildFragmentManager();
if (getContext()==null){
Log.e(DEBUG_TAG, "Asked for arrivals with null context");
return;
}
if (ID == null || ID.isEmpty()) {
// we're still in UI thread, no need to mess with Progress
showToastMessage(R.string.insert_bus_stop_number_error, true);
toggleSpinner(false);
} else{
var palinaTrial = new Palina(ID);
if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment fragment) {
if (fragment.isFragmentForTheSameStop(palinaTrial)){
// Run with previous fetchers
//fragment.getCurrentFetchers().toArray()
fragment.requestArrivalsForTheFragment();
} else{
// The rest of the case is handled by the fragment Helper
fragmentHelper.showArrivalsFragmentForStop(palinaTrial, true);
}
}
else {
// this is not needed any more
//prepareGUIForArrivals();
fragmentHelper.showArrivalsFragmentForStop(palinaTrial, true);
}
}
}
private boolean checkLocationPermission(){
final Context context = getContext();
if(context==null) return false;
final boolean noPermission = ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED;
return !noPermission;
}
private void requestLocationPermission(){
if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)){
makeToast(R.string.enable_position_message_nearby);
}
requestPermissionLauncher.launch(LOCATION_PERMISSIONS);
}
private void showNearbyFragmentIfPossible(boolean addToBackStack) {
if (isNearbyFragmentShown()) {
//nothing to do
Log.d(DEBUG_TAG, "Asked to show nearby fragment but we already are showing it");
return;
}
if (getContext() == null) {
Log.e(DEBUG_TAG, "Wanting to show nearby fragment but context is null");
return;
}
if (!childFragMan.isDestroyed()) {
//Go ahead with the request
swipeRefreshLayout.setVisibility(View.VISIBLE);
final Fragment existingFrag = childFragMan.findFragmentById(R.id.resultFrame);
// fragment;
if (!(existingFrag instanceof NearbyStopsFragment)){
Log.d(DEBUG_TAG, "actually showing Nearby Stops Fragment");
//there is no fragment showing
var nearbyFrag = (NearbyStopsFragment) childFragMan.findFragmentByTag(NearbyStopsFragment.FRAGMENT_TAG);
if(nearbyFrag==null){
nearbyFrag = NearbyStopsFragment.newInstance(NearbyStopsFragment.FragType.STOPS);
}
FragmentTransaction ft = childFragMan.beginTransaction();
ft.replace(R.id.resultFrame, nearbyFrag, NearbyStopsFragment.FRAGMENT_TAG);
if(addToBackStack) ft.addToBackStack(null);
if (getActivity()!=null && !getActivity().isFinishing())
ft.commit();
else Log.e(DEBUG_TAG, "Not showing nearby fragment because activity null or is finishing");
}
pendingNearbyStopsFragmentRequest = false;
}
}
}
\ No newline at end of file
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
@@ -1,77 +1,75 @@
/*
BusTO (util)
Copyright (C) 2019 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.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;
}
public RoutePositionSorter(GPSPoint position){
this(position.getLatitude(), position.getLongitude());
}
@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);
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 = (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;
}
@Override
public boolean equals(Object obj) {
return obj instanceof RoutePositionSorter;
}
}
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
@@ -1,156 +1,252 @@
/*
BusTO - View Model components
Copyright (C) 2023 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.viewmodels
import android.app.Application
import android.location.Location
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) {
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)
val stopsAtDistance = MediatorLiveData>()
private val dbUpdateRunning = MutableLiveData(false)
private val callback =
OldDataRepository.Callback> { res ->
if(res.isSuccess){
stopsAtDistance.postValue(res.result)
if(BuildConfig.DEBUG)
Log.d(DEBUG_TAG, "Setting value of stops in bounding box")
}
}
fun setLastLocation(location: Location) {
locationLiveData.value = GPSPoint(location.latitude, location.longitude)
}
fun setDistance(distance: Int) {
distanceMtLiveData.value = distance
}
fun setDBUpdateRunning(running: Boolean) {
dbUpdateRunning.value = (running)
}
/**
* Request stop in location [latitude], [longitude], at distance [distanceMeters]
* If [saveValues] is true, store the position and the distance used
*/
fun requestStopsAtDistance(latitude: Double, longitude: Double, distanceMeters: Int, saveValues: Boolean){
if(saveValues){
locationLiveData.postValue(GPSPoint(latitude, longitude))
distanceMtLiveData.postValue(distanceMeters)
}
oldRepo.requestStopsWithinDistance(latitude, longitude, distanceMeters, callback)
}
/**
* Request stops using the previously saved location
*/
fun requestStopsAtDistance(distanceMeters: Int, saveValue: Boolean){
if(saveValue){
distanceMtLiveData.postValue(distanceMeters)
}
oldRepo.requestStopsWithinDistance(
locationLiveData.value!!.latitude,
locationLiveData.value!!.longitude, distanceMeters, callback)
}
fun requestStopsCheckDBRunning(position: GPSPoint, distanceMt: Int){
if(dbUpdateRunning.value==null || !(dbUpdateRunning.value!!)){
oldRepo.requestStopsWithinDistance(position.latitude, position.longitude, distanceMt, callback)
} else{
Log.d(DEBUG_TAG, "Database update is running, cannot do it")
}
}
fun postLocation(location: Location){
locationLiveData.postValue(GPSPoint(location.latitude, location.longitude))
}
fun postLocation(location: GPSPoint){
locationLiveData.postValue(location)
}
fun postLastDistance(distanceMeters: Int){
distanceMtLiveData.postValue(distanceMeters)
}
init {
stopsAtDistance.addSource(locationLiveData){ point->
if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "New location: $point")
val distance = distanceMtLiveData.value ?: 40
//oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, distance, callback)
requestStopsCheckDBRunning(point, distance)
}
stopsAtDistance.addSource(distanceMtLiveData){ dist->
if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "New distance: $dist")
if(locationLiveData.value != null){
val point: GPSPoint = locationLiveData.value!!
//oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, dist, callback)
requestStopsCheckDBRunning(point, dist)
} else{
Log.d(DEBUG_TAG, "Modified distance but locationLiveData value is null")
}
}
stopsAtDistance.addSource(dbUpdateRunning){ running ->
if(BuildConfig.DEBUG) Log.d(DEBUG_TAG, "DB update running: $running")
if(!running) {
reRequestStops()
}
}
}
private fun reRequestStops(){
var req = false
locationLiveData.value?.let{ point ->
distanceMtLiveData.value ?.let { dist->
req = true
oldRepo.requestStopsWithinDistance(point.latitude, point.longitude, dist, callback)
}
}
if(!req){
Log.w(DEBUG_TAG, "Requested to rerun stops, but position or distance (or both) are null")
}
}
+ 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
@@ -1,152 +1,152 @@
-
+
+
-
-
+
-
-
+ android:layout_height="match_parent"
+ android:layout_alignParentEnd="true"
+ android:layout_alignParentRight="true"
+ android:animateLayoutChanges="true"
+ android:visibility="gone">
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_nearby_stops.xml b/app/src/main/res/layout/fragment_nearby_stops.xml
index f858094..4fc237a 100644
--- a/app/src/main/res/layout/fragment_nearby_stops.xml
+++ b/app/src/main/res/layout/fragment_nearby_stops.xml
@@ -1,99 +1,100 @@
+ android:layout_below="@+id/titleTextView"
+ android:layout_centerHorizontal="true"/>