Last active
September 2, 2020 14:22
-
-
Save PetkevichPavel/0c871f59fc825d689d4d4b9259b5988c to your computer and use it in GitHub Desktop.
Revisions
-
PetkevichPavel revised this gist
Aug 13, 2020 . 1 changed file with 33 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,33 @@ abstract class BaseEpoxyHolder : EpoxyHolder() { lateinit var view: View val context: Context get() = view.context @CallSuper override fun bindView(itemView: View) { view = itemView } protected fun <V : View> bind(id: Int): ReadOnlyProperty<BaseEpoxyHolder, V> = Lazy { holder: BaseEpoxyHolder, prop -> holder.view.findViewById(id) as V? ?: throw IllegalStateException("View ID $id for '${prop.name}' not found.") } private class Lazy<V>(private val initializer: (BaseEpoxyHolder, KProperty<*>) -> V) : ReadOnlyProperty<BaseEpoxyHolder, V> { private object EMPTY private var value: Any? = EMPTY /** * Taken from Kotterknife. * https://github.com/JakeWharton/kotterknife */ @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: BaseEpoxyHolder, property: KProperty<*>): V { if (value == EMPTY) value = initializer(thisRef, property) return value as V } } } -
PetkevichPavel revised this gist
Aug 13, 2020 . 3 changed files with 467 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,176 @@ package com.berider.app.common.utils import android.app.Activity import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.view.inputmethod.InputMethodManager import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.ActionBar import androidx.appcompat.app.AppCompatActivity import androidx.core.os.bundleOf import com.afollestad.materialdialogs.LayoutMode import com.afollestad.materialdialogs.customview.customView import com.berider.app.common.R import com.berider.app.common.navigation.Navigation import com.berider.app.common.sharedpref.Generic import com.berider.app.common.sharedpref.credential.CredentialsManager import kotlinx.android.synthetic.main.unauthorized_bottom_sheet.* import org.jetbrains.anko.contentView /** * Created by pavel.petkevich@skodaautodigilab.com on 12.March.2020 */ /** * AppCompatActivity extension function, make from primary colored action bar transparent. * @param isTransparent - Boolean, true-is transparent, otherwise primary colored. * @param showTile - Boolean, true for show title, otherwise do not show title. * @return The Activity's ActionBar, or null if it does not have one. */ fun AppCompatActivity.actionBarTransparent( isTransparent: Boolean = true, showTile: Boolean = false ) = supportActionBar?.apply { setBackgroundDrawable(getDrawable(if (isTransparent) R.color.transparent else R.color.colorOnPrimary)) setDisplayShowTitleEnabled(showTile) } /** * Actionbar extension, for hiding/showing action bar. * @param show - Boolean, true for show, otherwise false for hiding. */ fun ActionBar.showActionBar(show: Boolean = true) { if (show) show() else hide() } /** * AppCompatActivity extension function on registering onBack pressed callback. * @param onBackPressed - lambda function for overriding callback in calling place. * @return OnBackPressedCallback - on back pressed callback. */ fun AppCompatActivity.registerOnBackPressedListener(onBackPressed: () -> Unit): OnBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { onBackPressed.invoke() } }.also { onBackPressedDispatcher.addCallback(this, it) } /** * Activity extension function for safely of hiding the soft key board.s * @return Boolean - in case of hided true, and false if the keyboard wasn't open. */ fun Activity.hideKeyboard() = contentView?.windowToken?.let { wToken -> (getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager)?.hideSoftInputFromWindow(wToken, 0) } /** * Activity extension function for getting * Generic parcelable object from bundle. * @param bundleName - name of bundle. * @param argName - argument name. * @return T - generic parcelable object, nullable. */ fun <T : Parcelable> Activity.getBundleArg(bundleName: String, argName: String) = intent?.getBundleExtra(bundleName)?.getParcelable<T>(argName) /** * Activity extension function for getting argument with type Any from bundle. * @param bundleName - name of bundle. * @param argName - argument name. * @return Any - [Any] type, nullable. */ fun Activity.getBundleArg(bundleName: String, argName: String) = intent?.getBundleExtra(bundleName)?.get(argName) /** * Activity extension function for getting bundle name from name of ::class.java. * @return bundle name. */ fun Activity.getBundleName() = "${this::class.java.name}-bundle" /** * Class<T> extension function for getting bundle name from name of Class<T>. * @return bundle name. */ fun <T> Class<T>.getBundleName() = "$name-bundle" /** * Activity extension function, for getting bundle with [bundleName] and [argKeys]. * @return bundle - Returns a new [Bundle] with the given key/value pairs as elements. */ fun Activity.getBundleArgs(bundleName: String, vararg argKeys: String) = bundleOf().apply { argKeys.forEach { argKey -> intent?.getBundleExtra(bundleName)?.get(argKey)?.let { arg -> putAny(argKey, arg) } } } /** * Bundle extension function for putting Any [arg] in correct data type. * @param argKey - key for bundle element. * @param arg - bundle element. * @return Boolean - false in case of value is not supported otherwise true. */ fun Bundle.putAny(argKey: String, arg: Any): Boolean { when { Generic<String>().checkType(arg) -> (arg as? String)?.let { putString(argKey, it) } Generic<Boolean>().checkType(arg) -> (arg as? Boolean)?.let { putBoolean(argKey, it) } Generic<Float>().checkType(arg) -> (arg as? Float)?.let { putFloat(argKey, it) } Generic<Int>().checkType(arg) -> (arg as? Int)?.let { putInt(argKey, it) } Generic<Long>().checkType(arg) -> (arg as? Long)?.let { putLong(argKey, it) } else -> return false } return true } /** * Activity extension, finish activity with result. * @param result - Integer by default [Activity.RESULT_OK], which you can use also you will find here [Activity]. * @param intent - in case you want to return some data use the intent, by default is null. */ fun Activity.finishWithResult(result: Int = Activity.RESULT_OK, intent: Intent? = null) { setResult(result, intent) finish() } /** * Activity extension function, for rendering unauthorized state or continue [block] if the user authorized. * @param credentials - [CredentialsManager]. * @param navigation - [Navigation] - for navigating. */ fun Activity.renderUnauthorizedState(credentials: CredentialsManager, navigation: Navigation, block: () -> Unit) { if (credentials.hasValidCredentials()) block() else unauthorizedBottomSheet(navigation) } /** * Activity extension function, for showing unauthorized bottom sheet. * @param navigation - for navigating into specific sections of Auth Flow [Navigation]. */ fun Activity.unauthorizedBottomSheet(navigation: Navigation) { showBottomSheet(layoutMode = LayoutMode.WRAP_CONTENT) { md -> md.customView(R.layout.unauthorized_bottom_sheet).apply { btnUnauthorizedLogin?.setOnClickListener { navigation.navigateToLogin(this@unauthorizedBottomSheet) md.cancel() } btnUnauthorizedSingUp?.setOnClickListener { navigation.navigateToRegistration(this@unauthorizedBottomSheet) md.cancel() } } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,195 @@ package com.berider.app.common.utils import android.app.Activity import android.content.SharedPreferences import android.net.Uri import com.berider.app.common.BuildConfig import com.berider.app.common.base.BaseConstant import com.berider.app.common.sharedpref.Generic import com.google.crypto.tink.subtle.Hex import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody import timber.log.Timber import java.io.File import java.lang.Enum.valueOf import java.util.* import javax.crypto.KeyGenerator import kotlin.reflect.KVisibility import kotlin.reflect.full.memberProperties /** * Created by pavel.petkevich@skodaautodigilab.com on 18.March.2020 */ /** * Executes given block and returns it's return value of null on case of some exception. */ fun <T> safe(block: () -> T): T? = try { block.invoke() } catch (e: Exception) { Timber.i(e) null } /** * Function for generating key according to [algorithm] and with specific [length]. * @param algorithm - by default is HmacMD5. * @param length - by default is 56 chars. * @return String - Hex encoded key as a String value. */ fun generateEncKey(algorithm: String = "HmacMD5", length: Int = 56) = KeyGenerator.getInstance(algorithm)?.apply { init(length) }?.let { Hex.encode(it.generateKey().encoded) }.orEmpty() /** * Activity extension function. * Prepare file part for multipart. * @param fileName - name of file in multipart. * @param uri - uri to the file. * @return MultipartBody.Part - returns part can be null. */ fun Activity.prepareFilePart(fileName: String, uri: Uri): MultipartBody.Part? = uri.path?.let { path -> File(path).let { file -> MultipartBody.Part.createFormData( fileName, file.name, file.asRequestBody(contentResolver.getType(uri)?.toMediaTypeOrNull()) ) } } /** * String extension function for creating a RequestBody from string. * @param mediaTypeStr - media type string which will be transform into toMediaTypeOrNull, by default is [BaseConstants.MediaType.TEXT_PLAIN]. * @return RequestBody - request body that transmits this string. */ fun String.createPart(mediaTypeStr: String = BaseConstants.MediaType.TEXT_PLAIN): RequestBody = this.toRequestBody(mediaTypeStr.toMediaTypeOrNull()) /** * Any extension function which get string from Class<T> parameter via locale. * @param paramName - Class<T> parameter name for getting value. * @param locale - is optional, by default using [Locale.getDefault]. * @return string - parameter value as a string. */ fun Any.getStringViaLocale(paramName: String, locale: Locale? = Locale.getDefault()) = "${this.getParameter( if (listOf(*BuildConfig.APP_LOCALES).contains(locale?.language)) { "${paramName}_${locale?.language}" } else "${paramName}_${Locale.ENGLISH.language}" )}" /** * Any extension reflection function for return field data of Any object via field name. * @param paramName - Class<T> parameter name for getting value. * @return Any - field data of Any object via field name, can be null. */ fun Any.getParameter(paramName: String): Any? { this::class.memberProperties.forEach { property -> property.takeIf { it.visibility == KVisibility.PUBLIC }?.apply { if (name == paramName) return getter.call(this@getParameter) } } return null } /** * Int extension function, for checking if the index is the first element of Array/List. * @param block - lambda function, invoked in case of the first element. */ fun Int.isFirst(block: () -> Unit) = if (this == 0) block() else null /** * Map<String, String> extension function, where <[Locale.getLanguage], String>. * @param locale - is optional, by default using [Locale.getDefault]. * @return String - return prepared string or null. */ fun Map<String, String>.getItemViaLocale(locale: Locale? = Locale.getDefault()) = if (listOf(*BuildConfig.APP_LOCALES).contains(locale?.language)) { this[locale?.language] } else this[Locale.ENGLISH.language] /** * String extension function, where string is a currency code as a String. * @return currency symbol or empty string. */ fun String?.getCurrencySymbolOrEmpty() = safe { this?.takeIf { it.isNotBlank() }?.run { Currency.getInstance(this)?.symbol } ?: defaultCurrencyCode } ?: defaultCurrencyCode /** * Generic Enum valueOf function, which is via [value] safely return value as [T] or null using [safe] block. * @param value - string value. * @return T? - returns [T] or null. */ inline fun <reified T : Enum<T>> enumValueOf(value: String): T? = safe { valueOf(T::class.java, value) } /** * Generic T extension Infix function, for returning T or [otherVal] in case of T is null. * @param otherVal - otherVal to return. * @return T - should be always returns [T]. */ infix fun <T> T?.or(otherVal: T) = this ?: otherVal /** * Generic T extension function, for returning T or [otherVal] according to [isDefVal]. * @param otherVal - otherVal to return. * @param isDefVal - is true returns current value else [otherVal]. * @return T - should be always returns [T]. */ fun <T> T.or(otherVal: T, isDefVal: Boolean) = if (isDefVal) this else otherVal /** * General Boolean? extension function. * @return returns value(true/false) or in case of the value is null returns false. */ fun Boolean?.orFalse() = this ?: false /** * SharedPreferences.Editor extension function for putting [value] as Any into Shared preferences. * @param key - key for the value. * @param value - value as Any. */ @Suppress("UNCHECKED_CAST") fun SharedPreferences.Editor.putAny(key: String, value: Any) { when { Generic<String>().checkType(value) -> (value as? String)?.let { putString(key, it) } Generic<Boolean>().checkType(value) -> (value as? Boolean)?.let { putBoolean(key, it) } Generic<Float>().checkType(value) -> (value as? Float)?.let { putFloat(key, it) } Generic<Int>().checkType(value) -> (value as? Int)?.let { putInt(key, it) } Generic<Long>().checkType(value) -> (value as? Long)?.let { putLong(key, it) } Generic<Set<String>>().checkType(value) -> (value as? Set<String>)?.let { putStringSet(key, it) } } } /** * Boolean extension function with generic Params and returned value, for choosing the value via condition. * @param first - first value of DT returns in case the true. * @param second - second value of DT returns in case the false. */ fun <DT> Boolean.chooseByCondition(first: DT, second: DT) = takeIf { this }?.run { first } ?: second /** * T generic extension function, which returns value according to [condition], in case the condition is true will return [value] otherwise [T]. * @param value - value which has to be returned in case the condition is true. * @param condition - condition for processing the result of the function. */ fun <T> T.orWith(value: T, condition: Boolean) = takeIf { condition }?.run { value } ?: this /** * T generic extension function, which returns T as returned value or returns it in callback [block]. * @param condition - condition for the if, under which method will returns null & no triggering [block] callback. * @param block - returns the T as it is only in case the condition is true. * @return T - returning always T if it satisfies the given [condition] otherwise null. */ fun <T> T.continueWithIf(condition: Boolean, block: (T.() -> Unit?)? = null) = this.takeIf { condition }?.apply { block?.invoke(this@continueWithIf) } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,96 @@ package com.berider.app.common.utils import android.Manifest.permission.* import android.app.Activity import android.content.Context import android.content.pm.PackageManager import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.berider.app.analytics.base.AnalyticsService import com.berider.app.analytics.base.Event import com.berider.app.common.utils.PermissionConstants.REQUEST_LOCATION import com.berider.app.common.utils.PermissionConstants.locationPermissions /** * Created by pavel.petkevich@skodaautodigilab.com on 20.March.2020 */ object PermissionConstants { const val REQUEST_STORAGE = 1000 const val REQUEST_CAMERA = 1001 const val REQUEST_LOCATION = 1002 val permissionMap = mapOf( CAMERA to REQUEST_CAMERA, READ_EXTERNAL_STORAGE to REQUEST_STORAGE, ACCESS_FINE_LOCATION to REQUEST_LOCATION ) internal val locationPermissions = arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) } /** * Context extension function, check if permission is gived or not. * @return Boolean - true in case of gived otherwise false. */ fun Context.isLocationPermissionGranted() = ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED /** * Fragment extension function for running code with following [permission]. * @param permission - manifest permission. * @param granted - lambda function invoked in case of permission already granted. * @param showRationale - lambda function invoked in case of needs to show rationale before requesting [permission]. */ fun Fragment.runWithPermission( permission: String, granted: () -> Unit, analyticsService: AnalyticsService, showRationale: (() -> Unit)? = null ) { activity?.let { act -> when { ContextCompat.checkSelfPermission(act, permission) == PackageManager.PERMISSION_GRANTED -> granted.invoke() ActivityCompat.shouldShowRequestPermissionRationale( act, READ_CONTACTS // TODO PermissionRationale: ignoring(set it on READ_CONTACTS) replace with [permission]. ) -> showRationale?.invoke() else -> PermissionConstants.permissionMap[permission]?.let { analyticsService.logEvent(Event.Names.GLOBAL_PERMISSION.name, Event.Parameters.TYPE.name to permission) requestPermissions(it.getPermissions(permission), it) } } } } /** * Activity extension function for running code with following [permission]. * @param permission - manifest permission. * @param granted - lambda function invoked in case of permission already granted. * @param showRationale - lambda function invoked in case of needs to show rationale before requesting [permission]. */ fun Activity.runWithPermission( permission: String, granted: (() -> Unit)? = null, analyticsService: AnalyticsService, showRationale: (() -> Unit)? = null ) { when { ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -> granted?.invoke() ActivityCompat.shouldShowRequestPermissionRationale( this, READ_CONTACTS // TODO PermissionRationale: ignoring(set it on READ_CONTACTS) replace with [permission]. ) -> showRationale?.invoke() else -> PermissionConstants.permissionMap[permission]?.let { analyticsService.logEvent(Event.Names.GLOBAL_PERMISSION.name, Event.Parameters.TYPE.name to permission) requestPermissions(it.getPermissions(permission), it) } } } private fun Int.getPermissions(permission: String) = when (this) { REQUEST_LOCATION -> locationPermissions else -> arrayOf(permission) } -
PetkevichPavel revised this gist
Aug 13, 2020 . 2 changed files with 150 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,72 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/onbMainConstraint" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.berider.app.onboarding.ui.OnboardingActivity"> <Button android:id="@+id/onbSkipBtn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/content_4" android:layout_marginEnd="@dimen/content_4" android:background="?attr/selectableItemBackgroundBorderless" android:text="@string/general_skip" android:textAllCaps="false" android:textColor="@color/base_black" android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> <com.airbnb.epoxy.EpoxyRecyclerView android:id="@+id/epoxyRecyclerView" android:layout_width="0dp" android:layout_height="0dp" android:overScrollMode="never" app:layout_constraintBottom_toTopOf="@+id/guidelineContent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/onboarding_page" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guidelineContent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.8" /> <me.relex.circleindicator.CircleIndicator2 android:id="@+id/onbIndicator" android:layout_width="wrap_content" android:layout_height="@dimen/content_32" app:ci_drawable="@drawable/black_ci" app:layout_constraintBottom_toTopOf="@+id/onbMainBtn" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guidelineContent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guidelineBtnWidth" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" app:layout_constraintGuide_percent="0.55" /> <com.google.android.material.button.MaterialButton android:id="@+id/onbMainBtn" style="@style/BaseOutlineButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_margin="@dimen/content_16" android:text="@string/general_next" android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/guidelineBtnWidth" /> </androidx.constraintlayout.widget.ConstraintLayout> This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,78 @@ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/transparent"> <ImageView android:id="@+id/imgOnbPage" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" app:layout_constraintBottom_toTopOf="@+id/guidelineImg" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="ContentDescription" /> <ProgressBar android:id="@+id/progressOnb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintBottom_toBottomOf="@+id/imgOnbPage" app:layout_constraintEnd_toEndOf="@+id/imgOnbPage" app:layout_constraintStart_toStartOf="@+id/imgOnbPage" app:layout_constraintTop_toTopOf="@+id/imgOnbPage" tools:visibility="visible" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guidelineImg" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_percent="0.6" /> <TextView android:id="@+id/txtOnbTitle" android:layout_width="0dp" android:layout_height="wrap_content" android:fontFamily="sans-serif" android:letterSpacing="0.01" android:lineSpacingExtra="-4sp" android:paddingStart="@dimen/content_16" android:paddingTop="@dimen/content_16" android:paddingEnd="@dimen/content_16" android:textColor="#de000000" android:textSize="28sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guidelineImg" tools:text="Jezděte opatrně!" /> <TextView android:id="@+id/txtOnbContent" android:layout_width="0dp" android:layout_height="0dp" android:fontFamily="sans-serif" android:letterSpacing="0.03" android:lineSpacingExtra="8sp" android:padding="@dimen/content_16" android:textColor="#de000000" android:textSize="16sp" android:textStyle="normal" app:autoSizeMaxTextSize="16sp" app:autoSizeMinTextSize="10sp" app:autoSizeStepGranularity="1sp" app:autoSizeTextType="uniform" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/txtOnbTitle" tools:text="Dodržujte dopravní předpisy a buďte při jízdě obzvlášť opatrní. Vaše bezpečí je pro nás důležité." /> </androidx.constraintlayout.widget.ConstraintLayout> -
PetkevichPavel created this gist
Aug 13, 2020 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,135 @@ 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())) } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,50 @@ 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) } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,21 @@ 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) } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,62 @@ 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) } } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,33 @@ 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 ), } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,44 @@ 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), } }