diff --git a/app/src/main/java/it/reyboz/bustorino/adapters/StringListAdapter.kt b/app/src/main/java/it/reyboz/bustorino/adapters/StringListAdapter.kt new file mode 100644 --- /dev/null +++ b/app/src/main/java/it/reyboz/bustorino/adapters/StringListAdapter.kt @@ -0,0 +1,32 @@ +package it.reyboz.bustorino.adapters + + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +class StringListAdapter(private val stringList: List) : + RecyclerView.Adapter() { + + // ViewHolder class to hold the TextView for each item + class StringViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val textView: TextView = itemView.findViewById(android.R.id.text1) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StringViewHolder { + // Inflate the layout for each item in the RecyclerView (simple_list_item_1) + val view = LayoutInflater.from(parent.context) + .inflate(android.R.layout.simple_list_item_1, parent, false) + return StringViewHolder(view) + } + + override fun onBindViewHolder(holder: StringViewHolder, position: Int) { + // Bind the string from stringList at this position to the TextView + holder.textView.text = stringList[position] + } + + override fun getItemCount(): Int = stringList.size + +} \ No newline at end of file diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt --- a/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt +++ b/app/src/main/java/it/reyboz/bustorino/fragments/LinesGridShowingFragment.kt @@ -3,19 +3,22 @@ import android.content.Context import android.os.Bundle import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.view.* import android.view.animation.Animation import android.view.animation.LinearInterpolator import android.view.animation.RotateAnimation import android.widget.ImageView import android.widget.TextView +import androidx.appcompat.widget.SearchView +import androidx.core.view.MenuHost +import androidx.core.view.MenuProvider import androidx.fragment.app.viewModels +import androidx.lifecycle.Lifecycle import androidx.recyclerview.widget.RecyclerView import it.reyboz.bustorino.R import it.reyboz.bustorino.adapters.RouteAdapter import it.reyboz.bustorino.adapters.RouteOnlyLineAdapter +import it.reyboz.bustorino.adapters.StringListAdapter import it.reyboz.bustorino.backend.utils import it.reyboz.bustorino.data.PreferencesHolder import it.reyboz.bustorino.data.gtfs.GtfsRoute @@ -40,6 +43,7 @@ private lateinit var urbanLinesTitle: TextView private lateinit var extrurbanLinesTitle: TextView private lateinit var touristLinesTitle: TextView + //private lateinit var searchBar: SearchView private var routesByAgency = HashMap>() @@ -61,6 +65,8 @@ } private val arrows = HashMap() private val durations = HashMap() + //private val recyclerViewAdapters= HashMap() + private val lastQueryEmptyForAgency = HashMap(3) private var openRecyclerView = "AG_URBAN" override fun onCreateView( @@ -78,7 +84,6 @@ urbanLinesTitle = rootView.findViewById(R.id.urbanLinesTitleView) extrurbanLinesTitle = rootView.findViewById(R.id.extraurbanLinesTitleView) touristLinesTitle = rootView.findViewById(R.id.touristLinesTitleView) - arrows[AG_URBAN] = rootView.findViewById(R.id.arrowUrb) arrows[AG_TOUR] = rootView.findViewById(R.id.arrowTourist) arrows[AG_EXTRAURB] = rootView.findViewById(R.id.arrowExtraurban) @@ -101,29 +106,42 @@ favoritesRecyclerView.layoutManager = gridLayoutManager - viewModel.routesLiveData.observe(viewLifecycleOwner){ + viewModel.getLinesLiveData().observe(viewLifecycleOwner){ //routesList = ArrayList(it) //routesList.sortWith(linesComparator) routesByAgency.clear() + for (k in AGENCIES){ + routesByAgency[k] = ArrayList() + } for(route in it){ val agency = route.agencyID - if(!routesByAgency.containsKey(agency)){ - routesByAgency[agency] = ArrayList() + if(agency !in routesByAgency.keys){ + Log.e(DEBUG_TAG, "The agency $agency is not present in the predefined agencies (${routesByAgency.keys})") } routesByAgency[agency]?.add(route) - } - //val adapter = RouteOnlyLineAdapter(routesByAgency.map { route-> route.shortName }) //zip agencies and recyclerviews Companion.AGENCIES.zip(recViews) { ag, recView -> routesByAgency[ag]?.let { routeList -> - routeList.sortWith(linesComparator) - //val adapter = RouteOnlyLineAdapter(it.map { rt -> rt.shortName }) - val adapter = RouteAdapter(routeList,routeClickListener) - recView.adapter = adapter + if (routeList.size > 0) { + routeList.sortWith(linesComparator) + //val adapter = RouteOnlyLineAdapter(it.map { rt -> rt.shortName }) + val adapter = RouteAdapter(routeList, routeClickListener) + val lastQueryEmpty = if(ag in lastQueryEmptyForAgency.keys) lastQueryEmptyForAgency[ag]!! else true + if (lastQueryEmpty) + recView.adapter = adapter + else recView.swapAdapter(adapter, false) + lastQueryEmptyForAgency[ag] = false + } else { + val messageString = if(viewModel.getLineQueryValue().isNotEmpty()) getString(R.string.no_lines_found_query) else getString(R.string.no_lines_found) + val extraAdapter = StringListAdapter(listOf(messageString)) + recView.adapter = extraAdapter + lastQueryEmptyForAgency[ag] = true + + } durations[ag] = if(routeList.size < 20) ViewUtils.DEF_DURATION else 1000 } } @@ -164,6 +182,54 @@ return rootView } + fun setUserSearch(textSearch:String){ + viewModel.setLineQuery(textSearch) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val menuHost: MenuHost = requireActivity() + + // Add menu items without using the Fragment Menu APIs + // Note how we can tie the MenuProvider to the viewLifecycleOwner + // and an optional Lifecycle.State (here, RESUMED) to indicate when + // the menu should be visible + menuHost.addMenuProvider(object : MenuProvider { + override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { + // Add menu items here + menuInflater.inflate(R.menu.menu_search, menu) + + val search = menu.findItem(R.id.searchMenuItem).actionView as SearchView + search.setOnQueryTextListener(object : SearchView.OnQueryTextListener{ + override fun onQueryTextSubmit(query: String?): Boolean { + setUserSearch(query ?: "") + return true + } + + override fun onQueryTextChange(query: String?): Boolean { + setUserSearch(query ?: "") + return true + } + + }) + + search.queryHint = getString(R.string.search_box_lines_suggestion_filter) + } + + override fun onMenuItemSelected(menuItem: MenuItem): Boolean { + // Handle the menu selection + if (menuItem.itemId == R.id.searchMenuItem){ + Log.d(DEBUG_TAG, "Clicked on search menu") + } + else{ + Log.d(DEBUG_TAG, "Clicked on something else") + } + return false + } + }, viewLifecycleOwner, Lifecycle.State.RESUMED) + } + + private fun closeOpenFavorites(){ if(favoritesRecyclerView.visibility == View.VISIBLE){ //close it diff --git a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java --- a/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java +++ b/app/src/main/java/it/reyboz/bustorino/fragments/ScreenBaseFragment.java @@ -3,6 +3,7 @@ import android.Manifest; import android.content.Context; import android.content.SharedPreferences; +import android.os.Bundle; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Toast; diff --git a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt --- a/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt +++ b/app/src/main/java/it/reyboz/bustorino/viewmodels/LinesGridShowingViewModel.kt @@ -1,7 +1,10 @@ package it.reyboz.bustorino.viewmodels import android.app.Application +import android.util.Log import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.LiveData +import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.map @@ -12,15 +15,14 @@ class LinesGridShowingViewModel(application: Application) : AndroidViewModel(application) { - private val gtfsRepo: GtfsRepository - - init { - val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao() - gtfsRepo = GtfsRepository(gtfsDao) - + private val linesNameSorter = LinesNameSorter() + private val linesComparator = Comparator { a,b -> + return@Comparator linesNameSorter.compare(a.shortName, b.shortName) } - val routesLiveData = gtfsRepo.getAllRoutes() + private val gtfsRepo: GtfsRepository + + private val routesLiveData: LiveData> //= gtfsRepo.getAllRoutes() val isUrbanExpanded = MutableLiveData(true) val isExtraUrbanExpanded = MutableLiveData(false) @@ -29,15 +31,45 @@ val favoritesLinesIDs = MutableLiveData>() - private val linesNameSorter = LinesNameSorter() - private val linesComparator = Comparator { a,b -> - return@Comparator linesNameSorter.compare(a.shortName, b.shortName) + private val queryLiveData = MutableLiveData("") + fun setLineQuery(query: String){ + if(query!=queryLiveData.value) + queryLiveData.value = query + } + fun getLineQueryValue():String{ + return queryLiveData.value ?: "" + } + private val filteredLinesLiveData = MediatorLiveData>() + fun getLinesLiveData(): LiveData> { + return filteredLinesLiveData + } + + private fun filterLinesForQuery(lines: List, query: String): List{ + val result= lines.filter { r-> query.lowercase() in r.shortName.lowercase() } + + return result + } + + init { + val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao() + gtfsRepo = GtfsRepository(gtfsDao) + routesLiveData = gtfsRepo.getAllRoutes() + + filteredLinesLiveData.addSource(routesLiveData){ + filteredLinesLiveData.value = filterLinesForQuery(it,queryLiveData.value ?: "" ) + } + filteredLinesLiveData.addSource(queryLiveData){ + routesLiveData.value?.let { routes -> + filteredLinesLiveData.value = filterLinesForQuery(routes, it) + } + } } fun setFavoritesLinesIDs(linesIds: HashSet){ favoritesLinesIDs.value = linesIds } + val favoritesLines = favoritesLinesIDs.map {lineIds -> val linesList = ArrayList() if (lineIds.size == 0 || routesLiveData.value==null) return@map linesList diff --git a/app/src/main/res/drawable/magnifying_glass.xml b/app/src/main/res/drawable/magnifying_glass.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/drawable/magnifying_glass.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_lines_grid.xml b/app/src/main/res/layout/fragment_lines_grid.xml --- a/app/src/main/res/layout/fragment_lines_grid.xml +++ b/app/src/main/res/layout/fragment_lines_grid.xml @@ -37,7 +37,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintLeft_toRightOf="@id/arrowFavorites" app:layout_constraintBottom_toTopOf="@id/favoritesRecyclerView" - android:layout_marginLeft="6dp" + android:layout_marginStart="6dp" app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_chainStyle="packed"/> + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_search.xml b/app/src/main/res/menu/menu_search.xml new file mode 100644 --- /dev/null +++ b/app/src/main/res/menu/menu_search.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -26,6 +26,9 @@ Linee extraurbane Linee turistiche Direzione: + Nessuna linea in questa categoria + Nessuna linea corrisponde alla ricerca + Filtra per nome Linea: %1$s diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,6 +38,9 @@ Urban lines Extra urban lines Tourist lines + No lines found in this category + No lines match the searched name + Destination: Lines: %1$s @@ -245,6 +248,7 @@ Buy us a coffee Map Search by stop + Filter by name Launching database update Downloading data from MaTO server