Skip to content

Instantly share code, notes, and snippets.

@PetkevichPavel
Last active September 2, 2020 14:22
Show Gist options
  • Select an option

  • Save PetkevichPavel/0c871f59fc825d689d4d4b9259b5988c to your computer and use it in GitHub Desktop.

Select an option

Save PetkevichPavel/0c871f59fc825d689d4d4b9259b5988c to your computer and use it in GitHub Desktop.
onboarding
package com.berider.app.models.domain.onboarding
import android.os.Parcelable
import androidx.annotation.StringRes
import com.berider.app.models.R
import com.berider.app.models.domain.utils.ModelsConstants
import kotlinx.android.parcel.Parcelize
/**
* Created by pavel.petkevich@skodaautodigilab.com on 09.April.2020
*/
object Onboarding {
val onboardings = listOf(Type.MAIN, Type.PRE_RIDE, Type.POST_RIDE)
@Parcelize
enum class Type(val defRcFile: String, val rcKeyName: String, @StringRes val stringRes: Int) : Parcelable {
MAIN(
"OnboardingMain.json",
if (ModelsConstants.isStagingOrDev) "onb_main_staging" else "onb_main_prod",
R.string.onboarding_main_item
),
PRE_RIDE(
"OnboardingPostRide.json",
if (ModelsConstants.isStagingOrDev) "onb_before_ride_staging" else "onb_before_ride_prod",
R.string.onboarding_pre_ride_item
),
POST_RIDE(
"OnboardingPreRide.json",
if (ModelsConstants.isStagingOrDev) "onb_after_ride_staging" else "onb_after_ride_prod",
R.string.onboarding_post_ride_item
),
}
}
package com.berider.app.models.domain.onboarding
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.annotation.StyleRes
import com.berider.app.models.R
import com.squareup.moshi.JsonClass
/**
* Created by pavel.petkevich@skodaautodigilab.com on 06.April.2020
*/
@JsonClass(generateAdapter = true)
data class OnboardingPage(
val id: Int,
val imageURL: String,
val title_cs: String,
val title_en: String,
val content_cs: String,
val content_en: String,
val isSkippable: Boolean,
val isIosOnly: Boolean,
val type: String
) {
companion object {
const val TITLE_PARAM = "title"
const val CONTENT_PARAM = "content"
const val SMALL_GUIDELINE = 0.55f
const val FULL_GUIDELINE = 0f
fun getPageType(str: String) = Type.values().find { it.name == str }
}
enum class Type(
@StringRes val stringResId: Int,
@StyleRes val styleResId: Int,
@ColorRes val colorResId: Int,
val guidelinePosition: Float
) {
BASE(R.string.general_next, R.style.BaseOutlineButton, R.color.transparent, SMALL_GUIDELINE),
LOCATION(R.string.general_allow, R.style.MaterialPrimaryButtonBlack, R.color.base_black, FULL_GUIDELINE),
START_RIDE(R.string.general_ride, R.style.MaterialPrimaryButtonBlack, R.color.base_black, SMALL_GUIDELINE),
FINISH_RIDE(R.string.general_continue, R.style.MaterialPrimaryButtonBlack, R.color.base_black, SMALL_GUIDELINE),
}
}
package com.berider.app.onboarding.ui
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyControllerAdapter
import com.berider.app.analytics.base.Event
import com.berider.app.common.base.BaseActivity
import com.berider.app.common.utils.*
import com.berider.app.models.domain.onboarding.Onboarding
import com.berider.app.models.domain.onboarding.OnboardingPage
import com.berider.app.models.domain.onboarding.OnboardingPage.Companion.getPageType
import com.berider.app.onboarding.R
import com.berider.app.onboarding.epoxy.PageController
import kotlinx.android.synthetic.main.activity_onboarding.*
import org.koin.androidx.viewmodel.ext.android.viewModel
class OnboardingActivity : BaseActivity() {
companion object {
const val ONBOARDING_TYPE = "onboarding_type"
fun prepareBundle(onboardingType: Onboarding.Type) = bundleOf(ONBOARDING_TYPE to onboardingType)
}
private val viewModel by viewModel<OnboardingViewModel>()
private var data: List<OnboardingPage>? = null
private var currentPagePos = 0
private var currentPage: OnboardingPage? = null
private var layoutManager: LinearLayoutManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_onboarding)
getBundleArg<Onboarding.Type>(getBundleName(), ONBOARDING_TYPE)?.let {
analyticsService.logEvent(Event.Names.ONB_VIEW.name, Event.Parameters.TYPE.name to it.name)
viewModel.fetchOnBoarding(it, isLocationPermissionGranted())
} ?: finish()
onbMainBtn?.onClick(lifecycleScope) {
currentPage?.chooseAction()
}
onbSkipBtn?.setOnClickListener {
analyticsService.logEvent(Event.Names.ONB_PAGE_BTN_SKIP.name, *getData(currentPagePos))
finish()
}
}
override fun setObservers() {
super.setObservers()
viewModel.data.observe(this, Observer { data ->
this.data = data
PageController.setData(data).adapter.setRecyclerView()
onbMainConstraint.makeVisible()
})
viewModel.uiState.observe(this, Observer { state ->
processState(state)
})
}
private fun EpoxyControllerAdapter.setRecyclerView() {
layoutManager = LinearLayoutManager(this@OnboardingActivity, LinearLayoutManager.HORIZONTAL, false)
epoxyRecyclerView?.layoutManager = layoutManager
epoxyRecyclerView?.adapter = this
PagerSnapHelper().apply {
epoxyRecyclerView.onFlingListener = null
attachToRecyclerView(epoxyRecyclerView)
onbIndicator.attachToRecyclerView(epoxyRecyclerView, this)
}
epoxyRecyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
layoutManager?.findLastCompletelyVisibleItemPosition().takeIf { it != -1 }?.apply {
currentPagePos = this
data?.get(this)?.run {
setButton()
analyticsService.logEvent(Event.Names.ONB_PAGE_VIEW.name, *getData(this@apply))
}
}
}
})
}
private fun OnboardingPage.setButton() {
currentPage = this@setButton
getPageType(type)?.apply {
onbSkipBtn?.makeVisible(isSkippable)
guidelineBtnWidth?.moveWithAnim(guidelineBtnWidth.currentPercent(), guidelinePosition)
onbMainBtn?.setWith(stringResId, styleResId, colorResId)?.makeVisible()
}
}
private fun OnboardingPage.chooseAction() {
when (getPageType(type)) {
OnboardingPage.Type.BASE -> nextPage()
OnboardingPage.Type.LOCATION -> runWithPermission(
Manifest.permission.ACCESS_FINE_LOCATION,
analyticsService = analyticsService
)
OnboardingPage.Type.START_RIDE -> finish()
OnboardingPage.Type.FINISH_RIDE -> finish()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
when (requestCode) {
PermissionConstants.REQUEST_LOCATION -> finish()
}
}
}
private fun getData(position: Int) = data?.get(position)?.run {
arrayOf(Event.Parameters.TYPE.name to type, Event.Parameters.PAGE_NUMBER.name to position)
} ?: emptyArray()
private fun nextPage() {
layoutManager?.findLastCompletelyVisibleItemPosition()?.plus(1)?.let { pos ->
if (currentPage == data?.last()) finish()
else epoxyRecyclerView?.smoothScrollToPosition(pos)
analyticsService.logEvent(Event.Names.ONB_PAGE_BTN_NEXT.name, *getData(data?.getOrLast(currentPagePos).orZero()))
}
}
}
package com.berider.app.onboarding.data
import com.berider.app.common.base.BaseRepository
import com.berider.app.common.core.ResponseState
import com.berider.app.common.utils.SingleLiveEvent
import com.berider.app.common.utils.emit
import com.berider.app.models.domain.onboarding.Onboarding
import com.berider.app.models.domain.onboarding.OnboardingPage
import com.berider.app.models.domain.onboarding.OnboardingPage.Companion.getPageType
import com.berider.app.onboarding.R
import com.berider.app.remoteconfig.RemoteConfigFunctions
/**
* Created by pavel.petkevich@skodaautodigilab.com on 06.April.2020
*/
interface IOnboardingRepository {
val uiState: SingleLiveEvent<ResponseState>
val data: SingleLiveEvent<List<OnboardingPage>?>
fun fetchOnBoarding(onboardingType: Onboarding.Type, isLocationPermissionGranted: Boolean)
}
class OnboardingRepository(private val remoteConfigFunctions: RemoteConfigFunctions) : IOnboardingRepository, BaseRepository() {
override val uiState: SingleLiveEvent<ResponseState>
get() = responseState
private val _data = SingleLiveEvent<List<OnboardingPage>?>()
override val data: SingleLiveEvent<List<OnboardingPage>?>
get() = _data
override fun fetchOnBoarding(onboardingType: Onboarding.Type, isLocationPermissionGranted: Boolean) {
responseState.emit(ResponseState.BlockingLoadingState.Start(bgColorRes = R.color.transparent))
remoteConfigFunctions.apply {
Array<OnboardingPage>::class.fetchRemoteConfig(
onboardingType.defRcFile,
onboardingType.rcKeyName
) { list ->
_data.emit(list?.removeLocationPermPage(onboardingType, isLocationPermissionGranted)?.filter { !it.isIosOnly })
responseState.emit(ResponseState.BlockingLoadingState.Stop())
}
}
}
private fun Array<OnboardingPage>.removeLocationPermPage(
onboardingType: Onboarding.Type,
isLocationPermissionGranted: Boolean
) = toMutableList().apply {
this.takeIf { onboardingType == Onboarding.Type.MAIN && isLocationPermissionGranted }
?.find { getPageType(it.type) == OnboardingPage.Type.LOCATION }?.let { remove(it) }
}
}
package com.berider.app.onboarding.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.berider.app.models.domain.onboarding.Onboarding
import com.berider.app.onboarding.data.IOnboardingRepository
import kotlinx.coroutines.launch
/**
* Created by pavel.petkevich@skodaautodigilab.com on 06.April.2020
*/
class OnboardingViewModel(private val onboardingRepository: IOnboardingRepository) : ViewModel() {
val uiState = onboardingRepository.uiState
val data = onboardingRepository.data
fun fetchOnBoarding(onboardingType: Onboarding.Type, isLocationPermissionGranted: Boolean) {
viewModelScope.launch {
onboardingRepository.fetchOnBoarding(onboardingType, isLocationPermissionGranted)
}
}
}
package com.berider.app.onboarding.epoxy
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass
import com.airbnb.epoxy.EpoxyModelWithHolder
import com.airbnb.epoxy.TypedEpoxyController
import com.berider.app.common.epoxy.BaseEpoxyHolder
import com.berider.app.common.utils.getStringViaLocale
import com.berider.app.common.utils.makeVisible
import com.berider.app.common.utils.setImageToView
import com.berider.app.models.domain.onboarding.OnboardingPage
import com.berider.app.onboarding.R
/**
* Created by pavel.petkevich@skodaautodigilab.com on 07.April.2020
*/
@EpoxyModelClass
abstract class PageModel : EpoxyModelWithHolder<PageModel.Holder?>() {
@EpoxyAttribute
lateinit var page: OnboardingPage
override fun getDefaultLayout(): Int = R.layout.onboarding_page
override fun bind(holder: Holder) {
super.bind(holder)
with(holder) {
progressOnb.apply {
makeVisible()
imgOnbPage.setImageToView(page.imageURL) {
makeVisible(false)
}
}
txtOnbTitle.text = page.getStringViaLocale(OnboardingPage.TITLE_PARAM)
txtOnbContent.text = page.getStringViaLocale(OnboardingPage.CONTENT_PARAM)
}
}
class Holder : BaseEpoxyHolder() {
val progressOnb: ProgressBar by bind(R.id.progressOnb)
val imgOnbPage: ImageView by bind(R.id.imgOnbPage)
val txtOnbTitle: TextView by bind(R.id.txtOnbTitle)
val txtOnbContent: TextView by bind(R.id.txtOnbContent)
}
}
class PageController : TypedEpoxyController<List<OnboardingPage>>() {
companion object {
fun setData(data: List<OnboardingPage>?) = PageController().apply { setData(data) }
}
override fun buildModels(pages: List<OnboardingPage>) {
pages.forEach {
page {
id(it.id)
page(it)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment