Skip to content
This repository was archived by the owner on May 24, 2022. It is now read-only.

Commit 8cc435d

Browse files
authored
feat: dynamic facets list showcase (#34)
1 parent ff0dfb5 commit 8cc435d

File tree

9 files changed

+194
-24
lines changed

9 files changed

+194
-24
lines changed

Diff for: guides/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ apply plugin: 'kotlinx-serialization'
55
apply plugin: 'kotlin-kapt'
66

77
android {
8-
compileSdkVersion 29
8+
compileSdkVersion 30
99
buildToolsVersion build_tools_version
1010
defaultConfig {
1111
applicationId "com.algolia.instantsearch.guides"
1212
minSdkVersion 23
13-
targetSdkVersion 29
13+
targetSdkVersion 30
1414
versionCode 1
1515
versionName "1.0"
1616
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

Diff for: showcase/build.gradle

+2-4
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,14 @@ android {
3737
}
3838

3939
dependencies {
40-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
41-
4240
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
4341
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
4442
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
45-
implementation "com.google.android.material:material:1.2.1"
43+
implementation "com.google.android.material:material:1.3.0"
4644
implementation "com.github.bumptech.glide:glide:4.10.0"
4745
implementation "com.algolia:instantsearch-android:$instantsearch"
4846
implementation "io.apptik.widget:multislider:1.3"
49-
implementation 'androidx.appcompat:appcompat:1.2.0'
47+
implementation 'androidx.appcompat:appcompat:1.3.0'
5048
debugImplementation "com.squareup.leakcanary:leakcanary-android:$canary"
5149

5250
testImplementation 'junit:junit:4.12'

Diff for: showcase/src/main/AndroidManifest.xml

+4
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@
114114
android:name=".filter.rating.RatingShowcase"
115115
android:parentActivityName=".directory.DirectoryShowcase" />
116116

117+
<activity
118+
android:name="com.algolia.instantsearch.showcase.filter.facet.dynamic.DynamicFacetShowcase"
119+
android:parentActivityName=".directory.DirectoryShowcase" />
120+
117121
<activity android:name=".customdata.TemplateActivity">
118122
<intent-filter>
119123
<action android:name="android.intent.action.VIEW" />

Diff for: showcase/src/main/kotlin/com/algolia/instantsearch/showcase/directory/Directory.kt

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.algolia.instantsearch.showcase.directory
33
import com.algolia.instantsearch.showcase.customdata.QueryRuleCustomDataShowcase
44
import com.algolia.instantsearch.showcase.filter.clear.FilterClearShowcase
55
import com.algolia.instantsearch.showcase.filter.current.ShowcaseFilterCurrent
6+
import com.algolia.instantsearch.showcase.filter.facet.dynamic.DynamicFacetShowcase
67
import com.algolia.instantsearch.showcase.filter.facet.FacetListPersistentShowcase
78
import com.algolia.instantsearch.showcase.filter.facet.FacetListSearchShowcase
89
import com.algolia.instantsearch.showcase.filter.facet.FacetListShowcase
@@ -58,4 +59,5 @@ val showcases = mapOf(
5859
ObjectID("personalisation_related_items") to RelatedItemsShowcase::class,
5960
ObjectID("query_rule_custom_data") to QueryRuleCustomDataShowcase::class,
6061
ObjectID("filter_rating") to RatingShowcase::class,
62+
ObjectID("dynamic_facets") to DynamicFacetShowcase::class,
6163
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package com.algolia.instantsearch.showcase.filter.facet.dynamic
2+
3+
import android.os.Bundle
4+
import androidx.appcompat.app.AppCompatActivity
5+
import com.algolia.instantsearch.core.connection.ConnectionHandler
6+
import com.algolia.instantsearch.core.selectable.list.SelectionMode
7+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetListAdapter
8+
import com.algolia.instantsearch.helper.android.searchbox.SearchBoxViewAppCompat
9+
import com.algolia.instantsearch.helper.filter.facet.dynamic.DynamicFacetListConnector
10+
import com.algolia.instantsearch.helper.filter.facet.dynamic.connectView
11+
import com.algolia.instantsearch.helper.filter.state.FilterOperator
12+
import com.algolia.instantsearch.helper.filter.state.FilterState
13+
import com.algolia.instantsearch.helper.searchbox.SearchBoxConnector
14+
import com.algolia.instantsearch.helper.searchbox.connectView
15+
import com.algolia.instantsearch.helper.searcher.SearcherSingleIndex
16+
import com.algolia.instantsearch.showcase.R
17+
import com.algolia.instantsearch.showcase.configureRecyclerView
18+
import com.algolia.instantsearch.showcase.configureSearchView
19+
import com.algolia.instantsearch.showcase.configureToolbar
20+
import com.algolia.search.client.ClientSearch
21+
import com.algolia.search.model.APIKey
22+
import com.algolia.search.model.ApplicationID
23+
import com.algolia.search.model.Attribute
24+
import com.algolia.search.model.IndexName
25+
import io.ktor.client.features.logging.*
26+
import kotlinx.android.synthetic.main.include_search.*
27+
import kotlinx.android.synthetic.main.showcase_facet_list_search.*
28+
29+
class DynamicFacetShowcase : AppCompatActivity() {
30+
31+
val client = ClientSearch(
32+
ApplicationID("RVURKQXRHU"),
33+
APIKey("937e4e6ec422ff69fe89b569dba30180"),
34+
LogLevel.ALL
35+
)
36+
val index = client.initIndex(IndexName("test_facet_ordering"))
37+
val searcher = SearcherSingleIndex(index)
38+
val filterState = FilterState()
39+
val searchBox = SearchBoxConnector(searcher)
40+
val color = Attribute("color")
41+
val country = Attribute("country")
42+
val brand = Attribute("brand")
43+
val size = Attribute("size")
44+
val dynamicFacets = DynamicFacetListConnector(
45+
searcher = searcher,
46+
filterState = filterState,
47+
selectionModeForAttribute = mapOf(
48+
color to SelectionMode.Multiple,
49+
country to SelectionMode.Multiple
50+
),
51+
filterGroupForAttribute = mapOf(
52+
brand to (brand to FilterOperator.Or),
53+
color to (color to FilterOperator.Or),
54+
)
55+
)
56+
private val connection = ConnectionHandler(searchBox, dynamicFacets)
57+
58+
override fun onCreate(savedInstanceState: Bundle?) {
59+
super.onCreate(savedInstanceState)
60+
setContentView(R.layout.showcase_dynamic_facet_list)
61+
62+
val searchBoxView = SearchBoxViewAppCompat(searchView)
63+
connection += searchBox.connectView(searchBoxView)
64+
65+
val factory = ViewHolderFactory()
66+
val adapter = DynamicFacetListAdapter(factory)
67+
connection += dynamicFacets.connectView(adapter)
68+
69+
configureToolbar(toolbar)
70+
configureSearchView(searchView, getString(R.string.search_brands))
71+
configureRecyclerView(hits, adapter)
72+
73+
searcher.query.facets = setOf(brand, color, size, country)
74+
searcher.searchAsync()
75+
}
76+
77+
override fun onDestroy() {
78+
super.onDestroy()
79+
searcher.cancel()
80+
connection.clear()
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.algolia.instantsearch.showcase.filter.facet.dynamic
2+
3+
import com.algolia.search.model.Attribute
4+
import com.algolia.search.model.search.Facet
5+
6+
sealed class FacetItem {
7+
data class Header(val attribute: Attribute) : FacetItem()
8+
data class Value(val attribute: Attribute, val facet: Facet, val selected: Boolean) : FacetItem()
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.algolia.instantsearch.showcase.filter.facet.dynamic
2+
3+
import android.view.View
4+
import android.view.ViewGroup
5+
import android.widget.TextView
6+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetListHeaderViewHolder
7+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetListItemViewHolder
8+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetListViewHolder
9+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetListViewHolder.ViewType
10+
import com.algolia.instantsearch.helper.android.filter.facet.dynamic.DynamicFacetModel
11+
import com.algolia.instantsearch.helper.android.inflate
12+
import com.algolia.instantsearch.showcase.R
13+
import kotlinx.android.synthetic.main.list_item_selectable.view.*
14+
15+
class ViewHolderFactory : DynamicFacetListViewHolder.Factory {
16+
17+
override fun createViewHolder(
18+
parent: ViewGroup,
19+
viewType: ViewType
20+
): DynamicFacetListViewHolder<out DynamicFacetModel> {
21+
return when (viewType) {
22+
ViewType.Header -> HeaderViewHolder(parent.inflate<TextView>(R.layout.header_item))
23+
ViewType.Item -> ItemViewHolder(parent.inflate(R.layout.list_item_selectable))
24+
}
25+
}
26+
27+
class HeaderViewHolder(view: TextView) : DynamicFacetListHeaderViewHolder(view) {
28+
override fun bind(item: DynamicFacetModel.Header, onClick: View.OnClickListener?) {
29+
val textView = view as TextView
30+
textView.text = item.attribute.raw
31+
}
32+
}
33+
34+
class ItemViewHolder(view: View) : DynamicFacetListItemViewHolder(view) {
35+
override fun bind(item: DynamicFacetModel.Item, onClick: View.OnClickListener?) {
36+
view.selectableItemName.text = item.facet.value
37+
view.selectableItemSubtitle.text = "${item.facet.count}"
38+
view.selectableItemSubtitle.visibility = View.VISIBLE
39+
view.selectableItemIcon.visibility = if (item.selected) View.VISIBLE else View.GONE
40+
view.setOnClickListener(onClick)
41+
}
42+
}
43+
}

Diff for: showcase/src/main/res/layout/list_item_selectable.xml

+18-18
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,6 @@
1212
android:layout_width="match_parent"
1313
android:layout_height="match_parent">
1414

15-
<ImageView
16-
android:id="@+id/selectableItemIcon"
17-
style="@style/Icon"
18-
android:layout_width="wrap_content"
19-
android:layout_height="wrap_content"
20-
android:layout_marginEnd="12dp"
21-
android:src="@drawable/ic_check"
22-
android:tint="?attr/colorPrimary"
23-
android:visibility="invisible"
24-
app:layout_constrainedHeight="true"
25-
app:layout_constrainedWidth="true"
26-
app:layout_constraintBottom_toBottomOf="parent"
27-
app:layout_constraintEnd_toEndOf="parent"
28-
app:layout_constraintTop_toTopOf="parent"
29-
tools:visibility="visible" />
30-
3115
<TextView
3216
android:id="@+id/selectableItemSubtitle"
3317
android:layout_width="wrap_content"
@@ -42,11 +26,27 @@
4226
app:layout_constrainedHeight="true"
4327
app:layout_constrainedWidth="true"
4428
app:layout_constraintBottom_toBottomOf="parent"
45-
app:layout_constraintEnd_toStartOf="@id/selectableItemIcon"
29+
app:layout_constraintEnd_toEndOf="parent"
4630
app:layout_constraintTop_toTopOf="parent"
4731
tools:text="@tools:sample/lorem"
4832
tools:visibility="visible" />
4933

34+
<ImageView
35+
android:id="@+id/selectableItemIcon"
36+
style="@style/Icon"
37+
android:layout_width="wrap_content"
38+
android:layout_height="wrap_content"
39+
android:layout_marginEnd="12dp"
40+
android:src="@drawable/ic_check"
41+
android:visibility="invisible"
42+
app:layout_constrainedHeight="true"
43+
app:layout_constrainedWidth="true"
44+
app:layout_constraintBottom_toBottomOf="parent"
45+
app:layout_constraintEnd_toStartOf="@id/selectableItemSubtitle"
46+
app:layout_constraintTop_toTopOf="parent"
47+
tools:visibility="visible"
48+
app:tint="?attr/colorPrimary" />
49+
5050
<TextView
5151
android:id="@+id/selectableItemName"
5252
android:layout_width="0dp"
@@ -57,7 +57,7 @@
5757
android:maxLines="1"
5858
android:textAppearance="?attr/textAppearanceBody1"
5959
app:layout_constraintBottom_toBottomOf="parent"
60-
app:layout_constraintEnd_toStartOf="@id/selectableItemSubtitle"
60+
app:layout_constraintEnd_toStartOf="@id/selectableItemIcon"
6161
app:layout_constraintStart_toStartOf="parent"
6262
app:layout_constraintTop_toTopOf="parent"
6363
tools:text="@tools:sample/lorem/random" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
xmlns:tools="http://schemas.android.com/tools"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent">
7+
8+
<androidx.appcompat.widget.Toolbar
9+
android:id="@+id/toolbar"
10+
android:layout_width="0dp"
11+
android:layout_height="?attr/actionBarSize"
12+
app:layout_constraintEnd_toEndOf="parent"
13+
app:layout_constraintStart_toStartOf="parent"
14+
app:layout_constraintTop_toTopOf="parent">
15+
16+
<include layout="@layout/include_search" />
17+
18+
</androidx.appcompat.widget.Toolbar>
19+
20+
<androidx.recyclerview.widget.RecyclerView
21+
android:id="@+id/hits"
22+
android:layout_width="0dp"
23+
android:layout_height="0dp"
24+
android:layout_marginTop="16dp"
25+
app:layout_constraintBottom_toBottomOf="parent"
26+
app:layout_constraintEnd_toEndOf="parent"
27+
app:layout_constraintStart_toStartOf="parent"
28+
app:layout_constraintTop_toBottomOf="@+id/toolbar"
29+
tools:itemCount="100"
30+
tools:listitem="@layout/list_item_small" />
31+
32+
</androidx.constraintlayout.widget.ConstraintLayout>

0 commit comments

Comments
 (0)