diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java --- a/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java +++ b/app/src/main/java/it/reyboz/bustorino/adapters/SquareStopAdapter.java @@ -31,7 +31,9 @@ import it.reyboz.bustorino.backend.Stop; import it.reyboz.bustorino.util.StopSorterByDistance; import it.reyboz.bustorino.fragments.FragmentListenerMain; +import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -40,12 +42,14 @@ //private List stops; private @Nullable GPSPoint userPosition; private FragmentListenerMain listener; - private List stops; + private boolean showLocation; + private ArrayList stops; - public SquareStopAdapter(@Nullable List stopList, FragmentListenerMain fragmentListener, @Nullable GPSPoint pos) { + public SquareStopAdapter(@NotNull ArrayList stopList, FragmentListenerMain fragmentListener, @Nullable GPSPoint pos, boolean showLocation) { listener = fragmentListener; userPosition = pos; stops = stopList; + this.showLocation = showLocation; } @@ -54,21 +58,24 @@ public SquareViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false); //sort the stops by distance - if(stops != null && stops.size() > 0) - Collections.sort(stops,new StopSorterByDistance(userPosition)); + if (userPosition!=null) { + if (stops != null && stops.size() > 0) + stops.sort(new StopSorterByDistance(userPosition)); + } return new SquareViewHolder(view); } @Override public void onBindViewHolder(SquareViewHolder holder, int position) { //DO THE ACTUAL WORK TO PUT THE DATA - if(stops==null || stops.size() == 0) return; //NO STOPS + if(stops==null || stops.isEmpty()) return; //NO STOPS final Stop stop = stops.get(position); final Context context = holder.itemView.getContext(); if(stop!=null){ - if(stop.getDistanceFromLocation(userPosition)!=Double.POSITIVE_INFINITY){ + if(userPosition!=null && stop.getDistanceFromLocation(userPosition)!=Double.POSITIVE_INFINITY){ Double distance = stop.getDistanceFromLocation(userPosition); holder.distancetextView.setText(distance.intValue()+" m"); + holder.distancetextView.setVisibility(View.VISIBLE); } else { holder.distancetextView.setVisibility(View.GONE); } @@ -80,10 +87,19 @@ if(whatStopsHere == null) { holder.routesView.setVisibility(View.GONE); } else { - holder.routesView.setText(context.getString(R.string.lines_fill, whatStopsHere)); + holder.routesView.setText(whatStopsHere); + //context.getString(R.string.lines_fill, whatStopsHere)); holder.routesView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern } holder.stopID =stop.ID; + if (showLocation && !( + stop.location==null || stop.location.isEmpty() || stop.location.contains("null")) + ){ + holder.locationTextView.setText(stop.location); + holder.locationTextView.setVisibility(View.VISIBLE); + } else { + holder.locationTextView.setVisibility(View.GONE); + } } else { Log.w("SquareStopAdapter","!! The selected stop is null !!"); } @@ -97,6 +113,7 @@ class SquareViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { TextView stopIDView; TextView stopNameView; + TextView locationTextView; TextView routesView; TextView distancetextView; String stopID; @@ -107,6 +124,7 @@ stopIDView = (TextView) holdView.findViewById(R.id.stop_numberText); stopNameView = (TextView) holdView.findViewById(R.id.stop_nameText); routesView = (TextView) holdView.findViewById(R.id.stop_linesText); + locationTextView = holdView.findViewById(R.id.stop_locationTextView); distancetextView = (TextView) holdView.findViewById(R.id.stop_distanceTextView); } diff --git a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java --- a/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java +++ b/app/src/main/java/it/reyboz/bustorino/data/NextGenDB.java @@ -337,6 +337,28 @@ }); } + @NonNull + public synchronized List searchStopsByCodeOrName(String query, boolean searchById){ + String escaped = query.replace("\\", "\\\\") + .replace("%", "\\%") + .replace("_", "\\_"); + String sql; + final String like = "%" + escaped + "%"; + if (searchById){ + sql = "SELECT * FROM "+StopsTable.TABLE_NAME+" WHERE "+StopsTable.COL_ID+" LIKE ? ESCAPE '\\'"; + } else{ + sql = "SELECT * FROM "+StopsTable.TABLE_NAME+" WHERE "+StopsTable.COL_NAME+" LIKE ? ESCAPE '\\'"; + } + final List out = new ArrayList<>(); + SQLiteDatabase db = getReadableDatabase(); + try (Cursor c= db.rawQuery(sql, new String[]{like} + ) ) { + out.addAll(getStopsFromCursorAllFields(c)); + } + + return out; + } + /** * Get the list of stop in the query, with all the possible fields {NextGenDB.QUERY_COLUMN_stops_all} diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/MainScreenFragment.java @@ -22,18 +22,22 @@ import android.content.pm.PackageManager; import android.os.Bundle; +import android.text.InputType; 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.appcompat.widget.SearchView; 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.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import android.util.Log; @@ -48,15 +52,18 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; import it.reyboz.bustorino.BuildConfig; import it.reyboz.bustorino.R; +import it.reyboz.bustorino.adapters.SquareStopAdapter; import it.reyboz.bustorino.backend.*; import it.reyboz.bustorino.util.Permissions; import it.reyboz.bustorino.viewmodels.IntroViewModel; +import it.reyboz.bustorino.viewmodels.SearchStopsDBViewModel; import org.jetbrains.annotations.NotNull; import static it.reyboz.bustorino.util.Permissions.LOCATION_PERMISSIONS; @@ -81,7 +88,7 @@ public final static String FRAGMENT_TAG = "MainScreenFragment"; private enum SearchMode {SEARCH_ID,SEARCH_NAME,INITIAL} - public enum InternalScreen { + public enum InitialScreen { HOME_BUTTONS(0), NEARBY_STOPS(1), ARRIVALS(2), @@ -89,21 +96,21 @@ NEARBY_ARRIVALS(4); public final int code; - InternalScreen(int code) { this.code = code; } + InitialScreen(int code) { this.code = code; } @Nullable - public static InternalScreen fromCode(int code) { - for (InternalScreen c : values()) if (c.code == code) return c; + public static InitialScreen fromCode(int code) { + for (InitialScreen c : values()) if (c.code == code) return c; return null; } @NonNull - public static InternalScreen fromFragmentKind(@NonNull FragmentKind kind){ + public static InitialScreen fromFragmentKind(@NonNull FragmentKind kind){ switch (kind){ - case HOME_BUTTONS -> { return InternalScreen.HOME_BUTTONS; } - case NEARBY_STOPS -> { return InternalScreen.NEARBY_STOPS; } - case FragmentKind.ARRIVALS -> { return InternalScreen.ARRIVALS; } - case FragmentKind.STOPS -> { return InternalScreen.STOP_SEARCH; } - case FragmentKind.NEARBY_ARRIVALS -> { return InternalScreen.NEARBY_ARRIVALS; } + case HOME_BUTTONS -> { return InitialScreen.HOME_BUTTONS; } + case NEARBY_STOPS -> { return InitialScreen.NEARBY_STOPS; } + case FragmentKind.ARRIVALS -> { return InitialScreen.ARRIVALS; } + case FragmentKind.STOPS -> { return InitialScreen.STOP_SEARCH; } + case FragmentKind.NEARBY_ARRIVALS -> { return InitialScreen.NEARBY_ARRIVALS; } default -> { throw new IllegalArgumentException("Unknown fragment kind"); } @@ -113,13 +120,14 @@ private FragmentHelper fragmentHelper; private SwipeRefreshLayout swipeRefreshLayout; - private EditText busStopSearchByIDEditText; - private EditText busStopSearchByNameEditText; + //private EditText busStopSearchByIDEditText; + //private EditText busStopSearchByNameEditText; + + private SearchView busStopSearchView; private ProgressBar progressBar; private FloatingActionButton floatingActionButton; - /// VIEW MODELS in BaseFragment - + private SearchStopsDBViewModel searchStopsViewModel; private boolean setupOnStart = true; private boolean suppressArrivalsReload = false; @@ -138,9 +146,12 @@ private String pendingStopID = null; private String pendingSearchQuery = null; - private InternalScreen internalScreen = InternalScreen.HOME_BUTTONS; + private InitialScreen initialScreen = InitialScreen.HOME_BUTTONS; private CoordinatorLayout coordLayout; + private RecyclerView suggestionsRecyclerView; + private SquareStopAdapter suggestionsAdapter = null; + //this is really a hackish thing, but it works private final LinkedBlockingQueue thingsToDoOnStart = new LinkedBlockingQueue<>(); @@ -203,14 +214,14 @@ } - public static MainScreenFragment newInstance(@NonNull InternalScreen kind, + public static MainScreenFragment newInstance(@NonNull InitialScreen kind, @Nullable String stopId, @Nullable String query) { MainScreenFragment f = new MainScreenFragment(); f.setArguments(makeArgs(kind, stopId, query)); return f; } - public static MainScreenFragment newInstance(@NonNull InternalScreen kind, @Nullable Bundle args){ + public static MainScreenFragment newInstance(@NonNull InitialScreen kind, @Nullable Bundle args){ MainScreenFragment f = new MainScreenFragment(); if (args != null) { f.setArguments(args); @@ -225,7 +236,7 @@ * @param query * @return */ - public static Bundle makeArgs(@NonNull InternalScreen kind, @Nullable String stopId, @Nullable String query) { + public static Bundle makeArgs(@NonNull InitialScreen kind, @Nullable String stopId, @Nullable String query) { Bundle b = new Bundle(); b.putInt(ARG_INITIAL_CONTENT, kind.code); if (stopId != null) b.putString(ARG_STOP_ID, stopId); @@ -233,16 +244,16 @@ return b; } public static Bundle makeArgsArrivals(@NonNull String stopID){ - return makeArgs(InternalScreen.ARRIVALS, stopID, null); + return makeArgs(InitialScreen.ARRIVALS, stopID, null); } public static Bundle makeArgsStops(@NonNull String query){ - return makeArgs(InternalScreen.STOP_SEARCH, query, null); + return makeArgs(InitialScreen.STOP_SEARCH, query, null); } public static Bundle makeArgsNearby(){ - return makeArgs(InternalScreen.NEARBY_STOPS, null, null); + return makeArgs(InitialScreen.NEARBY_STOPS, null, null); } public static Bundle makeArgsButtonsScreen(){ - return makeArgs(InternalScreen.HOME_BUTTONS, null, null); + return makeArgs(InitialScreen.HOME_BUTTONS, null, null); } @@ -254,9 +265,9 @@ Log.d(DEBUG_TAG, "ARGS ARE NOT NULL: "+ args); if (args.containsKey(ARG_INITIAL_CONTENT)) { - int code = args.getInt(ARG_INITIAL_CONTENT, InternalScreen.HOME_BUTTONS.code); - InternalScreen parsed = InternalScreen.fromCode(code); - internalScreen = (parsed != null) ? parsed : InternalScreen.HOME_BUTTONS; + int code = args.getInt(ARG_INITIAL_CONTENT, InitialScreen.HOME_BUTTONS.code); + InitialScreen parsed = InitialScreen.fromCode(code); + initialScreen = (parsed != null) ? parsed : InitialScreen.HOME_BUTTONS; } String stopId = args.getString(ARG_STOP_ID); if (stopId != null) @@ -266,7 +277,6 @@ } fragmentHelper = new FragmentHelper(this, getChildFragmentManager(), getContext(), R.id.resultFrame); - } @Override @@ -285,14 +295,30 @@ // Inflate the layout for this fragment View root = inflater.inflate(R.layout.fragment_main_screen, container, false); /// UI ELEMENTS // - busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText); - busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText); + //busStopSearchByIDEditText = root.findViewById(R.id.busStopSearchByIDEditText); + //busStopSearchByNameEditText = root.findViewById(R.id.busStopSearchByNameEditText); + busStopSearchView = root.findViewById(R.id.busStopSearchView); progressBar = root.findViewById(R.id.progressBar); swipeRefreshLayout = root.findViewById(R.id.listRefreshLayout); floatingActionButton = root.findViewById(R.id.floatingActionButton); - busStopSearchByIDEditText.setSelectAllOnFocus(true); - busStopSearchByIDEditText + suggestionsRecyclerView = root.findViewById(R.id.suggestionsRecyclerView); + suggestionsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(),LinearLayoutManager.VERTICAL, false)); + EditText searchEditText = busStopSearchView.findViewById(androidx.appcompat.R.id.search_src_text); + searchEditText.setSelectAllOnFocus(true); + searchEditText.setOnFocusChangeListener( (view,hasFocus) ->{ + if(hasFocus){ + showFloatingActionButton(true); + } + }); + + busStopSearchView.setOnFocusChangeListener( (view, hasFocus) ->{ + if(hasFocus){ + showFloatingActionButton(true); + } + }); + //busStopSearchView.setSelectAllOnFocus(true); + /*busStopSearchByIDEditText .setOnEditorActionListener((v, actionId, event) -> { // IME_ACTION_SEARCH alphabetical option if (actionId == EditorInfo.IME_ACTION_SEARCH) { @@ -301,7 +327,9 @@ } return false; }); - busStopSearchByNameEditText + + */ + searchEditText .setOnEditorActionListener((v, actionId, event) -> { // IME_ACTION_SEARCH alphabetical option if (actionId == EditorInfo.IME_ACTION_SEARCH) { @@ -311,6 +339,19 @@ return false; }); + busStopSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String q) { + return false; + } + + @Override + public boolean onQueryTextChange(String q) { + searchStopsViewModel.searchStops(q, searchMode != SearchMode.SEARCH_NAME); + return true; + } + }); + swipeRefreshLayout .setOnRefreshListener(this::refreshStop); swipeRefreshLayout.setColorSchemeResources(R.color.blue_500, R.color.orange_500); @@ -319,7 +360,7 @@ floatingActionButton.setImageResource(R.drawable.magnifying_glass_larger); floatingActionButton.setOnClickListener((this::onToggleKeyboardLayout)); - busStopSearchByIDEditText.setOnFocusChangeListener((v, hasFocus) -> { + /*busStopSearchByIDEditText.setOnFocusChangeListener((v, hasFocus) -> { //Log.d(DEBUG_TAG, "stop search by ID has focus: " + hasFocus); if(hasFocus) setSearchModeBusStopID(); @@ -331,6 +372,8 @@ setSearchModeBusStopName(); }); + */ + AppCompatImageButton qrButton = root.findViewById(R.id.QRButton); qrButton.setOnClickListener(this::onQRButtonClick); @@ -381,16 +424,19 @@ super.onViewCreated(view, savedInstanceState); Log.d(DEBUG_TAG, "onViewCreated, SwipeRefreshLayout visible: "+(swipeRefreshLayout.getVisibility()==View.VISIBLE)); Log.d(DEBUG_TAG, "Saved instance state is: "+savedInstanceState); - //Restore instance state - /*if (savedInstanceState!=null){ - Fragment fragment = getChildFragmentManager().getFragment(savedInstanceState, SAVED_FRAGMENT); - if (fragment!=null){ - getChildFragmentManager().beginTransaction().add(R.id.resultFrame, fragment).commit(); - setupOnStart = false; + searchStopsViewModel = new ViewModelProvider(requireActivity()).get(SearchStopsDBViewModel.class); + + searchStopsViewModel.getFilteredStopsSearch().observe(getViewLifecycleOwner(), MainScreenFragment.this::setSuggestionsStopRecyclerView); + + /*if(res){ + if(!searchStopsResultsFragmentShown()) + showSearchStopsResultsFragment(); + } else if (searchStopsResultsFragmentShown()) { + childFragMan.popBackStack(); //remove the fragment } - } - */ + */ + if (getChildFragmentManager().findFragmentById(R.id.resultFrame)!= null){ swipeRefreshLayout.setVisibility(View.VISIBLE); // The child FragmentManager has restored its content — don't dispatch again @@ -399,13 +445,13 @@ if (savedInstanceState != null) return; - showDifferentSubFragments(internalScreen); + showDifferentSubFragments(initialScreen); } /** * Installs the initial child fragment based on the arguments supplied as arguments */ - private void showDifferentSubFragments(@NonNull InternalScreen screen) { + private void showDifferentSubFragments(@NonNull InitialScreen screen) { boolean firstTime = !initialScreenShown; switch (screen) { case NEARBY_STOPS: @@ -451,6 +497,26 @@ } + public void setSuggestionsStopRecyclerView(List stops){ + + var res = !stops.isEmpty(); + Log.d(DEBUG_TAG, "received stops suggestions, are "+stops.size()); + if(res){ + if(suggestionsAdapter!=null){ + suggestionsAdapter.setStops(stops); + } else{ + suggestionsAdapter = new SquareStopAdapter(new ArrayList<>(stops), this, null, true); + suggestionsRecyclerView.setAdapter(suggestionsAdapter); + } + suggestionsRecyclerView.setVisibility(View.VISIBLE); + + } else{ + if(suggestionsAdapter !=null) suggestionsAdapter = null; + suggestionsRecyclerView.setVisibility(View.GONE); + + } + } + public void setSuppressArrivalsReload(boolean value){ suppressArrivalsReload = value; // we have to suppress the reloading of the (possible) ArrivalsFragment @@ -644,7 +710,8 @@ @Override public void onQrScanSuccess(@NotNull String busIDToSearch) { - busStopSearchByIDEditText.setText(busIDToSearch); + EditText searchEditText = busStopSearchView.findViewById(androidx.appcompat.R.id.search_src_text); + searchEditText.setText(busIDToSearch); requestArrivalsForStopID(busIDToSearch); } @@ -664,13 +731,13 @@ */ public void onSearchClick(View v) { //final StopsFinderByName[] stopsFinderByNames = new StopsFinderByName[]{new GTTStopsFetcher(), new FiveTStopsFetcher()}; - if (searchMode == SearchMode.SEARCH_ID) { - String busStopID = busStopSearchByIDEditText.getText().toString(); + if (searchMode == SearchMode.SEARCH_ID || searchMode == SearchMode.INITIAL) { + String busStopID = busStopSearchView.getQuery().toString(); fragmentHelper.stopLastRequestIfNeeded(); requestArrivalsForStopID(busStopID); } else if (searchMode == SearchMode.SEARCH_NAME) { // searchMode == SEARCH_BY_NAME - String query = busStopSearchByNameEditText.getText().toString(); + String query = busStopSearchView.getQuery().toString(); query = query.trim(); if(getContext()!=null) { if (query.length() < 1) { @@ -688,15 +755,15 @@ public void onToggleKeyboardLayout(View v) { switch (searchMode){ case SEARCH_ID: - setSearchModeBusStopName(); - if (busStopSearchByNameEditText.requestFocus()) { + case INITIAL: + setSearchMode(SearchMode.SEARCH_NAME); + if (busStopSearchView.requestFocus()) { showKeyboard(); } break; case SEARCH_NAME: - case INITIAL: - setSearchModeBusStopID(); - if (busStopSearchByIDEditText.requestFocus()) { + setSearchMode(SearchMode.SEARCH_ID); + if (busStopSearchView.requestFocus()) { showKeyboard(); } } @@ -712,7 +779,7 @@ public void showKeyboard() { if(getActivity() == null) return; InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); - View view; + /*View view; if(searchMode == SearchMode.SEARCH_ID) view= busStopSearchByIDEditText; else if(searchMode == SearchMode.SEARCH_NAME) @@ -721,10 +788,12 @@ Log.e(DEBUG_TAG, "Asking to show keyboard but SearchMode is "+searchMode+", ignoring"); return; } + */ - imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + imm.showSoftInput(busStopSearchView, InputMethodManager.SHOW_IMPLICIT); } + /* private void setSearchModeBusStopID() { searchMode = SearchMode.SEARCH_ID; busStopSearchByNameEditText.setVisibility(View.GONE); @@ -740,21 +809,41 @@ busStopSearchByNameEditText.setVisibility(View.VISIBLE); floatingActionButton.setImageResource(R.drawable.numeric); } + + */ protected boolean isNearbyFragmentShown(){ Fragment fragment = getChildFragmentManager().findFragmentByTag(NearbyStopsFragment.FRAGMENT_TAG); return (fragment!= null && fragment.isResumed()); } - /** + private void setSearchMode(SearchMode mode) { + busStopSearchView.setQuery("", false); + boolean byId = mode != SearchMode.SEARCH_NAME; + busStopSearchView.setInputType(byId + ? InputType.TYPE_CLASS_NUMBER + : InputType.TYPE_CLASS_TEXT); + floatingActionButton.setImageResource( byId ? R.drawable.numeric : R.drawable.alphabetical); + + busStopSearchView.setQueryHint(getString(byId + ? R.string.insert_bus_stop_number + : R.string.insert_bus_stop_name)); + searchMode =mode; + } + + /* * Having that cursor at the left of the edit text makes me cancer. * * @param busStopID bus stop ID */ + // + /* private void setBusStopSearchByIDEditText(String busStopID) { busStopSearchByIDEditText.setText(busStopID); busStopSearchByIDEditText.setSelection(busStopID.length()); } + */ + @Nullable @Override public View getBaseViewForSnackBar() { @@ -777,12 +866,15 @@ private void prepareGUIForArrivals() { swipeRefreshLayout.setEnabled(true); swipeRefreshLayout.setVisibility(View.VISIBLE); + setSuggestionsStopRecyclerView(List.of()); //actionHelpMenuItem.setVisible(true); } private void prepareGUIForBusStops() { swipeRefreshLayout.setEnabled(false); swipeRefreshLayout.setVisibility(View.VISIBLE); + setSuggestionsStopRecyclerView(List.of()); + //actionHelpMenuItem.setVisible(false); } @@ -832,6 +924,34 @@ } + boolean searchStopsResultsFragmentShown(){ + Fragment frag = childFragMan.findFragmentById(R.id.resultFrame); + return frag instanceof StopSearchResultFragment; + } + void showSearchStopsResultsFragment(){ + final String TAG = "stopSearchResult"; + Fragment frag = childFragMan.findFragmentById(R.id.resultFrame); + if (frag instanceof StopSearchResultFragment){ + return; //nothing to do + } + boolean exists = frag != null; + var trans = childFragMan.beginTransaction(); + + frag = childFragMan.findFragmentByTag(TAG); + if (!(frag instanceof StopSearchResultFragment)) { + frag = StopSearchResultFragment.newInstance(); + } + if(exists){ + //show the fragment + trans.replace(R.id.resultFrame,frag, TAG); + } else{ + //create new fragment + trans.add(R.id.resultFrame,frag, TAG); + } + trans.addToBackStack(null); + trans.commit(); + } + @Override public void openLineFromStop(String routeGtfsId, @Nullable String stopIDFrom) { //pass to activity diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/NearbyStopsFragment.kt @@ -732,7 +732,7 @@ Collections.sort(stops, StopSorterByDistance(location)) if (dataAdapter == null) { - dataAdapter = SquareStopAdapter(stops, mListener, lastPosition) + dataAdapter = SquareStopAdapter(ArrayList(stops), mListener, lastPosition, false) firstLocForStops = false } else { dataAdapter!!.setUserPosition(lastPosition) diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/StopSearchResultFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/StopSearchResultFragment.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/fragments/StopSearchResultFragment.kt @@ -0,0 +1,53 @@ +package it.reyboz.bustorino.fragments + +import android.os.Bundle +import android.view.View +import android.widget.ListView +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.ViewModelProvider +import it.reyboz.bustorino.adapters.StopAdapter +import it.reyboz.bustorino.viewmodels.SearchStopsDBViewModel +import kotlin.math.min + +class StopSearchResultFragment: ResultListFragment() { + + + private var stopAdapter: StopAdapter? = null + + private val searchViewModel: SearchStopsDBViewModel by activityViewModels() + + private lateinit var listView: ListView + + + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + + + //searchViewModel = ViewModelProvider(requireActivity()).get(SearchStopsDBViewModel::class.java) + searchViewModel.queryResultsLiveData.observe(viewLifecycleOwner){ stops -> + + val adapter = StopAdapter(requireContext(), stops.subList(0, min(15, stops.size)),) + //resetListAdapter(adapter) + resultsListView.adapter = adapter + resultsListView.visibility = View.VISIBLE + } + + } + + companion object { + @JvmStatic + fun newInstance(): StopSearchResultFragment { + val args = Bundle() + //TODO: search stops inside the DB + //args.putString(StopListFragment.WORDS_SEARCHED, searchQuery) + val fragment = StopSearchResultFragment() + args.putSerializable(LIST_TYPE, FragmentKind.STOPS) + fragment.setArguments(args) + return fragment + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/util/StopSorterSearch.kt b/app/src/main/java/it/reyboz/bustorino/util/StopSorterSearch.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/util/StopSorterSearch.kt @@ -0,0 +1,31 @@ +package it.reyboz.bustorino.util + +import it.reyboz.bustorino.backend.Stop + +class StopSorterSearch(val searchById: Boolean): Comparator { + override fun compare(s0: Stop?, s1: Stop?): Int { + if (s0 ==null) { + if (s1 != null) return 1 + else return 0 + } + else if(s1 == null){ + //s0 is not null + return -1 + } + if(s0.location==null){ + if(s1.location != null) return 1 + } else if(s1.location==null) return -1 + + if(searchById){ + val lenDiff = s0.ID.length - s1.ID.length + if(lenDiff!=0) return lenDiff + //second criterion + val diff = s0.ID.toInt() - s1.ID.toInt() + //if(diff!=0) return diff + return diff + } else{ + return -1*(s0.numRoutesStopping - s1.numRoutesStopping) + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/SearchStopsDBViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/SearchStopsDBViewModel.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/SearchStopsDBViewModel.kt @@ -0,0 +1,82 @@ +package it.reyboz.bustorino.viewmodels + +import android.app.Application +import android.util.Log +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.distinctUntilChanged +import androidx.lifecycle.liveData +import androidx.lifecycle.map +import androidx.lifecycle.switchMap +import androidx.lifecycle.viewModelScope +import it.reyboz.bustorino.backend.Stop +import it.reyboz.bustorino.data.NextGenDB +import it.reyboz.bustorino.util.StopSorterSearch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlin.math.min +import kotlin.time.Duration.Companion.milliseconds + +class SearchStopsDBViewModel(application: Application): AndroidViewModel(application) { + + private data class Request(val q: String, val byId: Boolean) + + private val nextGenDB = NextGenDB.getInstance(application) + + + private val queryLiveData = MutableLiveData(Request("", true)) + private val numberLimitStop = MutableLiveData(20) + val queryResultsLiveData = queryLiveData.distinctUntilChanged().switchMap { r-> + liveData(viewModelScope.coroutineContext + Dispatchers.IO, timeoutInMs = 0) { + if (r.q.isEmpty()) { + //showQueryResult.postValue(false) + emit(emptyList()) + } + else { + delay(250.milliseconds) + //showQueryResult.postValue(true) + val list = nextGenDB.searchStopsByCodeOrName(r.q, r.byId) + /*if (r.byId) + emit(list.sortedBy { it.ID }) + else + emit(list.sortedBy { it.numRoutesStopping }.reversed()) + + */ + emit(list.sortedWith(StopSorterSearch(r.byId))) + } + } + } + + val showQueryResult = queryResultsLiveData.map { it.isNotEmpty() } + + fun searchStops(query: String, byId: Boolean){ + Log.d(TAG, "setting query $query with id $byId") + this.queryLiveData.value = Request(query, byId) + } + fun setLimitSearchStops(num: Int){ + numberLimitStop.value = num + } + + + val filteredStopsSearch = MediatorLiveData>() + private fun setFilteredStops(stops:List, num: Int){ + filteredStopsSearch.postValue(stops.subList(0, min(num, stops.size))) + } + + init { + filteredStopsSearch.addSource(queryResultsLiveData) { + setFilteredStops(it, numberLimitStop.value!!) + } + filteredStopsSearch.addSource(numberLimitStop) { + queryResultsLiveData.value?.let{ stops -> + setFilteredStops(stops, it) + } + } + } + + companion object{ + private const val TAG = "BusTO-SearchStopsVM" + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_screen.xml b/app/src/main/res/layout/fragment_main_screen.xml --- a/app/src/main/res/layout/fragment_main_screen.xml +++ b/app/src/main/res/layout/fragment_main_screen.xml @@ -26,51 +26,27 @@ app:layout_constraintStart_toEndOf="@id/QRButton" /> - - - - - - + app:layout_constraintEnd_toStartOf="@id/searchButton" /> - - + + - + + + + + --> + \ No newline at end of file