diff --git a/res/drawable/ic_baseline_settings_24.xml b/res/drawable/ic_baseline_settings_24.xml new file mode 100644 --- /dev/null +++ b/res/drawable/ic_baseline_settings_24.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" android:tint="#FFFFFF" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/> +</vector> diff --git a/res/layout/activity_about.xml b/res/layout/activity_about.xml --- a/res/layout/activity_about.xml +++ b/res/layout/activity_about.xml @@ -2,15 +2,21 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingLeft="@dimen/activity_horizontal_margin" - android:paddingRight="@dimen/activity_horizontal_margin" tools:context="it.reyboz.bustorino.ActivityAbout"> + <!-- The ActionBar displayed at the top --> + + <include + layout="@layout/default_toobar" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> <TextView android:id="@+id/aboutTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dip" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" android:textAppearance="?android:attr/textAppearanceMedium"/> </RelativeLayout> \ No newline at end of file diff --git a/res/layout/new_main_activity.xml b/res/layout/activity_principal.xml rename from res/layout/new_main_activity.xml rename to res/layout/activity_principal.xml diff --git a/res/layout/fragment_main_screen.xml b/res/layout/fragment_main_screen.xml --- a/res/layout/fragment_main_screen.xml +++ b/res/layout/fragment_main_screen.xml @@ -3,7 +3,9 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".fragments.MainScreenFragment"> + tools:context=".fragments.MainScreenFragment" + android:paddingTop="10dip" + > <ImageButton android:id="@+id/QRButton" @@ -17,7 +19,7 @@ android:layout_marginStart="16dp" android:background="@drawable/qrcode_button_custom" android:contentDescription="@string/qrcode" - android:onClick="onQRButtonClick" /> + /> <EditText android:id="@+id/busStopSearchByIDEditText" @@ -44,21 +46,22 @@ <!--android:layout_alignParentLeft="true" android:layout_alignParentStart="true"--> <EditText - android:id="@+id/busStopSearchByNameEditText" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginEnd="5dp" - android:layout_marginLeft="5dip" - android:layout_marginRight="5dip" - android:layout_toEndOf="@+id/QRButton" - android:layout_toLeftOf="@+id/searchButton" - android:layout_toRightOf="@+id/QRButton" - android:layout_toStartOf="@+id/searchButton" - android:ems="10" - android:hint="@string/insert_bus_stop_name" - android:imeOptions="actionSearch" - android:singleLine="true" - android:visibility="gone"> + android:id="@+id/busStopSearchByNameEditText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="5dp" + android:layout_marginLeft="5dip" + android:layout_marginRight="5dip" + android:layout_toEndOf="@+id/QRButton" + android:layout_toLeftOf="@+id/searchButton" + android:layout_toRightOf="@+id/QRButton" + android:layout_toStartOf="@+id/searchButton" + + android:ems="10" + android:hint="@string/insert_bus_stop_name" + android:imeOptions="actionSearch" + android:singleLine="true" + android:visibility="gone"> <requestFocus /> </EditText> @@ -75,7 +78,7 @@ android:layout_marginRight="16dp" android:background="@drawable/search_button_custom" android:contentDescription="@string/search" - android:onClick="onSearchClick" /> + /> <ProgressBar android:id="@+id/progressBar" @@ -121,7 +124,6 @@ android:layout_marginEnd="16dp" android:layout_marginRight="16dp" android:background="@drawable/route_background_bus" - android:onClick="onHideHint" android:text="@string/hint_button" android:textColor="@color/grey_100" android:textSize="19sp" @@ -152,7 +154,6 @@ android:layout_alignParentRight="true" android:layout_gravity="bottom|end" android:layout_margin="16dp" - android:onClick="onToggleKeyboardLayout" android:src="@drawable/alphabetical" fabSize="normal" backgroundTint="@color/teal_500" diff --git a/res/menu/drawer_main.xml b/res/menu/drawer_main.xml --- a/res/menu/drawer_main.xml +++ b/res/menu/drawer_main.xml @@ -16,4 +16,9 @@ android:title="@string/nav_favorites_text" /> </group> + <item android:id="@+id/drawer_action_settings" + android:title="@string/action_settings" + android:icon="@drawable/ic_baseline_settings_24" + /> + </menu> \ No newline at end of file diff --git a/res/menu/extra_menu_items.xml b/res/menu/extra_menu_items.xml new file mode 100644 --- /dev/null +++ b/res/menu/extra_menu_items.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + > + <item + android:id="@+id/action_about" + android:orderInCategory="4" + android:title="@string/action_about_more" /> + <item + android:id="@+id/action_hack" + android:orderInCategory="6" + android:title="@string/action_hack" + app:showAsAction="never" /> + <item + android:id="@+id/action_source" + android:orderInCategory="7" + android:title="@string/action_source" + app:showAsAction="never" /> + <item + android:id="@+id/action_licence" + android:orderInCategory="8" + android:title="@string/action_licence" + app:showAsAction="never" /> +</menu> \ No newline at end of file diff --git a/res/values/theme.xml b/res/values/theme.xml --- a/res/values/theme.xml +++ b/res/values/theme.xml @@ -7,7 +7,7 @@ <item name="colorAccent">@color/teal_500</item> </style> - <style name="AboutTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <style name="AboutTheme" parent="AppTheme.NoActionBar"> <item name="colorPrimary">@color/teal_300</item> <item name="colorPrimaryDark">@color/teal_500</item> <item name="colorAccent">@color/blue_700</item> diff --git a/src/it/reyboz/bustorino/ActivityAbout.java b/src/it/reyboz/bustorino/ActivityAbout.java --- a/src/it/reyboz/bustorino/ActivityAbout.java +++ b/src/it/reyboz/bustorino/ActivityAbout.java @@ -17,6 +17,7 @@ */ package it.reyboz.bustorino; +import androidx.appcompat.widget.Toolbar; import androidx.core.app.NavUtils; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; @@ -40,19 +41,18 @@ aboutTextView.setText(htmlText); aboutTextView.setMovementMethod(LinkMovementMethod.getInstance()); - // Back button - ActionBar ab = getSupportActionBar(); - assert ab != null; - ab.setDisplayHomeAsUpEnabled(true); + Toolbar mToolbar = findViewById(R.id.default_toolbar); + setSupportActionBar(mToolbar); + if (getSupportActionBar()!=null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - // Respond to the action bar's Up/Home button - case android.R.id.home: - NavUtils.navigateUpFromSameTask(this); - return true; + // Respond to the action bar's Up/Home button + if (item.getItemId() == android.R.id.home) {//NavUtils.navigateUpFromSameTask(this); + onBackPressed(); + return true; } return super.onOptionsItemSelected(item); } diff --git a/src/it/reyboz/bustorino/ActivityMain.java b/src/it/reyboz/bustorino/ActivityMain.java --- a/src/it/reyboz/bustorino/ActivityMain.java +++ b/src/it/reyboz/bustorino/ActivityMain.java @@ -18,6 +18,7 @@ package it.reyboz.bustorino; import android.Manifest; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -69,11 +70,14 @@ import it.reyboz.bustorino.data.UserDB; import it.reyboz.bustorino.fragments.*; import it.reyboz.bustorino.middleware.*; +import it.reyboz.bustorino.util.Permissions; import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeUnit; +import static it.reyboz.bustorino.backend.utils.getBusStopIDFromUri; + public class ActivityMain extends GeneralActivity implements FragmentListenerMain { /* @@ -132,15 +136,19 @@ * @see swipeRefreshLayout */ private final Handler theHandler = new Handler(); - private final Runnable refreshing = new Runnable() { + private final Runnable refreshStop = new Runnable() { public void run() { if (framan.findFragmentById(R.id.resultFrame) instanceof ArrivalsFragment) { ArrivalsFragment fragment = (ArrivalsFragment) framan.findFragmentById(R.id.resultFrame); - String stopName = fragment.getStopID(); + if (fragment == null){ + new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).execute(); + } else{ + String stopName = fragment.getStopID(); - new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray()).execute(stopName); + new AsyncDataDownload(fh, fragment.getCurrentFetchersAsArray(), getApplicationContext()).execute(stopName); + } } else //we create a new fragment, which is WRONG - new AsyncDataDownload(fh, arrivalsFetchers).execute(); + new AsyncDataDownload(fh, arrivalsFetchers, getApplicationContext()).execute(); } }; @@ -191,14 +199,14 @@ // Called when the layout is pulled down swipeRefreshLayout - .setOnRefreshListener(() -> theHandler.post(refreshing)); + .setOnRefreshListener(() -> theHandler.post(refreshStop)); /** * @author Marco Gagino!!! */ //swipeRefreshLayout.setColorSchemeColors(R.color.blue_500, R.color.orange_500); // setColorScheme is deprecated, setColorSchemeColors isn't swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500); - fh = new FragmentHelper(this, R.id.listRefreshLayout, R.id.resultFrame); + fh = new FragmentHelper(this, framan, getApplicationContext(),R.id.resultFrame); setSearchModeBusStopID(); //---------------------------- START INTENT CHECK QUEUE ------------------------------------ @@ -258,9 +266,6 @@ requestArrivalsForStopID(busStopID); } //Try (hopefully) database update - //TODO: Check if service shows the notification - //Old code for the db update - //DatabaseUpdateService.startDBUpdate(getApplicationContext()); PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS) @@ -317,7 +322,7 @@ cr.setCostAllowed(true); cr.setPowerRequirement(Criteria.NO_REQUIREMENT); //We want the nearby bus stops! - theHandler.post(new NearbyStopsRequester()); + theHandler.post(new NearbyStopsRequester(this)); //If there are no providers available, then, wait for them @@ -363,7 +368,7 @@ fh.setBlockAllActivities(false); //TODO: check if current LiveData-bound observer works if (pendingNearbyStopsRequest) - theHandler.post(new NearbyStopsRequester()); + theHandler.post(new NearbyStopsRequester(this)); ActionBar bar = getSupportActionBar(); if(bar!=null) bar.show(); else Log.w(DEBUG_TAG, "ACTION BAR IS NULL"); @@ -469,7 +474,7 @@ } else { // searchMode == SEARCH_BY_NAME String query = busStopSearchByNameEditText.getText().toString(); //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); - new AsyncDataDownload(fh, stopsFinderByNames).execute(query); + new AsyncDataDownload(fh, stopsFinderByNames, getApplicationContext()).execute(query); } } @@ -486,7 +491,7 @@ //if we sent a request for a new NearbyStopsFragment if (pendingNearbyStopsRequest) { pendingNearbyStopsRequest = false; - theHandler.post(new NearbyStopsRequester()); + theHandler.post(new NearbyStopsRequester(this)); } } else { @@ -535,13 +540,13 @@ if (fragment !=null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){ // Run with previous fetchers //fragment.getCurrentFetchers().toArray() - new AsyncDataDownload(fh,fragment.getCurrentFetchersAsArray()).execute(ID); + new AsyncDataDownload(fh,fragment.getCurrentFetchersAsArray(), this).execute(ID); } else{ - new AsyncDataDownload(fh, arrivalsFetchers).execute(ID); + new AsyncDataDownload(fh, arrivalsFetchers, this).execute(ID); } } else { - new AsyncDataDownload(fh,arrivalsFetchers).execute(ID); + new AsyncDataDownload(fh, arrivalsFetchers, this).execute(ID); Log.d("MainActiv", "Started search for arrivals of stop " + ID); } } @@ -609,7 +614,7 @@ Log.d(DEBUG_TAG, "Provider " + provider + " got enabled"); if (locmgr != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) { pendingNearbyStopsRequest = false; - theHandler.post(new NearbyStopsRequester()); + theHandler.post(new NearbyStopsRequester(this)); } } @@ -642,6 +647,13 @@ * Run location requests separately and asynchronously */ class NearbyStopsRequester implements Runnable { + + Activity runningAct; + + public NearbyStopsRequester(Activity runningAct) { + this.runningAct = runningAct; + } + @Override public void run() { final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false); @@ -653,7 +665,7 @@ if (noPermission) { if (!canRunPosition) { pendingNearbyStopsRequest = true; - assertLocationPermissions(); + Permissions.assertLocationPermissions(getApplicationContext(),runningAct); Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission); return; } else { @@ -703,26 +715,6 @@ ///////////////////////////////// OTHER STUFF ////////////////////////////////////////////////// - /** - * Check if the last Bus Stop is in the favorites - * - * @return - */ - public boolean isStopInFavorites(String busStopId) { - boolean found = false; - - // no stop no party - if (busStopId != null) { - SQLiteDatabase userDB = new UserDB(getApplicationContext()).getReadableDatabase(); - found = UserDB.isStopInFavorites(userDB, busStopId); - } - - return found; - } - - /** - * Add the last Stop to favorites - */ @Override @@ -841,55 +833,8 @@ } - /** - * Open an URL in the default browser. - * - * @param url URL - */ - public void openIceweasel(String url) { - Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - startActivity(browserIntent1); - } - - ///////////////////// INTENT HELPER //////////////////////////////////////////////////////////// - - /** - * Try to extract the bus stop ID from a URi - * - * @param uri The URL - * @return bus stop ID or null - */ - public static String getBusStopIDFromUri(Uri uri) { - String busStopID; - - // everithing catches fire when passing null to a switch. - String host = uri.getHost(); - if (host == null) { - Log.e("ActivityMain", "Not an URL: " + uri); - return null; - } - - switch (host) { - case "m.gtt.to.it": - // http://m.gtt.to.it/m/it/arrivi.jsp?n=1254 - busStopID = uri.getQueryParameter("n"); - if (busStopID == null) { - Log.e("ActivityMain", "Expected ?n from: " + uri); - } - break; - case "www.gtt.to.it": - case "gtt.to.it": - // http://www.gtt.to.it/cms/percorari/arrivi?palina=1254 - busStopID = uri.getQueryParameter("palina"); - if (busStopID == null) { - Log.e("ActivityMain", "Expected ?palina from: " + uri); - } - break; - default: - Log.e("ActivityMain", "Unexpected intent URL: " + uri); - busStopID = null; - } - return busStopID; + private void openIceweasel(String url){ + utils.openIceweasel(url, this); } } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/ActivityMap.java b/src/it/reyboz/bustorino/ActivityMap.java --- a/src/it/reyboz/bustorino/ActivityMap.java +++ b/src/it/reyboz/bustorino/ActivityMap.java @@ -87,7 +87,7 @@ protected ImageButton btCenterMap; protected ImageButton btFollowMe; - @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) + //@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java --- a/src/it/reyboz/bustorino/ActivityPrincipal.java +++ b/src/it/reyboz/bustorino/ActivityPrincipal.java @@ -1,33 +1,62 @@ package it.reyboz.bustorino; +import android.content.Intent; +import android.content.SharedPreferences; import android.content.res.Configuration; -import android.content.res.Resources; +import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.view.Menu; import android.view.MenuItem; +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.app.NavUtils; 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.work.BackoffPolicy; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.PeriodicWorkRequest; +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.concurrent.TimeUnit; + +import it.reyboz.bustorino.data.DBUpdateWorker; +import it.reyboz.bustorino.data.DatabaseUpdate; +import it.reyboz.bustorino.fragments.FragmentKind; +import it.reyboz.bustorino.fragments.FragmentListenerMain; +import it.reyboz.bustorino.fragments.MainScreenFragment; import it.reyboz.bustorino.middleware.GeneralActivity; -public class ActivityPrincipal extends 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 Snackbar snackbar; + + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.new_main_activity); + setContentView(R.layout.activity_principal); + final SharedPreferences theShPr = getMainSharedPreferences(); + Toolbar mToolbar = findViewById(R.id.default_toolbar); setSupportActionBar(mToolbar); @@ -35,6 +64,8 @@ getSupportActionBar().setDisplayHomeAsUpEnabled(true); else Log.w(DEBUG_TAG, "NO ACTION BAR"); + mToolbar.setOnMenuItemClickListener(new ToolbarItemClickListener()); + mDrawer = findViewById(R.id.drawer_layout); drawerToggle = setupDrawerToggle(mToolbar); @@ -50,6 +81,101 @@ setupDrawerContent(mNavView); + /// 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); + 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); + + requestArrivalsForStopID(busStopID); + } + //Try (hopefully) database update + + + PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 1, TimeUnit.DAYS) + .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES) + .setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED) + .build()) + .build(); + final WorkManager workManager = WorkManager.getInstance(this); + + final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10); + if (version >= 0) + workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, + ExistingPeriodicWorkPolicy.KEEP, wr); + else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG, + ExistingPeriodicWorkPolicy.REPLACE, wr); + /* + Set database update + */ + 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; + } + } + + if (showProgress) { + createDefaultSnackbar(); + } else { + if(snackbar!=null) { + snackbar.dismiss(); + snackbar = null; + } + } + + }); + // show the main fragment + showMainFragment(); + + } private ActionBarDrawerToggle setupDrawerToggle(Toolbar toolbar) { // NOTE: Make sure you pass in a valid toolbar reference. ActionBarDrawToggle() does not require it @@ -60,7 +186,12 @@ 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; + } //selectDrawerItem(menuItem); Log.d(DEBUG_TAG, "pressed item "+menuItem.toString()); @@ -70,6 +201,11 @@ } + 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 @@ -89,20 +225,24 @@ drawerToggle.onConfigurationChanged(newConfig); } + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.extra_menu_items, menu); + return super.onCreateOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { int[] cases = {R.id.nav_arrivals, R.id.nav_favorites_item}; + Log.d(DEBUG_TAG, "Item pressed"); + switch (item.getItemId()){ case android.R.id.home: mDrawer.openDrawer(GravityCompat.START); return true; - - case R.id.nav_arrivals: - //do something - break; default: } @@ -118,9 +258,116 @@ @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){ + shownFrag.getChildFragmentManager().popBackStack(); + } + else if (getSupportFragmentManager().getBackStackEntryCount() > 0) { + getSupportFragmentManager().popBackStack(); + } else super.onBackPressed(); } + + private void createDefaultSnackbar() { + if (snackbar == null) { + snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE); + } + snackbar.show(); + } + + private MainScreenFragment showMainFragment(){ + FragmentManager fraMan = getSupportFragmentManager(); + + MainScreenFragment fragment = MainScreenFragment.newInstance(); + + FragmentTransaction transaction = fraMan.beginTransaction(); + transaction.replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG); + transaction.commit(); + return fragment; + } + @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 + } + + @Override + public void readyGUIfor(FragmentKind fragmentType) { + MainScreenFragment probableFragment = getMainFragmentIfVisible(); + if (probableFragment!=null){ + probableFragment.readyGUIfor(fragmentType); + } + } + + @Override + public void requestArrivalsForStopID(String ID) { + FragmentManager fraMan = getSupportFragmentManager(); + Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG); + MainScreenFragment mainScreenFragment = null; + if (fragment==null | !(fragment instanceof MainScreenFragment)){ + mainScreenFragment = showMainFragment(); + } + else if(!fragment.isVisible()){ + + fraMan.beginTransaction().replace(R.id.mainActContentFrame, fragment) + .addToBackStack(null) + .commit(); + mainScreenFragment = (MainScreenFragment) fragment; + Log.d(DEBUG_TAG, "Found the main fragment"); + } else{ + mainScreenFragment = (MainScreenFragment) fragment; + } + + mainScreenFragment.requestArrivalsForStopID(ID); + } + + @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); + } + } + + class ToolbarItemClickListener implements Toolbar.OnMenuItemClickListener{ + + @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), getApplicationContext()); + return true; + case R.id.action_source: + openIceweasel("https://gitpull.it/source/libre-busto/", getApplicationContext()); + return true; + case R.id.action_licence: + openIceweasel("https://www.gnu.org/licenses/gpl-3.0.html", getApplicationContext()); + return true; + default: + } + return false; + } + } } diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java --- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java +++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java @@ -59,6 +59,7 @@ } } catch (JSONException ex){ res.set(result.PARSER_ERROR); + Log.w(DEBUG_NAME, "Couldn't get the JSON repr of:\n"+response); return null; } res.set(result.OK); diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java --- a/src/it/reyboz/bustorino/backend/Palina.java +++ b/src/it/reyboz/bustorino/backend/Palina.java @@ -20,6 +20,8 @@ import android.util.Log; +import androidx.annotation.NonNull; + import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; @@ -173,11 +175,14 @@ if(mSource == Passaggio.Source.UNDETERMINED) break; } + // if the Source is still null, set undetermined + if (mSource == null) mSource = Passaggio.Source.UNDETERMINED; //finished with the check, setting flags routesModified = false; allSource = mSource; } + @NonNull public Passaggio.Source getPassaggiSourceIfAny(){ if(allSource==null || routesModified){ checkPassaggi(); diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java --- a/src/it/reyboz/bustorino/backend/utils.java +++ b/src/it/reyboz/bustorino/backend/utils.java @@ -1,6 +1,11 @@ package it.reyboz.bustorino.backend; import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.util.Log; import android.view.View; public abstract class utils { @@ -28,4 +33,67 @@ float ncols = ((float)width)/pixelsize; return (int) Math.floor(ncols); } + + /** + * Check if there is an internet connection + * @param con context object to get the system service + * @return true if we are + */ + public static boolean isConnected(Context con) { + ConnectivityManager connMgr = (ConnectivityManager) con.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.isConnected(); + } + + + ///////////////////// INTENT HELPER //////////////////////////////////////////////////////////// + + /** + * Try to extract the bus stop ID from a URi + * + * @param uri The URL + * @return bus stop ID or null + */ + public static String getBusStopIDFromUri(Uri uri) { + String busStopID; + + // everithing catches fire when passing null to a switch. + String host = uri.getHost(); + if (host == null) { + Log.e("ActivityMain", "Not an URL: " + uri); + return null; + } + + switch (host) { + case "m.gtt.to.it": + // http://m.gtt.to.it/m/it/arrivi.jsp?n=1254 + busStopID = uri.getQueryParameter("n"); + if (busStopID == null) { + Log.e("ActivityMain", "Expected ?n from: " + uri); + } + break; + case "www.gtt.to.it": + case "gtt.to.it": + // http://www.gtt.to.it/cms/percorari/arrivi?palina=1254 + busStopID = uri.getQueryParameter("palina"); + if (busStopID == null) { + Log.e("ActivityMain", "Expected ?palina from: " + uri); + } + break; + default: + Log.e("ActivityMain", "Unexpected intent URL: " + uri); + busStopID = null; + } + return busStopID; + } + + /** + * Open an URL in the default browser. + * + * @param url URL + */ + public static void openIceweasel(String url, Context context) { + Intent browserIntent1 = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(browserIntent1); + } } diff --git a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java --- a/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java +++ b/src/it/reyboz/bustorino/fragments/ArrivalsFragment.java @@ -276,7 +276,10 @@ */ 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: diff --git a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java --- a/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java +++ b/src/it/reyboz/bustorino/fragments/CommonFragmentListener.java @@ -21,4 +21,9 @@ * @param ID the Stop ID */ void requestArrivalsForStopID(String ID); + + /** + * Method to call when we want to hide the keyboard + */ + void hideKeyboard(); } diff --git a/src/it/reyboz/bustorino/fragments/FragmentHelper.java b/src/it/reyboz/bustorino/fragments/FragmentHelper.java --- a/src/it/reyboz/bustorino/fragments/FragmentHelper.java +++ b/src/it/reyboz/bustorino/fragments/FragmentHelper.java @@ -18,18 +18,18 @@ package it.reyboz.bustorino.fragments; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.database.sqlite.SQLiteException; +import android.content.Context; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import android.util.Log; +import android.widget.Toast; + import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.Fetcher; import it.reyboz.bustorino.backend.Palina; import it.reyboz.bustorino.backend.Stop; +import it.reyboz.bustorino.backend.utils; import it.reyboz.bustorino.data.NextGenDB; import it.reyboz.bustorino.middleware.*; @@ -40,28 +40,30 @@ * Helper class to manage the fragments and their needs */ public class FragmentHelper { - GeneralActivity act; + //GeneralActivity act; + private final FragmentListenerMain listenerMain; + private final WeakReference<FragmentManager> managerWeakRef; private Stop lastSuccessfullySearchedBusStop; //support for multiple frames private final int secondaryFrameLayout; - private final int swipeRefID; private final int primaryFrameLayout; + private final Context context; public static final int NO_FRAME = -3; + private static final String DEBUG_TAG = "BusTO FragmHelper"; private WeakReference<AsyncDataDownload> lastTaskRef; - private NextGenDB newDBHelper; private boolean shouldHaltAllActivities=false; - public FragmentHelper(GeneralActivity act, int swipeRefID, int mainFrame) { - this(act,swipeRefID,mainFrame,NO_FRAME); + public FragmentHelper(FragmentListenerMain listener, FragmentManager framan, Context context, int mainFrame) { + this(listener,framan, context,mainFrame,NO_FRAME); } - public FragmentHelper(GeneralActivity act, int swipeRefID, int primaryFrameLayout, int secondaryFrameLayout) { - this.act = act; - this.swipeRefID = swipeRefID; + public FragmentHelper(FragmentListenerMain listener, FragmentManager fraMan, Context context, int primaryFrameLayout, int secondaryFrameLayout) { + this.listenerMain = listener; + this.managerWeakRef = new WeakReference<>(fraMan); this.primaryFrameLayout = primaryFrameLayout; this.secondaryFrameLayout = secondaryFrameLayout; - newDBHelper = new NextGenDB(act.getApplicationContext()); + this.context = context.getApplicationContext(); } /** @@ -89,19 +91,23 @@ boolean sameFragment; ArrivalsFragment arrivalsFragment; - if(act==null || shouldHaltAllActivities) { + if(managerWeakRef.get()==null || shouldHaltAllActivities) { //SOMETHING WENT VERY WRONG + Log.e(DEBUG_TAG, "We are asked for a new stop but we can't show anything"); return; } - FragmentManager fm = act.getSupportFragmentManager(); + FragmentManager fm = managerWeakRef.get(); if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) { arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); + //Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?"); sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); - } else + } else { sameFragment = false; + Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment"); + } setLastSuccessfullySearchedBusStop(p); if(!sameFragment) { @@ -122,7 +128,7 @@ // DO NOT CALL `setListAdapter` ever on arrivals fragment arrivalsFragment.updateFragmentData(p); - act.hideKeyboard(); + listenerMain.hideKeyboard(); toggleSpinner(false); } @@ -132,9 +138,14 @@ * @param query String queried */ public void createFragmentFor(List<Stop> resultList,String query){ - act.hideKeyboard(); + listenerMain.hideKeyboard(); StopListFragment listfragment = StopListFragment.newInstance(query); - attachFragmentToContainer(act.getSupportFragmentManager(),listfragment,false,"search_"+query); + if(managerWeakRef.get()==null || shouldHaltAllActivities) { + //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,false,"search_"+query); listfragment.setStopList(resultList); toggleSpinner(false); @@ -145,12 +156,7 @@ * @param on new status of spinner system */ public void toggleSpinner(boolean on){ - if (act instanceof FragmentListenerMain) - ((FragmentListenerMain) act).toggleSpinner(on); - else { - SwipeRefreshLayout srl = (SwipeRefreshLayout) act.findViewById(swipeRefID); - srl.setRefreshing(false); - } + listenerMain.toggleSpinner(on); } /** @@ -171,21 +177,6 @@ //fm.executePendingTransactions(); } - synchronized public int insertBatchDataInNextGenDB(ContentValues[] valuesArr,String tableName){ - if(newDBHelper !=null) - try { - return newDBHelper.insertBatchContent(valuesArr, tableName); - } catch (SQLiteException exc){ - Log.w("DB Batch inserting: ","ERROR Inserting the data batch: ",exc.fillInStackTrace()); - return -2; - } - else return -1; - } - - synchronized public ContentResolver getContentResolver(){ - return act.getContentResolver(); - } - public void setBlockAllActivities(boolean shouldI) { this.shouldHaltAllActivities = shouldI; } @@ -208,13 +199,13 @@ case OK: break; case CLIENT_OFFLINE: - act.showToastMessage(R.string.network_error, true); + showToastMessage(R.string.network_error, true); break; case SERVER_ERROR: - if (act.isConnected()) { - act.showToastMessage(R.string.parsing_error, true); + if (utils.isConnected(context)) { + showToastMessage(R.string.parsing_error, true); } else { - act.showToastMessage(R.string.network_error, true); + showToastMessage(R.string.network_error, true); } case PARSER_ERROR: default: @@ -229,9 +220,13 @@ } } - public void showShortToast(int message){ - if (act!=null) - act.showToastMessage(message,true); + 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); } } diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -1,25 +1,43 @@ package it.reyboz.bustorino.fragments; +import android.Manifest; import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationProvider; +import android.os.Build; import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatImageButton; +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.ImageButton; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.zxing.integration.android.IntentIntegrator; + +import it.reyboz.bustorino.ActivityMain; import it.reyboz.bustorino.R; import it.reyboz.bustorino.backend.ArrivalsFetcher; import it.reyboz.bustorino.backend.FiveTAPIFetcher; @@ -31,6 +49,10 @@ import it.reyboz.bustorino.backend.StopsFinderByName; import it.reyboz.bustorino.data.UserDB; import it.reyboz.bustorino.middleware.AsyncDataDownload; +import it.reyboz.bustorino.util.Permissions; + +import static android.content.Context.LOCATION_SERVICE; +import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSION_GIVEN; /** @@ -45,7 +67,8 @@ private static final String DEBUG_TAG = "BusTO - MainFragment"; - private CommonFragmentListener mListener; + public final static String FRAGMENT_TAG = "MainScreenFragment"; + /// UI ELEMENTS // private ImageButton addToFavorites; private FragmentHelper fragmentHelper; @@ -57,6 +80,8 @@ private Button hideHintButton; private MenuItem actionHelpMenuItem; private FloatingActionButton floatingActionButton; + + private boolean setupOnAttached = true; //private Snackbar snackbar; /* * Search mode @@ -67,8 +92,38 @@ private int searchMode; //private ImageButton addToFavorites; private final ArrivalsFetcher[] arrivalsFetchers = new ArrivalsFetcher[]{new FiveTAPIFetcher(), new GTTJSONFetcher(), new FiveTScraperFetcher()}; + //// HIDDEN BUT IMPORTANT ELEMENTS //// + FragmentManager fragMan; + Handler mainHandler; + private final Runnable refreshStop = new Runnable() { + public void run() { + 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 + new AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute(); + } else{ + String stopName = fragment.getStopID(); + + new AsyncDataDownload(fragmentHelper, fragment.getCurrentFetchersAsArray(), getContext()).execute(stopName); + } + } else //we create a new fragment, which is WRONG + new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute(); + } + }; + + + /// LOCATION STUFF /// + boolean pendingNearbyStopsRequest = false; + LocationManager locmgr; + + private final Criteria cr = new Criteria(); + + //// ACTIVITY ATTACHED (LISTENER /// + private CommonFragmentListener mListener; + public MainScreenFragment() { // Required empty public constructor } @@ -104,11 +159,100 @@ hideHintButton = root.findViewById(R.id.hideHintButton); 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(() -> mainHandler.post(refreshStop)); + swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500); + + floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout)); + hideHintButton.setOnClickListener(this::onHideHint); + + AppCompatImageButton qrButton = root.findViewById(R.id.QRButton); + qrButton.setOnClickListener(this::onQRButtonClick); + + AppCompatImageButton searchButton = root.findViewById(R.id.searchButton); + searchButton.setOnClickListener(this::onSearchClick); + + // Fragment stuff + fragMan = getChildFragmentManager(); + fragMan.addOnBackStackChangedListener(() -> Log.d("BusTO Main Fragment", "BACK STACK CHANGED")); + + fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame); + setSearchModeBusStopID(); + + + cr.setAccuracy(Criteria.ACCURACY_FINE); + cr.setAltitudeRequired(false); + cr.setBearingRequired(false); + cr.setCostAllowed(true); + cr.setPowerRequirement(Criteria.NO_REQUIREMENT); + + locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE); return root; } + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + mainHandler = new Handler(); + if (context instanceof CommonFragmentListener) { + mListener = (CommonFragmentListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement CommonFragmentListener"); + } + if (setupOnAttached){ + //We want the nearby bus stops! + mainHandler.post(new NearbyStopsRequester(getContext(),cr, locListener)); + //If there are no providers available, then, wait for them + + setupOnAttached = false; + } + + } + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + @Override + public void onResume() { + + final Context con = getContext(); + if (con != null) + locmgr = (LocationManager) getContext().getSystemService(LOCATION_SERVICE); + else { + Log.w(DEBUG_TAG, "Context is null at onResume"); + } + super.onResume(); + } + @Override + public void onPause() { + //mainHandler = null; + locmgr = null; + super.onPause(); + } /* GUI METHODS */ @@ -139,7 +283,7 @@ } else { // searchMode == SEARCH_BY_NAME String query = busStopSearchByNameEditText.getText().toString(); //new asyncWgetBusStopSuggestions(query, stopsDB, StopsFindersByNameRecursionHelper); - new AsyncDataDownload(fragmentHelper, stopsFinderByNames).execute(query); + new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query); } } @@ -201,16 +345,15 @@ private void showHints() { howDoesItWorkTextView.setVisibility(View.VISIBLE); hideHintButton.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(false); + //actionHelpMenuItem.setVisible(false); } private void hideHints() { howDoesItWorkTextView.setVisibility(View.GONE); hideHintButton.setVisibility(View.GONE); - actionHelpMenuItem.setVisible(true); + //actionHelpMenuItem.setVisible(true); } - //TODO: toggle spinner from mainActivity @Override public void toggleSpinner(boolean enable) { if (enable) { @@ -227,13 +370,13 @@ private void prepareGUIForBusLines() { swipeRefreshLayout.setEnabled(true); swipeRefreshLayout.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(true); + //actionHelpMenuItem.setVisible(true); } private void prepareGUIForBusStops() { swipeRefreshLayout.setEnabled(false); swipeRefreshLayout.setVisibility(View.VISIBLE); - actionHelpMenuItem.setVisible(false); + //actionHelpMenuItem.setVisible(false); } @@ -250,10 +393,9 @@ */ @Override public void readyGUIfor(FragmentKind fragmentType) { - throw new UnsupportedOperationException(); - /* + hideKeyboard(); - //TODO: fix this + //if we are getting results, already, stop waiting for nearbyStops if (pendingNearbyStopsRequest && (fragmentType == FragmentKind.ARRIVALS || fragmentType == FragmentKind.STOPS)) { locmgr.removeUpdates(locListener); @@ -277,7 +419,7 @@ return; } // Shows hints - */ + } @@ -293,16 +435,112 @@ if (fragment != null && fragment.getStopID() != null && fragment.getStopID().equals(ID)){ // Run with previous fetchers //fragment.getCurrentFetchers().toArray() - new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray()).execute(ID); + new AsyncDataDownload(fragmentHelper,fragment.getCurrentFetchersAsArray(), getContext()).execute(ID); } else{ - new AsyncDataDownload(fragmentHelper, arrivalsFetchers).execute(ID); + new AsyncDataDownload(fragmentHelper, arrivalsFetchers, getContext()).execute(ID); } } else { - new AsyncDataDownload(fragmentHelper,arrivalsFetchers).execute(ID); + new AsyncDataDownload(fragmentHelper,arrivalsFetchers, getContext()).execute(ID); Log.d("MainActiv", "Started search for arrivals of stop " + ID); } } + /////////// LOCATION METHODS ////////// + final LocationListener locListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + Log.d(DEBUG_TAG, "Location changed"); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + Log.d(DEBUG_TAG, "Location provider status: " + status); + if (status == LocationProvider.AVAILABLE) { + resolveStopRequest(provider); + } + } + + @Override + public void onProviderEnabled(String provider) { + resolveStopRequest(provider); + } + + @Override + public void onProviderDisabled(String provider) { + } + }; + + private void resolveStopRequest(String provider) { + Log.d(DEBUG_TAG, "Provider " + provider + " got enabled"); + if (locmgr != null && mainHandler != null && pendingNearbyStopsRequest && locmgr.getProvider(provider).meetsCriteria(cr)) { + pendingNearbyStopsRequest = false; + mainHandler.post(new NearbyStopsRequester(getContext(), cr, locListener)); + } + } + + /** + * Run location requests separately and asynchronously + */ + class NearbyStopsRequester implements Runnable { + Context appContext; + Criteria cr; + LocationListener listener; + + public NearbyStopsRequester(Context appContext, Criteria criteria, LocationListener listener) { + this.appContext = appContext.getApplicationContext(); + this.cr = criteria; + this.listener = listener; + } + + @Override + public void run() { + final boolean canRunPosition = Build.VERSION.SDK_INT < Build.VERSION_CODES.M || getOption(LOCATION_PERMISSION_GIVEN, false); + final boolean noPermission = ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(appContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED; + + //if we don't have the permission, we have to ask for it, if we haven't + // asked too many times before + if (noPermission) { + if (!canRunPosition) { + pendingNearbyStopsRequest = true; + Permissions.assertLocationPermissions(appContext,getActivity()); + Log.w(DEBUG_TAG, "Cannot get position: Asking permission, noPositionFromSys: " + noPermission); + return; + } else { + Toast.makeText(appContext, "Asked for permission position too many times", Toast.LENGTH_LONG).show(); + } + } else setOption(LOCATION_PERMISSION_GIVEN, true); + + LocationManager locManager = (LocationManager) appContext.getSystemService(LOCATION_SERVICE); + if (locManager == null) { + Log.e(DEBUG_TAG, "location manager is nihil, cannot create NearbyStopsFragment"); + return; + } + if (Permissions.anyLocationProviderMatchesCriteria(locManager, cr, true) + && fragmentHelper.getLastSuccessfullySearchedBusStop() == null + && !fragMan.isDestroyed()) { + //Go ahead with the request + Log.d("mainActivity", "Recreating stop fragment"); + swipeRefreshLayout.setVisibility(View.VISIBLE); + NearbyStopsFragment fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS); + Fragment oldFrag = fragMan.findFragmentById(R.id.resultFrame); + FragmentTransaction ft = fragMan.beginTransaction(); + if (oldFrag != null) + ft.remove(oldFrag); + ft.add(R.id.resultFrame, fragment, "nearbyStop_correct"); + ft.commit(); + //fragMan.executePendingTransactions(); + pendingNearbyStopsRequest = false; + } else if (!Permissions.anyLocationProviderMatchesCriteria(locManager, cr, true)) { + //Wait for the providers + Log.d(DEBUG_TAG, "Queuing position request"); + pendingNearbyStopsRequest = true; + + locManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10, 0.1f, listener); + } + + } + } } \ No newline at end of file diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java --- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java +++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java @@ -139,15 +139,15 @@ Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_nearby_stops, container, false); - gridRecyclerView = (RecyclerView) root.findViewById(R.id.stopGridRecyclerView); + gridRecyclerView = root.findViewById(R.id.stopGridRecyclerView); gridLayoutManager = new AutoFitGridLayoutManager(getContext().getApplicationContext(), utils.convertDipToPixels(getContext(),COLUMN_WIDTH_DP)); gridRecyclerView.setLayoutManager(gridLayoutManager); gridRecyclerView.setHasFixedSize(false); - circlingProgressBar = (ProgressBar) root.findViewById(R.id.loadingBar); - flatProgressBar = (ProgressBar) root.findViewById(R.id.horizontalProgressBar); - messageTextView = (TextView) root.findViewById(R.id.messageTextView); - titleTextView = (TextView) root.findViewById(R.id.titleTextView); - switchButton = (AppCompatButton) root.findViewById(R.id.switchButton); + circlingProgressBar = root.findViewById(R.id.loadingBar); + flatProgressBar = root.findViewById(R.id.horizontalProgressBar); + messageTextView = root.findViewById(R.id.messageTextView); + titleTextView = root.findViewById(R.id.titleTextView); + switchButton = root.findViewById(R.id.switchButton); preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override @@ -217,6 +217,7 @@ @Override public void onAttach(Context context) { super.onAttach(context); + /// TODO: RISOLVERE PROBLEMA: il context qui e' l'Activity non il Fragment if (context instanceof FragmentListenerMain) { mListener = (FragmentListenerMain) context; } else { diff --git a/src/it/reyboz/bustorino/middleware/AppLocationManager.java b/src/it/reyboz/bustorino/middleware/AppLocationManager.java --- a/src/it/reyboz/bustorino/middleware/AppLocationManager.java +++ b/src/it/reyboz/bustorino/middleware/AppLocationManager.java @@ -104,6 +104,7 @@ Log.d(DEBUG_TAG,"Added new stop requester, instance of "+req.getClass().getSimpleName()); } if(requestersRef.size()>0){ + Log.d(DEBUG_TAG,"Requesting position updates"); requestGPSPositionUpdates(); } @@ -136,7 +137,7 @@ @Override public void onLocationChanged(Location location) { - Log.d("GPSLocationListener","found location:\nlat: "+location.getLatitude()+" lon: "+location.getLongitude()+"\naccuracy: "+location.getAccuracy()); + Log.d(DEBUG_TAG,"found location:\nlat: "+location.getLatitude()+" lon: "+location.getLongitude()+"\naccuracy: "+location.getAccuracy()); ListIterator<WeakReference<LocationRequester>> iter = requestersRef.listIterator(); int new_min_interval = Integer.MAX_VALUE; while(iter.hasNext()){ @@ -175,16 +176,19 @@ } oldGPSLocStatus = status; } + Log.d(DEBUG_TAG, "Provider: "+provider+" status: "+status); } @Override public void onProviderEnabled(String provider) { requestGPSPositionUpdates(); + Log.d(DEBUG_TAG, "Provider: "+provider+" enabled"); } @Override public void onProviderDisabled(String provider) { locMan.removeUpdates(this); + Log.d(DEBUG_TAG, "Provider: "+provider+" disabled"); } /** diff --git a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java --- a/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java +++ b/src/it/reyboz/bustorino/middleware/AsyncDataDownload.java @@ -19,6 +19,7 @@ import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.database.SQLException; import android.net.Uri; import android.os.AsyncTask; @@ -28,6 +29,7 @@ import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.data.AppDataProvider; +import it.reyboz.bustorino.data.NextGenDB; import it.reyboz.bustorino.fragments.FragmentHelper; import it.reyboz.bustorino.data.NextGenDB.Contract.*; @@ -51,13 +53,15 @@ WeakReference<FragmentHelper> helperRef; private final ArrayList<Thread> otherActivities = new ArrayList<>(); private final Fetcher[] theFetchers; + private Context context; - public AsyncDataDownload(FragmentHelper fh, @NonNull Fetcher[] fetchers) { + public AsyncDataDownload(FragmentHelper fh, @NonNull Fetcher[] fetchers, Context context) { RequestType type; helperRef = new WeakReference<>(fh); fh.setLastTaskRef(new WeakReference<>(this)); res = new AtomicReference<>(); + this.context = context.getApplicationContext(); theFetchers = fetchers; if (theFetchers.length < 1){ @@ -118,7 +122,7 @@ List<Route> branches = ((FiveTAPIFetcher) f).getDirectionsForStop(stopID,gres); if(gres.get() == Fetcher.result.OK){ p.addInfoFromRoutes(branches); - Thread t = new Thread(new BranchInserter(branches,fh,stopID)); + Thread t = new Thread(new BranchInserter(branches, context)); t.start(); otherActivities.add(t); @@ -236,11 +240,13 @@ public class BranchInserter implements Runnable{ private final List<Route> routesToInsert; - private final FragmentHelper fragmentHelper; + private final Context context; + private final NextGenDB nextGenDB; - public BranchInserter(List<Route> routesToInsert,FragmentHelper fh,String stopID) { + public BranchInserter(List<Route> routesToInsert,@NonNull Context con) { this.routesToInsert = routesToInsert; - this.fragmentHelper = fh; + this.context = con; + nextGenDB = new NextGenDB(context); } @Override @@ -298,7 +304,7 @@ } } starttime = System.currentTimeMillis(); - ContentResolver cr = fragmentHelper.getContentResolver(); + ContentResolver cr = context.getContentResolver(); try { cr.bulkInsert(Uri.parse("content://" + AppDataProvider.AUTHORITY + "/branches/"), values); endtime = System.currentTimeMillis(); @@ -313,7 +319,7 @@ starttime = System.currentTimeMillis(); ContentValues[] valArr = connectionsVals.toArray(new ContentValues[0]); Log.d("DataDownloadInsert","inserting "+valArr.length+" connections"); - int rows = fragmentHelper.insertBatchDataInNextGenDB(valArr,ConnectionsTable.TABLE_NAME); + int rows = nextGenDB.insertBatchContent(valArr,ConnectionsTable.TABLE_NAME); endtime = System.currentTimeMillis(); Log.d("DataDownload","Inserted connections found, took "+(endtime-starttime)+" ms, inserted "+rows+" rows"); } diff --git a/src/it/reyboz/bustorino/middleware/GeneralActivity.java b/src/it/reyboz/bustorino/middleware/GeneralActivity.java --- a/src/it/reyboz/bustorino/middleware/GeneralActivity.java +++ b/src/it/reyboz/bustorino/middleware/GeneralActivity.java @@ -63,23 +63,12 @@ InputMethodManager.HIDE_NOT_ALWAYS); } } - public boolean isConnected() { - ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); - return networkInfo != null && networkInfo.isConnected(); - } public void showToastMessage(int messageID, boolean short_lenght) { final int length = short_lenght ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG; Toast.makeText(getApplicationContext(), messageID, length).show(); } - public void assertLocationPermissions() { - if(ContextCompat.checkSelfPermission(getApplicationContext(),Manifest.permission.ACCESS_FINE_LOCATION)!=PackageManager.PERMISSION_GRANTED){ - ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_POSITION); - } - } - public int askForPermissionIfNeeded(String permission, int requestID){ if(ContextCompat.checkSelfPermission(getApplicationContext(),permission)==PackageManager.PERMISSION_GRANTED){ diff --git a/src/it/reyboz/bustorino/util/Permissions.java b/src/it/reyboz/bustorino/util/Permissions.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/util/Permissions.java @@ -0,0 +1,41 @@ +package it.reyboz.bustorino.util; + +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Criteria; +import android.location.LocationManager; +import android.util.Log; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.util.List; + +public class Permissions { + final static public String DEBUG_TAG = "BusTO -Permissions"; + + final static public int PERMISSION_REQUEST_POSITION = 33; + final static public String LOCATION_PERMISSION_GIVEN = "loc_permission"; + final static public int STORAGE_PERMISSION_REQ = 291; + + final static public int PERMISSION_OK = 0; + final static public int PERMISSION_ASKING = 11; + final static public int PERMISSION_NEG_CANNOT_ASK = -3; + + public static boolean anyLocationProviderMatchesCriteria(LocationManager mng, Criteria cr, boolean enabled) { + List<String> providers = mng.getProviders(cr, enabled); + Log.d(DEBUG_TAG, "Getting enabled location providers: "); + for (String s : providers) { + Log.d(DEBUG_TAG, "Provider " + s); + } + return providers.size() > 0; + } + + public static void assertLocationPermissions(Context con, Activity activity) { + if(ContextCompat.checkSelfPermission(con, Manifest.permission.ACCESS_FINE_LOCATION)!= PackageManager.PERMISSION_GRANTED){ + ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_POSITION); + } + } +}