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 @@ + + + + + + + + + + + +