Skip to content

Commit

Permalink
[Sample] Replace direct Lottiefiles integration with a link to their …
Browse files Browse the repository at this point in the history
…app instead (#2476)

Since the launch of the sample app, it would hit the Lottiefiles API and show example animations. However, over time, this sample app would periodically break when they changed their API and they also now have their own app. Instead of maintaining the direct integration, the sample app will now provide a link to theirs instead
  • Loading branch information
gpeal committed Mar 4, 2024
1 parent 7340d68 commit e6425bc
Show file tree
Hide file tree
Showing 41 changed files with 99 additions and 874 deletions.
Expand Up @@ -136,4 +136,4 @@ class ComposeActivity : AppCompatActivity() {
LottieFiles(Route.LottieFiles, R.drawable.ic_lottie_files, R.string.bottom_tab_lottie_files),
Docs(Route.Examples, R.drawable.ic_examples, R.string.bottom_tab_examples),
}
}
}
Expand Up @@ -2,7 +2,6 @@ package com.airbnb.lottie.samples

import androidx.multidex.MultiDexApplication
import com.airbnb.lottie.L
import com.airbnb.lottie.samples.api.LottiefilesApi
import com.airbnb.mvrx.Mavericks
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
Expand Down Expand Up @@ -34,8 +33,6 @@ class LottieApplication : MultiDexApplication() {
.build()
}

val lottiefilesService: LottiefilesApi by lazy { retrofit.create(LottiefilesApi::class.java) }

