diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
index b000c13..4a34d68 100644
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -1,730 +1,731 @@
/*
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.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.DatabaseUpdate;
import it.reyboz.bustorino.data.PreferencesHolder;
import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
import it.reyboz.bustorino.fragments.*;
import it.reyboz.bustorino.middleware.GeneralActivity;
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;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Log.d(DEBUG_TAG, "onCreate, savedInstanceState is: "+savedInstanceState);
setContentView(R.layout.activity_principal);
final SharedPreferences theShPr = getMainSharedPreferences();
boolean showingArrivalsFromIntent = false;
//database check
GtfsDatabase gtfsDB = GtfsDatabase.Companion.getGtfsDatabase(this);
final int db_version = gtfsDB.getOpenHelper().getReadableDatabase().getVersion();
boolean dataUpdateRequested = false;
final int old_version = PreferencesHolder.getGtfsDBVersion(theShPr);
Log.d(DEBUG_TAG, "GTFS Database: old version is "+old_version+ ", new version is "+db_version);
if (old_version < db_version){
//decide update conditions in the future
if(old_version < 2 && db_version >= 2) {
dataUpdateRequested = true;
DatabaseUpdate.requestDBUpdateWithWork(this, true, true);
}
PreferencesHolder.setGtfsDBVersion(theShPr, db_version);
}
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;
}
//Try (hopefully) database update
if(!dataUpdateRequested)
DatabaseUpdate.requestDBUpdateWithWork(this, false, false);
/*
Watch for database update
*/
final WorkManager workManager = WorkManager.getInstance(this);
workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG)
.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) {
createDefaultSnackbar();
} 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, "");
//if (vl.length() == 0 || vl.equals("arrivals")) {
// showMainFragment();
Log.d(DEBUG_TAG, "The default screen to open is: "+vl);
if (showingArrivalsFromIntent){
//do nothing but exclude a case
- }
- else if (vl.equals("map")){
- requestMapFragment(false);
- } else if(vl.equals("favorites")){
- checkAndShowFavoritesFragment(getSupportFragmentManager(), false);
- } else if(vl.equals("lines")){
- showLinesFragment(getSupportFragmentManager(), false, null);
- } else {
- showMainFragment(false);
+ }else if (savedInstanceState==null) {
+ //we are not restarting the activity from nothing
+ if (vl.equals("map")) {
+ requestMapFragment(false);
+ } else if (vl.equals("favorites")) {
+ checkAndShowFavoritesFragment(getSupportFragmentManager(), false);
+ } else if (vl.equals("lines")) {
+ showLinesFragment(getSupportFragmentManager(), false, null);
+ } else {
+ showMainFragment(false);
+ }
}
onCreateComplete = true;
//last but not least, set the good default values
manageDefaultValuesForSettings();
}
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 -> {
if (menuItem.getItemId() == R.id.drawer_action_settings) {
Log.d("MAINBusTO", "Pressed button preferences");
closeDrawerIfOpen();
startActivity(new Intent(ActivityPrincipal.this, ActivitySettings.class));
return true;
} else if(menuItem.getItemId() == R.id.nav_favorites_item){
closeDrawerIfOpen();
//get Fragment
checkAndShowFavoritesFragment(getSupportFragmentManager(), true);
return true;
} else if(menuItem.getItemId() == R.id.nav_arrivals){
closeDrawerIfOpen();
showMainFragment(true);
return true;
} else if(menuItem.getItemId() == R.id.nav_map_item){
closeDrawerIfOpen();
requestMapFragment(true);
return true;
} else if (menuItem.getItemId() == R.id.nav_lines_item) {
closeDrawerIfOpen();
showLinesFragment(getSupportFragmentManager(), true,null);
return true;
} else if(menuItem.getItemId() == R.id.drawer_action_info) {
closeDrawerIfOpen();
startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class));
return true;
}
//selectDrawerItem(menuItem);
Log.d(DEBUG_TAG, "pressed item "+menuItem);
return true;
});
}
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);
}
@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 (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) {
int[] cases = {R.id.nav_arrivals, R.id.nav_favorites_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() {
boolean foundFragment = false;
Fragment shownFrag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
if (mDrawer.isDrawerOpen(GravityCompat.START))
mDrawer.closeDrawer(GravityCompat.START);
else if(shownFrag != null && shownFrag.isVisible() && shownFrag.getChildFragmentManager().getBackStackEntryCount() > 0){
//if we have been asked to show a stop from another fragment, we should go back even in the main
if(shownFrag instanceof MainScreenFragment){
//we have to stop the arrivals reload
((MainScreenFragment) shownFrag).cancelReloadArrivalsIfNeeded();
}
shownFrag.getChildFragmentManager().popBackStack();
if(showingMainFragmentFromOther && getSupportFragmentManager().getBackStackEntryCount() > 0){
getSupportFragmentManager().popBackStack();
Log.d(DEBUG_TAG, "Popping main back stack also");
}
}
else if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
Log.d(DEBUG_TAG, "Popping main frame backstack for fragments");
}
else
super.onBackPressed();
}
/**
* Create and show the SnackBar with the message
*/
private void createDefaultSnackbar() {
View baseView = null;
final Fragment frag = getSupportFragmentManager().findFragmentById(R.id.mainActContentFrame);
if (frag instanceof ScreenBaseFragment){
baseView = ((ScreenBaseFragment) frag).getBaseViewForSnackBar();
}
if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
snackbar.show();
}
/**
* Show the 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);
ft.commit();
}
/**
* Show the fragment by adding it to the backstack
* @param fraMan the fragmentManager
* @param arguments args for the fragment
*/
- private static void createShowMainFragment(FragmentManager fraMan, Bundle arguments, boolean addToBackStack){
+ private static void createShowMainFragment(FragmentManager fraMan,@Nullable Bundle arguments, boolean addToBackStack){
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 requestMapFragment(final boolean allowReturn){
final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ);
switch (result) {
case PERMISSION_OK:
createAndShowMapFragment(null, allowReturn);
break;
case PERMISSION_ASKING:
permissionDoneRunnables.put(permission,
() -> createAndShowMapFragment(null, allowReturn));
break;
case PERMISSION_NEG_CANNOT_ASK:
String storage_perm = getString(R.string.storage_permission);
String text = getString(R.string.too_many_permission_asks, storage_perm);
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
}
private static void checkAndShowFavoritesFragment(FragmentManager fragmentManager, boolean addToBackStack){
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 static void showLinesFragment(@NonNull FragmentManager fragmentManager, boolean addToBackStack, @Nullable Bundle fragArgs){
FragmentTransaction ft = fragmentManager.beginTransaction();
Fragment f = fragmentManager.findFragmentByTag(LinesFragment.FRAGMENT_TAG);
if(f!=null){
ft.replace(R.id.mainActContentFrame, f, LinesFragment.FRAGMENT_TAG);
}else{
//use new method
ft.replace(R.id.mainActContentFrame,LinesFragment.class,fragArgs,LinesFragment.FRAGMENT_TAG);
}
if (addToBackStack)
ft.addToBackStack("lines");
ft.setReorderingAllowed(true)
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.commit();
}
- private MainScreenFragment showMainFragment(boolean addToBackStack){
+ private void showMainFragment(boolean addToBackStack){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
final MainScreenFragment mainScreenFragment;
if (fragment==null | !(fragment instanceof MainScreenFragment)){
- mainScreenFragment = MainScreenFragment.newInstance();
- //mainScreenFragment = createAndShowMainFragment();
- showMainFragment(fraMan, mainScreenFragment, addToBackStack);
+ createShowMainFragment(fraMan, null, addToBackStack);
}
else if(!fragment.isVisible()){
mainScreenFragment = (MainScreenFragment) fragment;
showMainFragment(fraMan, mainScreenFragment, addToBackStack);
Log.d(DEBUG_TAG, "Found the main fragment");
} else{
mainScreenFragment = (MainScreenFragment) fragment;
}
- return mainScreenFragment;
+ //return mainScreenFragment;
}
@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) {
//TODO
}
/*
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 probableFragment = getMainFragmentIfVisible();
- if (probableFragment!=null){
- probableFragment.readyGUIfor(fragmentType);
+ MainScreenFragment mainFragmentIfVisible = getMainFragmentIfVisible();
+ if (mainFragmentIfVisible!=null){
+ mainFragmentIfVisible.readyGUIfor(fragmentType);
}
int titleResId;
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_arrivals);
break;
case STOPS:
titleResId = R.string.stop_search_view_title;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case MAIN_SCREEN_FRAGMENT:
case NEARBY_STOPS:
case NEARBY_ARRIVALS:
titleResId=R.string.app_name_full;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
case LINES:
titleResId=R.string.lines;
mNavView.setCheckedItem(R.id.nav_lines_item);
break;
default:
titleResId = 0;
}
if(getSupportActionBar()!=null && titleResId!=0)
getSupportActionBar().setTitle(titleResId);
}
@Override
public void requestArrivalsForStopID(String ID) {
//register if the request came from the main fragment or not
MainScreenFragment probableFragment = getMainFragmentIfVisible();
showingMainFragmentFromOther = (probableFragment==null);
if (showingMainFragmentFromOther){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
Log.d(DEBUG_TAG, "Requested main fragment, not visible. Search by TAG: "+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 {
- //createAndShowMainFragment
// we have no fragment
final Bundle args = new Bundle();
args.putString(MainScreenFragment.PENDING_STOP_SEARCH, ID);
//if onCreate is complete, then we are not asking for the first showing fragment
boolean addtobackstack = onCreateComplete;
createShowMainFragment(fraMan, args ,addtobackstack);
- //probableFragment = createAndShowMainFragment();
}
} else {
//the MainScreeFragment is shown, nothing to do
probableFragment.requestArrivalsForStopID(ID);
}
mNavView.setCheckedItem(R.id.nav_arrivals);
}
@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(Stop stop) {
createAndShowMapFragment(stop, true);
}
//Map Fragment stuff
void createAndShowMapFragment(@Nullable Stop stop, boolean addToBackStack){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
MapFragment fragment = stop == null? MapFragment.getInstance(): MapFragment.getInstance(stop);
ft.replace(R.id.mainActContentFrame, fragment, MapFragment.FRAGMENT_TAG);
if (addToBackStack) ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{
private final Context activityContext;
public ToolbarItemClickListener(Context activityContext) {
this.activityContext = activityContext;
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_about:
startActivity(new Intent(ActivityPrincipal.this, ActivityAbout.class));
return true;
case R.id.action_hack:
openIceweasel(getString(R.string.hack_url), activityContext);
return true;
case R.id.action_source:
openIceweasel("https://gitpull.it/source/libre-busto/", activityContext);
return true;
case R.id.action_licence:
openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", activityContext);
return true;
case R.id.action_experiments:
startActivity(new Intent(ActivityPrincipal.this, ActivityExperiments.class));
default:
}
return false;
}
}
/**
* Adjust setting to match the default ones
*/
private void manageDefaultValuesForSettings(){
SharedPreferences mainSharedPref = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = mainSharedPref.edit();
//Main fragment to show
String screen = mainSharedPref.getString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "");
boolean edit = false;
if (screen.isEmpty()){
editor.putString(SettingsFragment.PREF_KEY_STARTUP_SCREEN, "arrivals");
edit=true;
}
//Fetchers
final Set setSelected = mainSharedPref.getStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, new HashSet<>());
if (setSelected.isEmpty()){
String[] defaultVals = getResources().getStringArray(R.array.arrivals_sources_values_default);
editor.putStringSet(SettingsFragment.KEY_ARRIVALS_FETCHERS_USE, utils.convertArrayToSet(defaultVals));
edit=true;
}
if (edit){
editor.commit();
}
}
}
diff --git a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
index a25cb9e..3ec8ccf 100644
--- a/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/PalinaAdapter.java
@@ -1,241 +1,242 @@
/*
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.adapters;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.preference.PreferenceManager;
import android.content.SharedPreferences;
import android.os.Build;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.util.PassaggiSorter;
import it.reyboz.bustorino.util.RouteSorterByArrivalTime;
/**
* This once was a ListView Adapter for BusLine[].
*
* Thanks to Framentos developers for the guide:
* http://www.framentos.com/en/android-tutorial/2012/07/16/listview-in-android-using-custom-listadapter-and-viewcache/#
*
* @author Valerio Bozzolan
* @author Ludovico Pavesi
*/
public class PalinaAdapter extends ArrayAdapter implements SharedPreferences.OnSharedPreferenceChangeListener {
private LayoutInflater li;
private static int row_layout = R.layout.entry_bus_line_passage;
private static final int metroBg = R.drawable.route_background_metro;
private static final int busBg = R.drawable.route_background_bus;
private static final int extraurbanoBg = R.drawable.route_background_bus_long_distance;
private static final int busIcon = R.drawable.bus;
private static final int trainIcon = R.drawable.subway;
private static final int tramIcon = R.drawable.tram;
private final String KEY_CAPITALIZE;
private Capitalize capit;
//private static final int cityIcon = R.drawable.city;
// hey look, a pattern!
private static class ViewHolder {
TextView rowStopIcon;
TextView rowRouteDestination;
TextView rowRouteTimetable;
}
private static Capitalize getCapitalize(SharedPreferences shPr, String key){
String capitalize = shPr.getString(key, "");
switch (capitalize.trim()){
case "KEEP":
return Capitalize.DO_NOTHING;
case "CAPITALIZE_ALL":
return Capitalize.ALL;
case "CAPITALIZE_FIRST":
return Capitalize.FIRST;
}
return Capitalize.DO_NOTHING;
}
public PalinaAdapter(Context context, Palina p) {
super(context, row_layout, p.queryAllRoutes());
li = LayoutInflater.from(context);
Comparator sorter = null;
if (p.getPassaggiSourceIfAny()== Passaggio.Source.GTTJSON){
sorter = new PassaggiSorter();
}
for(Route r: p.queryAllRoutes()){
if (sorter==null) Collections.sort(r.passaggi);
else Collections.sort(r.passaggi, sorter);
}
sort(new RouteSorterByArrivalTime());
/*
sort(new Comparator() {
@Override
public int compare(Route route, Route t1) {
LinesNameSorter sorter = new LinesNameSorter();
if(route.getNameForDisplay()!= null){
if(t1.getNameForDisplay()!=null){
return sorter.compare(route.getNameForDisplay(), t1.getNameForDisplay());
}
else return -1;
} else if(t1.getNameForDisplay()!=null){
return +1;
}
else return 0;
}
});
*/
KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit);
SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
defSharPref.registerOnSharedPreferenceChangeListener(this);
this.capit = getCapitalize(defSharPref, KEY_CAPITALIZE);
}
/**
* Some parts taken from the AdapterBusLines class.
* Some parts inspired by these enlightening tutorials:
* http://www.simplesoft.it/android/guida-agli-adapter-e-le-listview-in-android.html
* https://www.codeofaninja.com/2013/09/android-viewholder-pattern-example.html
* And some other bits and bobs TIRATI FUORI DAL NULLA CON L'INTUIZIONE INTELLETTUALE PERCHÉ
* SEMBRA CHE NESSUNO ABBIA LA MINIMA IDEA DI COME FUNZIONA UN ADAPTER SU ANDROID.
*/
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
ViewHolder vh;
if(convertView == null) {
// INFLATE!
// setting a parent here is not supported and causes a fatal exception, apparently.
convertView = li.inflate(row_layout, null);
// STORE TEXTVIEWS!
vh = new ViewHolder();
vh.rowStopIcon = (TextView) convertView.findViewById(R.id.routeID);
vh.rowRouteDestination = (TextView) convertView.findViewById(R.id.routeDestination);
vh.rowRouteTimetable = (TextView) convertView.findViewById(R.id.routesThatStopHere);
// STORE VIEWHOLDER IN\ON\OVER\UNDER\ABOVE\BESIDE THE VIEW!
convertView.setTag(vh);
} else {
// RECOVER THIS STUFF!
vh = (ViewHolder) convertView.getTag();
}
Route route = getItem(position);
vh.rowStopIcon.setText(route.getNameForDisplay());
if(route.destinazione==null || route.destinazione.length() == 0) {
vh.rowRouteDestination.setVisibility(View.GONE);
// move around the route timetable
final ViewGroup.MarginLayoutParams pars = (ViewGroup.MarginLayoutParams) vh.rowRouteTimetable.getLayoutParams();
if (pars!=null){
pars.topMargin = 16;
if(Build.VERSION.SDK_INT >= 17)
pars.setMarginStart(20);
pars.leftMargin = 20;
}
} else {
// View Holder Pattern(R) renders each element from a previous one: if the other one had an invisible rowRouteDestination, we need to make it visible.
vh.rowRouteDestination.setVisibility(View.VISIBLE);
String dest = route.destinazione;
switch (capit){
case ALL:
dest = route.destinazione.toUpperCase(Locale.ROOT);
break;
case FIRST:
dest = utils.toTitleCase(route.destinazione, true);
break;
case DO_NOTHING:
default:
}
vh.rowRouteDestination.setText(dest);
}
switch (route.type) {
//UNKNOWN = BUS for the moment
case UNKNOWN:
case BUS:
default:
// convertView could contain another background, reset it
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
break;
case LONG_DISTANCE_BUS:
vh.rowStopIcon.setBackgroundResource(extraurbanoBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0);
break;
case METRO:
vh.rowStopIcon.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
vh.rowStopIcon.setBackgroundResource(metroBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0);
break;
case RAILWAY:
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0);
break;
case TRAM: // never used but whatever.
vh.rowStopIcon.setBackgroundResource(busBg);
vh.rowRouteDestination.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0);
break;
}
List passaggi = route.passaggi;
+ //TODO: Sort the passaggi with realtime first if source is GTTJSONFetcher
if(passaggi.size() == 0) {
vh.rowRouteTimetable.setText(R.string.no_passages);
} else {
vh.rowRouteTimetable.setText(route.getPassaggiToString());
}
return convertView;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(key.equals(KEY_CAPITALIZE)){
capit = getCapitalize(sharedPreferences, KEY_CAPITALIZE);
notifyDataSetChanged();
}
}
enum Capitalize{
DO_NOTHING, ALL, FIRST
}
}
diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
index 636c7b8..f4e1068 100644
--- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java
@@ -1,558 +1,559 @@
/*
BusTO - Fragments components
Copyright (C) 2018 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ListAdapter;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.adapters.PalinaAdapter;
import it.reyboz.bustorino.backend.ArrivalsFetcher;
import it.reyboz.bustorino.backend.DBStatusManager;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTNormalizer;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Passaggio;
import it.reyboz.bustorino.backend.Route;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.data.UserDB;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
public class ArrivalsFragment extends ResultListFragment implements LoaderManager.LoaderCallbacks {
private final static String KEY_STOP_ID = "stopid";
private final static String KEY_STOP_NAME = "stopname";
private final static String DEBUG_TAG_ALL = "BUSTOArrivalsFragment";
private String DEBUG_TAG = DEBUG_TAG_ALL;
private final static int loaderFavId = 2;
private final static int loaderStopId = 1;
static final String STOP_TITLE = "messageExtra";
private final static String SOURCES_TEXT="sources_textview_message";
private @Nullable String stopID,stopName;
private DBStatusManager prefs;
private DBStatusManager.OnDBUpdateStatusChangeListener listener;
private boolean justCreated = false;
private Palina lastUpdatedPalina = null;
private boolean needUpdateOnAttach = false;
private boolean fetchersChangeRequestPending = false;
private boolean stopIsInFavorites = false;
//Views
protected ImageButton addToFavorites;
protected TextView timesSourceTextView;
private List fetchers = null; //new ArrayList<>(Arrays.asList(utils.getDefaultArrivalsFetchers()));
private boolean reloadOnResume = true;
public static ArrivalsFragment newInstance(String stopID){
return newInstance(stopID, null);
}
public static ArrivalsFragment newInstance(@NonNull String stopID, @Nullable String stopName){
ArrivalsFragment fragment = new ArrivalsFragment();
Bundle args = new Bundle();
args.putString(KEY_STOP_ID,stopID);
//parameter for ResultListFragmentrequestArrivalsForStopID
args.putSerializable(LIST_TYPE,FragmentKind.ARRIVALS);
if (stopName != null){
args.putString(KEY_STOP_NAME,stopName);
}
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
stopID = getArguments().getString(KEY_STOP_ID);
DEBUG_TAG = DEBUG_TAG_ALL+" "+stopID;
//this might really be null
stopName = getArguments().getString(KEY_STOP_NAME);
final ArrivalsFragment arrivalsFragment = this;
listener = new DBStatusManager.OnDBUpdateStatusChangeListener() {
@Override
public void onDBStatusChanged(boolean updating) {
if(!updating){
getLoaderManager().restartLoader(loaderFavId,getArguments(),arrivalsFragment);
} else {
final LoaderManager lm = getLoaderManager();
lm.destroyLoader(loaderFavId);
lm.destroyLoader(loaderStopId);
}
}
@Override
public boolean defaultStatusValue() {
return true;
}
};
prefs = new DBStatusManager(getContext().getApplicationContext(),listener);
justCreated = true;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_arrivals, container, false);
messageTextView = root.findViewById(R.id.messageTextView);
addToFavorites = root.findViewById(R.id.addToFavorites);
resultsListView = root.findViewById(R.id.resultsListView);
timesSourceTextView = root.findViewById(R.id.timesSourceTextView);
timesSourceTextView.setOnLongClickListener(view -> {
if(!fetchersChangeRequestPending){
rotateFetchers();
//Show we are changing provider
timesSourceTextView.setText(R.string.arrival_source_changing);
mListener.requestArrivalsForStopID(stopID);
fetchersChangeRequestPending = true;
return true;
}
return false;
});
timesSourceTextView.setOnClickListener(view -> {
Toast.makeText(getContext(), R.string.change_arrivals_source_message, Toast.LENGTH_SHORT)
.show();
});
//Button
addToFavorites.setClickable(true);
addToFavorites.setOnClickListener(v -> {
// add/remove the stop in the favorites
toggleLastStopToFavorites();
});
resultsListView.setOnItemClickListener((parent, view, position, id) -> {
String routeName;
Route r = (Route) parent.getItemAtPosition(position);
routeName = FiveTNormalizer.routeInternalToDisplay(r.getNameForDisplay());
if (routeName == null) {
routeName = r.getNameForDisplay();
}
if (r.destinazione == null || r.destinazione.length() == 0) {
Toast.makeText(getContext(),
getString(R.string.route_towards_unknown, routeName), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(),
getString(R.string.route_towards_destination, routeName, r.destinazione), Toast.LENGTH_SHORT).show();
}
});
String displayName = getArguments().getString(STOP_TITLE);
if(displayName!=null)
setTextViewMessage(String.format(
getString(R.string.passages), displayName));
String probablemessage = getArguments().getString(MESSAGE_TEXT_VIEW);
if (probablemessage != null) {
//Log.d("BusTO fragment " + this.getTag(), "We have a possible message here in the savedInstaceState: " + probablemessage);
messageTextView.setText(probablemessage);
messageTextView.setVisibility(View.VISIBLE);
}
/*String sourcesTextViewData = getArguments().getString(SOURCES_TEXT);
if (sourcesTextViewData!=null){
timesSourceTextView.setText(sourcesTextViewData);
}*/
//need to do this when we recreate the fragment but we haven't updated the arrival times
if (lastUpdatedPalina!=null)
showArrivalsSources(lastUpdatedPalina);
return root;
}
@Override
public void onResume() {
super.onResume();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "OnResume, justCreated "+justCreated);
/*if(needUpdateOnAttach){
updateFragmentData(null);
needUpdateOnAttach=false;
}*/
if(stopID!=null){
//refresh the arrivals
if(!justCreated){
fetchers = utils.getDefaultArrivalsFetchers(getContext());
adjustFetchersToSource();
if (reloadOnResume)
mListener.requestArrivalsForStopID(stopID);
}
else justCreated = false;
//start the loader
if(prefs.isDBUpdating(true)){
prefs.registerListener();
} else {
Log.d(DEBUG_TAG, "Restarting loader for stop");
loaderManager.restartLoader(loaderFavId, getArguments(), this);
}
updateMessage();
}
}
@Override
public void onStart() {
super.onStart();
if (needUpdateOnAttach){
updateFragmentData(null);
needUpdateOnAttach = false;
}
}
@Override
public void onPause() {
if(listener!=null)
prefs.unregisterListener();
super.onPause();
LoaderManager loaderManager = getLoaderManager();
Log.d(DEBUG_TAG, "onPause, have running loaders: "+loaderManager.hasRunningLoaders());
loaderManager.destroyLoader(loaderFavId);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
//get fetchers
fetchers = utils.getDefaultArrivalsFetchers(context);
}
@Nullable
public String getStopID() {
return stopID;
}
public boolean reloadsOnResume() {
return reloadOnResume;
}
public void setReloadOnResume(boolean reloadOnResume) {
this.reloadOnResume = reloadOnResume;
}
/**
* Give the fetchers
* @return the list of the fetchers
*/
public ArrayList getCurrentFetchers(){
return new ArrayList<>(this.fetchers);
}
public ArrivalsFetcher[] getCurrentFetchersAsArray(){
ArrivalsFetcher[] arr = new ArrivalsFetcher[fetchers.size()];
fetchers.toArray(arr);
return arr;
}
private void rotateFetchers(){
Log.d(DEBUG_TAG, "Rotating fetchers, before: "+fetchers);
Collections.rotate(fetchers, -1);
Log.d(DEBUG_TAG, "Rotating fetchers, afterwards: "+fetchers);
}
/**
* Update the UI with the new data
* @param p the full Palina
*/
public void updateFragmentData(@Nullable Palina p){
if (p!=null)
lastUpdatedPalina = p;
if (!isAdded()){
//defer update at next show
if (p==null)
Log.w(DEBUG_TAG, "Asked to update the data, but we're not attached and the data is null");
else needUpdateOnAttach = true;
} else {
final PalinaAdapter adapter = new PalinaAdapter(getContext(), lastUpdatedPalina);
showArrivalsSources(lastUpdatedPalina);
super.resetListAdapter(adapter);
}
}
/**
* Set the message of the arrival times source
* @param p Palina with the arrival times
*/
protected void showArrivalsSources(Palina p){
final Passaggio.Source source = p.getPassaggiSourceIfAny();
if (source == null){
Log.e(DEBUG_TAG, "NULL SOURCE");
return;
}
String source_txt;
switch (source){
case GTTJSON:
source_txt = getString(R.string.gttjsonfetcher);
break;
case FiveTAPI:
source_txt = getString(R.string.fivetapifetcher);
break;
case FiveTScraper:
source_txt = getString(R.string.fivetscraper);
break;
case MatoAPI:
source_txt = getString(R.string.source_mato);
break;
case UNDETERMINED:
//Don't show the view
source_txt = getString(R.string.undetermined_source);
break;
default:
throw new IllegalStateException("Unexpected value: " + source);
}
//
final boolean updatedFetchers = adjustFetchersToSource(source);
if(!updatedFetchers)
Log.w(DEBUG_TAG, "Tried to update the source fetcher but it didn't work");
final String base_message = getString(R.string.times_source_fmt, source_txt);
timesSourceTextView.setText(base_message);
if (p.getTotalNumberOfPassages() > 0) {
timesSourceTextView.setVisibility(View.VISIBLE);
} else {
timesSourceTextView.setVisibility(View.INVISIBLE);
}
fetchersChangeRequestPending = false;
}
protected boolean adjustFetchersToSource(Passaggio.Source source){
if (source == null) return false;
int count = 0;
if (source!= Passaggio.Source.UNDETERMINED)
while (source != fetchers.get(0).getSourceForFetcher() && count < 200){
//we need to update the fetcher that is requested
rotateFetchers();
count++;
}
return count < 200;
+
}
protected boolean adjustFetchersToSource(){
if (lastUpdatedPalina == null) return false;
final Passaggio.Source source = lastUpdatedPalina.getPassaggiSourceIfAny();
return adjustFetchersToSource(source);
}
@Override
public void setNewListAdapter(ListAdapter adapter) {
throw new UnsupportedOperationException();
}
/**
* Update the message in the fragment
*
* It may eventually change the "Add to Favorite" icon
*/
private void updateMessage(){
String message = null;
if (stopName != null && stopID != null && stopName.length() > 0) {
message = (stopID.concat(" - ").concat(stopName));
} else if(stopID!=null) {
message = stopID;
} else {
Log.e("ArrivalsFragm"+getTag(),"NO ID FOR THIS FRAGMENT - something went horribly wrong");
}
if(message!=null) {
setTextViewMessage(getString(R.string.passages,message));
}
// whatever is the case, update the star icon
//updateStarIconFromLastBusStop();
}
@NonNull
@Override
public Loader onCreateLoader(int id, Bundle args) {
if(args.getString(KEY_STOP_ID)==null) return null;
final String stopID = args.getString(KEY_STOP_ID);
final Uri.Builder builder = AppDataProvider.getUriBuilderToComplete();
CursorLoader cl;
switch (id){
case loaderFavId:
builder.appendPath("favorites").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),UserDB.getFavoritesColumnNamesAsArray,null,null,null);
break;
case loaderStopId:
builder.appendPath("stop").appendPath(stopID);
cl = new CursorLoader(getContext(),builder.build(),new String[]{NextGenDB.Contract.StopsTable.COL_NAME},
null,null,null);
break;
default:
return null;
}
cl.setUpdateThrottle(500);
return cl;
}
@Override
public void onLoadFinished(Loader loader, Cursor data) {
switch (loader.getId()){
case loaderFavId:
final int colUserName = data.getColumnIndex(UserDB.getFavoritesColumnNamesAsArray[1]);
if(data.getCount()>0){
// IT'S IN FAVORITES
data.moveToFirst();
final String probableName = data.getString(colUserName);
stopIsInFavorites = true;
stopName = probableName;
//update the message in the textview
updateMessage();
} else {
stopIsInFavorites =false;
}
updateStarIcon();
if(stopName == null){
//stop is not inside the favorites and wasn't provided
Log.d("ArrivalsFragment"+getTag(),"Stop wasn't in the favorites and has no name, looking in the DB");
getLoaderManager().restartLoader(loaderStopId,getArguments(),this);
}
break;
case loaderStopId:
if(data.getCount()>0){
data.moveToFirst();
int index = data.getColumnIndex(
NextGenDB.Contract.StopsTable.COL_NAME
);
if (index == -1){
Log.e(DEBUG_TAG, "Index is -1, column not present. App may explode now...");
}
stopName = data.getString(index);
updateMessage();
} else {
Log.w("ArrivalsFragment"+getTag(),"Stop is not inside the database... CLOISTER BELL");
}
}
}
@Override
public void onLoaderReset(Loader loader) {
//NOTHING TO DO
}
public void toggleLastStopToFavorites() {
Stop stop = lastUpdatedPalina;
if (stop != null) {
// toggle the status in background
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.TOGGLE,
v->updateStarIconFromLastBusStop(v)).execute(stop);
} else {
// this case have no sense, but just immediately update the favorite icon
updateStarIconFromLastBusStop(true);
}
}
/**
* Update the star "Add to favorite" icon
*/
public void updateStarIconFromLastBusStop(Boolean toggleDone) {
if (stopIsInFavorites)
stopIsInFavorites = !toggleDone;
else stopIsInFavorites = toggleDone;
updateStarIcon();
// check if there is a last Stop
/*
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (isStopInFavorites(stopID)) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
*/
}
/**
* Update the star icon according to `stopIsInFavorites`
*/
public void updateStarIcon() {
// no favorites no party!
// check if there is a last Stop
if (stopID == null) {
addToFavorites.setVisibility(View.INVISIBLE);
} else {
// filled or outline?
if (stopIsInFavorites) {
addToFavorites.setImageResource(R.drawable.ic_star_filled);
} else {
addToFavorites.setImageResource(R.drawable.ic_star_outline);
}
addToFavorites.setVisibility(View.VISIBLE);
}
}
@Override
public void onDestroyView() {
getArguments().putString(SOURCES_TEXT, timesSourceTextView.getText().toString());
super.onDestroyView();
}
}
diff --git a/src/it/reyboz/bustorino/fragments/LinesFragment.kt b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
index 29b7a6f..b379f6b 100644
--- a/src/it/reyboz/bustorino/fragments/LinesFragment.kt
+++ b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
@@ -1,393 +1,418 @@
/*
BusTO - Fragments components
Copyright (C) 2022 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import android.view.*
import android.widget.*
import android.widget.AdapterView.INVALID_POSITION
import android.widget.AdapterView.OnItemSelectedListener
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import it.reyboz.bustorino.R
import it.reyboz.bustorino.adapters.NameCapitalize
import it.reyboz.bustorino.adapters.StopAdapterListener
import it.reyboz.bustorino.adapters.StopRecyclerAdapter
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.data.gtfs.GtfsRoute
import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
import it.reyboz.bustorino.data.gtfs.PatternStop
import it.reyboz.bustorino.util.LinesNameSorter
import it.reyboz.bustorino.util.PatternWithStopsSorter
class LinesFragment : ScreenBaseFragment() {
companion object {
fun newInstance(){
LinesFragment()
}
const val DEBUG_TAG="BusTO-LinesFragment"
const val FRAGMENT_TAG="LinesFragment"
val patternStopsComparator = PatternWithStopsSorter()
}
private lateinit var viewModel: LinesViewModel
private lateinit var linesSpinner: Spinner
private lateinit var patternsSpinner: Spinner
private lateinit var currentRoutes: List
- private lateinit var currentPatterns: List
- private lateinit var currentPatternStops: List
+ private lateinit var selectedPatterns: List
private lateinit var routeDescriptionTextView: TextView
private lateinit var stopsRecyclerView: RecyclerView
private var linesAdapter: ArrayAdapter? = null
private var patternsAdapter: ArrayAdapter? = null
private var mListener: CommonFragmentListener? = null
private val linesNameSorter = LinesNameSorter()
private val linesComparator = Comparator { a,b ->
return@Comparator linesNameSorter.compare(a.shortName, b.shortName)
}
private var firstClick = true
private var recyclerViewState:Parcelable? = null
private var patternsSpinnerState:Parcelable? = null
private val adapterListener = object : StopAdapterListener {
override fun onTappedStop(stop: Stop?) {
//var r = ""
//stop?.let { r= it.stopDisplayName.toString() }
if(viewModel.shouldShowMessage) {
Toast.makeText(context, R.string.long_press_stop_4_options, Toast.LENGTH_SHORT).show()
viewModel.shouldShowMessage=false
}
stop?.let {
mListener?.requestArrivalsForStopID(it.ID)
}
if(stop == null){
Log.e(DEBUG_TAG,"Passed wrong stop")
}
if(mListener == null){
Log.e(DEBUG_TAG, "Listener is null")
}
}
override fun onLongPressOnStop(stop: Stop?): Boolean {
Log.d(DEBUG_TAG, "LongPressOnStop")
return true
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
+
Log.d(DEBUG_TAG, "saveInstanceState bundle: $outState")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_lines, container, false)
linesSpinner = rootView.findViewById(R.id.linesSpinner)
patternsSpinner = rootView.findViewById(R.id.patternsSpinner)
+
routeDescriptionTextView = rootView.findViewById(R.id.routeDescriptionTextView)
stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
val llManager = LinearLayoutManager(context)
llManager.orientation = LinearLayoutManager.VERTICAL
stopsRecyclerView.layoutManager = llManager
//allow the context menu to be opened
registerForContextMenu(stopsRecyclerView)
Log.d(DEBUG_TAG, "Called onCreateView for LinesFragment")
Log.d(DEBUG_TAG, "OnCreateView, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
Log.d(DEBUG_TAG, "OnCreateView, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
//set requests
viewModel.routesGTTLiveData.observe(viewLifecycleOwner) {
setRoutes(it)
}
viewModel.patternsWithStopsByRouteLiveData.observe(viewLifecycleOwner){
patterns ->
run {
- currentPatterns = patterns.sortedBy { p-> p.pattern.code }
+ selectedPatterns = patterns.sortedBy { p-> p.pattern.code }
//patterns. //sortedBy {-1*it.stopsIndices.size}// "${p.pattern.directionId} - ${p.pattern.headsign}" }
patternsAdapter?.let {
it.clear()
- it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
+ it.addAll(selectedPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
it.notifyDataSetChanged()
}
+ viewModel.selectedPatternLiveData.value?.let {
+ setSelectedPattern(it)
+ }
val pos = patternsSpinner.selectedItemPosition
//might be possible that the selectedItem is different (larger than list size)
- if(pos!= INVALID_POSITION && pos >= 0 && (pos < currentPatterns.size)){
- val p = currentPatterns[pos]
+ if(pos!= INVALID_POSITION && pos >= 0 && (pos < selectedPatterns.size)){
+ val p = selectedPatterns[pos]
Log.d(DEBUG_TAG, "Setting patterns with pos $pos and p gtfsID ${p.pattern.code}")
- setPatternAndReqStops(currentPatterns[pos])
+ setPatternAndReqStops(selectedPatterns[pos])
}
}
}
viewModel.stopsForPatternLiveData.observe(viewLifecycleOwner){stops->
Log.d("BusTO-LinesFragment", "Setting stops from DB")
setCurrentStops(stops)
}
if(context!=null) {
patternsAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList())
patternsSpinner.adapter = patternsAdapter
linesAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_dropdown_item, ArrayList())
linesSpinner.adapter = linesAdapter
if (linesSpinner.onItemSelectedListener != null){
Log.d(DEBUG_TAG, "linesSpinner listener != null")
}
//listener
linesSpinner.onItemSelectedListener = object: OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
val selRoute = currentRoutes.get(pos)
routeDescriptionTextView.text = selRoute.longName
val oldRoute = viewModel.getRouteIDQueried()
val resetSpinner = (oldRoute != null) && (oldRoute.trim() != selRoute.gtfsId.trim())
Log.d(DEBUG_TAG, "Selected route: ${selRoute.gtfsId}, reset spinner: $resetSpinner, oldRoute: $oldRoute")
//launch query for this gtfsID
viewModel.setRouteIDQuery(selRoute.gtfsId)
//reset spinner position
if(resetSpinner) patternsSpinner.setSelection(0)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
patternsSpinner.onItemSelectedListener = object : OnItemSelectedListener{
override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
- val patternWithStops = currentPatterns.get(position)
+ val patternWithStops = selectedPatterns.get(position)
//
setPatternAndReqStops(patternWithStops)
+ //viewModel.currentPositionInPatterns.value = position
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
}
return rootView
}
override fun onAttach(context: Context) {
super.onAttach(context)
if(context is CommonFragmentListener)
mListener = context
else throw RuntimeException(context.toString()
+ " must implement CommonFragmentListener")
}
override fun onResume() {
super.onResume()
mListener?.readyGUIfor(FragmentKind.LINES)
Log.d(DEBUG_TAG, "Resuming lines fragment")
//Log.d(DEBUG_TAG, "OnResume, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
//Log.d(DEBUG_TAG, "OnResume, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this).get(LinesViewModel::class.java)
Log.d(DEBUG_TAG, "Fragment onCreate")
}
override fun getBaseViewForSnackBar(): View? {
return null
}
+ private fun setSelectedPattern(patternWs: MatoPatternWithStops){
+ Log.d(DEBUG_TAG, "Finding pattern to show: ${patternWs.pattern.code}")
+ var pos = -2
+ val code = patternWs.pattern.code.trim()
+ for(k in selectedPatterns.indices){
+ if(selectedPatterns[k].pattern.code.trim() == code){
+ pos = k
+ break
+ }
+ }
+ Log.d(DEBUG_TAG, "Found pattern $code in position: $pos")
+ if(pos>=0){
+ patternsSpinner.setSelection(pos)
+ }
+ }
+
private fun setRoutes(routes: List){
Log.d(DEBUG_TAG, "Resetting routes")
currentRoutes = routes.sortedWith(linesComparator)
if (linesAdapter!=null){
var selGtfsRoute = viewModel.getRouteIDQueried()
- var selPatternIndex = 0
+ var selRouteIdx = 0
if(selGtfsRoute == null){
selGtfsRoute =""
}
+ Log.d(DEBUG_TAG, "Setting routes, selected route gtfsID: $selGtfsRoute")
val adapter = linesAdapter!!
if (adapter.isEmpty) {
Log.d(DEBUG_TAG, "Lines adapter is empty")
}
else{
adapter.clear()
}
adapter.addAll(currentRoutes.map { r -> r.shortName })
adapter.notifyDataSetChanged()
- for(j in 0 until currentRoutes.size){
+ for(j in currentRoutes.indices){
val route = currentRoutes[j]
if (route.gtfsId == selGtfsRoute) {
- selPatternIndex = j
+ selRouteIdx = j
Log.d(DEBUG_TAG, "Route $selGtfsRoute has index $j")
}
}
- linesSpinner.setSelection(selPatternIndex)
+ linesSpinner.setSelection(selRouteIdx)
//
}
/*
linesAdapter?.clear()
linesAdapter?.addAll(currentRoutes.map { r -> r.shortName })
linesAdapter?.notifyDataSetChanged()
*/
}
private fun setCurrentStops(stops: List){
- val orderBy = currentPatternStops.withIndex().associate{it.value.stopGtfsId to it.index}
+ Log.d(DEBUG_TAG, "Setting stops from: "+viewModel.currentPatternStops.value)
+ val orderBy = viewModel.currentPatternStops.value!!.withIndex().associate{it.value.stopGtfsId to it.index}
val stopsSorted = stops.sortedBy { s -> orderBy[s.gtfsID] }
val numStops = stopsSorted.size
Log.d(DEBUG_TAG, "RecyclerView adapter is: ${stopsRecyclerView.adapter}")
var setNewAdapter = true
if(stopsRecyclerView.adapter is StopRecyclerAdapter){
val adapter = stopsRecyclerView.adapter as StopRecyclerAdapter
if(adapter.stops.size == stopsSorted.size && (adapter.stops.get(0).gtfsID == stopsSorted.get(0).gtfsID)
&& (adapter.stops.get(numStops-1).gtfsID == stopsSorted.get(numStops-1).gtfsID)
){
Log.d(DEBUG_TAG, "Found same stops on recyclerview")
setNewAdapter = false
}
/*else {
Log.d(DEBUG_TAG, "Found adapter on recyclerview, but not the same stops")
adapter.stops = stopsSorted
adapter.notifyDataSetChanged()
}*/
}
if(setNewAdapter){
stopsRecyclerView.adapter = StopRecyclerAdapter(
stopsSorted, adapterListener, StopRecyclerAdapter.Use.LINES,
NameCapitalize.FIRST
)
}
+
}
private fun setPatternAndReqStops(patternWithStops: MatoPatternWithStops){
Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
- currentPatternStops = patternWithStops.stopsIndices.sortedBy { i-> i.order }
-
+ //currentPatternStops = patternWithStops.stopsIndices.sortedBy { i-> i.order }
+ viewModel.currentPatternStops.value = patternWithStops.stopsIndices.sortedBy { i-> i.order }
+ viewModel.selectedPatternLiveData.value = patternWithStops
viewModel.requestStopsForPatternWithStops(patternWithStops)
}
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
Log.d("BusTO-LinesFragment", "Creating context menu ")
if (v.id == R.id.patternStopsRecyclerView) {
// if we aren't attached to activity, return null
if (activity == null) return
val inflater = requireActivity().menuInflater
inflater.inflate(R.menu.menu_line_item, menu)
}
}
override fun onContextItemSelected(item: MenuItem): Boolean {
if (stopsRecyclerView.getAdapter() !is StopRecyclerAdapter) return false
val adapter =stopsRecyclerView.adapter as StopRecyclerAdapter
val stop = adapter.stops.get(adapter.getPosition())
val acId = item.itemId
if(acId == R.id.action_view_on_map){
// view on the map
if ((stop.latitude == null) or (stop.longitude == null) or (mListener == null) ) {
Toast.makeText(context, R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show()
return true
}
mListener!!.showMapCenteredOnStop(stop)
return true
} else if (acId == R.id.action_show_arrivals){
mListener?.requestArrivalsForStopID(stop.ID)
return true
}
return false
}
override fun onStop() {
super.onStop()
Log.d(DEBUG_TAG, "Fragment stopped")
recyclerViewState = stopsRecyclerView.layoutManager?.onSaveInstanceState()
patternsSpinnerState = patternsSpinner.onSaveInstanceState()
}
override fun onStart() {
super.onStart()
Log.d(DEBUG_TAG, "OnStart, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
Log.d(DEBUG_TAG, "OnStart, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
if (recyclerViewState!=null){
stopsRecyclerView.layoutManager?.onRestoreInstanceState(recyclerViewState)
}
if(patternsSpinnerState!=null){
patternsSpinner.onRestoreInstanceState(patternsSpinnerState)
}
}
/*
override fun onDestroyView() {
super.onDestroyView()
Log.d(DEBUG_TAG, "Fragment view destroyed")
}
override fun onDestroy() {
super.onDestroy()
Log.d(DEBUG_TAG, "Fragment destroyed")
}
*/
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
Log.d(DEBUG_TAG, "OnViewStateRes, bundled saveinstancestate: $savedInstanceState")
Log.d(DEBUG_TAG, "OnViewStateRes, selected line spinner pos: ${linesSpinner.selectedItemPosition}")
Log.d(DEBUG_TAG, "OnViewStateRes, selected patterns spinner pos: ${patternsSpinner.selectedItemPosition}")
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/LinesViewModel.kt b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
index 50b4c59..90437f4 100644
--- a/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
+++ b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
@@ -1,91 +1,95 @@
package it.reyboz.bustorino.fragments
import android.app.Application
import android.util.Log
import androidx.lifecycle.*
import it.reyboz.bustorino.backend.Stop
import it.reyboz.bustorino.data.GtfsRepository
import it.reyboz.bustorino.data.NextGenDB
import it.reyboz.bustorino.data.OldDataRepository
import it.reyboz.bustorino.data.gtfs.GtfsDatabase
import it.reyboz.bustorino.data.gtfs.GtfsRoute
import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+import it.reyboz.bustorino.data.gtfs.PatternStop
import java.util.concurrent.Executors
class LinesViewModel(application: Application) : AndroidViewModel(application) {
private val gtfsRepo: GtfsRepository
private val oldRepo: OldDataRepository
//val patternsByRouteLiveData: LiveData>
private val routeIDToSearch = MutableLiveData()
private var lastShownPatternStops = ArrayList()
+ val currentPatternStops = MutableLiveData>()
+ val selectedPatternLiveData = MutableLiveData()
+
val stopsForPatternLiveData = MutableLiveData>()
private val executor = Executors.newFixedThreadPool(2)
init {
val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao()
gtfsRepo = GtfsRepository(gtfsDao)
oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
}
val routesGTTLiveData: LiveData> by lazy{
gtfsRepo.getLinesLiveDataForFeed("gtt")
}
val patternsWithStopsByRouteLiveData = routeIDToSearch.switchMap {
gtfsRepo.getPatternsWithStopsForRouteID(it)
}
fun setRouteIDQuery(routeID: String){
routeIDToSearch.value = routeID
}
fun getRouteIDQueried(): String?{
return routeIDToSearch.value
}
var shouldShowMessage = true
/**
* Find the
*/
private fun requestStopsForGTFSIDs(gtfsIDs: List){
if (gtfsIDs.equals(lastShownPatternStops)){
//nothing to do
return
}
oldRepo.requestStopsWithGtfsIDs(gtfsIDs) {
if (it.isSuccess) {
stopsForPatternLiveData.postValue(it.result)
} else {
Log.e("BusTO-LinesVM", "Got error on callback with stops for gtfsID")
it.exception?.printStackTrace()
}
}
lastShownPatternStops.clear()
for(id in gtfsIDs)
lastShownPatternStops.add(id)
}
fun requestStopsForPatternWithStops(patternStops: MatoPatternWithStops){
val gtfsIDs = ArrayList()
for(pat in patternStops.stopsIndices){
gtfsIDs.add(pat.stopGtfsId)
}
requestStopsForGTFSIDs(gtfsIDs)
}
/*fun getLinesGTT(): MutableLiveData> {
val routesData = MutableLiveData>()
viewModelScope.launch {
val routes=gtfsRepo.getLinesForFeed("gtt")
routesData.postValue(routes)
}
return routesData
}*/
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index 2b711f5..3859c30 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,808 +1,882 @@
package it.reyboz.bustorino.fragments;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Criteria;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import java.util.List;
import java.util.Map;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.middleware.AsyncArrivalsSearcher;
import it.reyboz.bustorino.middleware.AsyncStopsSearcher;
import it.reyboz.bustorino.middleware.BarcodeScanContract;
import it.reyboz.bustorino.middleware.BarcodeScanOptions;
import it.reyboz.bustorino.middleware.BarcodeScanUtils;
import it.reyboz.bustorino.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri;
import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS;
-import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MainScreenFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MainScreenFragment extends ScreenBaseFragment implements FragmentListenerMain{
private static final String OPTION_SHOW_LEGEND = "show_legend";
private static final String SAVED_FRAGMENT="saved_fragment";
private static final String DEBUG_TAG = "BusTO - MainFragment";
public static final String PENDING_STOP_SEARCH="PendingStopSearch";
public final static String FRAGMENT_TAG = "MainScreenFragment";
/// UI ELEMENTS //
private ImageButton addToFavorites;
private FragmentHelper fragmentHelper;
private SwipeRefreshLayout swipeRefreshLayout;
private EditText busStopSearchByIDEditText;
private EditText busStopSearchByNameEditText;
private ProgressBar progressBar;
private TextView howDoesItWorkTextView;
private Button hideHintButton;
private MenuItem actionHelpMenuItem;
private FloatingActionButton floatingActionButton;
+ private FrameLayout resultFrameLayout;
- private boolean setupOnResume = true;
+ private boolean setupOnStart = true;
private boolean suppressArrivalsReload = false;
private boolean instanceStateSaved = false;
//private Snackbar snackbar;
/*
* Search mode
*/
private static final int SEARCH_BY_NAME = 0;
private static final int SEARCH_BY_ID = 1;
private static final int SEARCH_BY_ROUTE = 2; // TODO: implement this -- https://gitpull.it/T12
private int searchMode;
//private ImageButton addToFavorites;
//// HIDDEN BUT IMPORTANT ELEMENTS ////
FragmentManager fragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
if(getContext() == null) return;
List fetcherList = utils.getDefaultArrivalsFetchers(getContext());
ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[fetcherList.size()];
arrivalsFetchers = fetcherList.toArray(arrivalsFetchers);
if (fragMan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) {
ArrivalsFragment fragment = (ArrivalsFragment) fragMan.findFragmentById(R.id.resultFrame);
if (fragment == null){
//we create a new fragment, which is WRONG
Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment");
// AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute();
} else{
String stopName = fragment.getStopID();
new AsyncArrivalsSearcher(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName);
}
} else //we create a new fragment, which is WRONG
new AsyncArrivalsSearcher(fragmentHelper, arrivalsFetchers, getContext()).execute();
}
};
//
private final ActivityResultLauncher barcodeLauncher = registerForActivityResult(new BarcodeScanContract(),
result -> {
if(result!=null && result.getContents()!=null) {
//Toast.makeText(MyActivity.this, "Cancelled", Toast.LENGTH_LONG).show();
Uri uri;
try {
uri = Uri.parse(result.getContents()); // this apparently prevents NullPointerException. Somehow.
} catch (NullPointerException e) {
if (getContext()!=null)
Toast.makeText(getContext().getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
return;
}
String busStopID = getBusStopIDFromUri(uri);
busStopSearchByIDEditText.setText(busStopID);
requestArrivalsForStopID(busStopID);
} else {
//Toast.makeText(MyActivity.this, "Scanned: " + result.getContents(), Toast.LENGTH_LONG).show();
if (getContext()!=null)
Toast.makeText(getContext().getApplicationContext(),
R.string.no_qrcode, Toast.LENGTH_SHORT).show();
}
});
/// LOCATION STUFF ///
boolean pendingNearbyStopsRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
AppLocationManager locationManager;
+ private final ActivityResultLauncher requestPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback