Converted tab fragments to MvRx
diff --git a/LottieSample/build.gradle b/LottieSample/build.gradle
index 0a02220..a9c8c04 100644
--- a/LottieSample/build.gradle
+++ b/LottieSample/build.gradle
@@ -61,8 +61,9 @@
implementation 'android.arch.lifecycle:extensions:1.1.0'
kapt "android.arch.lifecycle:compiler:1.1.0"
implementation "com.android.support:customtabs:$supportLibVersion"
- implementation 'com.airbnb.android:epoxy:2.16.1'
- kapt 'com.airbnb.android:epoxy-processor:2.16.1'
+ implementation 'com.airbnb.android:epoxy:2.16.4'
+ kapt 'com.airbnb.android:epoxy-processor:2.16.4'
+ implementation 'com.airbnb.android:mvrx:0.5.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.0-beta5'
implementation 'androidx.core:core-ktx:0.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt
new file mode 100644
index 0000000..cd982d2
--- /dev/null
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/BaseEpoxyFragment.kt
@@ -0,0 +1,36 @@
+package com.airbnb.lottie.samples
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import com.airbnb.epoxy.AsyncEpoxyController
+import com.airbnb.epoxy.EpoxyController
+import com.airbnb.mvrx.BaseMvRxFragment
+import kotlinx.android.synthetic.main.fragment_base.*
+import kotlinx.android.synthetic.main.fragment_base.view.*
+
+
+private class BaseEpoxyController(
+ private val buildModelsCallback: EpoxyController.() -> Unit
+) : AsyncEpoxyController() {
+ override fun buildModels() {
+ buildModelsCallback()
+ }
+}
+
+
+abstract class BaseEpoxyFragment : BaseMvRxFragment() {
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
+ inflater.inflate(R.layout.fragment_base, container, false).apply {
+ recyclerView.setController(BaseEpoxyController { buildModels() })
+ }
+
+
+ override fun invalidate() {
+ recyclerView.requestModelBuild()
+ }
+
+ abstract fun EpoxyController.buildModels()
+}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
index cba0db4..6bf7709 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesFragment.kt
@@ -1,82 +1,103 @@
package com.airbnb.lottie.samples
-import android.arch.lifecycle.Observer
-import android.arch.lifecycle.ViewModelProviders
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.support.v4.app.FragmentActivity
import com.airbnb.epoxy.EpoxyController
-import com.airbnb.epoxy.EpoxyRecyclerView
+import com.airbnb.lottie.samples.model.AnimationData
+import com.airbnb.lottie.samples.model.AnimationResponse
import com.airbnb.lottie.samples.model.CompositionArgs
-import com.airbnb.lottie.samples.views.LoadingViewModel_
-import com.airbnb.lottie.samples.views.LottiefilesTabBarModel_
-import com.airbnb.lottie.samples.views.MarqueeModel_
-import com.airbnb.lottie.samples.views.SearchInputItemViewModel_
-import kotlinx.android.synthetic.main.fragment_epoxy_recycler_view.*
+import com.airbnb.lottie.samples.views.loadingView
+import com.airbnb.lottie.samples.views.lottiefilesTabBar
+import com.airbnb.lottie.samples.views.marquee
+import com.airbnb.lottie.samples.views.searchInputItemView
+import com.airbnb.mvrx.*
-private val TAG = LottiefilesFragment::class.simpleName
-class LottiefilesFragment : Fragment(), EpoxyRecyclerView.ModelBuilderCallback {
- private val lottiefilesService by lazy { (requireContext().applicationContext as LottieApplication).lottiefilesService }
- private val viewModel by lazy { ViewModelProviders.of(this).get(LottiefilesViewModel::class.java) }
+data class LottiefilesState(
+ val mode: LottiefilesMode = LottiefilesMode.Recent,
+ val items: List<AnimationData> = emptyList(),
+ val request: Async<AnimationResponse> = Uninitialized,
+ val query: String = ""
+) : MvRxState
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- viewModel.fetchMoreAnimations()
+class LottiefilesViewModel(
+ initialState: LottiefilesState,
+ private val service: LottiefilesService) : MvRxViewModel<LottiefilesState>(initialState
+) {
+ init {
+ selectSubscribe(LottiefilesState::mode) { fetchMoreItems() }
}
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_epoxy_recycler_view, container, false)
+ fun fetchMoreItems() = withState { state ->
+ if (state.request is Loading) return@withState
+ val page = (state.request()?.currentPage ?: -1) + 1
+ if (state.request()?.lastPage == page && page > 0) return@withState
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- recyclerView.buildModelsWith(this)
- viewModel.loading.observe(this, Observer { recyclerView.requestModelBuild() })
- viewModel.animationDataList.observe(this, Observer { recyclerView.requestModelBuild() })
+ when (state.mode) {
+ LottiefilesMode.Recent -> service.getRecent(page)
+ LottiefilesMode.Popular -> service.getPopular(page)
+ LottiefilesMode.Search -> service.search(state.query)
+ }.execute { copy(request = it) }
}
- override fun buildModels(controller: EpoxyController) {
- MarqueeModel_()
- .id("lottiefiles")
- .title(R.string.lottiefiles)
- .subtitle(R.string.lottiefiles_airbnb)
- .addTo(controller)
+ fun setMode(mode: LottiefilesMode, query: String = "") = setState {
+ if (this.mode == mode && mode != LottiefilesMode.Search) return@setState this
+ if (this.mode == mode && mode == LottiefilesMode.Search && this.query == query) return@setState this
- LottiefilesTabBarModel_()
- .id("mode")
- .mode(viewModel.mode())
- .recentClickListener { _-> viewModel.setMode(LottiefilesMode.Recent) }
- .popularClickListener { _ -> viewModel.setMode(LottiefilesMode.Popular) }
- .searchClickListener { _ -> viewModel.setMode(LottiefilesMode.Search) }
- .addTo(controller)
+ copy(mode = mode, request = Uninitialized, items = emptyList(), query = query)
+ }
- SearchInputItemViewModel_()
- .id("search")
- .searchClickListener { viewModel.setMode(LottiefilesMode.Search, it) }
- .addIf(viewModel.mode() == LottiefilesMode.Search, controller)
-
- val lastAnimationData = viewModel.animationDataList.value?.lastOrNull()
- viewModel.animationDataList.value?.forEach {
- it ?: return@forEach
- val args = CompositionArgs(animationData = it)
- AnimationItemViewModel_()
- .id(it.id)
- .animationData(it)
- .clickListener { _ ->
- startActivity(PlayerActivity.intent(requireContext(), args))
- }
- .onBind({ _, _, _ ->
- if (it == lastAnimationData) {
- viewModel.fetchMoreAnimations()
- }
- })
- .addTo(controller)
+ companion object : MvRxViewModelFactory<LottiefilesState> {
+ @JvmStatic
+ override fun create(activity: FragmentActivity, state: LottiefilesState): LottiefilesViewModel {
+ val service = (activity.applicationContext as LottieApplication).lottiefilesService
+ return LottiefilesViewModel(state, service)
}
- LoadingViewModel_()
- .id("loading")
- .onBind { _, _, _ -> viewModel.fetchMoreAnimations() }
- .addIf(viewModel.loading.value ?: false, controller)
+ }
+}
+
+class LottiefilesFragment : BaseEpoxyFragment() {
+ private val viewModel: LottiefilesViewModel by fragmentViewModel()
+
+ override fun EpoxyController.buildModels() = withState(viewModel) { state ->
+ marquee {
+ id("lottiefiles")
+ title(R.string.lottiefiles)
+ subtitle(R.string.lottiefiles_airbnb)
+ }
+
+ lottiefilesTabBar {
+ id("mode")
+ mode(state.mode)
+ recentClickListener { _ -> viewModel.setMode(LottiefilesMode.Recent) }
+ popularClickListener { _ -> viewModel.setMode(LottiefilesMode.Popular) }
+ searchClickListener { _ -> viewModel.setMode(LottiefilesMode.Search) }
+ }
+
+ if (state.mode == LottiefilesMode.Search) {
+ searchInputItemView {
+ id("search")
+ searchClickListener { viewModel.setMode(LottiefilesMode.Search, it) }
+ }
+ }
+
+ state.items.forEach {
+ val args = CompositionArgs(animationData = it)
+ animationItemView {
+ id(it.id)
+ animationData(it)
+ clickListener { _ ->
+ startActivity(PlayerActivity.intent(requireContext(), args))
+ }
+ onBind { _, _, _ -> viewModel.fetchMoreItems() }
+ }
+ }
+
+ if (state.request is Loading) {
+ loadingView {
+ id("loading")
+ onBind { _, _, _ -> viewModel.fetchMoreItems() }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt
deleted file mode 100644
index 4244a03..0000000
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/LottiefilesViewModel.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.airbnb.lottie.samples
-
-import android.app.Application
-import android.arch.lifecycle.AndroidViewModel
-import android.arch.lifecycle.Lifecycle
-import android.arch.lifecycle.MutableLiveData
-import android.arch.lifecycle.OnLifecycleEvent
-import android.util.Log
-import com.airbnb.lottie.L
-import com.airbnb.lottie.samples.model.AnimationData
-import com.airbnb.lottie.samples.model.AnimationResponse
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.schedulers.Schedulers
-
-class LottiefilesViewModel(application: Application) : AndroidViewModel(application) {
-
- val animationDataList = MutableLiveData<List<AnimationData?>>()
- val loading = MutableLiveData<Boolean>()
- val mode = MutableLiveData<LottiefilesMode>().apply { value = LottiefilesMode.Recent }
- private var disposables = CompositeDisposable()
- private val responses = ArrayList<AnimationResponse>()
-
- private var searchQuery: String? = null
-
- fun mode() = mode.value ?: throw IllegalStateException("Mode must be set")
-
- fun setMode(mode: LottiefilesMode, searchQuery: String? = null) {
- this.searchQuery = searchQuery
- disposables.dispose()
- disposables = CompositeDisposable()
- this.mode.value = mode
- animationDataList.value = null
- loading.value = false
- responses.clear()
- fetchMoreAnimations()
- }
-
- fun fetchMoreAnimations() {
- if (loading.value == true) return
-
- val page = (responses.lastOrNull()?.currentPage ?: -1) + 1
- if (!responses.isEmpty() && page > responses.last().lastPage) return
-
- val service = getApplication<LottieApplication>().lottiefilesService
- val observable = when (mode()) {
- LottiefilesMode.Recent -> service.getRecent(page)
- LottiefilesMode.Popular -> service.getPopular(page)
- LottiefilesMode.Search ->
- if (searchQuery == null) Observable.empty() else service.search(searchQuery ?: "")
- }
-
- disposables.add(observable
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .retry(3)
- .doOnSubscribe { loading.value = true }
- .subscribe({
- responses.add(it)
- animationDataList.value = flatten(animationDataList.value, it.data)
- }, {
- Log.d(L.TAG, "e#\t", it);
- }, {
- loading.value = false
- }))
- }
-
- @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
- fun cleanupDisposables() = disposables.dispose()
-}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt
new file mode 100644
index 0000000..41ad0ea
--- /dev/null
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/MvRxViewModel.kt
@@ -0,0 +1,6 @@
+package com.airbnb.lottie.samples
+
+import com.airbnb.mvrx.BaseMvRxViewModel
+import com.airbnb.mvrx.MvRxState
+
+open class MvRxViewModel<S : MvRxState>(initialState: S) : BaseMvRxViewModel<S>(initialState, BuildConfig.DEBUG)
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
index 1cf9b1c..3e80d76 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewFragment.kt
@@ -3,76 +3,93 @@
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
+import android.content.ActivityNotFoundException
import android.content.Intent
import android.content.pm.PackageManager
-import android.os.Bundle
import android.support.design.widget.Snackbar
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.EditText
import android.widget.Toast
+import com.airbnb.epoxy.EpoxyController
import com.airbnb.lottie.samples.model.CompositionArgs
-import kotlinx.android.synthetic.main.fragment_preview.*
+import com.airbnb.lottie.samples.views.marquee
+import kotlinx.android.synthetic.main.fragment_player.*
private const val RC_FILE = 1000
private const val RC_CAMERA_PERMISSION = 1001
-class PreviewFragment : Fragment() {
+class PreviewFragment : BaseEpoxyFragment() {
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_preview, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- qr.setOnClickListener {
- if (requireContext().hasPermission(Manifest.permission.CAMERA)) {
- startActivity(QRScanActivity.intent(requireContext()))
- } else {
- requestPermissions(arrayOf(Manifest.permission.CAMERA), RC_CAMERA_PERMISSION)
- }
+ override fun EpoxyController.buildModels() {
+ marquee {
+ id("marquee")
+ title(R.string.preview_title)
}
- file.setOnClickListener {
- try {
- val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
- type = "*/*"
- addCategory(Intent.CATEGORY_OPENABLE)
- }
- startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE)
- } catch (ex: android.content.ActivityNotFoundException) {
- // Potentially direct the user to the Market with a Dialog
- Toast.makeText(context, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
- }
- }
-
- url.setOnClickListener {
- val urlOrJsonView = EditText(context)
- AlertDialog.Builder(context)
- .setTitle(R.string.preview_url)
- .setView(urlOrJsonView)
- .setPositiveButton(R.string.preview_load) { _, _ ->
- val args = CompositionArgs(url = urlOrJsonView.text.toString())
- startActivity(PlayerActivity.intent(requireContext(), args))
- }
- .setNegativeButton(R.string.preview_cancel) { dialog, _ -> dialog.dismiss() }
- .show()
- }
-
- assets.setOnClickListener {
- val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.select_dialog_item).apply {
- requireContext().assets.list("").forEach {
- if (it.endsWith(".json") || it.endsWith(".zip")) {
- add(it)
- }
+ previewItemView {
+ id("qr")
+ title(R.string.preview_qr)
+ icon(R.drawable.ic_qr_scan)
+ clickListener { _ ->
+ if (requireContext().hasPermission(Manifest.permission.CAMERA)) {
+ startActivity(QRScanActivity.intent(requireContext()))
+ } else {
+ requestPermissions(arrayOf(Manifest.permission.CAMERA), RC_CAMERA_PERMISSION)
}
}
- AlertDialog.Builder(context)
- .setAdapter(adapter) { _, which ->
- val args = CompositionArgs(asset = adapter.getItem(which))
- startActivity(PlayerActivity.intent(requireContext(), args))
+ }
+
+ previewItemView {
+ id("file")
+ title(R.string.preview_file)
+ icon(R.drawable.ic_file)
+ clickListener { _ ->
+ try {
+ val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+ type = "*/*"
+ addCategory(Intent.CATEGORY_OPENABLE)
}
- .show()
+ startActivityForResult(Intent.createChooser(intent, "Select a JSON file"), RC_FILE)
+ } catch (ex: ActivityNotFoundException) {
+ // Potentially direct the user to the Market with a Dialog
+ Toast.makeText(context, "Please install a File Manager.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+ previewItemView {
+ id("url")
+ title(R.string.preview_url)
+ icon(R.drawable.ic_network)
+ clickListener { _ ->
+ val urlOrJsonView = EditText(context)
+ AlertDialog.Builder(context)
+ .setTitle(R.string.preview_url)
+ .setView(urlOrJsonView)
+ .setPositiveButton(R.string.preview_load) { _, _ ->
+ val args = CompositionArgs(url = urlOrJsonView.text.toString())
+ startActivity(PlayerActivity.intent(requireContext(), args))
+ }
+ .setNegativeButton(R.string.preview_cancel) { dialog, _ -> dialog.dismiss() }
+ .show()
+ }
+ }
+
+ previewItemView {
+ id("assets")
+ title(R.string.preview_assets)
+ icon(R.drawable.ic_storage)
+ clickListener { _ ->
+ val adapter = ArrayAdapter<String>(requireContext(), android.R.layout.select_dialog_item)
+ requireContext().assets.list("").asSequence()
+ .filter { it.endsWith(".json") || it.endsWith(".zip") }
+ .forEach { adapter.add(it) }
+ AlertDialog.Builder(context)
+ .setAdapter(adapter) { _, which ->
+ val args = CompositionArgs(asset = adapter.getItem(which))
+ startActivity(PlayerActivity.intent(requireContext(), args))
+ }
+ .show()
+ }
}
}
@@ -89,7 +106,7 @@
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
startActivity(QRScanActivity.intent(requireContext()))
} else {
- Snackbar.make(container, R.string.qr_permission_denied, Snackbar.LENGTH_LONG).show()
+ Snackbar.make(coordinatorLayout, R.string.qr_permission_denied, Snackbar.LENGTH_LONG).show()
}
}
}
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
index ff06595..c6830d8 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/PreviewItemView.kt
@@ -1,11 +1,17 @@
package com.airbnb.lottie.samples
import android.content.Context
+import android.support.annotation.DrawableRes
import android.util.AttributeSet
-import android.util.TypedValue
+import android.view.View
import android.widget.LinearLayout
+import com.airbnb.epoxy.CallbackProp
+import com.airbnb.epoxy.ModelProp
+import com.airbnb.epoxy.ModelView
+import com.airbnb.epoxy.TextProp
import kotlinx.android.synthetic.main.list_item_preview.view.*
+@ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
class PreviewItemView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@@ -15,20 +21,20 @@
init {
orientation = VERTICAL
inflate(R.layout.list_item_preview)
+ }
- attrs?.let {
- val ta = context.obtainStyledAttributes(it, R.styleable.PreviewItemView)
- val titleText = resources.getText(ta.getResourceId(R.styleable.PreviewItemView_titleText, 0))
- val iconRes = ta.getResourceId(R.styleable.PreviewItemView_icon, 0)
+ @TextProp
+ fun setTitle(title: CharSequence) {
+ titleView.text = title
+ }
- titleView.text = titleText
- iconView.setImageResource(iconRes)
+ @ModelProp
+ fun setIcon(@DrawableRes icon: Int) {
+ iconView.setImageResource(icon)
+ }
- ta.recycle()
- }
-
- val outValue = TypedValue()
- getContext().theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
- setBackgroundResource(outValue.resourceId)
+ @CallbackProp
+ fun setClickListener(clickListener: View.OnClickListener?) {
+ container.setOnClickListener(clickListener)
}
}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
index f3fa425..876fbc3 100644
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
+++ b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseFragment.kt
@@ -1,24 +1,36 @@
package com.airbnb.lottie.samples
-import android.arch.lifecycle.Observer
-import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
-import android.os.Bundle
-import android.support.v4.app.Fragment
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.support.v4.app.FragmentActivity
import com.airbnb.epoxy.EpoxyController
-import com.airbnb.epoxy.EpoxyRecyclerView
+import com.airbnb.lottie.samples.model.AnimationResponse
import com.airbnb.lottie.samples.model.CompositionArgs
import com.airbnb.lottie.samples.model.ShowcaseItem
-import com.airbnb.lottie.samples.views.LoadingViewModel_
-import com.airbnb.lottie.samples.views.MarqueeModel_
-import com.airbnb.lottie.samples.views.ShowcaseAnimationItemViewModel_
-import com.airbnb.lottie.samples.views.ShowcaseCarouselModel_
-import kotlinx.android.synthetic.main.fragment_epoxy_recycler_view.*
+import com.airbnb.lottie.samples.views.loadingView
+import com.airbnb.lottie.samples.views.marquee
+import com.airbnb.lottie.samples.views.showcaseAnimationItemView
+import com.airbnb.lottie.samples.views.showcaseCarousel
+import com.airbnb.mvrx.*
-class ShowcaseFragment : Fragment(), EpoxyRecyclerView.ModelBuilderCallback {
+data class ShowcaseState(val response: Async<AnimationResponse> = Uninitialized) : MvRxState
+
+class ShowcaseViewModel(initialState: ShowcaseState, service: LottiefilesService) : MvRxViewModel<ShowcaseState>(initialState) {
+ init {
+ service.getCollection("lottie-showcase")
+ .retry(3)
+ .execute { copy(response = it) }
+ }
+
+ companion object : MvRxViewModelFactory<ShowcaseState> {
+ @JvmStatic
+ override fun create(activity: FragmentActivity, state: ShowcaseState): ShowcaseViewModel {
+ val service = (activity.applicationContext as LottieApplication).lottiefilesService
+ return ShowcaseViewModel(state, service)
+ }
+ }
+}
+
+class ShowcaseFragment : BaseEpoxyFragment() {
private val showcaseItems = listOf(
ShowcaseItem(R.drawable.showcase_preview_lottie, R.string.showcase_item_app_intro) {
@@ -40,48 +52,33 @@
startActivity(Intent(requireContext(), ListActivity::class.java))
}
)
- private val viewModel by lazy { ViewModelProviders.of(this).get(ShowcaseViewModel::class.java) }
+ private val viewModel: ShowcaseViewModel by fragmentViewModel()
- override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
- inflater.inflate(R.layout.fragment_epoxy_recycler_view, container, false)
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- recyclerView.buildModelsWith(this)
-
- viewModel.collection.observe(this, Observer {
- recyclerView.requestModelBuild()
- })
-
- viewModel.loading.observe(this, Observer {
- recyclerView.requestModelBuild()
- })
-
- viewModel.fetchAnimations()
- }
-
- override fun buildModels(controller: EpoxyController) {
- MarqueeModel_()
- .id("showcase")
- .title("Showcase")
- .addTo(controller)
- ShowcaseCarouselModel_()
- .id("carousel")
- .showcaseItems(showcaseItems)
- .addTo(controller)
-
- viewModel.collection.value?.data?.forEach {
- ShowcaseAnimationItemViewModel_()
- .id(it.id)
- .title(it.title)
- .previewUrl(it.preview)
- .onClickListener { _ ->
- startActivity(PlayerActivity.intent(requireContext(), CompositionArgs(animationData = it)))
- }
- .addTo(controller)
+ override fun EpoxyController.buildModels() = withState(viewModel) { state ->
+ marquee {
+ id("showcase")
+ title("Showcase")
+ }
+ showcaseCarousel {
+ id("carousel")
+ showcaseItems(showcaseItems)
}
- LoadingViewModel_()
- .id("loading")
- .addIf(viewModel.loading.value ?: false, controller)
+ val collectionItems = state.response()?.data
+
+ if (collectionItems == null) {
+ loadingView {
+ id("loading")
+ }
+ } else {
+ collectionItems.forEach {
+ showcaseAnimationItemView {
+ id(it.id)
+ title(it.title)
+ previewUrl(it.preview)
+ onClickListener { _ -> startActivity(PlayerActivity.intent(requireContext(), CompositionArgs(animationData = it))) }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt b/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt
deleted file mode 100644
index cf74c72..0000000
--- a/LottieSample/src/main/kotlin/com/airbnb/lottie/samples/ShowcaseViewModel.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.airbnb.lottie.samples
-
-import android.app.Application
-import android.arch.lifecycle.AndroidViewModel
-import android.arch.lifecycle.MutableLiveData
-import android.util.Log
-import com.airbnb.lottie.samples.model.AnimationResponse
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.CompositeDisposable
-import io.reactivex.schedulers.Schedulers
-
-private const val COLLECTION = "lottie-showcase"
-private val TAG = ShowcaseViewModel::class.java.simpleName
-class ShowcaseViewModel(application: Application) : AndroidViewModel(application) {
-
- private val lottiefilesService by lazy { (application as LottieApplication).lottiefilesService }
-
- private var disposables = CompositeDisposable()
-
- val collection = MutableLiveData<AnimationResponse>()
- val loading = MutableLiveData<Boolean>().apply { value = false }
-
- fun fetchAnimations() {
- disposables.add(lottiefilesService.getCollection(COLLECTION)
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .retry(3)
- .doOnSubscribe { loading.value = true }
- .subscribe({
- collection.value = it
- }, {
- Log.e(TAG, "Error loading collection", it)
- }, {
- loading.value = false
- }))
- }
-}
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/fragment_base.xml b/LottieSample/src/main/res/layout/fragment_base.xml
new file mode 100644
index 0000000..05ad3cb
--- /dev/null
+++ b/LottieSample/src/main/res/layout/fragment_base.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/coordinatorLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.airbnb.epoxy.EpoxyRecyclerView
+ android:id="@+id/recyclerView"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/fragment_preview.xml b/LottieSample/src/main/res/layout/fragment_preview.xml
deleted file mode 100644
index 5d5506a..0000000
--- a/LottieSample/src/main/res/layout/fragment_preview.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <com.airbnb.lottie.samples.views.Marquee
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:titleText="@string/preview_title"/>
-
- <com.airbnb.lottie.samples.PreviewItemView
- android:id="@+id/qr"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:titleText="@string/preview_qr"
- app:icon="@drawable/ic_qr_scan"/>
-
- <com.airbnb.lottie.samples.PreviewItemView
- android:id="@+id/file"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:titleText="@string/preview_file"
- app:icon="@drawable/ic_file"/>
-
- <com.airbnb.lottie.samples.PreviewItemView
- android:id="@+id/url"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:titleText="@string/preview_url"
- app:icon="@drawable/ic_network"/>
-
- <com.airbnb.lottie.samples.PreviewItemView
- android:id="@+id/assets"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- app:titleText="@string/preview_assets"
- app:icon="@drawable/ic_storage"/>
- </LinearLayout>
-</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/LottieSample/src/main/res/layout/list_item_preview.xml b/LottieSample/src/main/res/layout/list_item_preview.xml
index 64e7c9d..e44c95a 100644
--- a/LottieSample/src/main/res/layout/list_item_preview.xml
+++ b/LottieSample/src/main/res/layout/list_item_preview.xml
@@ -4,14 +4,16 @@
tools:parentTag="android.widget.LinearLayout">
<LinearLayout
+ android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginBottom="18dp"
- android:layout_marginLeft="24dp"
- android:layout_marginRight="24dp"
- android:layout_marginTop="18dp"
+ android:paddingBottom="18dp"
+ android:paddingLeft="24dp"
+ android:paddingRight="24dp"
+ android:paddingTop="18dp"
android:gravity="center_vertical"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:background="?attr/selectableItemBackground">
<ImageView
android:id="@+id/iconView"
diff --git a/LottieSample/src/main/res/values/attrs.xml b/LottieSample/src/main/res/values/attrs.xml
index 5f0ca96..94acd1a 100644
--- a/LottieSample/src/main/res/values/attrs.xml
+++ b/LottieSample/src/main/res/values/attrs.xml
@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="titleText" format="string" />
- <declare-styleable name="PreviewItemView">
- <attr name="icon" format="reference" />
- <attr name="titleText" />
- </declare-styleable>
<declare-styleable name="ControlBarItemToggleView">
<attr name="src" format="reference" />
<attr name="text" format="string" />
diff --git a/build.gradle b/build.gradle
index 6ebcd4a..c43e143 100644
--- a/build.gradle
+++ b/build.gradle
@@ -3,6 +3,7 @@
buildscript {
ext.kotlinVersion = '1.2.51'
ext.supportLibVersion = '27.1.1'
+ ext.navVersion = '1.0.0-alpha04'
repositories {
jcenter()