diff --git a/library/src/main/java/io/constructor/injection/module/NetworkModule.kt b/library/src/main/java/io/constructor/injection/module/NetworkModule.kt
index e205e9c2..bf0092f3 100755
--- a/library/src/main/java/io/constructor/injection/module/NetworkModule.kt
+++ b/library/src/main/java/io/constructor/injection/module/NetworkModule.kt
@@ -38,7 +38,7 @@ class NetworkModule(private val context: Context) {
val httpClientBuilder = OkHttpClient.Builder()
httpClientBuilder.addInterceptor(requestInterceptor)
if (BuildConfig.DEBUG) {
- // httpClientBuilder.addInterceptor(httpLoggingInterceptor)
+ httpClientBuilder.addInterceptor(httpLoggingInterceptor)
}
return httpClientBuilder.build()
@@ -47,7 +47,7 @@ class NetworkModule(private val context: Context) {
@Provides
@Singleton
internal fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
- HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
+ HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC)
@Provides
@Singleton
diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml
index d022dff3..bbf5c7fa 100755
--- a/library/src/main/res/values/colors.xml
+++ b/library/src/main/res/values/colors.xml
@@ -4,6 +4,7 @@
#D32F2F
#FF5722
#FFFFFF
+ #000000
#F1F1F1
#ADADAD
\ No newline at end of file
diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml
index 706eb027..314ab164 100755
--- a/library/src/main/res/values/strings.xml
+++ b/library/src/main/res/values/strings.xml
@@ -1,6 +1,4 @@
- Constructor.io
Search…
in %s
-
diff --git a/sample/build.gradle b/sample/build.gradle
index 4ed096b5..1d45d64a 100755
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -31,9 +31,12 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation "com.android.support:cardview-v7:28.0.0"
- implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
- implementation 'io.reactivex.rxjava2:rxjava:2.1.13'
+ implementation "com.android.support:design:28.0.0"
+ implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
+ implementation 'io.reactivex.rxjava2:rxjava:2.2.6'
implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
+ implementation 'com.github.bumptech.glide:glide:4.9.0'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
testImplementation 'junit:junit:4.12'
-}
+}
\ No newline at end of file
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index e3440e8f..b4971627 100755
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -3,27 +3,32 @@
package="io.constructor.sample">
-
-
-
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/SampleApp.kt b/sample/src/main/java/io/constructor/sample/BodegaApp.kt
similarity index 58%
rename from sample/src/main/java/io/constructor/sample/SampleApp.kt
rename to sample/src/main/java/io/constructor/sample/BodegaApp.kt
index 03a9ff29..80572f5b 100755
--- a/sample/src/main/java/io/constructor/sample/SampleApp.kt
+++ b/sample/src/main/java/io/constructor/sample/BodegaApp.kt
@@ -4,12 +4,11 @@ import android.app.Application
import io.constructor.core.ConstructorIo
import io.constructor.core.ConstructorIoConfig
-class SampleApp : Application() {
+class BodegaApp : Application() {
override fun onCreate() {
super.onCreate()
- ConstructorIo.init(this, ConstructorIoConfig("key_OucJxxrfiTVUQx0C",
- testCells = listOf("ab" to "cd", "11" to "22")))
+ ConstructorIo.init(this, ConstructorIoConfig("key_K2hlXt5aVSwoI1Uw"))
ConstructorIo.userId = "uid"
}
}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/MainActivity.kt b/sample/src/main/java/io/constructor/sample/MainActivity.kt
deleted file mode 100755
index 5eda6e4b..00000000
--- a/sample/src/main/java/io/constructor/sample/MainActivity.kt
+++ /dev/null
@@ -1,26 +0,0 @@
-package io.constructor.sample
-
-import android.content.Intent
-import android.os.Bundle
-import android.support.v7.app.AppCompatActivity
-import io.constructor.core.ConstructorIo
-import io.reactivex.schedulers.Schedulers
-import kotlinx.android.synthetic.main.activity_main.*
-import java.util.*
-
-class MainActivity : AppCompatActivity() {
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- ConstructorIo.appMovedToForeground()
- button.setOnClickListener { startActivity(Intent(this, SampleActivity::class.java)) }
- button2.setOnClickListener { startActivity(Intent(this, SampleActivityCustom::class.java)) }
- button3.setOnClickListener { ConstructorIo.trackConversion("testId", "id", 11.0) }
- button4.setOnClickListener { ConstructorIo.trackSearchResultClick("testTerm", "testId", "1") }
- button5.setOnClickListener { ConstructorIo.trackSearchResultsLoaded("testTerm", Random().nextInt(99) + 1) }
- button6.setOnClickListener { ConstructorIo.getSearchResults("corn").subscribeOn(Schedulers.io()).subscribe {
-
- } }
- }
-}
diff --git a/sample/src/main/java/io/constructor/sample/SampleActivity.kt b/sample/src/main/java/io/constructor/sample/SampleActivity.kt
deleted file mode 100755
index 1dd7540e..00000000
--- a/sample/src/main/java/io/constructor/sample/SampleActivity.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package io.constructor.sample
-
-import android.os.Bundle
-import android.support.v7.app.AppCompatActivity
-import android.util.Log
-import io.constructor.core.ConstructorIo
-
-import io.constructor.core.ConstructorListener
-import io.constructor.data.model.Group
-import io.constructor.data.model.Suggestion
-
-class SampleActivity : AppCompatActivity() {
-
- private val TAG = this.javaClass.name
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_sample)
-
- val fragment = supportFragmentManager.findFragmentById(R.id.fragment_suggestions) as SuggestionsFragment
- fragment.setConstructorListener(object : ConstructorListener {
- override fun onErrorGettingSuggestions(error: Throwable) {
- Log.d(TAG, "handle network error getting suggestion")
- }
-
- override fun onSuggestionsRetrieved(suggestions: List) {
- Log.d(TAG, "onSuggestionsRetrieved")
- }
-
- override fun onQuerySentToServer(query: String) {
- Log.d(TAG, "onQuerySentToServer")
- }
-
- override fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?) {
- Log.d(TAG, "onSuggestionSelected")
- }
- })
- }
-}
diff --git a/sample/src/main/java/io/constructor/sample/SampleActivityCustom.kt b/sample/src/main/java/io/constructor/sample/SampleActivityCustom.kt
deleted file mode 100755
index 50fb92ee..00000000
--- a/sample/src/main/java/io/constructor/sample/SampleActivityCustom.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package io.constructor.sample
-
-import android.os.Bundle
-import android.support.v7.app.AppCompatActivity
-import android.util.Log
-import io.constructor.core.ConstructorListener
-import io.constructor.data.model.Group
-import io.constructor.data.model.Suggestion
-import io.constructor.ui.base.BaseSuggestionFragment
-import io.constructor.ui.base.BaseSuggestionsAdapter
-import io.constructor.ui.suggestion.SuggestionsAdapter
-import kotlinx.android.synthetic.main.fragment_custom_suggestions.view.*
-
-class SampleActivityCustom : AppCompatActivity() {
-
- private val TAG = this.javaClass.name
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_sample_custom)
- if (savedInstanceState == null) {
- val fragment = CustomSearchFragment()
- fragment.setConstructorListener(object : ConstructorListener {
- override fun onErrorGettingSuggestions(error: Throwable) {
- Log.d(TAG, "onError")
- }
-
- override fun onSuggestionsRetrieved(suggestions: List) {
- Log.d(TAG, "onSuggestionsRetrieved")
- }
-
- override fun onQuerySentToServer(query: String) {
- Log.d(TAG, "onQuerySentToServer")
- }
-
- override fun onSuggestionSelected(term: String, group: Group?, autocompleteSection: String?) {
- Log.d(TAG, "onSuggestionSelected")
- }
- })
- fragment.retainInstance = true
- supportFragmentManager.beginTransaction().add(R.id.content, fragment, "").commit()
- }
- }
-
- class CustomSearchFragment : BaseSuggestionFragment() {
-
- override fun onActivityCreated(savedInstanceState: Bundle?) {
- super.onActivityCreated(savedInstanceState)
- view?.backButton?.setOnClickListener {
- view?.input?.text?.clear()
- clearSuggestions()
- }
- view?.searchButton?.setOnClickListener { triggerSearch() }
- }
-
- override fun getSuggestionAdapter(): BaseSuggestionsAdapter {
- return SuggestionsAdapter()
- }
-
- override fun getSuggestionsInputId(): Int {
- return R.id.input
- }
-
- override fun getSuggestionListId(): Int {
- return R.id.suggestions
- }
-
- override fun getProgressId(): Int {
- return 0
- }
-
- override fun layoutId(): Int {
- return R.layout.fragment_custom_suggestions
- }
-
- }
-}
diff --git a/sample/src/main/java/io/constructor/sample/common/BaseActivity.kt b/sample/src/main/java/io/constructor/sample/common/BaseActivity.kt
new file mode 100644
index 00000000..9a259c7b
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/common/BaseActivity.kt
@@ -0,0 +1,21 @@
+package io.constructor.sample.common
+
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+
+abstract class BaseActivity> : AppCompatActivity() {
+
+ protected lateinit var presenter: T
+
+ abstract fun initPresenter(): T
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ presenter = initPresenter()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ presenter.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/common/BasePresenter.kt b/sample/src/main/java/io/constructor/sample/common/BasePresenter.kt
new file mode 100644
index 00000000..b4ec3fe3
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/common/BasePresenter.kt
@@ -0,0 +1,14 @@
+package io.constructor.sample.common
+
+import io.reactivex.disposables.CompositeDisposable
+
+open class BasePresenter(protected var view: V) {
+
+ protected val compositeDisposable = CompositeDisposable()
+
+ fun onDestroy() {
+ if (!compositeDisposable.isDisposed) {
+ compositeDisposable.dispose()
+ }
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/common/BaseView.kt b/sample/src/main/java/io/constructor/sample/common/BaseView.kt
new file mode 100644
index 00000000..cbe3f05e
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/common/BaseView.kt
@@ -0,0 +1,3 @@
+package io.constructor.sample.common
+
+interface BaseView
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/data/CartDataStorage.kt b/sample/src/main/java/io/constructor/sample/data/CartDataStorage.kt
new file mode 100644
index 00000000..bfb6c5c9
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/data/CartDataStorage.kt
@@ -0,0 +1,67 @@
+package io.constructor.sample.data
+
+import android.content.Context
+import io.constructor.data.model.search.SearchResult
+import java.io.*
+
+class CartDataStorage(private val context: Context) {
+
+ private var cartFile: File = File(context.filesDir, "cart")
+
+ init {
+ if (!cartFile.exists()) {
+ cartFile.createNewFile()
+ }
+ }
+
+ fun addToCart(item: SearchResult, quantity: Int = 1) {
+ readAndWrite {
+ if (it.containsKey(item.result.id)) {
+ it[item.result.id] = it[item.result.id]!!.first to (it[item.result.id]!!.second + quantity)
+ } else {
+ it[item.result.id] = item to quantity
+ }
+ }
+ }
+
+ fun removeFromCart(item: SearchResult) {
+ readAndWrite {
+ if (it.containsKey(item.result.id)) {
+ if (it.get(item.result.id)!!.second == 1) {
+ it.remove(item.result.id)
+ } else {
+ it[item.result.id] = it[item.result.id]!!.first to (it[item.result.id]!!.second - 1)
+ }
+ }
+ }
+ }
+
+ fun removeAll() {
+ readAndWrite {
+ it.clear()
+ }
+ }
+
+ fun getCartContent(): LinkedHashMap> {
+ var searchResult: LinkedHashMap> = linkedMapOf()
+ readAndWrite {
+ searchResult = it
+ }
+ return searchResult
+ }
+
+ fun readAndWrite(action: (LinkedHashMap>) -> Unit) {
+ var cartItems: LinkedHashMap> = linkedMapOf()
+ try {
+ val input = ObjectInputStream(FileInputStream(cartFile))
+ cartItems = input.readObject() as LinkedHashMap>
+ input.close()
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ action.invoke(cartItems)
+ val output = ObjectOutputStream(FileOutputStream(cartFile))
+ output.writeObject(cartItems)
+ output.close()
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/di/DependencyProvider.kt b/sample/src/main/java/io/constructor/sample/di/DependencyProvider.kt
new file mode 100644
index 00000000..ace1e0f7
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/di/DependencyProvider.kt
@@ -0,0 +1,14 @@
+package io.constructor.sample.di
+
+import android.content.Context
+import io.constructor.sample.data.CartDataStorage
+
+class DependencyProvider {
+ companion object {
+
+ fun provideCartStorage(context: Context): CartDataStorage {
+ return CartDataStorage(context)
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/extensions/CompositeDisposableExtension.kt b/sample/src/main/java/io/constructor/sample/extensions/CompositeDisposableExtension.kt
new file mode 100644
index 00000000..9305905c
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/extensions/CompositeDisposableExtension.kt
@@ -0,0 +1,8 @@
+package io.constructor.sample.extensions
+
+import io.reactivex.disposables.CompositeDisposable
+import io.reactivex.disposables.Disposable
+
+operator fun CompositeDisposable.plusAssign(disposable: Disposable) {
+ this.add(disposable)
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/extensions/ResultDataExt.kt b/sample/src/main/java/io/constructor/sample/extensions/ResultDataExt.kt
new file mode 100644
index 00000000..d215d25d
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/extensions/ResultDataExt.kt
@@ -0,0 +1,23 @@
+package io.constructor.sample.extensions
+
+import io.constructor.data.model.search.ResultData
+
+fun ResultData.price(): Double? {
+ val price = metadata["price"]
+ if (price is String) {
+ return (metadata["price"] as String).trim('$').toDouble()
+ } else if (price is Double) {
+ return price
+ }
+ return null
+}
+
+fun ResultData.priceFormatted(): String? {
+ val price = metadata["price"]
+ if (price is String) {
+ return price
+ } else if (price is Double) {
+ return price.toString()
+ }
+ return null
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/extensions/RxExtensions.kt b/sample/src/main/java/io/constructor/sample/extensions/RxExtensions.kt
new file mode 100644
index 00000000..d4d02a62
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/extensions/RxExtensions.kt
@@ -0,0 +1,9 @@
+package io.constructor.sample.extensions
+
+import io.reactivex.Observable
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+
+internal fun Observable.io2ui(): Observable {
+ return this.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/cart/CartActivity.kt b/sample/src/main/java/io/constructor/sample/feature/cart/CartActivity.kt
new file mode 100755
index 00000000..f57f144e
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/cart/CartActivity.kt
@@ -0,0 +1,60 @@
+package io.constructor.sample.feature.cart
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.widget.LinearLayoutManager
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.R
+import io.constructor.sample.common.BaseActivity
+import io.constructor.sample.di.DependencyProvider
+import io.constructor.sample.feature.checkout.CheckoutActivity
+import io.constructor.sample.feature.checkout.CheckoutView
+import io.constructor.sample.feature.productdetail.ProductDetailActivity
+import kotlinx.android.synthetic.main.activity_cart.*
+import kotlinx.android.synthetic.main.activity_home.toolbar
+
+class CartActivity : BaseActivity(), CheckoutView {
+
+ private lateinit var adapter: CartContentAdapter
+
+ override fun initPresenter(): CartPresenter {
+ return CartPresenter(this, DependencyProvider.provideCartStorage(this))
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_cart)
+ toolbar.title = getString(R.string.cart_content)
+ toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
+ toolbar.setNavigationOnClickListener { onBackPressed() }
+ toolbar.inflateMenu(R.menu.cart)
+ toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.removeAll -> {
+ presenter.removeCartContents()
+ true
+ }
+ else -> false
+
+ }
+ }
+ presenter.loadCartContents()
+ checkout.setOnClickListener {
+ startActivity(Intent(this, CheckoutActivity::class.java))
+ }
+ }
+
+ override fun renderContent(content: LinkedHashMap>) {
+ cartContent.layoutManager = LinearLayoutManager(this)
+ adapter = CartContentAdapter({
+ ProductDetailActivity.start(this, it.first)
+ }, {
+ presenter.removeFromCart(it)
+ }) {
+ presenter.addToCart(it)
+ }
+ adapter.setData(content)
+ cartContent.adapter = adapter
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/cart/CartContentAdapter.kt b/sample/src/main/java/io/constructor/sample/feature/cart/CartContentAdapter.kt
new file mode 100644
index 00000000..7b21bd1a
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/cart/CartContentAdapter.kt
@@ -0,0 +1,61 @@
+package io.constructor.sample.feature.cart
+
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.R
+import io.constructor.sample.extensions.price
+import kotlinx.android.synthetic.main.item_cart_content.view.*
+
+class CartContentAdapter(var clickListener: (Pair) -> Unit, val decrementAction: (SearchResult) -> Unit, val incrementAction: (SearchResult) -> Unit) : RecyclerView.Adapter() {
+
+ private val data = mutableListOf>()
+
+ fun setData(data: LinkedHashMap>) {
+ this.data.clear()
+ this.data.addAll(data.map { it.value })
+ notifyDataSetChanged()
+ }
+
+ override fun onCreateViewHolder(p0: ViewGroup, p1: Int): RecyclerView.ViewHolder {
+ return CartContentViewHolder(LayoutInflater.from(p0.context).inflate(R.layout.item_cart_content, p0, false))
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int) {
+ val item = data[p1]
+ with(p0 as CartContentViewHolder) {
+ Glide.with(image).load(item.first.result.imageUrl).diskCacheStrategy(DiskCacheStrategy.ALL).into(image)
+ title.text = item.first.value
+ price.text = price.context.getString(R.string.price, (item.first.result.price() ?: 0.0) * item.second)
+ increment.setOnClickListener {
+ incrementAction.invoke(item.first)
+ }
+ decrement.setOnClickListener {
+ decrementAction.invoke(item.first)
+ }
+ itemCount.text = item.second.toString()
+ rootView.setOnClickListener {
+ clickListener.invoke(item)
+ }
+ }
+ }
+
+ internal class CartContentViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val rootView = itemView.rootView
+ val image = itemView.image
+ val title = itemView.title
+ val price = itemView.price
+ val itemCount = itemView.itemCount
+ val increment = itemView.increment
+ val decrement = itemView.decrement
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/cart/CartPresenter.kt b/sample/src/main/java/io/constructor/sample/feature/cart/CartPresenter.kt
new file mode 100644
index 00000000..e01b7e1c
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/cart/CartPresenter.kt
@@ -0,0 +1,30 @@
+package io.constructor.sample.feature.cart
+
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.common.BasePresenter
+import io.constructor.sample.data.CartDataStorage
+import io.constructor.sample.feature.checkout.CheckoutView
+
+class CartPresenter(view: CheckoutView, private val cartStorage: CartDataStorage) : BasePresenter(view) {
+
+ fun loadCartContents() {
+ val content = cartStorage.getCartContent()
+ view.renderContent(content)
+ }
+
+ fun removeFromCart(item: SearchResult) {
+ cartStorage.removeFromCart(item)
+ loadCartContents()
+ }
+
+ fun addToCart(it: SearchResult) {
+ cartStorage.addToCart(it)
+ loadCartContents()
+ }
+
+ fun removeCartContents() {
+ cartStorage.removeAll()
+ loadCartContents()
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/cart/CartView.kt b/sample/src/main/java/io/constructor/sample/feature/cart/CartView.kt
new file mode 100644
index 00000000..b7c5b8d1
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/cart/CartView.kt
@@ -0,0 +1,8 @@
+package io.constructor.sample.feature.cart
+
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.common.BaseView
+
+interface CartView : BaseView {
+ fun renderContent(content: LinkedHashMap>)
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutActivity.kt b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutActivity.kt
new file mode 100755
index 00000000..06a4aa36
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutActivity.kt
@@ -0,0 +1,28 @@
+package io.constructor.sample.feature.checkout
+
+import android.os.Bundle
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.R
+import io.constructor.sample.common.BaseActivity
+import io.constructor.sample.di.DependencyProvider
+import kotlinx.android.synthetic.main.activity_home.*
+
+class CheckoutActivity : BaseActivity(), CheckoutView {
+
+ override fun initPresenter(): CheckoutPresenter {
+ return CheckoutPresenter(this, DependencyProvider.provideCartStorage(this))
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_checkout)
+ toolbar.title = getString(R.string.checkout)
+ toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
+ toolbar.setNavigationOnClickListener { onBackPressed() }
+ presenter.checkout()
+ }
+
+ override fun renderContent(content: LinkedHashMap>) {
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutPresenter.kt b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutPresenter.kt
new file mode 100644
index 00000000..f84628f6
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutPresenter.kt
@@ -0,0 +1,18 @@
+package io.constructor.sample.feature.checkout
+
+import io.constructor.core.ConstructorIo
+import io.constructor.sample.common.BasePresenter
+import io.constructor.sample.data.CartDataStorage
+import io.constructor.sample.extensions.price
+import kotlin.random.Random
+
+class CheckoutPresenter(view: CheckoutView, private val cartStorage: CartDataStorage) : BasePresenter(view) {
+
+ fun checkout() {
+ val total = cartStorage.getCartContent().map { it.value }.sumByDouble {
+ ((it.first.result.price() ?: 0.0) * it.second)
+ }
+ ConstructorIo.trackPurchase(arrayOf("item-001", "item-002", "item-003"), total, "ORD" + Random.nextInt(0, 10000))
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutView.kt b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutView.kt
new file mode 100644
index 00000000..aa619e66
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/checkout/CheckoutView.kt
@@ -0,0 +1,8 @@
+package io.constructor.sample.feature.checkout
+
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.common.BaseView
+
+interface CheckoutView : BaseView {
+ fun renderContent(content: LinkedHashMap>)
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/home/AutoSuggestAdapter.kt b/sample/src/main/java/io/constructor/sample/feature/home/AutoSuggestAdapter.kt
new file mode 100644
index 00000000..27d2f94f
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/home/AutoSuggestAdapter.kt
@@ -0,0 +1,28 @@
+package io.constructor.sample.feature.home
+
+import android.content.Context
+import android.widget.ArrayAdapter
+import io.constructor.data.model.Suggestion
+
+class AutoSuggestAdapter(context: Context, resource: Int) : ArrayAdapter(context, resource) {
+
+ private val data = mutableListOf()
+
+ fun setData(suggestions: List) {
+ data.clear()
+ data.addAll(suggestions)
+ notifyDataSetChanged()
+ }
+
+ override fun getItem(position: Int): String {
+ return data[position].value
+ }
+
+ fun getSuggestion(position: Int): Suggestion {
+ return data[position]
+ }
+
+ override fun getCount(): Int {
+ return data.size
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/home/HomeActivity.kt b/sample/src/main/java/io/constructor/sample/feature/home/HomeActivity.kt
new file mode 100755
index 00000000..1c555b9a
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/home/HomeActivity.kt
@@ -0,0 +1,86 @@
+package io.constructor.sample.feature.home
+
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import io.constructor.data.model.Suggestion
+import io.constructor.sample.R
+import io.constructor.sample.common.BaseActivity
+import io.constructor.sample.feature.cart.CartActivity
+import io.constructor.sample.feature.searchresult.SearchResultActivity
+import kotlinx.android.synthetic.main.activity_home.*
+
+class HomeActivity : BaseActivity(), HomeView {
+
+ override fun renderAutcompleteData(it: List) {
+ autocompleteAdapter.setData(it)
+ autocompleteAdapter.notifyDataSetChanged()
+ }
+
+ override fun initPresenter(): HomePresenter {
+ return HomePresenter(this)
+ }
+
+ private lateinit var autocompleteAdapter: AutoSuggestAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_home)
+ setupToolbar()
+ setupSearch()
+ presenter.onCreate()
+ }
+
+ private fun setupToolbar() {
+ toolbar.inflateMenu(R.menu.home)
+ toolbar.setTitle(R.string.app_name)
+ toolbar.navigationIcon = null
+ toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.search -> {
+ if (searchInput.visibility == View.GONE) {
+ searchInput.visibility = View.VISIBLE
+ searchInput.requestFocus()
+ } else {
+ if (searchInput.text.toString().isNotEmpty()) {
+ SearchResultActivity.start(this, searchInput.text.toString())
+ }
+ }
+
+ true
+ }
+ R.id.cart -> {
+ startActivity(Intent(this, CartActivity::class.java))
+ true
+ }
+ else -> {
+ false
+ }
+ }
+ }
+ }
+
+ private fun setupSearch() {
+ autocompleteAdapter = AutoSuggestAdapter(this, android.R.layout.simple_dropdown_item_1line)
+ searchInput.setAdapter(autocompleteAdapter)
+ searchInput.setOnItemClickListener { parent, view, position, id ->
+ val item = autocompleteAdapter.getSuggestion(position)
+ SearchResultActivity.start(this, item.value)
+ }
+ searchInput.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ presenter.onQuery(s.toString())
+ }
+
+ })
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/home/HomePresenter.kt b/sample/src/main/java/io/constructor/sample/feature/home/HomePresenter.kt
new file mode 100644
index 00000000..5bb366de
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/home/HomePresenter.kt
@@ -0,0 +1,29 @@
+package io.constructor.sample.feature.home
+
+import io.constructor.core.ConstructorIo
+import io.constructor.sample.common.BasePresenter
+import io.constructor.sample.extensions.io2ui
+import io.constructor.sample.extensions.plusAssign
+import io.reactivex.subjects.PublishSubject
+import java.util.concurrent.TimeUnit
+
+class HomePresenter(view: HomeView) : BasePresenter(view) {
+
+ private val queryString = PublishSubject.create()
+
+ fun onCreate() {
+ compositeDisposable += queryString.debounce(300, TimeUnit.MILLISECONDS).flatMap {
+ ConstructorIo.getAutocompleteResults(it)
+ }.io2ui().subscribe {
+ it.onValue {
+ it?.let {
+ view.renderAutcompleteData(it)
+ }
+ }
+ }
+ }
+
+ fun onQuery(s: String) {
+ queryString.onNext(s)
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/home/HomeView.kt b/sample/src/main/java/io/constructor/sample/feature/home/HomeView.kt
new file mode 100644
index 00000000..119d8e61
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/home/HomeView.kt
@@ -0,0 +1,8 @@
+package io.constructor.sample.feature.home
+
+import io.constructor.data.model.Suggestion
+import io.constructor.sample.common.BaseView
+
+interface HomeView : BaseView {
+ fun renderAutcompleteData(it: List)
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailActivity.kt b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailActivity.kt
new file mode 100755
index 00000000..82848fe7
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailActivity.kt
@@ -0,0 +1,60 @@
+package io.constructor.sample.feature.productdetail
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.design.widget.Snackbar
+import com.bumptech.glide.Glide
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.R
+import io.constructor.sample.common.BaseActivity
+import io.constructor.sample.di.DependencyProvider
+import io.constructor.sample.extensions.priceFormatted
+import kotlinx.android.synthetic.main.activity_product_detail.*
+
+class ProductDetailActivity : BaseActivity(), ProductDetailView {
+
+ private val item by lazy {
+ intent.getSerializableExtra(EXTRA_ITEM) as SearchResult
+ }
+
+ override fun initPresenter(): ProductDetailPresenter {
+ return ProductDetailPresenter(this, DependencyProvider.provideCartStorage(applicationContext))
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_product_detail)
+ toolbar.inflateMenu(R.menu.product_detail)
+ toolbar.title = item.value
+ toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white_24dp)
+ toolbar.setNavigationOnClickListener { onBackPressed() }
+ toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.addToCart -> {
+ presenter.addToCart(item)
+ Snackbar.make(rootContent, getString(R.string.item_added_to_cart), Snackbar.LENGTH_SHORT).show()
+ true
+ }
+ else -> {
+ false
+ }
+ }
+ }
+ Glide.with(image).load(item.result.imageUrl).into(image)
+ price.text = item.result.priceFormatted()
+ descriptionTV.text = item.result.description
+ }
+
+ companion object {
+
+ const val EXTRA_ITEM = "item"
+
+ fun start(context: Context, item: SearchResult) {
+ context.startActivity(Intent(context, ProductDetailActivity::class.java).apply {
+ putExtra(EXTRA_ITEM, item)
+ })
+ }
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailPresenter.kt b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailPresenter.kt
new file mode 100644
index 00000000..898cb154
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailPresenter.kt
@@ -0,0 +1,16 @@
+package io.constructor.sample.feature.productdetail
+
+import io.constructor.core.ConstructorIo
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.common.BasePresenter
+import io.constructor.sample.data.CartDataStorage
+import io.constructor.sample.extensions.price
+
+class ProductDetailPresenter(view: ProductDetailView, private val cartDataStorage: CartDataStorage) : BasePresenter(view) {
+
+ fun addToCart(item: SearchResult) {
+ ConstructorIo.trackConversion(item.value, item.result.id, item.result.price())
+ cartDataStorage.addToCart(item)
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailView.kt b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailView.kt
new file mode 100644
index 00000000..2003d555
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/productdetail/ProductDetailView.kt
@@ -0,0 +1,6 @@
+package io.constructor.sample.feature.productdetail
+
+import io.constructor.sample.common.BaseView
+
+interface ProductDetailView : BaseView {
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultActivity.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultActivity.kt
new file mode 100755
index 00000000..b6d64aa5
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultActivity.kt
@@ -0,0 +1,114 @@
+package io.constructor.sample.feature.searchresult
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.support.v7.widget.GridLayoutManager
+import android.support.v7.widget.RecyclerView
+import io.constructor.data.model.search.SearchResult
+import io.constructor.data.model.search.SearchData
+import io.constructor.sample.R
+import io.constructor.sample.common.BaseActivity
+import io.constructor.sample.feature.productdetail.ProductDetailActivity
+import io.constructor.sample.feature.searchresult.filterdialog.FilterDialog
+import io.constructor.sample.feature.searchresult.sortdialog.SortDialog
+import kotlinx.android.synthetic.main.activity_home.*
+
+class SearchResultActivity : BaseActivity(), SearchView {
+
+ private lateinit var adapter: SearchResultAdapter
+
+ private val query by lazy {
+ intent.getStringExtra(EXTRA_SEARCH_QUERY)
+ }
+
+ override fun renderData(it: SearchData, totalCount: Int) {
+ adapter.setData(it)
+ toolbar.title = "$query, found: $totalCount"
+ }
+
+ override fun clearData() {
+ adapter.clearData()
+ }
+
+ override fun initPresenter(): SearchResultPresenter {
+ return SearchResultPresenter(this)
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_search_result)
+ adapter = SearchResultAdapter {
+ presenter.handleClick(it, query)
+ }
+ searchResult.adapter = adapter
+ searchResult.layoutManager = GridLayoutManager(this, 2)
+ addIniniteScrollToRecycler()
+ setupToolbar()
+ presenter.onCreate(query)
+ }
+
+ private fun setupToolbar() {
+ toolbar.inflateMenu(R.menu.search_result)
+ toolbar.title = query
+ toolbar.navigationIcon = null
+ toolbar.setOnMenuItemClickListener {
+ when (it.itemId) {
+ R.id.sort -> {
+ presenter.availableFacets?.let {
+ val dialog = FilterDialog.newInstance(ArrayList(it), presenter.selectedFacets)
+ dialog.dismissListener = {
+ presenter.facetsSelected(it)
+ }
+ dialog.show(supportFragmentManager, DIALOG_TAG)
+ }
+ true
+ }
+ R.id.filter -> {
+ presenter.availableSortOptions?.let {
+ val dialog = SortDialog.newInstance(ArrayList(it), presenter.selectedSortOption)
+ dialog.dismissListener = {
+ presenter.sortOptionSelected(it)
+ }
+ dialog.show(supportFragmentManager, DIALOG_TAG)
+ }
+ true
+ }
+ else -> {
+ false
+ }
+ }
+ }
+ }
+
+ private fun addIniniteScrollToRecycler() {
+ searchResult.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+ super.onScrollStateChanged(recyclerView, newState)
+ if (!recyclerView.canScrollVertically(1)) {
+ presenter.loadNextDataBatch(false)
+ }
+ }
+
+ })
+ }
+
+ override fun navigateToDetails(it: SearchResult) {
+ ProductDetailActivity.start(this, it)
+ }
+
+ companion object {
+
+ const val DIALOG_TAG = "dialog"
+
+ const val EXTRA_SEARCH_QUERY = "query"
+
+ fun start(context: Context, query: String) {
+ context.startActivity(Intent(context, SearchResultActivity::class.java).apply {
+ putExtra(EXTRA_SEARCH_QUERY, query)
+ })
+ }
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultAdapter.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultAdapter.kt
new file mode 100644
index 00000000..743ba244
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultAdapter.kt
@@ -0,0 +1,61 @@
+package io.constructor.sample.feature.searchresult
+
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.bumptech.glide.Glide
+import io.constructor.data.model.search.SearchData
+import io.constructor.data.model.search.SearchResult
+import io.constructor.sample.R
+import io.constructor.sample.extensions.priceFormatted
+import kotlinx.android.synthetic.main.item_search_result.view.*
+
+class SearchResultAdapter(var clickListener: (SearchResult) -> Unit) : RecyclerView.Adapter() {
+
+ private val data = mutableListOf()
+
+ fun setData(it: SearchData) {
+ it.searchResults?.let {
+ data.addAll(it)
+ notifyDataSetChanged()
+ }
+ }
+
+ override fun onCreateViewHolder(p0: ViewGroup, p1: Int): RecyclerView.ViewHolder {
+ return SearchItemViewHolder(LayoutInflater.from(p0.context).inflate(R.layout.item_search_result, p0, false))
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int) {
+ val item = data[p1]
+ with(p0 as SearchItemViewHolder) {
+ Glide.with(image).load(item.result.imageUrl).into(image)
+ title.text = item.value
+ description.text = item.result.description
+ val priceFormatted = item.result.priceFormatted()
+ price.text = priceFormatted
+ rootView.setOnClickListener {
+ clickListener.invoke(item)
+ }
+ }
+ }
+
+ fun clearData() {
+ data.clear()
+ notifyDataSetChanged()
+ }
+
+
+ internal class SearchItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val rootView = itemView.rootView
+ val image = itemView.image
+ val price = itemView.price
+ val title = itemView.title
+ val description = itemView.description
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultPresenter.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultPresenter.kt
new file mode 100644
index 00000000..e33df084
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchResultPresenter.kt
@@ -0,0 +1,75 @@
+package io.constructor.sample.feature.searchresult
+
+import io.constructor.core.ConstructorIo
+import io.constructor.data.model.search.SearchResult
+import io.constructor.data.model.search.SearchFacet
+import io.constructor.data.model.search.SortOption
+import io.constructor.sample.common.BasePresenter
+import io.constructor.sample.extensions.io2ui
+import io.constructor.sample.extensions.plusAssign
+
+class SearchResultPresenter(view: SearchView) : BasePresenter(view) {
+
+ var availableFacets: List? = null
+ var availableSortOptions: List? = null
+ private var page = 1
+ private val limit = 10
+ private lateinit var query: String
+ var selectedFacets: HashMap>? = null
+ var selectedSortOption: SortOption? = null
+
+ private var totalCount: Int? = null
+
+ fun onCreate(query: String) {
+ this.query = query
+ loadNextDataBatch()
+ }
+
+ fun loadNextDataBatch(firstRun: Boolean = true) {
+ if (firstRun) {
+ loadInternal(firstRun)
+ } else {
+ totalCount?.let {
+ if ((page - 1) * limit <= it) {
+ loadInternal(firstRun)
+ }
+ }
+ }
+ }
+
+ private fun loadInternal(firstRun: Boolean) {
+ compositeDisposable += ConstructorIo.getSearchResults(query, selectedFacets?.map { it.key to it.value }, page = page, perPage = limit, sortBy = selectedSortOption?.sortBy, sortOrder = selectedSortOption?.sortOrder).io2ui().subscribe {
+ it.onValue {
+ if (firstRun) {
+ availableFacets = it.searchData.facets
+ availableSortOptions = it.searchData.sortOptions
+ ConstructorIo.trackSearchResultsLoaded(query, it.searchData.resultCount)
+ }
+ it.searchData?.let {
+ page += 1
+ totalCount = it.resultCount
+ view.renderData(it, totalCount!!)
+ }
+ }
+ }
+ }
+
+ fun handleClick(it: SearchResult, query: String) {
+ ConstructorIo.trackSearchResultClick(it.value, it.result.id, query)
+ view.navigateToDetails(it)
+ }
+
+ fun facetsSelected(facets: HashMap>) {
+ page = 1
+ selectedFacets = facets
+ view.clearData()
+ loadNextDataBatch()
+ }
+
+ fun sortOptionSelected(option: SortOption?) {
+ page = 1
+ selectedSortOption = option
+ view.clearData()
+ loadNextDataBatch()
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchView.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchView.kt
new file mode 100644
index 00000000..a8c06c61
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/SearchView.kt
@@ -0,0 +1,11 @@
+package io.constructor.sample.feature.searchresult
+
+import io.constructor.data.model.search.SearchResult
+import io.constructor.data.model.search.SearchData
+import io.constructor.sample.common.BaseView
+
+interface SearchView : BaseView {
+ fun renderData(it: SearchData, totalCount: Int)
+ fun navigateToDetails(it: SearchResult)
+ fun clearData()
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterDialog.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterDialog.kt
new file mode 100644
index 00000000..0e50e64d
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterDialog.kt
@@ -0,0 +1,67 @@
+package io.constructor.sample.feature.searchresult.filterdialog
+
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v4.app.DialogFragment
+import android.support.v7.app.AlertDialog
+import android.support.v7.widget.LinearLayoutManager
+import android.view.LayoutInflater
+import android.view.View
+import io.constructor.data.model.search.SearchFacet
+import io.constructor.sample.R
+import kotlinx.android.synthetic.main.dialog_filter.view.*
+
+class FilterDialog : DialogFragment() {
+
+ private lateinit var adapter: FilterListAdapter
+
+ var dismissListener: ((HashMap>) -> Unit)? = null
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = LayoutInflater.from(activity).inflate(R.layout.dialog_filter, null)
+ val dialog = AlertDialog.Builder(context!!)
+ .setView(view)
+ .create()
+ setDialogInteractions(view)
+ return dialog
+ }
+
+
+ private fun setDialogInteractions(view: View) {
+ val list = arguments?.getSerializable(EXTRA_FACETS) as ArrayList
+ val selectedFacets = arguments?.getSerializable(EXTRA_SELECTED_FACETS) as? HashMap>
+ view.filterList.layoutManager = LinearLayoutManager(view.context)
+ adapter = FilterListAdapter().apply {
+ list?.let {
+ setData(it, selectedFacets)
+ }
+ }
+ view.title.text = "Filters"
+ view.cancel.setOnClickListener {
+ dismiss()
+ }
+ view.apply.setOnClickListener {
+ dismissListener?.invoke(adapter.getSelected())
+ dismiss()
+ }
+ view.filterList.adapter = adapter
+ }
+
+ companion object {
+
+ const val EXTRA_FACETS = "facets"
+ const val EXTRA_SELECTED_FACETS = "selected-facets"
+
+ fun newInstance(facets: ArrayList, selectedFacets: HashMap>? = null): FilterDialog {
+ return FilterDialog().apply {
+ arguments = Bundle().apply {
+ putSerializable(EXTRA_FACETS, facets)
+ selectedFacets?.let {
+ putSerializable(EXTRA_SELECTED_FACETS, selectedFacets)
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterListAdapter.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterListAdapter.kt
new file mode 100644
index 00000000..41d29f0a
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/filterdialog/FilterListAdapter.kt
@@ -0,0 +1,105 @@
+package io.constructor.sample.feature.searchresult.filterdialog
+
+import android.support.v7.widget.RecyclerView
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import io.constructor.data.model.search.FacetOption
+import io.constructor.data.model.search.SearchFacet
+import io.constructor.sample.R
+import kotlinx.android.synthetic.main.item_facet.view.*
+import kotlinx.android.synthetic.main.item_header.view.*
+
+class FilterListAdapter : RecyclerView.Adapter() {
+
+ private val TYPE_HEADER = 0
+ private val TYPE_ITEM = 1
+
+ private val data = mutableListOf>()
+
+ fun setData(facets: ArrayList, selected: HashMap>? = null) {
+ facets.forEach { facet ->
+ val selectedFacets = selected?.get(facet.displayName!!)
+ data.add(HeaderWrapper(facet))
+ facet.options?.forEach {
+ data.add(ItemWrapper(facet.displayName!! to it).apply {
+ this.checked = selectedFacets?.contains(it.value) == true
+ })
+ }
+ }
+ notifyDataSetChanged()
+ }
+
+ fun getSelected(): HashMap> {
+ val result = hashMapOf>()
+ data.forEach {
+ if (it is ItemWrapper && it.checked) {
+ if (result.containsKey(it.data.first)) {
+ result[it.data.first]?.add(it.data.second.value!!)
+ } else {
+ result[it.data.first] = mutableListOf()
+ result[it.data.first]?.add(it.data.second.value!!)
+ }
+ }
+ }
+ return result
+ }
+
+ override fun onCreateViewHolder(p0: ViewGroup, p1: Int): RecyclerView.ViewHolder {
+ when (p1) {
+ TYPE_HEADER -> {
+ return HeaderViewHolder(LayoutInflater.from(p0.context).inflate(R.layout.item_header, p0, false))
+ }
+ else -> {
+ return ItemViewHolder(LayoutInflater.from(p0.context).inflate(R.layout.item_facet, p0, false))
+ }
+ }
+
+ }
+
+ override fun getItemCount(): Int {
+ return data.size
+ }
+
+ override fun getItemViewType(position: Int): Int {
+ val item = data[position]
+ return when (item) {
+ is HeaderWrapper -> TYPE_HEADER
+ else -> TYPE_ITEM
+ }
+ }
+
+ override fun onBindViewHolder(p0: RecyclerView.ViewHolder, p1: Int) {
+ val item = data[p1]
+ when (p0) {
+ is HeaderViewHolder -> {
+ p0.title.text = (item as HeaderWrapper).data.displayName
+ }
+ is ItemViewHolder -> {
+ item as ItemWrapper
+ p0.checkbox.text = item.data.second.value
+ p0.checkbox.setOnCheckedChangeListener { buttonView, isChecked ->
+ item.checked = isChecked
+ }
+ p0.checkbox.isChecked = item.checked
+ }
+ }
+ }
+
+ internal class HeaderWrapper(data: SearchFacet) : DataWrapper(data)
+
+ internal class ItemWrapper(data: Pair) : DataWrapper>(data)
+
+ internal abstract class DataWrapper(var data: T) {
+ var checked = false
+ }
+
+ internal class HeaderViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val title = itemView.title
+ }
+
+ internal class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
+ val checkbox = itemView.checkBox
+ }
+
+}
\ No newline at end of file
diff --git a/sample/src/main/java/io/constructor/sample/feature/searchresult/sortdialog/SortDialog.kt b/sample/src/main/java/io/constructor/sample/feature/searchresult/sortdialog/SortDialog.kt
new file mode 100644
index 00000000..e0ecde68
--- /dev/null
+++ b/sample/src/main/java/io/constructor/sample/feature/searchresult/sortdialog/SortDialog.kt
@@ -0,0 +1,74 @@
+package io.constructor.sample.feature.searchresult.sortdialog
+
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v4.app.DialogFragment
+import android.support.v7.app.AlertDialog
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.RadioButton
+import io.constructor.data.model.search.SortOption
+import io.constructor.sample.R
+import kotlinx.android.synthetic.main.dialog_sort.view.*
+
+class SortDialog : DialogFragment() {
+
+ var dismissListener: ((SortOption?) -> Unit)? = null
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val view = LayoutInflater.from(activity).inflate(R.layout.dialog_sort, null)
+ val dialog = AlertDialog.Builder(context!!)
+ .setView(view)
+ .create()
+ setDialogInteractions(view)
+ return dialog
+ }
+
+
+ private fun setDialogInteractions(view: View) {
+ val list = arguments?.getSerializable(EXTRA_OPTIONS) as ArrayList
+ val selectedSortOption = arguments?.getSerializable(EXTRA_SELECTED_OPTION) as? SortOption
+ list.forEachIndexed { index, sortOption ->
+ val radioButton = LayoutInflater.from(view.context).inflate(R.layout.item_sort_option, view.filterRadioGroup, false) as RadioButton
+ radioButton.id = index + 1
+ radioButton.text = sortOption.displayName
+ if (sortOption.displayName == selectedSortOption?.displayName) {
+ radioButton.isChecked = true
+ }
+ view.filterRadioGroup.addView(radioButton)
+ }
+ view.title.text = "Sort options"
+ view.clear.setOnClickListener {
+ dismissListener?.invoke(null)
+ dismiss()
+ }
+ view.apply.setOnClickListener {
+ for (i in 0 until view.filterRadioGroup.childCount) {
+ val child = view.filterRadioGroup.getChildAt(i) as RadioButton
+ if (child.isChecked) {
+ dismissListener?.invoke(list[i])
+ break
+ }
+ }
+ dismiss()
+ }
+ }
+
+ companion object {
+
+ const val EXTRA_OPTIONS = "options"
+ const val EXTRA_SELECTED_OPTION = "selected-option"
+
+ fun newInstance(sortOptions: ArrayList, selectedSortOption: SortOption? = null): SortDialog {
+ return SortDialog().apply {
+ arguments = Bundle().apply {
+ putSerializable(EXTRA_OPTIONS, sortOptions)
+ selectedSortOption?.let {
+ putSerializable(EXTRA_SELECTED_OPTION, selectedSortOption)
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/sample/src/main/res/drawable/ic_add_shopping_cart_white_24dp.xml b/sample/src/main/res/drawable/ic_add_shopping_cart_white_24dp.xml
new file mode 100644
index 00000000..77a1224a
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_add_shopping_cart_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_arrow_back_white_24dp.xml b/sample/src/main/res/drawable/ic_arrow_back_white_24dp.xml
new file mode 100644
index 00000000..71d5bbd2
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_arrow_back_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_filter_list_white_24dp.xml b/sample/src/main/res/drawable/ic_filter_list_white_24dp.xml
new file mode 100644
index 00000000..5d4ec18e
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_filter_list_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_remove_shopping_cart_white_24dp.xml b/sample/src/main/res/drawable/ic_remove_shopping_cart_white_24dp.xml
new file mode 100644
index 00000000..fc36ddb3
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_remove_shopping_cart_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_search_white_24dp.xml b/sample/src/main/res/drawable/ic_search_white_24dp.xml
new file mode 100644
index 00000000..be5ad99c
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_search_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_shopping_cart_white_24dp.xml b/sample/src/main/res/drawable/ic_shopping_cart_white_24dp.xml
new file mode 100644
index 00000000..baac1d6c
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_shopping_cart_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/ic_sort_white_24dp.xml b/sample/src/main/res/drawable/ic_sort_white_24dp.xml
new file mode 100644
index 00000000..b3e29abc
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_sort_white_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/layout/activity_cart.xml b/sample/src/main/res/layout/activity_cart.xml
new file mode 100755
index 00000000..dfdd8fdb
--- /dev/null
+++ b/sample/src/main/res/layout/activity_cart.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_checkout.xml b/sample/src/main/res/layout/activity_checkout.xml
new file mode 100755
index 00000000..f1aed886
--- /dev/null
+++ b/sample/src/main/res/layout/activity_checkout.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_home.xml b/sample/src/main/res/layout/activity_home.xml
new file mode 100755
index 00000000..5d4fd55a
--- /dev/null
+++ b/sample/src/main/res/layout/activity_home.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
deleted file mode 100755
index 03103d6d..00000000
--- a/sample/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,68 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_product_detail.xml b/sample/src/main/res/layout/activity_product_detail.xml
new file mode 100755
index 00000000..75644c73
--- /dev/null
+++ b/sample/src/main/res/layout/activity_product_detail.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/activity_search_result.xml b/sample/src/main/res/layout/activity_search_result.xml
new file mode 100755
index 00000000..096c3cc2
--- /dev/null
+++ b/sample/src/main/res/layout/activity_search_result.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/dialog_filter.xml b/sample/src/main/res/layout/dialog_filter.xml
new file mode 100644
index 00000000..d2756c5d
--- /dev/null
+++ b/sample/src/main/res/layout/dialog_filter.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/dialog_sort.xml b/sample/src/main/res/layout/dialog_sort.xml
new file mode 100644
index 00000000..3ee60b3b
--- /dev/null
+++ b/sample/src/main/res/layout/dialog_sort.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/item_cart_content.xml b/sample/src/main/res/layout/item_cart_content.xml
new file mode 100644
index 00000000..061ca86c
--- /dev/null
+++ b/sample/src/main/res/layout/item_cart_content.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/item_facet.xml b/sample/src/main/res/layout/item_facet.xml
new file mode 100644
index 00000000..9caa0fd5
--- /dev/null
+++ b/sample/src/main/res/layout/item_facet.xml
@@ -0,0 +1,10 @@
+
+
+
+
diff --git a/sample/src/main/res/layout/item_header.xml b/sample/src/main/res/layout/item_header.xml
new file mode 100644
index 00000000..35ef5e8a
--- /dev/null
+++ b/sample/src/main/res/layout/item_header.xml
@@ -0,0 +1,12 @@
+
+
+
diff --git a/sample/src/main/res/layout/item_search_result.xml b/sample/src/main/res/layout/item_search_result.xml
new file mode 100644
index 00000000..033cdb1f
--- /dev/null
+++ b/sample/src/main/res/layout/item_search_result.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/layout/item_sort_option.xml b/sample/src/main/res/layout/item_sort_option.xml
new file mode 100644
index 00000000..68a04722
--- /dev/null
+++ b/sample/src/main/res/layout/item_sort_option.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/sample/src/main/res/menu/cart.xml b/sample/src/main/res/menu/cart.xml
new file mode 100644
index 00000000..983fe1cb
--- /dev/null
+++ b/sample/src/main/res/menu/cart.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/menu/home.xml b/sample/src/main/res/menu/home.xml
new file mode 100644
index 00000000..abecb016
--- /dev/null
+++ b/sample/src/main/res/menu/home.xml
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/menu/product_detail.xml b/sample/src/main/res/menu/product_detail.xml
new file mode 100644
index 00000000..0e02abb0
--- /dev/null
+++ b/sample/src/main/res/menu/product_detail.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/menu/search_result.xml b/sample/src/main/res/menu/search_result.xml
new file mode 100644
index 00000000..ef72a6ae
--- /dev/null
+++ b/sample/src/main/res/menu/search_result.xml
@@ -0,0 +1,16 @@
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
index d17b2cdd..da8d22bb 100755
--- a/sample/src/main/res/values/strings.xml
+++ b/sample/src/main/res/values/strings.xml
@@ -1,3 +1,13 @@
- sample
+ Finkelshteyn\'s Bodega
+ Finkelshteyn\'s Bodega - what to eat today. Use search bar at the top
+ Search
+ Item added to cart
+ Cart content
+ -
+ +
+ Quantity:
+ Checkout
+ $%.2f
+ Clear
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
index 96c0d5f1..4e9eec89 100755
--- a/sample/src/main/res/values/styles.xml
+++ b/sample/src/main/res/values/styles.xml
@@ -1,7 +1,7 @@
-