Last active
September 2, 2020 14:22
-
-
Save PetkevichPavel/0c871f59fc825d689d4d4b9259b5988c to your computer and use it in GitHub Desktop.
onboarding
This file contains 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 characters
<?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 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 characters
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 [email protected] 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 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 characters
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 | |
} | |
} | |
} |
This file contains 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 characters
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 [email protected] 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 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 characters
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 [email protected] 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 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 characters
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 [email protected] 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), | |
} | |
} |
This file contains 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 characters
<?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> |
This file contains 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 characters
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 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 characters
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 [email protected] 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 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 characters
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 [email protected] 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 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 characters
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 [email protected] 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 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 characters
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 [email protected] 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) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment