diff --git a/build.gradle b/build.gradle --- a/build.gradle +++ b/build.gradle @@ -104,6 +104,7 @@ 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' 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 @@ -56,7 +56,7 @@ android:layout_toLeftOf="@+id/searchButton" android:layout_toRightOf="@+id/QRButton" android:layout_toStartOf="@+id/searchButton" - + android:inputType="text" android:ems="10" android:hint="@string/insert_bus_stop_name" android:imeOptions="actionSearch" @@ -101,11 +101,12 @@ android:id="@+id/howDoesItWorkTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginBottom="10dp" + android:layout_marginBottom="5dp" android:layout_marginEnd="16dp" android:layout_marginLeft="16dip" android:layout_marginRight="16dp" android:layout_marginStart="16dip" + android:layout_marginTop="10dp" android:layout_toLeftOf="@+id/hideHintButton" android:layout_toStartOf="@+id/hideHintButton" android:text="@string/howDoesItWork" @@ -129,36 +130,48 @@ android:textSize="19sp" android:visibility="gone" /> - - - - + > + + + + + + \ No newline at end of file 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 @@ -58,11 +58,7 @@ 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; @@ -154,7 +150,7 @@ 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. */ @@ -381,10 +377,19 @@ 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(); } diff --git a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt --- a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt +++ b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt @@ -124,10 +124,10 @@ 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) { diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java --- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java +++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java @@ -31,7 +31,6 @@ import android.widget.AdapterView; import android.widget.EditText; import android.widget.ImageView; -import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; @@ -45,21 +44,20 @@ 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; @@ -116,7 +114,7 @@ */ - llManager = new LinearLayoutManager(getContext()); + LinearLayoutManager llManager = new LinearLayoutManager(getContext()); llManager.setOrientation(LinearLayoutManager.VERTICAL); favoriteRecyclerView.setLayoutManager(llManager); DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(favoriteRecyclerView.getContext(), @@ -139,7 +137,7 @@ if (context instanceof CommonFragmentListener) { mListener = (CommonFragmentListener) context; } else { - throw new RuntimeException(context.toString() + throw new RuntimeException(context + " must implement CommonFragmentListener"); } @@ -182,7 +180,7 @@ 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: @@ -214,6 +212,11 @@ } } + @Nullable + @Override + public View getBaseViewForSnackBar() { + return null; + } void showStops(List busStops){ // If no data is found show a friendly message @@ -256,43 +259,32 @@ 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(); } @@ -300,11 +292,8 @@ 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 --- a/src/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/src/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -15,6 +15,7 @@ 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; @@ -58,7 +59,7 @@ * 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"; @@ -191,6 +192,7 @@ private CommonFragmentListener mListener; private String pendingStopID = null; + private CoordinatorLayout coordLayout; public MainScreenFragment() { // Required empty public constructor @@ -219,7 +221,7 @@ Bundle savedInstanceState) { // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_main_screen, container, false); - addToFavorites = (ImageButton) root.findViewById(R.id.addToFavorites); + addToFavorites = root.findViewById(R.id.addToFavorites); busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText); busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText); progressBar = root.findViewById(R.id.progressBar); @@ -251,6 +253,8 @@ .setOnRefreshListener(() -> mainHandler.post(refreshStop)); swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500); + coordLayout = root.findViewById(R.id.coord_layout); + floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout)); hideHintButton.setOnClickListener(this::onHideHint); @@ -274,7 +278,7 @@ cr.setCostAllowed(true); cr.setPowerRequirement(Criteria.NO_REQUIREMENT); - locationManager = AppLocationManager.getInstance(getContext()); + locationManager = AppLocationManager.getInstance(getContext()); Log.d(DEBUG_TAG, "OnCreateView, savedInstanceState null: "+(savedInstanceState==null)); @@ -324,6 +328,7 @@ */ } + /** * Cancel the reload of the arrival times * because we are going to pop the fragment @@ -543,6 +548,13 @@ //actionHelpMenuItem.setVisible(true); } + @Nullable + @org.jetbrains.annotations.Nullable + @Override + public View getBaseViewForSnackBar() { + return coordLayout; + } + @Override public void toggleSpinner(boolean enable) { if (enable) { diff --git a/src/it/reyboz/bustorino/fragments/MapFragment.java b/src/it/reyboz/bustorino/fragments/MapFragment.java --- a/src/it/reyboz/bustorino/fragments/MapFragment.java +++ b/src/it/reyboz/bustorino/fragments/MapFragment.java @@ -69,7 +69,7 @@ import it.reyboz.bustorino.middleware.GeneralActivity; import it.reyboz.bustorino.util.Permissions; -public class MapFragment extends BaseFragment { +public class MapFragment extends ScreenBaseFragment { private static final String TAG = "Busto-MapActivity"; private static final String MAP_CURRENT_ZOOM_KEY = "map-current-zoom"; @@ -131,27 +131,24 @@ }; private final ActivityResultLauncher positionRequestLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), new ActivityResultCallback>() { - @Override - @SuppressLint("MissingPermission") - public void onActivityResult(Map result) { - if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){ - - map.getOverlays().remove(mLocationOverlay); - startLocationOverlay(true, map); - if(getContext()==null || getContext().getSystemService(Context.LOCATION_SERVICE)==null) - return; - LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); - Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - if (userLocation != null) { - map.getController().setZoom(POSITION_FOUND_ZOOM); - GeoPoint startPoint = new GeoPoint(userLocation); - setLocationFollowing(true); - map.getController().setCenter(startPoint); - } + registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { + if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){ + + map.getOverlays().remove(mLocationOverlay); + startLocationOverlay(true, map); + if(getContext()==null || getContext().getSystemService(Context.LOCATION_SERVICE)==null) + return; + LocationManager locationManager = (LocationManager) getContext().getSystemService(Context.LOCATION_SERVICE); + @SuppressLint("MissingPermission") + Location userLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (userLocation != null) { + map.getController().setZoom(POSITION_FOUND_ZOOM); + GeoPoint startPoint = new GeoPoint(userLocation); + setLocationFollowing(true); + map.getController().setCenter(startPoint); } - else Log.w(DEBUG_TAG,"No location permission"); } + else Log.w(DEBUG_TAG,"No location permission"); }); public MapFragment() { @@ -583,6 +580,13 @@ return marker; } + @Nullable + @org.jetbrains.annotations.Nullable + @Override + public View getBaseViewForSnackBar() { + return null; + } + /** * Simple asyncTask class to load the stops in the background * Holds a weak reference to the fragment to do callbacks diff --git a/src/it/reyboz/bustorino/fragments/BaseFragment.java b/src/it/reyboz/bustorino/fragments/ScreenBaseFragment.java rename from src/it/reyboz/bustorino/fragments/BaseFragment.java rename to src/it/reyboz/bustorino/fragments/ScreenBaseFragment.java --- a/src/it/reyboz/bustorino/fragments/BaseFragment.java +++ b/src/it/reyboz/bustorino/fragments/ScreenBaseFragment.java @@ -6,13 +6,15 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Toast; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.google.android.material.floatingactionbutton.FloatingActionButton; import it.reyboz.bustorino.BuildConfig; import static android.content.Context.MODE_PRIVATE; -public abstract class BaseFragment extends Fragment { +public abstract class ScreenBaseFragment extends Fragment { protected String PREF_FILE= BuildConfig.APPLICATION_ID+".fragment_prefs"; @@ -35,6 +37,7 @@ } public void hideKeyboard() { + if (getActivity()==null) return; View view = getActivity().getCurrentFocus(); if (view != null) { ((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) @@ -42,4 +45,11 @@ InputMethodManager.HIDE_NOT_ALWAYS); } } + + /** + * Find the view on which the snackbar should be shown + * @return + */ + @Nullable + public abstract View getBaseViewForSnackBar(); } diff --git a/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java b/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java --- a/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java +++ b/src/it/reyboz/bustorino/middleware/AsyncStopsSearcher.java @@ -115,7 +115,7 @@ protected void onPostExecute(List stops) { final FragmentHelper fh = helperWR.get(); - if (stops==null || fh==null || theQuery==null) { + if (stops==null || stops.size() == 0 || fh==null || theQuery==null) { if (fh!=null) fh.toggleSpinner(false); cancel(true); return;