diff --git a/build.gradle b/build.gradle
index 3b4be30..c2a20dd 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,144 +1,145 @@
buildscript {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
google()
}
ext {
//multidex
multidex_version = "2.0.1"
//libraries versions
fragment_version = "1.3.6"
activity_version = "1.3.1"
appcompat_version = "1.3.1"
preference_version = "1.1.1"
work_version = "2.5.0"
acra_version = "5.7.0"
lifecycle_version = "2.3.1"
arch_version = "2.1.0"
room_version = "2.3.0"
//kotlin
kotlin_version = '1.6.0'
coroutines_version = "1.5.0"
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' }
google()
mavenCentral()
}
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 30
buildToolsVersion '30.0.3'
defaultConfig {
applicationId "it.reyboz.bustorino"
minSdkVersion 16
targetSdkVersion 30
versionCode 36
versionName "1.16.0"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
buildTypes {
debug {
applicationIdSuffix ".debug"
versionNameSuffix "-dev"
}
}
lintOptions {
abortOnError false
}
repositories {
jcenter()
mavenLocal()
}
dependencies {
//new libraries
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.annotation:annotation:1.2.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation "androidx.preference:preference:$preference_version"
implementation "androidx.work:work-runtime:$work_version"
implementation "com.google.android.material:material:1.4.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
+ implementation "androidx.coordinatorlayout:coordinatorlayout:1.1.0"
implementation 'org.jsoup:jsoup:1.13.1'
implementation 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
implementation 'com.android.volley:volley:1.2.1'
implementation 'org.osmdroid:osmdroid-android:6.1.10'
// ACRA
implementation "ch.acra:acra-mail:$acra_version"
implementation "ch.acra:acra-dialog:$acra_version"
// google transit realtime
implementation 'com.google.protobuf:protobuf-java:3.14.0'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
// Room components
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
androidTestImplementation "androidx.room:room-testing:$room_version"
//multidex - we need this to build the app
implementation "androidx.multidex:multidex:$multidex_version"
implementation 'de.siegmar:fastcsv:2.0.0'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
}
diff --git a/res/layout/fragment_main_screen.xml b/res/layout/fragment_main_screen.xml
index 746319c..b4357aa 100644
--- a/res/layout/fragment_main_screen.xml
+++ b/res/layout/fragment_main_screen.xml
@@ -1,164 +1,177 @@
-
-
-
-
+ >
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
index 2cca929..5913140 100644
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -1,590 +1,595 @@
/*
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.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.Arrays;
import java.util.concurrent.TimeUnit;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.DBUpdateWorker;
import it.reyboz.bustorino.data.DatabaseUpdate;
-import it.reyboz.bustorino.fragments.FavoritesFragment;
-import it.reyboz.bustorino.fragments.FragmentKind;
-import it.reyboz.bustorino.fragments.FragmentListenerMain;
-import it.reyboz.bustorino.fragments.MainScreenFragment;
-import it.reyboz.bustorino.fragments.MapFragment;
+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;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_principal);
final SharedPreferences theShPr = getMainSharedPreferences();
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);
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
DatabaseUpdate.requestDBUpdateWithWork(this, 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
showMainFragment();
}
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
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
FavoritesFragment fragment = FavoritesFragment.newInstance();
ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES);
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
return true;
} else if(menuItem.getItemId() == R.id.nav_arrivals){
closeDrawerIfOpen();
showMainFragment();
return true;
} else if(menuItem.getItemId() == R.id.nav_map_item){
closeDrawerIfOpen();
final String permission = Manifest.permission.WRITE_EXTERNAL_STORAGE;
int result = askForPermissionIfNeeded(permission, STORAGE_PERMISSION_REQ);
switch (result) {
case PERMISSION_OK:
createAndShowMapFragment(null);
break;
case PERMISSION_ASKING:
permissionDoneRunnables.put(permission,
() -> createAndShowMapFragment(null));
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();
}
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().popBackStackImmediate();
if(showingMainFragmentFromOther && getSupportFragmentManager().getBackStackEntryCount() > 0){
getSupportFragmentManager().popBackStack();
}
}
else if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack();
}
else
super.onBackPressed();
}
+ /**
+ * Create and show the SnackBar with the message
+ */
private void createDefaultSnackbar() {
- if (snackbar == null) {
- snackbar = Snackbar.make(findViewById(R.id.searchButton), R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
+
+ 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_message, Snackbar.LENGTH_INDEFINITE);
snackbar.show();
}
private MainScreenFragment createAndShowMainFragment(){
FragmentManager fraMan = getSupportFragmentManager();
MainScreenFragment fragment = MainScreenFragment.newInstance();
FragmentTransaction transaction = fraMan.beginTransaction();
transaction.replace(R.id.mainActContentFrame, fragment, MainScreenFragment.FRAGMENT_TAG);
transaction.commit();
return fragment;
}
/**
* 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){
fraMan.beginTransaction().replace(R.id.mainActContentFrame, fragment)
.setReorderingAllowed(true)
.addToBackStack(null)
/*.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)
.commit();
}
private MainScreenFragment showMainFragment(){
FragmentManager fraMan = getSupportFragmentManager();
Fragment fragment = fraMan.findFragmentByTag(MainScreenFragment.FRAGMENT_TAG);
final MainScreenFragment mainScreenFragment;
if (fragment==null | !(fragment instanceof MainScreenFragment)){
mainScreenFragment = createAndShowMainFragment();
}
else if(!fragment.isVisible()){
mainScreenFragment = (MainScreenFragment) fragment;
showMainFragment(fraMan, mainScreenFragment);
Log.d(DEBUG_TAG, "Found the main fragment");
} else{
mainScreenFragment = (MainScreenFragment) fragment;
}
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);
}
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;
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);
if(fragment!=null){
//the fragment is there but not shown
probableFragment = (MainScreenFragment) fragment;
// set the flag
probableFragment.setSuppressArrivalsReload(true);
showMainFragment(fraMan, probableFragment);
} else {
// we have no fragment
probableFragment = createAndShowMainFragment();
}
}
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);
}
//Map Fragment stuff
void createAndShowMapFragment(@Nullable Stop stop){
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);
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;
}
}
}
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
index 057400f..7f2152f 100644
--- a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
@@ -1,268 +1,268 @@
/*
BusTO - Backend components
Copyright (C) 2021 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.backend.mato
import android.content.Context
import android.util.Log
import com.android.volley.toolbox.RequestFuture
import it.reyboz.bustorino.BuildConfig
import it.reyboz.bustorino.backend.*
import org.json.JSONObject
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
open class MatoAPIFetcher(val minNumPassaggi: Int) : ArrivalsFetcher {
var appContext: Context? = null
set(value) {
field = value!!.applicationContext
}
constructor(): this(2)
override fun ReadArrivalTimesAll(stopID: String?, res: AtomicReference?): Palina {
stopID!!
val now = Calendar.getInstance().time
var numMinutes = 0
var palina = Palina(stopID)
var numPassaggi = 0
var trials = 0
val numDepartures = 4
while (numPassaggi < minNumPassaggi && trials < 4) {
//numDepartures+=2
numMinutes += 20
val future = RequestFuture.newFuture()
val request = MapiArrivalRequest(stopID, now, numMinutes * 60, numDepartures, res, future, future)
if (appContext == null || res == null) {
Log.e("BusTO:MatoAPIFetcher", "ERROR: Given null context or null result ref")
return Palina(stopID)
}
val requestQueue = NetworkVolleyManager.getInstance(appContext).requestQueue
request.setTag(getVolleyReqTag(QueryType.ARRIVALS))
requestQueue.add(request)
try {
val palinaResult = future.get(5, TimeUnit.SECONDS)
if (palinaResult!=null) {
if (BuildConfig.DEBUG)
for (r in palinaResult.queryAllRoutes()){
Log.d(DEBUG_TAG, "route " + r.gtfsId + " has " + r.passaggi.size + " passaggi: "+ r.passaggiToString)
}
palina = palinaResult
numPassaggi = palina.minNumberOfPassages
} else{
Log.d(DEBUG_TAG, "Result palina is null")
}
} catch (e: InterruptedException) {
e.printStackTrace()
res.set(Fetcher.Result.PARSER_ERROR)
} catch (e: ExecutionException) {
e.printStackTrace()
if (res.get() == Fetcher.Result.OK)
res.set(Fetcher.Result.SERVER_ERROR)
} catch (e: TimeoutException) {
res.set(Fetcher.Result.CONNECTION_ERROR)
e.printStackTrace()
}
trials++
}
return palina
}
override fun getSourceForFetcher(): Passaggio.Source {
return Passaggio.Source.MatoAPI
}
companion object{
const val VOLLEY_TAG = "MatoAPIFetcher"
const val DEBUG_TAG = "BusTO:MatoAPIFetcher"
val REQ_PARAMETERS = mapOf(
"Content-Type" to "application/json; charset=utf-8",
"DNT" to "1",
"Host" to "mapi.5t.torino.it")
fun getVolleyReqTag(type: QueryType): String{
return when (type){
QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
}
}
/**
* Get stops from the MatoAPI, set [res] accordingly
*/
fun getAllStopsGTT(context: Context, res: AtomicReference?): List{
val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
val future = RequestFuture.newFuture>()
val request = VolleyAllStopsRequest(future, future)
request.tag = getVolleyReqTag(QueryType.ALL_STOPS)
requestQueue.add(request)
- var palinaList:List = mutableListOf()
+ var palinaList:List = mutableListOf()
try {
- palinaList = future.get(30, TimeUnit.SECONDS)
+ palinaList = future.get(60, TimeUnit.SECONDS)
res?.set(Fetcher.Result.OK)
}catch (e: InterruptedException) {
e.printStackTrace()
res?.set(Fetcher.Result.PARSER_ERROR)
} catch (e: ExecutionException) {
e.printStackTrace()
res?.set(Fetcher.Result.SERVER_ERROR)
} catch (e: TimeoutException) {
res?.set(Fetcher.Result.CONNECTION_ERROR)
e.printStackTrace()
}
return palinaList
}
/*
fun makeRequest(type: QueryType?, variables: JSONObject) : String{
type.let {
val requestData = JSONObject()
when (it){
QueryType.ARRIVALS ->{
requestData.put("operationName","AllStopsDirect")
requestData.put("variables", variables)
requestData.put("query", MatoQueries.QUERY_ARRIVALS)
}
else -> {
//TODO all other cases
}
}
//todo make the request...
//https://pablobaxter.github.io/volley-docs/com/android/volley/toolbox/RequestFuture.html
//https://stackoverflow.com/questions/16904741/can-i-do-a-synchronous-request-with-volley
}
return ""
}
*/
fun parseStopJSON(jsonStop: JSONObject): Palina{
val latitude = jsonStop.getDouble("lat")
val longitude = jsonStop.getDouble("lon")
val palina = Palina(
jsonStop.getString("code"),
jsonStop.getString("name"),
null, null, latitude, longitude
)
palina.gtfsID = jsonStop.getString("gtfsId")
val routesStoppingJSON = jsonStop.getJSONArray("routes")
val baseRoutes = mutableListOf()
// get all the possible routes
for (i in 0 until routesStoppingJSON.length()){
val routeBaseInfo = routesStoppingJSON.getJSONObject(i)
val r = Route(routeBaseInfo.getString("shortName"), Route.Type.UNKNOWN,"")
r.setGtfsId(routeBaseInfo.getString("gtfsId").trim())
baseRoutes.add(r)
}
if (jsonStop.has("desc")){
palina.location = jsonStop.getString("desc")
}
//there is also "zoneId" which is the zone of the stop (0-> city, etc)
if(jsonStop.has("stoptimesForPatterns")) {
val routesStopTimes = jsonStop.getJSONArray("stoptimesForPatterns")
for (i in 0 until routesStopTimes.length()) {
val patternJSON = routesStopTimes.getJSONObject(i)
val mRoute = parseRouteStoptimesJSON(patternJSON)
//Log.d("BusTO-MapiFetcher")
//val directionId = patternJSON.getJSONObject("pattern").getInt("directionId")
//TODO: use directionId
palina.addRoute(mRoute)
for (r in baseRoutes) {
if (mRoute.gtfsId != null && r.gtfsId.equals(mRoute.gtfsId)) {
baseRoutes.remove(r)
break
}
}
}
}
for (noArrivalRoute in baseRoutes){
palina.addRoute(noArrivalRoute)
}
//val gtfsRoutes = mutableListOf<>()
return palina
}
fun parseRouteStoptimesJSON(jsonPatternWithStops: JSONObject): Route{
val patternJSON = jsonPatternWithStops.getJSONObject("pattern")
val routeJSON = patternJSON.getJSONObject("route")
val passaggiJSON = jsonPatternWithStops.getJSONArray("stoptimes")
val gtfsId = routeJSON.getString("gtfsId").trim()
val passages = mutableListOf()
for( i in 0 until passaggiJSON.length()){
val stoptime = passaggiJSON.getJSONObject(i)
val scheduledTime = stoptime.getInt("scheduledArrival")
val realtimeTime = stoptime.getInt("realtimeArrival")
val realtime = stoptime.getBoolean("realtime")
passages.add(
Passaggio(realtimeTime,realtime, realtimeTime-scheduledTime,
Passaggio.Source.MatoAPI)
)
}
var routeType = Route.Type.UNKNOWN
if (gtfsId[gtfsId.length-1] == 'E')
routeType = Route.Type.LONG_DISTANCE_BUS
else when( routeJSON.getString("mode").trim()){
"BUS" -> routeType = Route.Type.BUS
"TRAM" -> routeType = Route.Type.TRAM
}
val route = Route(
routeJSON.getString("shortName"),
patternJSON.getString("headsign"),
routeType,
passages,
)
route.setGtfsId(gtfsId)
return route
}
fun makeRequestParameters(requestName:String, variables: JSONObject, query: String): JSONObject{
val data = JSONObject()
data.put("operationName", requestName)
data.put("variables", variables)
data.put("query", query)
return data
}
}
enum class QueryType {
ARRIVALS, ALL_STOPS
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
index 3c45a71..d44a58e 100644
--- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
+++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
@@ -1,310 +1,299 @@
/*
BusTO - Fragments components
Copyright (C) 2021 Fabio Mazza
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package it.reyboz.bustorino.fragments;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ImageView;
-import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
import it.reyboz.bustorino.*;
import it.reyboz.bustorino.adapters.AdapterListener;
-import it.reyboz.bustorino.adapters.StopAdapter;
import it.reyboz.bustorino.adapters.StopRecyclerAdapter;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.FavoritesViewModel;
import it.reyboz.bustorino.middleware.AsyncStopFavoriteAction;
-public class FavoritesFragment extends BaseFragment {
+public class FavoritesFragment extends ScreenBaseFragment {
private RecyclerView favoriteRecyclerView;
private EditText busStopNameText;
private TextView favoriteTipTextView;
private ImageView angeryBusImageView;
- private LinearLayoutManager llManager;
@Nullable
private CommonFragmentListener mListener;
public static final String FRAGMENT_TAG = "BusTOFavFragment";
private final AdapterListener adapterListener = new AdapterListener() {
@Override
public void onTappedStop(Stop stop) {
mListener.requestArrivalsForStopID(stop.ID);
}
};
public static FavoritesFragment newInstance() {
FavoritesFragment fragment = new FavoritesFragment();
Bundle args = new Bundle();
//args.putString(ARG_PARAM1, param1);
//args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
public FavoritesFragment(){
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
//do nothing
}
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_favorites, container, false);
favoriteRecyclerView = root.findViewById(R.id.favoritesRecyclerView);
//favoriteListView = root.findViewById(R.id.favoriteListView);
/*favoriteRecyclerView.setOn((parent, view, position, id) -> {
/*
* Casting because of Javamerda
* @url http://stackoverflow.com/questions/30549485/androids-list-view-parameterized-type-in-adapterview-onitemclicklistener
*/
/*
Stop busStop = (Stop) parent.getItemAtPosition(position);
if(mListener!=null){
mListener.requestArrivalsForStopID(busStop.ID);
}
});
*/
- llManager = new LinearLayoutManager(getContext());
+ LinearLayoutManager llManager = new LinearLayoutManager(getContext());
llManager.setOrientation(LinearLayoutManager.VERTICAL);
favoriteRecyclerView.setLayoutManager(llManager);
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(favoriteRecyclerView.getContext(),
llManager.getOrientation());
favoriteRecyclerView.addItemDecoration(dividerItemDecoration);
angeryBusImageView = root.findViewById(R.id.angeryBusImageView);
favoriteTipTextView = root.findViewById(R.id.favoriteTipTextView);
registerForContextMenu(favoriteRecyclerView);
FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class);
model.getFavorites().observe(getViewLifecycleOwner(), this::showStops);
showStops(new ArrayList<>());
return root;
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof CommonFragmentListener) {
mListener = (CommonFragmentListener) context;
} else {
- throw new RuntimeException(context.toString()
+ throw new RuntimeException(context
+ " must implement CommonFragmentListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/*
This method is apparently NOT CALLED ANYMORE
*/
@Override
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
Log.d("Favorites Fragment", "Creating context menu on "+v);
if (v.getId() == R.id.favoritesRecyclerView) {
// if we aren't attached to activity, return null
if (getActivity()==null) return;
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.menu_favourites_entry, menu);
}
}
@Override
public void onResume() {
super.onResume();
if (mListener!=null) mListener.readyGUIfor(FragmentKind.FAVORITES);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item
.getMenuInfo();
if(!(favoriteRecyclerView.getAdapter() instanceof StopRecyclerAdapter))
return false;
StopRecyclerAdapter adapter = (StopRecyclerAdapter) favoriteRecyclerView.getAdapter();
- Stop busStop = (Stop) adapter.getStops().get(adapter.getPosition());
+ Stop busStop = adapter.getStops().get(adapter.getPosition());
switch (item.getItemId()) {
case R.id.action_favourite_entry_delete:
if (getContext()!=null)
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.REMOVE,
result -> {
}).execute(busStop);
return true;
case R.id.action_rename_bus_stop_username:
showBusStopUsernameInputDialog(busStop);
return true;
case R.id.action_view_on_map:
if (busStop.getLatitude() == null | busStop.getLongitude() == null |
mListener==null
) {
Toast.makeText(getContext(), R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show();
return true;
}
//GeoPoint point = new GeoPoint(busStop.getLatitude(), busStop.getLongitude());
mListener.showMapCenteredOnStop(busStop);
return true;
default:
return super.onContextItemSelected(item);
}
}
+ @Nullable
+ @Override
+ public View getBaseViewForSnackBar() {
+ return null;
+ }
void showStops(List busStops){
// If no data is found show a friendly message
if(BuildConfig.DEBUG)
Log.d("BusTO - Favorites", "We have "+busStops.size()+" favorites in the list");
if (busStops.size() == 0) {
favoriteRecyclerView.setVisibility(View.INVISIBLE);
// TextView favoriteTipTextView = (TextView) findViewById(R.id.favoriteTipTextView);
//assert favoriteTipTextView != null;
favoriteTipTextView.setVisibility(View.VISIBLE);
//ImageView angeryBusImageView = (ImageView) findViewById(R.id.angeryBusImageView);
angeryBusImageView.setVisibility(View.VISIBLE);
} else {
favoriteRecyclerView.setVisibility(View.VISIBLE);
favoriteTipTextView.setVisibility(View.INVISIBLE);
angeryBusImageView.setVisibility(View.INVISIBLE);
}
/* There's a nice method called notifyDataSetChanged() to avoid building the ListView
* all over again. This method exists in a billion answers on Stack Overflow, but
* it's nowhere to be seen around here, Android Studio can't find it no matter what.
* Anyway, it only works from Android 2.3 onward (which is why it refuses to appear, I
* guess) and requires to modify the list with .add() and .clear() and some other
* methods, so to update a single stop we need to completely rebuild the list for no
* reason. It would probably end up as "slow" as throwing away the old ListView and
* redrwaing everything.
*/
// Show results
favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener));
}
public void showBusStopUsernameInputDialog(final Stop busStop) {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
LayoutInflater inflater = this.getLayoutInflater();
View renameDialogLayout = inflater.inflate(R.layout.rename_dialog, null);
busStopNameText = (EditText) renameDialogLayout.findViewById(R.id.rename_dialog_bus_stop_name);
busStopNameText.setText(busStop.getStopDisplayName());
busStopNameText.setHint(busStop.getStopDefaultName());
builder.setTitle(getString(R.string.dialog_rename_bus_stop_username_title));
builder.setView(renameDialogLayout);
- builder.setPositiveButton(getString(android.R.string.ok), new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- String busStopUsername = busStopNameText.getText().toString();
- String oldUserName = busStop.getStopUserName();
-
- // changed to none
- if(busStopUsername.length() == 0) {
- // unless it was already empty, set new
- if(oldUserName != null) {
- busStop.setStopUserName(null);
-
- }
- } else { // changed to something
- // something different?
- if(!busStopUsername.equals(oldUserName)) {
- busStop.setStopUserName(busStopUsername);
-
- }
+ builder.setPositiveButton(getString(android.R.string.ok), (dialog, which) -> {
+ String busStopUsername = busStopNameText.getText().toString();
+ String oldUserName = busStop.getStopUserName();
+
+ // changed to none
+ if(busStopUsername.length() == 0) {
+ // unless it was already empty, set new
+ if(oldUserName != null) {
+ busStop.setStopUserName(null);
+
+ }
+ } else { // changed to something
+ // something different?
+ if(!busStopUsername.equals(oldUserName)) {
+ busStop.setStopUserName(busStopUsername);
+
}
- launchUpdate(busStop);
- }
- });
- builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.cancel();
}
+ launchUpdate(busStop);
});
- builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // delete user name from database
- busStop.setStopUserName(null);
- launchUpdate(busStop);
+ builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> dialog.cancel());
+ builder.setNeutralButton(R.string.dialog_rename_bus_stop_username_reset_button, (dialog, which) -> {
+ // delete user name from database
+ busStop.setStopUserName(null);
+ launchUpdate(busStop);
- }
});
builder.show();
}
private void launchUpdate(Stop busStop){
if (getContext()!=null)
new AsyncStopFavoriteAction(getContext().getApplicationContext(), AsyncStopFavoriteAction.Action.UPDATE,
- new AsyncStopFavoriteAction.ResultListener() {
- @Override
- public void doStuffWithResult(Boolean result) {
- //Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show();
- }
+ result -> {
+ //Toast.makeText(getApplicationContext(), R.string.tip_add_favorite, Toast.LENGTH_SHORT).show();
}).execute(busStop);
}
}
diff --git a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
index d7d3a68..71f227e 100644
--- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java
@@ -1,740 +1,752 @@
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.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.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 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.util.LocationCriteria;
import it.reyboz.bustorino.util.Permissions;
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 BaseFragment implements FragmentListenerMain{
+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 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 boolean setupOnAttached = true;
private boolean suppressArrivalsReload = 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;
private final ArrivalsFetcher[] arrivalsFetchers = utils.getDefaultArrivalsFetchers();
//// HIDDEN BUT IMPORTANT ELEMENTS ////
FragmentManager fragMan;
Handler mainHandler;
private final Runnable refreshStop = new Runnable() {
public void run() {
if(getContext() == null) return;
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();
}
};
/// LOCATION STUFF ///
boolean pendingNearbyStopsRequest = false;
boolean locationPermissionGranted, locationPermissionAsked = false;
AppLocationManager locationManager;
private final LocationCriteria cr = new LocationCriteria(2000, 10000);
//Location
private AppLocationManager.LocationRequester requester = new AppLocationManager.LocationRequester() {
@Override
public void onLocationChanged(Location loc) {
}
@Override
public void onLocationStatusChanged(int status) {
if(status == AppLocationManager.LOCATION_GPS_AVAILABLE && !isNearbyFragmentShown()){
//request Stops
pendingNearbyStopsRequest = false;
if (getContext()!= null)
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public long getLastUpdateTimeMillis() {
return 50;
}
@Override
public LocationCriteria getLocationCriteria() {
return cr;
}
@Override
public void onLocationProviderAvailable() {
//Log.w(DEBUG_TAG, "pendingNearbyStopRequest: "+pendingNearbyStopsRequest);
if(!isNearbyFragmentShown() && getContext()!=null){
pendingNearbyStopsRequest = false;
mainHandler.post(new NearbyStopsRequester(getContext(), cr));
}
}
@Override
public void onLocationDisabled() {
}
};
private final ActivityResultLauncher requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback