diff --git a/res/layout/fragment_favorites.xml b/res/layout/fragment_favorites.xml --- a/res/layout/fragment_favorites.xml +++ b/res/layout/fragment_favorites.xml @@ -28,13 +28,14 @@ android:layout_marginRight="@dimen/activity_horizontal_margin" android:visibility="invisible" /> - + android:layout_alignParentTop="true" + /> \ No newline at end of file diff --git a/src/it/reyboz/bustorino/adapters/AdapterListener.java b/src/it/reyboz/bustorino/adapters/AdapterListener.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/adapters/AdapterListener.java @@ -0,0 +1,7 @@ +package it.reyboz.bustorino.adapters; + +import it.reyboz.bustorino.backend.Stop; + +public interface AdapterListener { + void onTappedStop(Stop stop); +} diff --git a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java new file mode 100644 --- /dev/null +++ b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java @@ -0,0 +1,157 @@ +package it.reyboz.bustorino.adapters; + +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import it.reyboz.bustorino.R; +import it.reyboz.bustorino.backend.Stop; + +public class StopRecyclerAdapter extends RecyclerView.Adapter { + private List stops; + private static final int row_layout = R.layout.entry_bus_stop; + private static final int busIcon = R.drawable.bus; + private static final int trainIcon = R.drawable.subway; + private static final int tramIcon = R.drawable.tram; + private static final int cityIcon = R.drawable.city; + + private AdapterListener listener; + private int position; + + + + protected static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener{ + TextView busStopIDTextView; + TextView busStopNameTextView; + //TextView busLineVehicleIcon; + TextView busStopLinesTextView; + TextView busStopLocaLityTextView; + Stop mStop; + + public ViewHolder(@NonNull View itemView, AdapterListener listener) { + super(itemView); + busStopIDTextView = (TextView) itemView.findViewById(R.id.busStopID); + busStopNameTextView = (TextView) itemView.findViewById(R.id.busStopName); + busStopLinesTextView = (TextView) itemView.findViewById(R.id.routesThatStopHere); + busStopLocaLityTextView = (TextView) itemView.findViewById(R.id.busStopLocality); + + mStop = new Stop(""); + + itemView.setOnClickListener(view -> { + listener.onTappedStop(mStop); + }); + } + //many thanks to https://stackoverflow.com/questions/26466877/how-to-create-context-menu-for-recyclerview + @Override + public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) { + MenuInflater inflater = new MenuInflater(view.getContext()); + inflater.inflate(R.menu.menu_favourites_entry, contextMenu); + } + } + + public StopRecyclerAdapter(List stops,AdapterListener listener) { + this.stops = stops; + this.listener = listener; + } + + public void setStops(List stops){ + this.stops = stops; + notifyDataSetChanged(); + } + + public List getStops() { + return stops; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + @NonNull + @Override + public StopRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(row_layout, parent, false); + + return new StopRecyclerAdapter.ViewHolder(view, listener); + } + + @Override + public void onViewRecycled(@NonNull StopRecyclerAdapter.ViewHolder holder) { + holder.itemView.setOnLongClickListener(null); + super.onViewRecycled(holder); + } + + @Override + public void onBindViewHolder(@NonNull StopRecyclerAdapter.ViewHolder vh, int position) { + Log.d("StopRecyclerAdapter", "Called for position "+position); + Stop stop = stops.get(position); + vh.busStopIDTextView.setText(stop.ID); + vh.mStop = stop; + Log.d("StopRecyclerAdapter", "Stop: "+stop.ID); + + // NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it? + vh.busStopNameTextView.setText(stop.getStopDisplayName()); + String whatStopsHere = stop.routesThatStopHereToString(); + if(whatStopsHere == null) { + vh.busStopLinesTextView.setVisibility(View.GONE); + } else { + vh.busStopLinesTextView.setText(whatStopsHere); + vh.busStopLinesTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern + } + + if(stop.type == null) { + vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + } else { + switch(stop.type) { + case BUS: + default: + vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(busIcon, 0, 0, 0); + break; + case METRO: + case RAILWAY: + vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(trainIcon, 0, 0, 0); + break; + case TRAM: + vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(tramIcon, 0, 0, 0); + break; + case LONG_DISTANCE_BUS: + // è l'opposto della città ma va beh, dettagli. + vh.busStopLinesTextView.setCompoundDrawablesWithIntrinsicBounds(cityIcon, 0, 0, 0); + } + } + + if (stop.location == null) { + vh.busStopLocaLityTextView.setVisibility(View.GONE); + } else { + vh.busStopLocaLityTextView.setText(stop.location); + vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern + } + //trick to set the position + vh.itemView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + setPosition(vh.getAdapterPosition()); + return false; + } + }); + } + + @Override + public int getItemCount() { + return stops.size(); + } +} diff --git a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java --- a/src/it/reyboz/bustorino/fragments/CommonScrollListener.java +++ b/src/it/reyboz/bustorino/fragments/CommonScrollListener.java @@ -17,6 +17,7 @@ */ package it.reyboz.bustorino.fragments; +import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import android.util.Log; import android.widget.AbsListView; @@ -35,10 +36,8 @@ this.enableRefreshLayout = enableRefreshLayout; } @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - /* - * This seems to be a totally useless method - */ + public void onScrollStateChanged(AbsListView absListView, int i) { + } @Override @@ -76,7 +75,7 @@ }} @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { FragmentListenerMain listener = listenerWeakReference.get(); if(newState!=SCROLL_STATE_IDLE) listener.showFloatingActionButton(false); else listener.showFloatingActionButton(true); 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 @@ -38,29 +38,40 @@ 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 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 { - private ListView favoriteListView; + 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() { @@ -87,12 +98,14 @@ @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_favorites, container, false); - favoriteListView = root.findViewById(R.id.favoriteListView); - favoriteListView.setOnItemClickListener((parent, view, position, id) -> { - /** + 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){ @@ -100,9 +113,19 @@ } }); + + */ + + 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(favoriteListView); + registerForContextMenu(favoriteRecyclerView); FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class); model.getFavorites().observe(getViewLifecycleOwner(), this::showStops); @@ -127,14 +150,18 @@ 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); - if (v.getId() == R.id.favoriteListView) { + 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); } @@ -151,7 +178,11 @@ AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item .getMenuInfo(); - Stop busStop = (Stop) favoriteListView.getItemAtPosition(info.position); + if(!(favoriteRecyclerView.getAdapter() instanceof StopRecyclerAdapter)) + return false; + + StopRecyclerAdapter adapter = (StopRecyclerAdapter) favoriteRecyclerView.getAdapter(); + Stop busStop = (Stop) adapter.getStops().get(adapter.getPosition()); switch (item.getItemId()) { case R.id.action_favourite_entry_delete: @@ -189,14 +220,14 @@ if(BuildConfig.DEBUG) Log.d("BusTO - Favorites", "We have "+busStops.size()+" favorites in the list"); if (busStops.size() == 0) { - favoriteListView.setVisibility(View.INVISIBLE); + 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 { - favoriteListView.setVisibility(View.VISIBLE); + favoriteRecyclerView.setVisibility(View.VISIBLE); favoriteTipTextView.setVisibility(View.INVISIBLE); angeryBusImageView.setVisibility(View.INVISIBLE); } @@ -210,7 +241,7 @@ * redrwaing everything. */ // Show results - favoriteListView.setAdapter(new StopAdapter(getContext(), busStops)); + favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener)); } public void showBusStopUsernameInputDialog(final Stop busStop) { 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 @@ -89,7 +89,7 @@ */ public void createOrUpdateStopFragment(Palina p, boolean addToBackStack){ boolean sameFragment; - ArrivalsFragment arrivalsFragment; + ArrivalsFragment arrivalsFragment = null; if(managerWeakRef.get()==null || shouldHaltAllActivities) { //SOMETHING WENT VERY WRONG @@ -102,19 +102,22 @@ if(fm.findFragmentById(primaryFrameLayout) instanceof ArrivalsFragment) { arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); //Log.d(DEBUG_TAG, "Arrivals are for fragment with same stop?"); - assert arrivalsFragment != null; - sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); + if (arrivalsFragment == null) sameFragment = false; + else sameFragment = arrivalsFragment.isFragmentForTheSameStop(p); } else { sameFragment = false; Log.d(DEBUG_TAG, "We aren't showing an ArrivalsFragment"); } setLastSuccessfullySearchedBusStop(p); - + if (sameFragment){ + Log.d("BusTO", "Same bus stop, accessing existing fragment"); + arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); + if (arrivalsFragment == null) sameFragment = false; + } if(!sameFragment) { //set the String to be displayed on the fragment String displayName = p.getStopDisplayName(); - String displayStuff; if (displayName != null && displayName.length() > 0) { arrivalsFragment = ArrivalsFragment.newInstance(p.ID,displayName); @@ -123,9 +126,6 @@ } String probableTag = ResultListFragment.getFragmentTag(p); attachFragmentToContainer(fm,arrivalsFragment,new AttachParameters(probableTag, true, addToBackStack)); - } else { - Log.d("BusTO", "Same bus stop, accessing existing fragment"); - arrivalsFragment = (ArrivalsFragment) fm.findFragmentById(primaryFrameLayout); } // DO NOT CALL `setListAdapter` ever on arrivals fragment arrivalsFragment.updateFragmentData(p); @@ -197,11 +197,11 @@ this.shouldHaltAllActivities = shouldI; } - public void stopLastRequestIfNeeded(){ + public void stopLastRequestIfNeeded(boolean interruptIfRunning){ if(lastTaskRef == null) return; AsyncDataDownload task = lastTaskRef.get(); if(task!=null){ - task.cancel(false); + task.cancel(interruptIfRunning); } } 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 @@ -101,7 +101,8 @@ 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(); + Log.e("BusTO-RefreshStop", "Asking for refresh when there is no fragment"); + // AsyncDataDownload(fragmentHelper, arrivalsFetchers,getContext()).execute(); } else{ String stopName = fragment.getStopID(); @@ -168,6 +169,10 @@ if(result==null || result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null ||result.get(Manifest.permission.ACCESS_FINE_LOCATION) ) return; + if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) == null || + result.get(Manifest.permission.ACCESS_FINE_LOCATION) == null) + return; + if(result.get(Manifest.permission.ACCESS_COARSE_LOCATION) && result.get(Manifest.permission.ACCESS_FINE_LOCATION)){ locationPermissionGranted = true; Log.w(DEBUG_TAG, "Starting position"); @@ -326,7 +331,7 @@ if(getContext()==null) return; //we are not attached //Fragment fr = getChildFragmentManager().findFragmentById(R.id.resultFrame); - fragmentHelper.stopLastRequestIfNeeded(); + fragmentHelper.stopLastRequestIfNeeded(true); toggleSpinner(false); } @@ -418,7 +423,7 @@ locationManager.removeLocationRequestFor(requester); super.onPause(); fragmentHelper.setBlockAllActivities(true); - fragmentHelper.stopLastRequestIfNeeded(); + fragmentHelper.stopLastRequestIfNeeded(true); } @@ -448,6 +453,7 @@ final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()}; if (searchMode == SEARCH_BY_ID) { String busStopID = busStopSearchByIDEditText.getText().toString(); + fragmentHelper.stopLastRequestIfNeeded(true); requestArrivalsForStopID(busStopID); } else { // searchMode == SEARCH_BY_NAME String query = busStopSearchByNameEditText.getText().toString(); @@ -458,8 +464,10 @@ } else if(query.length()< 3){ Toast.makeText(getContext(), R.string.query_too_short, Toast.LENGTH_SHORT).show(); } - else + else { + fragmentHelper.stopLastRequestIfNeeded(true); new AsyncDataDownload(fragmentHelper, stopsFinderByNames, getContext()).execute(query); + } } } } @@ -561,13 +569,19 @@ void showNearbyStopsFragment(){ 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, NearbyStopsFragment.FRAGMENT_TAG); - ft.commit(); + final Fragment existingFrag = fragMan.findFragmentById(R.id.resultFrame); + NearbyStopsFragment fragment; + if (!(existingFrag instanceof NearbyStopsFragment)){ + //there is no fragment showing + fragment = NearbyStopsFragment.newInstance(NearbyStopsFragment.TYPE_STOPS); + + FragmentTransaction ft = fragMan.beginTransaction(); + //if (oldFrag != null) + // ft.remove(oldFrag); + + ft.replace(R.id.resultFrame, fragment, NearbyStopsFragment.FRAGMENT_TAG); + ft.commit(); + } } diff --git a/src/it/reyboz/bustorino/fragments/ResultListFragment.java b/src/it/reyboz/bustorino/fragments/ResultListFragment.java --- a/src/it/reyboz/bustorino/fragments/ResultListFragment.java +++ b/src/it/reyboz/bustorino/fragments/ResultListFragment.java @@ -23,6 +23,8 @@ import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Parcelable; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -55,13 +57,11 @@ protected static final String MESSAGE_TEXT_VIEW = "message_text_view"; private FragmentKind adapterKind; - private boolean adapterSet = false; protected FragmentListenerMain mListener; protected TextView messageTextView; protected ListView resultsListView; - private FloatingActionButton fabutton; private ListAdapter mListAdapter = null; boolean listShown; private Parcelable mListInstanceState = null; @@ -211,7 +211,7 @@ switch (adapterKind) { case ARRIVALS: resultsListView.setOnScrollListener(new CommonScrollListener(mListener, true)); - fabutton.show(); + mListener.showFloatingActionButton(true); break; case STOPS: resultsListView.setOnScrollListener(new CommonScrollListener(mListener, false)); @@ -223,6 +223,7 @@ } + @Override public void onPause() { if (adapterKind.equals(FragmentKind.ARRIVALS)) { @@ -234,11 +235,10 @@ } @Override - public void onAttach(Context context) { + public void onAttach(@NonNull Context context) { super.onAttach(context); if (context instanceof FragmentListenerMain) { mListener = (FragmentListenerMain) context; - fabutton = (FloatingActionButton) getActivity().findViewById(R.id.floatingActionButton); } else { throw new RuntimeException(context.toString() + " must implement ResultFragmentListener"); @@ -249,9 +249,8 @@ @Override public void onDetach() { + mListener.showFloatingActionButton(false); mListener = null; - if (fabutton != null) - fabutton.show(); super.onDetach(); }