Bimba.git

commit 20e217c955b231d402ddd0a32a14d38b34f728b9

Author: Adam Pioterek <adam.pioterek@protonmail.ch>

new search bar

%!v(PANIC=String method: strings: negative Repeat count)


diff --git a/app/build.gradle b/app/build.gradle
index 04479a25f93c37a117a1a81152f7fbeafe6eb9b5..ac5d2cf405e6b9b958c6d687dbc214b60c501e61 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -29,17 +29,21 @@         exclude group: 'com.android.support', module: 'support-annotations'
     })
     implementation 'androidx.appcompat:appcompat:1.0.2'
     implementation 'androidx.cardview:cardview:1.0.0'
-    implementation 'com.google.android.material:material:1.1.0-alpha01'
     implementation 'androidx.vectordrawable:vectordrawable:1.0.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'
+
+    implementation 'com.google.android.material:material:1.1.0-alpha01'
+
     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
-    implementation 'com.github.arimorty:floatingsearchview:2.1.1'
-    implementation 'com.google.code.gson:gson:2.8.2'
-    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
-    implementation 'io.requery:sqlite-android:3.22.0'
     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.27.0-eap13'
     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.27.0-eap13'
+
     testImplementation 'junit:junit:4.12'
+
+    implementation 'io.requery:sqlite-android:3.22.0'
+    implementation 'com.google.code.gson:gson:2.8.2'
+    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
+    implementation 'com.github.mancj:MaterialSearchBar:0.8.1'
 }
 repositories {
     maven { url "https://maven.google.com" }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
index 4ea2d18684dbc87c372054090c654a2fc0155d69..3ea37db9c54afed9b83800a02ac5a1306d965836 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/activities/DashActivity.kt
@@ -5,8 +5,9 @@ import android.app.Activity
 import android.content.*
 import android.os.*
 import android.preference.PreferenceManager.getDefaultSharedPreferences
+import android.text.Editable
 import androidx.appcompat.app.*
-import android.text.Html
+import android.text.TextWatcher
 import android.view.*
 import android.view.inputmethod.InputMethodManager
 import kotlin.collections.ArrayList
@@ -21,14 +22,14 @@ import ml.adamsprogs.bimba.models.suggestions.*
 import ml.adamsprogs.bimba.models.adapters.*
 import ml.adamsprogs.bimba.collections.*
 
-import com.arlib.floatingsearchview.FloatingSearchView
-import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
 import com.google.android.material.navigation.NavigationView
 import com.google.android.material.snackbar.Snackbar
+import com.mancj.materialsearchbar.MaterialSearchBar
+import com.mancj.materialsearchbar.MaterialSearchBar.BUTTON_BACK
+import com.mancj.materialsearchbar.MaterialSearchBar.BUTTON_NAVIGATION
 
-//todo<p:1> searchView integration
 class DashActivity : AppCompatActivity(), MessageReceiver.OnTimetableDownloadListener,
-        FavouritesAdapter.OnMenuItemClickListener, FavouritesAdapter.ViewHolder.OnClickListener, ProviderProxy.OnDeparturesReadyListener {
+        FavouritesAdapter.OnMenuItemClickListener, FavouritesAdapter.ViewHolder.OnClickListener, ProviderProxy.OnDeparturesReadyListener, SuggestionsAdapter.OnSuggestionClickListener {
 
     val context: Context = this
     private val receiver = MessageReceiver.getMessageReceiver()
@@ -37,13 +38,14 @@     private var suggestions: List? = null
     private lateinit var drawerLayout: androidx.drawerlayout.widget.DrawerLayout
     private lateinit var drawerView: NavigationView
     lateinit var favouritesList: androidx.recyclerview.widget.RecyclerView
-    lateinit var searchView: FloatingSearchView
+    lateinit var searchView: MaterialSearchBar
     private lateinit var favourites: FavouriteStorage
     private lateinit var adapter: FavouritesAdapter
     private val actionModeCallback = ActionModeCallback()
     private var actionMode: ActionMode? = null
     private var isWarned = false
     private lateinit var providerProxy: ProviderProxy
+    private lateinit var suggestionsAdapter: SuggestionsAdapter
 
     companion object {
         const val REQUEST_EDIT_FAVOURITE = 1
@@ -89,63 +91,76 @@
         showValidityInDrawer()
 
         searchView = search_view
+        searchView.setCardViewElevation(8)
+        suggestionsAdapter = SuggestionsAdapter(layoutInflater, this, this)
+        searchView.setCustomSuggestionAdapter(suggestionsAdapter)
 
-        searchView.setOnFocusChangeListener(object : FloatingSearchView.OnFocusChangeListener {
-            override fun onFocus() {
-                favouritesList.visibility = View.GONE
-                providerProxy.getSuggestions(searchView.query) {
-                    searchView.swapSuggestions(it)
+        searchView.addTextChangeListener(object : TextWatcher {
+            override fun afterTextChanged(s: Editable?) {
+                if (searchView.isSearchEnabled) {
+                    searchView.clearSuggestions()
+                    searchView.updateLastSuggestions(listOf<GtfsSuggestion>())
+                    providerProxy.getSuggestions(s.toString()) { suggestions ->
+                        suggestions.forEach {
+                            if (it.name.contains(s as CharSequence, true)) {
+                                suggestionsAdapter.addSuggestion(it)
+                            }
+                        }
+                        searchView.showSuggestionsList()
+                    }
                 }
             }
 
-            override fun onFocusCleared() {
-                favouritesList.visibility = View.VISIBLE
+            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
             }
+
+            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+            }
+
         })
 
-        searchView.setOnQueryChangeListener { oldQuery, newQuery ->
-            if (oldQuery != "" && newQuery == "")
-                searchView.clearSuggestions()
-            providerProxy.getSuggestions(newQuery) {
-                searchView.swapSuggestions(it)
+        searchView.setOnSearchActionListener(object : MaterialSearchBar.OnSearchActionListener {
+            override fun onButtonClicked(buttonCode: Int) {
+                when (buttonCode) {
+                    BUTTON_NAVIGATION -> {
+                        if (drawerLayout.isDrawerOpen(drawerView))
+                            drawerLayout.closeDrawer(drawerView)
+                        else
+                            drawerLayout.openDrawer(drawerView)
+                    }
+                    BUTTON_BACK -> {
+                        searchView.disableSearch()
+                    }
+                }
             }
-        }
 
-        searchView.setOnSearchListener(object : FloatingSearchView.OnSearchListener {
-            override fun onSuggestionClicked(searchSuggestion: SearchSuggestion) {
-                val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
-                var view = (context as DashActivity).currentFocus
-                if (view == null) {
-                    view = View(context)
-                }
-                imm.hideSoftInputFromWindow(view.windowToken, 0)
-                if (searchSuggestion is StopSuggestion) {
-                    val intent = Intent(context, StopSpecifyActivity::class.java)
-                    intent.putExtra(StopSpecifyActivity.EXTRA_STOP_NAME, searchSuggestion.name)
-                    startActivity(intent)
-                } else if (searchSuggestion is LineSuggestion) {
-                    val intent = Intent(context, LineSpecifyActivity::class.java)
-                    intent.putExtra(LineSpecifyActivity.EXTRA_LINE_ID, searchSuggestion.name)
-                    startActivity(intent)
-                }
+            override fun onSearchStateChanged(enabled: Boolean) {
             }
 
-            override fun onSearchAction(query: String) {
+            override fun onSearchConfirmed(text: CharSequence?) {
+                // todo re-search
+                println("OnSearchConfirmed")
             }
+
         })
+    }
 
-        searchView.setOnBindSuggestionCallback { _, iconView, textView, item, _ ->
-            val suggestion = item as GtfsSuggestion
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
-                textView.text = Html.fromHtml(item.body, Html.FROM_HTML_MODE_LEGACY)
-            } else {
-                @Suppress("DEPRECATION")
-                textView.text = Html.fromHtml(item.body)
-            }
-            iconView.setImageDrawable(getDrawable(suggestion.getIcon(), context))
+    override fun onSuggestionClickListener(suggestion: GtfsSuggestion) {
+        val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+        var view = (context as DashActivity).currentFocus
+        if (view == null) {
+            view = View(context)
         }
-
-        //searchView.attachNavigationDrawerToMenuButton(drawer_layout as androidx.drawerlayout.widget.DrawerLayout)
+        imm.hideSoftInputFromWindow(view.windowToken, 0)
+        if (suggestion is StopSuggestion) {
+            val intent = Intent(context, StopSpecifyActivity::class.java)
+            intent.putExtra(StopSpecifyActivity.EXTRA_STOP_NAME, suggestion.name)
+            startActivity(intent)
+        } else if (suggestion is LineSuggestion) {
+            val intent = Intent(context, LineSpecifyActivity::class.java)
+            intent.putExtra(LineSpecifyActivity.EXTRA_LINE_ID, suggestion.name)
+            startActivity(intent)
+        }
     }
 
     override fun onRestart() {
@@ -256,8 +271,10 @@         showError(drawer_layout, code, this)
     }
 
     private fun getSuggestions() {
-        providerProxy.getSuggestions {
-            searchView.swapSuggestions(it)
+        providerProxy.getSuggestions { suggestions ->
+            suggestions.forEach {
+                suggestionsAdapter.addSuggestion(it)
+            }
         }
     }
 
@@ -274,12 +291,14 @@         if (getDefaultSharedPreferences(this).getBoolean(getString(R.string.key_timetable_automatic_update), false) or force)
             startService(Intent(context, TimetableDownloader::class.java))
     }
 
-    override fun onBackPressed() { //fixme
+    override fun onBackPressed() {
         if (drawerLayout.isDrawerOpen(drawerView)) {
             drawerLayout.closeDrawer(drawerView)
             return
         }
-        if (!searchView.setSearchFocused(false)) {
+        if (searchView.isSearchEnabled) {
+            searchView.disableSearch()
+        } else {
             super.onBackPressed()
         }
     }




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/adapters/SuggestionsAdapter.kt b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/SuggestionsAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..4769220894b85cb69b1596ece78cf47291d1e34b
--- /dev/null
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/adapters/SuggestionsAdapter.kt
@@ -0,0 +1,70 @@
+package ml.adamsprogs.bimba.models.adapters
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.os.Build
+import android.text.Html
+import android.view.*
+import android.widget.*
+import androidx.core.content.ContextCompat.getColor
+import ml.adamsprogs.bimba.R
+import ml.adamsprogs.bimba.getDrawable
+import com.mancj.materialsearchbar.adapter.SuggestionsAdapter as SearchBarSuggestionsAdapter
+import ml.adamsprogs.bimba.models.suggestions.GtfsSuggestion
+import ml.adamsprogs.bimba.models.suggestions.LineSuggestion
+import ml.adamsprogs.bimba.models.suggestions.StopSuggestion
+
+class SuggestionsAdapter(inflater: LayoutInflater, private val onSuggestionClickListener: OnSuggestionClickListener, private val context: Context) :
+        SearchBarSuggestionsAdapter<GtfsSuggestion, SuggestionsAdapter.ViewHolder>(inflater) {
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+        val rowView = layoutInflater.inflate(R.layout.row_suggestion, parent, false)
+        return ViewHolder(rowView)
+    }
+
+    override fun getSingleViewHeight(): Int = 48
+
+    override fun onBindSuggestionHolder(suggestion: GtfsSuggestion, holder: ViewHolder?, pos: Int) {
+        holder!!.root.setOnClickListener {
+            onSuggestionClickListener.onSuggestionClickListener(suggestion)
+        }
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            holder.text.text = Html.fromHtml(suggestion.getBody(), Html.FROM_HTML_MODE_LEGACY)
+        } else {
+            @Suppress("DEPRECATION")
+            holder.text.text = Html.fromHtml(suggestion.getBody())
+        }
+
+        holder.text.setTextColor(getColor(context, R.color.textDark))
+
+        val icon = getDrawable(suggestion.getIcon(), context)
+        icon.mutate()
+        icon.colorFilter = null
+        if (suggestion is StopSuggestion)
+            when (suggestion.zone) {
+                "A" -> icon.setColorFilter(getColor(context, R.color.zoneA), PorterDuff.Mode.SRC_IN)
+                "B" -> icon.setColorFilter(getColor(context, R.color.zoneB), PorterDuff.Mode.SRC_IN)
+                "C" -> icon.setColorFilter(getColor(context, R.color.zoneC), PorterDuff.Mode.SRC_IN)
+                else -> icon.setColorFilter(getColor(context, R.color.textDark), PorterDuff.Mode.SRC_IN)
+            }
+        else if (suggestion is LineSuggestion) {
+            icon.setColorFilter(suggestion.getColour(), PorterDuff.Mode.SRC_IN)
+            holder.icon.setBackgroundColor(suggestion.getBgColour())
+        }
+        holder.icon.setImageDrawable(icon)
+
+    }
+
+    operator fun contains(suggestion: GtfsSuggestion): Boolean {
+        return suggestion in suggestions || suggestion in suggestions_clone
+    }
+
+    inner class ViewHolder(itemView: View) : androidx.recyclerview.widget.RecyclerView.ViewHolder(itemView) {
+        val root: View = itemView.findViewById(R.id.row_suggestion)
+        val icon: ImageView = itemView.findViewById(R.id.suggestion_row_image)
+        val text: TextView = itemView.findViewById(R.id.suggestion_row_text)
+    }
+
+    interface OnSuggestionClickListener {
+        fun onSuggestionClickListener(suggestion: GtfsSuggestion)
+    }
+}




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
index 1fab1c029c41a7e5dc26178ae5f5e86b33ab375a..da730c9e177b77109707e111e790a19a59f0f3ec 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/GtfsSuggestion.kt
@@ -1,11 +1,11 @@
 package ml.adamsprogs.bimba.models.suggestions
 
-import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
-
-abstract class GtfsSuggestion(val name: String) : SearchSuggestion, Comparable<GtfsSuggestion> {
+abstract class GtfsSuggestion(val name: String) : Comparable<GtfsSuggestion> {
     abstract fun getIcon(): Int
 
     abstract fun getColour(): Int
 
     abstract fun getBgColour(): Int
+
+    abstract fun getBody(): String
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
index 0b71ad0cce6bf32496a79b791082caa7a5ebf5c6..1014c31c25da4d8e854c5e035df24eba42fa9ea9 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/LineSuggestion.kt
@@ -1,24 +1,9 @@
 package ml.adamsprogs.bimba.models.suggestions
 
-import android.os.Parcel
-import android.os.Parcelable
 import ml.adamsprogs.bimba.R
 import ml.adamsprogs.bimba.models.gtfs.Route
 
 class LineSuggestion(name: String, private val route: Route) : GtfsSuggestion(name) {
-    constructor(parcel: Parcel) : this(
-            parcel.readString(),
-            parcel.readParcelable(Route::class.java.classLoader))
-
-    override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeString(name)
-        parcel.writeParcelable(route, flags)
-    }
-
-    override fun describeContents(): Int {
-        return 0
-    }
-
     override fun getIcon(): Int {
         return when (route.type) {
             Route.TYPE_BUS -> R.drawable.ic_bus
@@ -46,13 +31,18 @@         else
             name.compareTo(other.name)
     }
 
-    companion object CREATOR : Parcelable.Creator<LineSuggestion> {
-        override fun createFromParcel(parcel: Parcel): LineSuggestion {
-            return LineSuggestion(parcel)
-        }
+    override fun equals(other: Any?): Boolean {
+        if (other == null || other !is GtfsSuggestion)
+            return false
+        return if (other is LineSuggestion)
+            name.padStart(3, '0') == other.name.padStart(3, '0')
+        else
+            name == other.name
+    }
 
-        override fun newArray(size: Int): Array<LineSuggestion?> {
-            return arrayOfNulls(size)
-        }
+    override fun hashCode(): Int {
+        var result = route.hashCode()
+        result = 31 * result + name.hashCode()
+        return result
     }
 }
\ No newline at end of file




diff --git a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
index cd3ef7762cb9f0e16a905cba2df8193e9d8b4568..cd3c4c7c57cda7ec6e1b6ab88a3d1ad6463fce3b 100644
--- a/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
+++ b/app/src/main/java/ml/adamsprogs/bimba/models/suggestions/StopSuggestion.kt
@@ -1,22 +1,8 @@
 package ml.adamsprogs.bimba.models.suggestions
 
-import android.os.Parcel
-import android.os.Parcelable
 import ml.adamsprogs.bimba.R
 
-class StopSuggestion(name: String, private val zone: String, private val zoneColour: String) : GtfsSuggestion(name){
-    @Suppress("UNCHECKED_CAST")
-    constructor(parcel: Parcel) : this(parcel.readString(), parcel.readString(), parcel.readString())
-
-    override fun describeContents(): Int {
-        return Parcelable.CONTENTS_FILE_DESCRIPTOR
-    }
-
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeString(name)
-        dest?.writeString(zone)
-        dest?.writeString(zoneColour)
-    }
+class StopSuggestion(name: String, val zone: String, private val zoneColour: String) : GtfsSuggestion(name) {
 
     override fun getBody(): String {
         return name
@@ -27,7 +13,7 @@         return R.drawable.ic_stop
     }
 
     override fun getColour(): Int {
-        return zoneColour.filter { it in "0123456789abcdef" }.toInt(16)
+        return 0xffffff
     }
 
     override fun getBgColour(): Int {
@@ -38,13 +24,15 @@     override fun compareTo(other: GtfsSuggestion): Int {
         return name.compareTo(other.name)
     }
 
-    companion object CREATOR : Parcelable.Creator<StopSuggestion> {
-        override fun createFromParcel(parcel: Parcel): StopSuggestion {
-            return StopSuggestion(parcel)
-        }
+    override fun equals(other: Any?): Boolean {
+        if (other == null || other !is GtfsSuggestion)
+            return false
+        return name == other.name
+    }
 
-        override fun newArray(size: Int): Array<StopSuggestion?> {
-            return arrayOfNulls(size)
-        }
+    override fun hashCode(): Int {
+        var result = zone.hashCode()
+        result = 31 * result + name.hashCode()
+        return result
     }
 }
\ No newline at end of file




diff --git a/app/src/main/res/layout/activity_dash.xml b/app/src/main/res/layout/activity_dash.xml
index d7c30f0ce549eb0ba692f4b96e273fa86f617c2e..a85c89ba8d5384563c3efc2eb8235cf69d24032d 100644
--- a/app/src/main/res/layout/activity_dash.xml
+++ b/app/src/main/res/layout/activity_dash.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/drawer_layout"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
@@ -33,35 +34,37 @@                      android:id="@+id/favourites_list"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            android:layout_marginBottom="0dp"
-            android:layout_marginTop="80dp"
+            android:layout_marginTop="8dp"
             android:scrollbars="none"
+            app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toBottomOf="@id/search_view" />
+            app:layout_constraintTop_toBottomOf="@+id/search_view" />
 
-        <com.arlib.floatingsearchview.FloatingSearchView
+        <com.mancj.materialsearchbar.MaterialSearchBar
             android:id="@+id/search_view"
+            style="@style/SearchBarTheme"
             android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            app:floatingSearch_close_search_on_keyboard_dismiss="true"
-            app:floatingSearch_leftActionMode="showHamburger"
-            app:floatingSearch_searchBarMarginLeft="16dp"
-            app:floatingSearch_searchBarMarginRight="16dp"
-            app:floatingSearch_searchBarMarginTop="16dp"
-            app:floatingSearch_searchHint="@string/search_placeholder"
-            app:floatingSearch_showSearchKey="false"
-            app:floatingSearch_suggestionsListAnimDuration="250" />
-
+            android:layout_height="wrap_content"
+            android:layout_marginStart="8dp"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="8dp"
+            android:fadeScrollbars="false"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:mt_hint="@string/search_placeholder"
+            app:mt_maxSuggestionsCount="10"
+            app:mt_navIconEnabled="true"
+            app:mt_placeholder="@string/search_placeholder"
+            app:mt_roundedSearchBarEnabled="true"
+            app:mt_speechMode="false" />
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-    <!-- The navigation drawer -->
     <com.google.android.material.navigation.NavigationView
         android:id="@+id/drawer"
         android:layout_width="240dp"
         android:layout_height="match_parent"
         android:layout_gravity="start"
-        app:menu="@menu/menu_drawer">
-    </com.google.android.material.navigation.NavigationView>
+        app:menu="@menu/menu_drawer" />
 </androidx.drawerlayout.widget.DrawerLayout>
\ No newline at end of file




diff --git a/app/src/main/res/layout/row_suggestion.xml b/app/src/main/res/layout/row_suggestion.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7d5a28b73069aa5c15378d1f2094be901fd4ba94
--- /dev/null
+++ b/app/src/main/res/layout/row_suggestion.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/row_suggestion"
+    android:layout_width="match_parent"
+    android:layout_height="48dp"
+    android:orientation="vertical">
+
+
+    <ImageView
+        android:id="@+id/suggestion_row_image"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:layout_marginBottom="8dp"
+        android:contentDescription="@string/suggestion_row_image"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <TextView
+        android:id="@+id/suggestion_row_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginTop="8dp"
+        android:text=""
+        android:textAppearance="@style/TextAppearance.AppCompat.SearchResult.Title"
+        app:layout_constraintStart_toEndOf="@+id/suggestion_row_image"
+        app:layout_constraintTop_toTopOf="parent" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file




diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 513e4652283dcb5cd773feefafbc6ce810b49bcd..ebccb8676c3f063a9db025b7aa91f8440dee7bb5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -91,4 +91,5 @@     Sat
     <string name="Sun">Sun</string>
     <string name="summary_timetable_automatic_update">Automatically check for and download timetable updates</string>
     <string name="server_error">Server error</string>
+    <string name="suggestion_row_image" translatable="false">suggestion row image</string>
 </resources>




diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 6e8b3469a57e5c647a8154d464cee71d51fd504d..6d69eba5bfa47ba8533df940f075bbf037a47384 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -27,4 +27,25 @@