override fun onCreate() {
super.onCreate()
Mavericks.initialize(this)
Expand Down
@@ -1,170 +1,21 @@
package com.airbnb.lottie.samples

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingDataAdapter
import androidx.paging.PagingSource
import androidx.paging.PagingState
import androidx.paging.cachedIn
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.lottie.samples.api.LottiefilesApi
import com.airbnb.lottie.samples.databinding.LottiefilesFragmentBinding
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.utils.BaseFragment
import com.airbnb.lottie.samples.utils.hideKeyboard
import com.airbnb.lottie.samples.utils.viewBinding
import com.airbnb.lottie.samples.views.AnimationItemView
import com.airbnb.mvrx.MavericksState
import com.airbnb.mvrx.MavericksView
import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.ViewModelContext
import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch

data class LottiefilesState(
val mode: LottiefilesMode = LottiefilesMode.Recent,
val query: String = ""
) : MavericksState

class LottiefilesViewModel(initialState: LottiefilesState, private val api: LottiefilesApi) : MavericksViewModel<LottiefilesState>(initialState) {

private var mode = initialState.mode
private var query = initialState.query

private var dataSource: LottiefilesDataSource? = null
val pager = Pager(PagingConfig(pageSize = 16)) {
LottiefilesDataSource(api, mode, query).also { dataSource = it }
}.flow.cachedIn(viewModelScope)

init {
onEach(LottiefilesState::mode, LottiefilesState::query) { mode, query ->
this.mode = mode
this.query = query
dataSource?.invalidate()
}
}

fun setMode(mode: LottiefilesMode) = setState { copy(mode = mode) }

fun setQuery(query: String) = setState { copy(query = query) }

companion object : MavericksViewModelFactory<LottiefilesViewModel, LottiefilesState> {
override fun create(viewModelContext: ViewModelContext, state: LottiefilesState): LottiefilesViewModel {
val service = viewModelContext.app<LottieApplication>().lottiefilesService
return LottiefilesViewModel(state, service)
}
}
}

class LottiefilesDataSource(
private val api: LottiefilesApi,
val mode: LottiefilesMode,
private val query: String
) : PagingSource<Int, AnimationData>() {

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AnimationData> {
val page = params.key ?: 1
return try {
val response = when (mode) {
LottiefilesMode.Popular -> api.getPopular(page)
LottiefilesMode.Recent -> api.getRecent(page)
LottiefilesMode.Search -> {
if (query.isBlank()) {
AnimationResponse(page, emptyList(), "", page, null, "", 0, "", 0, 0)
} else {
api.search(query, page)
}
}
}

LoadResult.Page(
response.data,
if (page == 1) null else page + 1,
(page + 1).takeIf { page < response.lastPage }
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}

override fun getRefreshKey(state: PagingState<Int, AnimationData>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}

class LottiefilesFragment : BaseFragment(R.layout.lottiefiles_fragment) {
class LottiefilesFragment : Fragment(R.layout.lottiefiles_fragment) {
private val binding: LottiefilesFragmentBinding by viewBinding()
private val viewModel: LottiefilesViewModel by fragmentViewModel()

private object AnimationItemDataDiffCallback : DiffUtil.ItemCallback<AnimationData>() {
override fun areItemsTheSame(oldItem: AnimationData, newItem: AnimationData) = oldItem.id == newItem.id

override fun areContentsTheSame(oldItem: AnimationData, newItem: AnimationData) = oldItem == newItem
}

private class AnimationItemViewHolder(context: Context) : RecyclerView.ViewHolder(AnimationItemView(context)) {
fun bind(data: AnimationData?) {
val view = itemView as AnimationItemView
view.setTitle(data?.title)
view.setPreviewUrl(data?.preview)
view.setPreviewBackgroundColor(data?.bgColorInt)
view.setOnClickListener {
val intent = PlayerActivity.intent(view.context, CompositionArgs(animationData = data))
view.context.startActivity(intent)
}
}
}


private val adapter = object : PagingDataAdapter<AnimationData, AnimationItemViewHolder>(AnimationItemDataDiffCallback) {
override fun onBindViewHolder(holder: AnimationItemViewHolder, position: Int) = holder.bind(getItem(position))

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = AnimationItemViewHolder(parent.context)

}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.recyclerView.adapter = adapter
viewLifecycleOwner.lifecycleScope.launch {
viewModel.pager.collectLatest(adapter::submitData)
}
binding.tabBar.setRecentClickListener {
viewModel.setMode(LottiefilesMode.Recent)
requireContext().hideKeyboard()
binding.getItOnPlay.setOnClickListener {
val uri = Uri.parse("https://play.google.com/store/apps/details?id=com.lottiefiles.LottiePreview")
startActivity(Intent(Intent.ACTION_VIEW, uri))
}
binding.tabBar.setPopularClickListener {
viewModel.setMode(LottiefilesMode.Popular)
requireContext().hideKeyboard()
}
binding.tabBar.setSearchClickListener {
viewModel.setMode(LottiefilesMode.Search)
requireContext().hideKeyboard()
}
binding.searchView.query.onEach { query ->
viewModel.setQuery(query)
}.launchIn(viewLifecycleOwner.lifecycleScope)
}

override fun invalidate(): Unit = withState(viewModel) { state ->
binding.searchView.isVisible = state.mode == LottiefilesMode.Search
binding.tabBar.setMode(state.mode)
}
}

This file was deleted.

11 changes: 6 additions & 5 deletions sample/src/main/kotlin/com/airbnb/lottie/samples/MainActivity.kt
Expand Up @@ -6,6 +6,7 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.fragment.app.commit
import com.airbnb.lottie.samples.databinding.MainActivityBinding
import com.airbnb.lottie.samples.utils.viewBinding

Expand All @@ -17,16 +18,16 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
binding.bottomNavigation.setOnItemSelectedListener listener@{ item ->
when (item.itemId) {
R.id.showcase -> showFragment(ShowcaseFragment())
R.id.preview -> showFragment(PreviewFragment())
R.id.lottiefiles -> showFragment(LottiefilesFragment())
R.id.learn -> showShowcase()
}
true
}
binding.bottomNavigation.itemIconTintList = null

if (savedInstanceState == null) {
showFragment(ShowcaseFragment())
showFragment(PreviewFragment())
}
}

Expand All @@ -36,8 +37,8 @@ class MainActivity : AppCompatActivity() {
}

private fun showFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.replace(R.id.content, fragment)
.commit()
supportFragmentManager.commit {
replace(R.id.content, fragment)
}
}
}

This file was deleted.

11 changes: 0 additions & 11 deletions sample/src/main/kotlin/com/airbnb/lottie/samples/PlayerFragment.kt
Expand Up @@ -133,17 +133,6 @@ class PlayerFragment : BaseFragment(R.layout.player_fragment) {

val args = arguments?.getParcelableCompat(EXTRA_ANIMATION_ARGS, CompositionArgs::class.java)
?: throw IllegalArgumentException("No composition args specified")
args.animationData?.bgColorInt?.let {
binding.controlBarBackgroundColor.backgroundButton1.setBackgroundColor(it)
binding.animationContainer.setBackgroundColor(it)
invertColor(it)
}

args.animationDataV2?.bgColorInt?.let {
binding.controlBarBackgroundColor.backgroundButton1.setBackgroundColor(it)
binding.animationContainer.setBackgroundColor(it)
invertColor(it)
}

binding.controlBarTrim.minFrameView.setOnClickListener { showMinFrameDialog() }
binding.controlBarTrim.maxFrameView.setOnClickListener { showMaxFrameDialog() }
Expand Down
Expand Up @@ -42,7 +42,7 @@ class PlayerViewModel(
) : MavericksViewModel<PlayerState>(initialState) {

fun fetchAnimation(args: CompositionArgs) {
val url = args.url ?: args.animationDataV2?.file ?: args.animationData?.lottieLink
val url = args.url

when {
url != null -> LottieCompositionFactory.fromUrl(application, url, null)
Expand Down

0 comments on commit e6425bc

Please sign in to comment.