├── .gitignore ├── .idea ├── .gitignore ├── .name ├── appInsightsSettings.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── kotlinc.xml ├── migrations.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── appmason │ │ └── jetplayground │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── appmason │ │ │ └── jetplayground │ │ │ ├── MainActivity.kt │ │ │ ├── otp_verifier │ │ │ ├── receiver │ │ │ │ └── OTPReceiver.kt │ │ │ └── util │ │ │ │ └── AppSignatureHelper.kt │ │ │ └── ui │ │ │ ├── components │ │ │ ├── AutoFill.kt │ │ │ └── OtpInputField.kt │ │ │ ├── navigation │ │ │ ├── NavGraph.kt │ │ │ └── Screens.kt │ │ │ ├── screens │ │ │ ├── OtpScreen.kt │ │ │ └── SplashScreen.kt │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable │ │ ├── ic_back.xml │ │ ├── ic_launcher_background.xml │ │ └── ic_launcher_foreground.xml │ │ ├── mipmap-anydpi │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── raw │ │ └── loading.json │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── appmason │ └── jetplayground │ └── ExampleUnitTest.kt ├── art └── screens │ ├── otp_autofill.png │ ├── otp_ui.png │ └── otp_verification.gif ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ComposeOtpVerify -------------------------------------------------------------------------------- /.idea/appInsightsSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 26 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OTP Verification with Jetpack Compose 2 | 3 | This repository contains a custom-built OTP (One Time Password) Input Field and integration with [Google's SMS Retriever API](https://developers.google.com/identity/sms-retriever/overview) 4 | for OTP fetch and populate functionality, developed using Jetpack Compose. This component is designed to cater to modern Android applications requiring OTP verification, offering a blend of customization and ease of use. 5 | 6 | ![Compose](https://img.shields.io/badge/Compose_BOM-1.5.4-blue.svg?color=blue&style=for-the-badge) 7 | ![Kotlin](https://img.shields.io/badge/Kotlin-1.9.0-blue.svg?color=blue&style=for-the-badge) 8 | 9 | > This is not a library. This is a demo project to experiment and try out a functionality. 10 | 11 | ### Screenshots 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
OTP VerificationOTP Screen UI
OTP VerificationOTP Screen UI
23 | 24 | ### Features 25 | - Integration with [Google's SMS Retriever API](https://developers.google.com/identity/sms-retriever/overview) to auto verify OTP and populate. 26 | - OTP input field can be set up for any length of OTP. 27 | - Supports automatic filling of the OTP, a convenient feature for OTPs received from servers or other sources. 28 | - Offers options to show a cursor, and control its blinking, enhancing the user experience. 29 | 30 | ### Note on Compose AutoFill 31 | 32 | > Right now Jetpack Compose does not have good support for [Autofill](https://developer.android.com/guide/topics/text/autofill). 33 | > There have been some temporary solutions like [ExplicitAutofillTypesDemo.kt](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt). 34 | > Google is working to add support and we may have it in the future. Autofill will benefit from filling fields like name, emails, credit card numbers, and OTPs from IME directly. 35 | > 36 | > Open issues related to Compose Autofill: 37 | > - https://issuetracker.google.com/issues/268596603 38 | > - https://issuetracker.google.com/issues/265911809 39 | > - https://issuetracker.google.com/issues/176949051 40 | 41 | I did some experiments with the temporary solution and looks like this. 42 | But it does not work smoothly and has many issues: 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |
Compose Autofill
OTP Autofill
52 | 53 | ### Reference 54 | 55 | Thanks to these resources which helped me to learn the concepts: 56 | - https://proandroiddev.com/jetpack-compose-otp-input-field-bcfa22c85e5f 57 | - https://medium.com/@aayan.talukdar/android-automatic-otp-retrieval-in-android-and-jetpackcompose-17be181909a5 58 | - https://bryanherbst.com/2021/04/13/compose-autofill/ 59 | 60 | 61 | ### Find this project useful? 62 | 63 | - Support it by clicking the ⭐️ button on the upper right of this page. ✌️ 64 | 65 | ### License 66 | 67 | ``` 68 | MIT License 69 | 70 | Copyright (c) 2024 Pushpal Roy 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a 73 | copy of this software and associated documentation files (the "Software"), 74 | to deal in the Software without restriction, including without limitation 75 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 76 | and/or sell copies of the Software, and to permit persons to whom the 77 | Software is furnished to do so, subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included 80 | in all copies or substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 85 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 86 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 87 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 88 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("org.jetbrains.kotlin.android") 4 | } 5 | 6 | android { 7 | namespace = "com.appmason.composeotpverify" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.appmason.composeotpverify" 12 | minSdk = 28 13 | targetSdk = 34 14 | versionCode = 1 15 | versionName = "1.0" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary = true 20 | } 21 | } 22 | 23 | buildTypes { 24 | release { 25 | isMinifyEnabled = false 26 | proguardFiles( 27 | getDefaultProguardFile("proguard-android-optimize.txt"), 28 | "proguard-rules.pro" 29 | ) 30 | } 31 | } 32 | compileOptions { 33 | sourceCompatibility = JavaVersion.VERSION_1_8 34 | targetCompatibility = JavaVersion.VERSION_1_8 35 | } 36 | kotlinOptions { 37 | jvmTarget = "1.8" 38 | } 39 | buildFeatures { 40 | compose = true 41 | } 42 | composeOptions { 43 | kotlinCompilerExtensionVersion = "1.5.1" 44 | } 45 | packaging { 46 | resources { 47 | excludes += "/META-INF/{AL2.0,LGPL2.1}" 48 | } 49 | } 50 | } 51 | 52 | dependencies { 53 | 54 | implementation("androidx.core:core-ktx:1.12.0") 55 | implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") 56 | implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0") 57 | implementation("androidx.activity:activity-compose:1.8.2") 58 | implementation(platform("androidx.compose:compose-bom:2023.10.01")) 59 | implementation("androidx.compose.ui:ui") 60 | implementation("androidx.compose.ui:ui-graphics") 61 | implementation("androidx.compose.ui:ui-tooling-preview") 62 | implementation("androidx.compose.material3:material3:1.1.2") 63 | implementation("com.airbnb.android:lottie-compose:6.3.0") 64 | implementation("androidx.navigation:navigation-compose:2.7.6") 65 | implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7") 66 | 67 | //SMSRetrieval API Dependencies for Auto OTP Verification 68 | implementation("com.google.android.gms:play-services-auth-api-phone:18.0.1") 69 | implementation("com.google.android.gms:play-services-auth:20.7.0") 70 | 71 | testImplementation("junit:junit:4.13.2") 72 | androidTestImplementation("androidx.test.ext:junit:1.1.5") 73 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") 74 | androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) 75 | androidTestImplementation("androidx.compose.ui:ui-test-junit4") 76 | debugImplementation("androidx.compose.ui:ui-tooling") 77 | debugImplementation("androidx.compose.ui:ui-test-manifest") 78 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/androidTest/java/com/appmason/jetplayground/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.appmason.jetplayground", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | import androidx.navigation.compose.rememberNavController 8 | import com.appmason.jetplayground.otp_verifier.util.AppSignatureHelper 9 | import com.appmason.jetplayground.ui.navigation.SetupNavGraph 10 | import com.appmason.jetplayground.ui.theme.JetPlaygroundTheme 11 | 12 | class MainActivity : ComponentActivity() { 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | 16 | /** 17 | * For OTP verification 18 | * 19 | * For development, once the app signature has is fetched, a OTP can be sent from any device like: 20 | * "DO NOT SHARE: Your JetPlayground OTP code is 643908. Message ID: j7fPi8fNhTk." 21 | * 22 | * Here "j7fPi8fNhTk" is the unique hash for this app which needs to be added below the SMS. 23 | * Without the correct hash, your app won't receive the message callback. This only needs to be 24 | * generated once per app and stored. 25 | * 26 | * For production app, use keytool to generate the hash: 27 | * https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string 28 | */ 29 | val signatureHelper = AppSignatureHelper(this) 30 | val appSignatures = signatureHelper.appSignatures 31 | for (signature in appSignatures) { 32 | Log.e("Otp", "App Signature for OTP: $signature") 33 | } 34 | 35 | setContent { 36 | JetPlaygroundTheme { 37 | val navController = rememberNavController() 38 | SetupNavGraph(navController = navController) 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/otp_verifier/receiver/OTPReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.otp_verifier.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.util.Log 8 | import com.google.android.gms.auth.api.phone.SmsRetriever 9 | import com.google.android.gms.auth.api.phone.SmsRetrieverClient 10 | import com.google.android.gms.common.api.CommonStatusCodes 11 | import com.google.android.gms.common.api.Status 12 | 13 | /** 14 | * BroadcastReceiver to wait for SMS messages. This can be registered either 15 | * in the AndroidManifest or at runtime. Should filter Intents on 16 | * SmsRetriever.SMS_RETRIEVED_ACTION. 17 | * 18 | * TODO: Working in Android 13 (API 33) and 14 (API 34), but not working in Android 12 (API 31) 19 | * 20 | */ 21 | class OTPReceiver : BroadcastReceiver() { 22 | private var otpReceiveListener: OTPReceiveListener? = null 23 | 24 | fun init(otpReceiveListener: OTPReceiveListener?) { 25 | this.otpReceiveListener = otpReceiveListener 26 | } 27 | 28 | override fun onReceive(context: Context?, intent: Intent?) { 29 | if (SmsRetriever.SMS_RETRIEVED_ACTION == intent?.action) { 30 | val extras: Bundle? = intent.extras 31 | val status: Status = extras?.get(SmsRetriever.EXTRA_STATUS) as Status 32 | 33 | when (status.statusCode) { 34 | CommonStatusCodes.SUCCESS -> { 35 | // Get SMS message contents 36 | val msg = extras.getString(SmsRetriever.EXTRA_SMS_MESSAGE) as String 37 | Log.e("OTPReceiver", "SMS Received in OTPReceiver: $msg") 38 | 39 | // extract the 6-digit code from the SMS 40 | val smsCode = msg.let { "[0-9]{6}".toRegex().find(it) } 41 | Log.e("OTPReceiver", "OTP fetched from SMS in OTPReceiver: $smsCode") 42 | 43 | smsCode?.value?.let { otpReceiveListener?.onOTPReceived(it) } 44 | } 45 | 46 | CommonStatusCodes.TIMEOUT -> { 47 | otpReceiveListener?.onOTPTimeOut() 48 | } 49 | } 50 | } 51 | } 52 | 53 | interface OTPReceiveListener { 54 | fun onOTPReceived(otp: String?) 55 | fun onOTPTimeOut() 56 | } 57 | } 58 | 59 | fun startSMSRetrieverClient(context: Context) { 60 | val client: SmsRetrieverClient = SmsRetriever.getClient(context) 61 | val smsRetrieverTask = client.startSmsRetriever() 62 | smsRetrieverTask.addOnSuccessListener { 63 | Log.e("OTPReceiver", "startSMSRetrieverClient addOnSuccessListener") 64 | } 65 | smsRetrieverTask.addOnFailureListener { e -> 66 | Log.e("OTPReceiver", "startSMSRetrieverClient addOnFailureListener" + e.stackTrace) 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/otp_verifier/util/AppSignatureHelper.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.otp_verifier.util 2 | 3 | import android.content.Context 4 | import android.content.ContextWrapper 5 | import android.content.pm.PackageInfo 6 | import android.content.pm.PackageManager 7 | import android.util.Log 8 | import java.nio.charset.StandardCharsets 9 | import java.security.MessageDigest 10 | import java.security.NoSuchAlgorithmException 11 | import java.util.Arrays 12 | import android.content.pm.Signature; 13 | import android.util.Base64; 14 | 15 | /** 16 | * DO NOT USE THIS IN PRODUCTION 17 | * 18 | * This class is only for development purpose. For production app, use keytool to generate the hash: 19 | * https://developers.google.com/identity/sms-retriever/verify#computing_your_apps_hash_string 20 | * 21 | * This is a helper class to generate your message hash to be included in your SMS message. 22 | * Once the hash has been generated this class can be deleted. 23 | * 24 | * Without the correct hash, your app won't receive the message callback. This only needs to be 25 | * generated once per app and stored. Then you can remove this helper class from your code. 26 | */ 27 | class AppSignatureHelper(context: Context?) : ContextWrapper(context) { 28 | val appSignatures: ArrayList 29 | /** 30 | * Get all the app signatures for the current package 31 | * @return 32 | */ 33 | get() { 34 | val appCodes = ArrayList() 35 | try { 36 | // Get all package signatures for the current package 37 | val packageName = packageName 38 | val packageManager = packageManager 39 | val packageInfo: PackageInfo = 40 | packageManager.getPackageInfo( 41 | packageName, 42 | PackageManager.GET_SIGNING_CERTIFICATES 43 | ) 44 | 45 | val signatures: Array = if (packageInfo.signingInfo != null) { 46 | // New method (API level 28 and above) 47 | if (packageInfo.signingInfo.hasMultipleSigners()) { 48 | // Handle multiple signers if necessary 49 | packageInfo.signingInfo.apkContentsSigners 50 | } else { 51 | // Single signer 52 | packageInfo.signingInfo.signingCertificateHistory 53 | } 54 | } else { 55 | // Old method (deprecated) 56 | packageInfo.signatures 57 | } 58 | 59 | // For each signature create a compatible hash 60 | for (signature in signatures) { 61 | val hash = hash(packageName, signature.toCharsString()) 62 | if (hash != null) { 63 | appCodes.add(String.format("%s", hash)) 64 | } 65 | } 66 | } catch (e: PackageManager.NameNotFoundException) { 67 | Log.e(TAG, "Unable to find package to obtain hash.", e) 68 | } 69 | return appCodes 70 | } 71 | 72 | companion object { 73 | val TAG = AppSignatureHelper::class.java.simpleName 74 | private const val HASH_TYPE = "SHA-256" 75 | private const val NUM_HASHED_BYTES = 9 76 | private const val NUM_BASE64_CHAR = 11 77 | private fun hash(packageName: String, signature: String): String? { 78 | val appInfo = "$packageName $signature" 79 | try { 80 | val messageDigest = MessageDigest.getInstance(HASH_TYPE) 81 | messageDigest.update(appInfo.toByteArray(StandardCharsets.UTF_8)) 82 | var hashSignature = messageDigest.digest() 83 | 84 | // truncated into NUM_HASHED_BYTES 85 | hashSignature = Arrays.copyOfRange(hashSignature, 0, NUM_HASHED_BYTES) 86 | // encode into Base64 87 | var base64Hash: String = 88 | Base64.encodeToString(hashSignature, Base64.NO_PADDING or Base64.NO_WRAP) 89 | base64Hash = base64Hash.substring(0, NUM_BASE64_CHAR) 90 | Log.d(TAG, String.format("pkg: %s -- hash: %s", packageName, base64Hash)) 91 | return base64Hash 92 | } catch (e: NoSuchAlgorithmException) { 93 | Log.e(TAG, "hash:NoSuchAlgorithm", e) 94 | } 95 | return null 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/components/AutoFill.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.ui.ExperimentalComposeUiApi 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.autofill.AutofillNode 8 | import androidx.compose.ui.autofill.AutofillType 9 | import androidx.compose.ui.composed 10 | import androidx.compose.ui.focus.onFocusChanged 11 | import androidx.compose.ui.layout.boundsInWindow 12 | import androidx.compose.ui.layout.onGloballyPositioned 13 | import androidx.compose.ui.platform.LocalAutofill 14 | import androidx.compose.ui.platform.LocalAutofillTree 15 | 16 | /** 17 | * Referred from: 18 | * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/autofill/ExplicitAutofillTypesDemo.kt 19 | * 20 | * We do not have a proper support for Autofill in Compose right now. 21 | * 22 | * Google is working to add support: 23 | * https://issuetracker.google.com/issues/268596603 24 | * https://issuetracker.google.com/issues/265911809 25 | * https://issuetracker.google.com/issues/176949051 26 | */ 27 | @OptIn(ExperimentalComposeUiApi::class) 28 | @Composable 29 | fun Autofill( 30 | autofillTypes: List, 31 | onFill: ((String) -> Unit), 32 | content: @Composable (AutofillNode) -> Unit 33 | ) { 34 | val autofillNode = AutofillNode( 35 | onFill = onFill, 36 | autofillTypes = autofillTypes 37 | ) 38 | 39 | val autofillTree = LocalAutofillTree.current 40 | autofillTree += autofillNode 41 | 42 | Box( 43 | Modifier.onGloballyPositioned { 44 | autofillNode.boundingBox = it.boundsInWindow() 45 | } 46 | ) { 47 | content(autofillNode) 48 | } 49 | } 50 | 51 | /** 52 | * Provides autofill to a view. 53 | * 54 | * This should be removed once compose completely supports autofill nicely. 55 | * Google is working to add support: 56 | * https://issuetracker.google.com/issues/268596603 57 | * https://issuetracker.google.com/issues/265911809 58 | * https://issuetracker.google.com/issues/176949051 59 | */ 60 | @OptIn(ExperimentalComposeUiApi::class) 61 | fun Modifier.autofill( 62 | autofillTypes: List, 63 | onFill: ((String) -> Unit), 64 | ) = composed { 65 | val autofill = LocalAutofill.current 66 | val autofillNode = AutofillNode(onFill = onFill, autofillTypes = autofillTypes) 67 | LocalAutofillTree.current += autofillNode 68 | this 69 | .onGloballyPositioned { 70 | autofillNode.boundingBox = it.boundsInWindow() 71 | } 72 | .onFocusChanged { focusState -> 73 | autofill?.run { 74 | if (focusState.isFocused) { 75 | requestAutofillForNode(autofillNode) 76 | } else { 77 | cancelAutofillForNode(autofillNode) 78 | } 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/components/OtpInputField.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.components 2 | 3 | import androidx.compose.animation.AnimatedVisibility 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.border 6 | import androidx.compose.foundation.layout.Arrangement 7 | import androidx.compose.foundation.layout.Box 8 | import androidx.compose.foundation.layout.Row 9 | import androidx.compose.foundation.layout.Spacer 10 | import androidx.compose.foundation.layout.height 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.layout.width 13 | import androidx.compose.foundation.shape.RoundedCornerShape 14 | import androidx.compose.foundation.text.BasicTextField 15 | import androidx.compose.foundation.text.KeyboardOptions 16 | import androidx.compose.material3.MaterialTheme 17 | import androidx.compose.material3.Text 18 | import androidx.compose.runtime.Composable 19 | import androidx.compose.runtime.LaunchedEffect 20 | import androidx.compose.runtime.mutableStateOf 21 | import androidx.compose.runtime.remember 22 | import androidx.compose.ui.Alignment 23 | import androidx.compose.ui.Modifier 24 | import androidx.compose.ui.text.TextRange 25 | import androidx.compose.ui.text.input.ImeAction 26 | import androidx.compose.ui.text.input.KeyboardType 27 | import androidx.compose.ui.text.input.TextFieldValue 28 | import androidx.compose.ui.text.style.TextAlign 29 | import androidx.compose.ui.unit.dp 30 | import com.appmason.jetplayground.ui.theme.BorderDark 31 | import com.appmason.jetplayground.ui.theme.BorderLight 32 | import kotlinx.coroutines.delay 33 | 34 | /** 35 | * A composable function for creating an OTP input field. 36 | * 37 | * This OTP input field allows for the entry of a One Time Password (OTP) with a configurable number of characters. 38 | * It supports automatic population of OTP from different sources (e.g., server). 39 | * 40 | * @param modifier Modifier for styling and layout of the input field. 41 | * @param otpText The current text of the OTP input field. 42 | * @param otpLength The length of the OTP. Default is 6 characters. 43 | * @param shouldShowCursor Boolean flag to indicate if the cursor should be shown. 44 | * @param shouldCursorBlink Boolean flag to indicate if the cursor should blink. 45 | * @param onOtpModified Lambda function that is triggered when the OTP text changes. 46 | * It provides the updated text and a flag indicating if the OTP is complete. 47 | * @throws IllegalArgumentException if the initial otpText length is greater than otpLength. 48 | * 49 | * Usage example: 50 | * OtpInputField( 51 | * otpText = viewModel.otpText, 52 | * otpLength = 6, 53 | * onOtpTextChange = { otp, isComplete -> /* handle OTP change */ } 54 | * ) 55 | */ 56 | @Composable 57 | fun OtpInputField( 58 | modifier: Modifier = Modifier, 59 | otpText: String, 60 | otpLength: Int = 6, 61 | shouldShowCursor: Boolean = false, 62 | shouldCursorBlink: Boolean = false, 63 | onOtpModified: (String, Boolean) -> Unit 64 | ) { 65 | LaunchedEffect(Unit) { 66 | if (otpText.length > otpLength) { 67 | throw IllegalArgumentException("OTP should be $otpLength digits") 68 | } 69 | } 70 | BasicTextField( 71 | modifier = modifier, 72 | value = TextFieldValue(otpText, selection = TextRange(otpText.length)), 73 | onValueChange = { 74 | if (it.text.length <= otpLength) { 75 | onOtpModified.invoke(it.text, it.text.length == otpLength) 76 | } 77 | }, 78 | keyboardOptions = KeyboardOptions( 79 | keyboardType = KeyboardType.NumberPassword, 80 | imeAction = ImeAction.Done 81 | ), 82 | decorationBox = { 83 | Row(horizontalArrangement = Arrangement.Center) { 84 | repeat(otpLength) { index -> 85 | CharacterContainer( 86 | index = index, 87 | text = otpText, 88 | shouldShowCursor = shouldShowCursor, 89 | shouldCursorBlink = shouldCursorBlink, 90 | ) 91 | Spacer(modifier = Modifier.width(8.dp)) 92 | } 93 | } 94 | } 95 | ) 96 | } 97 | 98 | /** 99 | * An internal composable function used within [OtpInputField] to render individual character containers. 100 | * 101 | * Each character container displays a single character of the OTP and manages cursor visibility and blinking. 102 | * 103 | * @param index The position of this character in the OTP. 104 | * @param text The current text of the OTP input field. 105 | * @param shouldShowCursor Boolean flag to indicate if the cursor should be shown for this container. 106 | * @param shouldCursorBlink Boolean flag to indicate if the cursor should blink when shown. 107 | * 108 | * Note: This function cannot be used outside the context of [OtpInputField] as it is tailored to its specific use-case. 109 | */ 110 | @Composable 111 | internal fun CharacterContainer( 112 | index: Int, 113 | text: String, 114 | shouldShowCursor: Boolean, 115 | shouldCursorBlink: Boolean, 116 | ) { 117 | val isFocused = text.length == index 118 | val character = when { 119 | index < text.length -> text[index].toString() 120 | else -> "" 121 | } 122 | 123 | // Cursor visibility state 124 | val cursorVisible = remember { mutableStateOf(shouldShowCursor) } 125 | 126 | // Blinking effect for the cursor 127 | LaunchedEffect(key1 = isFocused) { 128 | if (isFocused && shouldShowCursor && shouldCursorBlink) { 129 | while (true) { 130 | delay(800) // Adjust the blinking speed here 131 | cursorVisible.value = !cursorVisible.value 132 | } 133 | } 134 | } 135 | 136 | Box(contentAlignment = Alignment.Center) { 137 | Text( 138 | modifier = Modifier 139 | .width(36.dp) 140 | .border( 141 | width = when { 142 | isFocused -> 2.dp 143 | else -> 1.dp 144 | }, 145 | color = when { 146 | isFocused -> BorderDark 147 | else -> BorderLight 148 | }, 149 | shape = RoundedCornerShape(6.dp) 150 | ) 151 | .padding(2.dp), 152 | text = character, 153 | style = MaterialTheme.typography.headlineLarge, 154 | color = if (isFocused) BorderLight else BorderDark, 155 | textAlign = TextAlign.Center 156 | ) 157 | 158 | // Display cursor when focused 159 | AnimatedVisibility(visible = isFocused && cursorVisible.value) { 160 | Box( 161 | modifier = Modifier 162 | .align(Alignment.Center) 163 | .width(2.dp) 164 | .height(24.dp) // Adjust height according to your design 165 | .background(BorderDark) 166 | ) 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/navigation/NavGraph.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavHostController 5 | import androidx.navigation.compose.NavHost 6 | import androidx.navigation.compose.composable 7 | import com.appmason.jetplayground.ui.screens.OtpScreen 8 | import com.appmason.jetplayground.ui.screens.SplashScreen 9 | 10 | @Composable 11 | fun SetupNavGraph(navController: NavHostController) { 12 | NavHost( 13 | navController = navController, 14 | startDestination = Screen.Splash.route 15 | ) { 16 | composable(route = Screen.Splash.route) { 17 | SplashScreen(navController = navController) 18 | } 19 | composable(route = Screen.OtpScreen.route) { 20 | OtpScreen(navController = navController) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/navigation/Screens.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.navigation 2 | 3 | sealed class Screen(val route: String) { 4 | data object Splash : Screen("splash_screen") 5 | data object OtpScreen : Screen("otp_screen") 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/screens/OtpScreen.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.screens 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.IntentFilter 6 | import android.os.Build 7 | import android.util.Log 8 | import androidx.annotation.RequiresApi 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.clickable 11 | import androidx.compose.foundation.layout.Box 12 | import androidx.compose.foundation.layout.Column 13 | import androidx.compose.foundation.layout.WindowInsets 14 | import androidx.compose.foundation.layout.WindowInsetsSides 15 | import androidx.compose.foundation.layout.fillMaxSize 16 | import androidx.compose.foundation.layout.fillMaxWidth 17 | import androidx.compose.foundation.layout.only 18 | import androidx.compose.foundation.layout.padding 19 | import androidx.compose.foundation.layout.size 20 | import androidx.compose.foundation.layout.systemBars 21 | import androidx.compose.material3.Button 22 | import androidx.compose.material3.CenterAlignedTopAppBar 23 | import androidx.compose.material3.ExperimentalMaterial3Api 24 | import androidx.compose.material3.Icon 25 | import androidx.compose.material3.MaterialTheme 26 | import androidx.compose.material3.Scaffold 27 | import androidx.compose.material3.Surface 28 | import androidx.compose.material3.Text 29 | import androidx.compose.material3.TopAppBarDefaults 30 | import androidx.compose.runtime.Composable 31 | import androidx.compose.runtime.LaunchedEffect 32 | import androidx.compose.runtime.getValue 33 | import androidx.compose.runtime.mutableStateOf 34 | import androidx.compose.runtime.remember 35 | import androidx.compose.runtime.setValue 36 | import androidx.compose.ui.Alignment 37 | import androidx.compose.ui.ExperimentalComposeUiApi 38 | import androidx.compose.ui.Modifier 39 | import androidx.compose.ui.draw.drawWithContent 40 | import androidx.compose.ui.focus.FocusRequester 41 | import androidx.compose.ui.focus.focusRequester 42 | import androidx.compose.ui.graphics.Color 43 | import androidx.compose.ui.graphics.CompositingStrategy 44 | import androidx.compose.ui.graphics.graphicsLayer 45 | import androidx.compose.ui.graphics.toArgb 46 | import androidx.compose.ui.platform.LocalContext 47 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController 48 | import androidx.compose.ui.platform.LocalView 49 | import androidx.compose.ui.res.painterResource 50 | import androidx.compose.ui.text.style.TextAlign 51 | import androidx.compose.ui.unit.dp 52 | import androidx.lifecycle.compose.LifecycleResumeEffect 53 | import androidx.navigation.NavHostController 54 | import com.appmason.composeotpverify.R 55 | import com.appmason.jetplayground.otp_verifier.receiver.OTPReceiver 56 | import com.appmason.jetplayground.otp_verifier.receiver.startSMSRetrieverClient 57 | import com.appmason.jetplayground.ui.components.OtpInputField 58 | import com.google.android.gms.auth.api.phone.SmsRetriever 59 | 60 | @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) 61 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 62 | @Composable 63 | fun OtpScreen(navController: NavHostController) { 64 | val context = LocalContext.current 65 | var otpValue by remember { mutableStateOf("") } 66 | var isOtpFilled by remember { mutableStateOf(false) } 67 | val focusRequester = remember { FocusRequester() } 68 | val keyboardController = LocalSoftwareKeyboardController.current 69 | 70 | /** 71 | * Right now we don't have support for Autofill in Compose. 72 | * See [com.appmason.jetplayground.ui.components.Autofill] for some temporary solutions. 73 | * 74 | * If we have support in the future and want user to autofill OTP from keyboard manually, 75 | * then we do not need to fetch OTP automatically using Google SMS Retriever API and in 76 | * that case, we can totally remove this [OtpReceiverEffect] and let Autofill handle it. 77 | * But Google SMS Retriever API is a great way anyways to fetch and populate OTP! 78 | */ 79 | OtpReceiverEffect( 80 | context = context, 81 | onOtpReceived = { otp -> 82 | otpValue = otp 83 | if (otpValue.length == 6) { 84 | keyboardController?.hide() 85 | isOtpFilled = true 86 | } 87 | } 88 | ) 89 | 90 | LaunchedEffect(Unit) { 91 | focusRequester.requestFocus() 92 | keyboardController?.show() 93 | } 94 | 95 | /** 96 | * Set status bar color for this screen 97 | */ 98 | (LocalView.current.context as Activity).window.statusBarColor = Color.White.toArgb() 99 | 100 | /** 101 | * OTP Screen UI starts here 102 | */ 103 | Scaffold( 104 | topBar = { 105 | CenterAlignedTopAppBar( 106 | modifier = Modifier 107 | .graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen } 108 | .drawWithContent { 109 | drawContent() 110 | }, 111 | navigationIcon = { 112 | Box( 113 | Modifier 114 | .size(48.dp) 115 | .clickable { navController.popBackStack() }) { 116 | Icon( 117 | painter = painterResource(id = R.drawable.ic_back), 118 | tint = Color.DarkGray, 119 | contentDescription = "Back", 120 | modifier = Modifier.align(Alignment.Center) 121 | ) 122 | } 123 | }, 124 | colors = TopAppBarDefaults.topAppBarColors( 125 | containerColor = Color.White, 126 | titleContentColor = Color.DarkGray, 127 | actionIconContentColor = Color.DarkGray 128 | ), 129 | title = { Text(text = "Enter One Time Password") }, 130 | windowInsets = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal) 131 | ) 132 | }, 133 | bottomBar = { 134 | Button( 135 | onClick = {}, 136 | enabled = isOtpFilled, 137 | modifier = Modifier 138 | .fillMaxWidth() 139 | .padding(24.dp), 140 | ) { 141 | Text(text = "Continue") 142 | } 143 | } 144 | ) { innerPadding -> 145 | Surface( 146 | modifier = Modifier 147 | .fillMaxSize() 148 | .background(Color.White) 149 | .padding(24.dp), 150 | color = Color.White 151 | ) { 152 | Column( 153 | modifier = Modifier 154 | .fillMaxWidth() 155 | .padding(innerPadding) 156 | ) { 157 | Text( 158 | modifier = Modifier 159 | .fillMaxWidth() 160 | .padding(24.dp, 0.dp), 161 | text = "Please verify your phone number with the OTP we sent to (***)***-2193.", 162 | style = MaterialTheme.typography.bodyMedium, 163 | color = Color.DarkGray, 164 | textAlign = TextAlign.Center 165 | ) 166 | Surface( 167 | modifier = Modifier 168 | .fillMaxWidth() 169 | .background(Color.White) 170 | .padding(24.dp), 171 | color = Color.White 172 | ) { 173 | OtpInputField( 174 | modifier = Modifier 175 | .padding(top = 48.dp) 176 | .focusRequester(focusRequester), 177 | otpText = otpValue, 178 | shouldCursorBlink = false, 179 | onOtpModified = { value, otpFilled -> 180 | otpValue = value 181 | isOtpFilled = otpFilled 182 | if (otpFilled) { 183 | keyboardController?.hide() 184 | } 185 | } 186 | ) 187 | } 188 | } 189 | } 190 | } 191 | 192 | } 193 | 194 | @RequiresApi(Build.VERSION_CODES.TIRAMISU) 195 | @Composable 196 | fun OtpReceiverEffect( 197 | context: Context, 198 | onOtpReceived: (String) -> Unit 199 | ) { 200 | val otpReceiver = remember { OTPReceiver() } 201 | 202 | /** 203 | * This function should not be used to listen for Lifecycle.Event.ON_DESTROY because Compose 204 | * stops recomposing after receiving a Lifecycle.Event.ON_STOP and will never be aware of an 205 | * ON_DESTROY to launch onEvent. 206 | * 207 | * This function should also not be used to launch tasks in response to callback events by way 208 | * of storing callback data as a Lifecycle.State in a MutableState. Instead, see currentStateAsState 209 | * to obtain a State that may be used to launch jobs in response to state changes. 210 | */ 211 | LifecycleResumeEffect { 212 | // add ON_RESUME effect here 213 | Log.e("OTPReceiverEffect", "SMS retrieval has been started.") 214 | startSMSRetrieverClient(context) 215 | otpReceiver.init(object : OTPReceiver.OTPReceiveListener { 216 | override fun onOTPReceived(otp: String?) { 217 | Log.e("OTPReceiverEffect ", "OTP Received: $otp") 218 | otp?.let { onOtpReceived(it) } 219 | try { 220 | Log.e("OTPReceiverEffect ", "Unregistering receiver") 221 | context.unregisterReceiver(otpReceiver) 222 | } catch (e: IllegalArgumentException) { 223 | Log.e("OTPReceiverEffect ", "Error in registering receiver: ${e.message}}") 224 | } 225 | } 226 | 227 | override fun onOTPTimeOut() { 228 | Log.e("OTPReceiverEffect ", "Timeout") 229 | } 230 | }) 231 | try { 232 | Log.e("OTPReceiverEffect ", "Lifecycle.Event.ON_RESUME") 233 | Log.e("OTPReceiverEffect ", "Registering receiver") 234 | context.registerReceiver( 235 | otpReceiver, 236 | IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION), 237 | Context.RECEIVER_EXPORTED 238 | ) 239 | } catch (e: IllegalArgumentException) { 240 | Log.e("OTPReceiverEffect ", "Error in registering receiver: ${e.message}}") 241 | } 242 | onPauseOrDispose { 243 | // add clean up for work kicked off in the ON_RESUME effect here 244 | try { 245 | Log.e("OTPReceiverEffect ", "Lifecycle.Event.ON_PAUSE") 246 | Log.e("OTPReceiverEffect ", "Unregistering receiver") 247 | context.unregisterReceiver(otpReceiver) 248 | } catch (e: IllegalArgumentException) { 249 | Log.e("OTPReceiverEffect ", "Error in unregistering receiver: ${e.message}}") 250 | } 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/screens/SplashScreen.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.screens 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.getValue 9 | import androidx.compose.ui.Modifier 10 | import androidx.navigation.NavHostController 11 | import com.airbnb.lottie.compose.LottieAnimation 12 | import com.airbnb.lottie.compose.LottieCompositionSpec 13 | import com.airbnb.lottie.compose.animateLottieCompositionAsState 14 | import com.airbnb.lottie.compose.rememberLottieComposition 15 | import com.appmason.composeotpverify.R 16 | import com.appmason.jetplayground.ui.navigation.Screen 17 | 18 | 19 | @Composable 20 | fun SplashScreen(navController: NavHostController) { 21 | val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.loading)) 22 | val logoAnimationState = animateLottieCompositionAsState( 23 | composition = composition, 24 | speed = 2f 25 | ) 26 | if (logoAnimationState.isAtEnd && logoAnimationState.isPlaying) { 27 | navController.navigate(Screen.OtpScreen.route) 28 | } 29 | Box( 30 | modifier = Modifier 31 | .fillMaxSize() 32 | .background(MaterialTheme.colorScheme.primary) 33 | ) { 34 | LottieAnimation( 35 | composition = composition, 36 | progress = { logoAnimationState.progress } 37 | ) 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple80 = Color(0xFFD0BCFF) 6 | val PurpleGrey80 = Color(0xFFCCC2DC) 7 | val Pink80 = Color(0xFFEFB8C8) 8 | 9 | val Purple40 = Color(0xFF6650a4) 10 | val PurpleGrey40 = Color(0xFF625b71) 11 | val Pink40 = Color(0xFF7D5260) 12 | 13 | val BorderLight = Color(0xFFBCD3E6) 14 | val BorderDark = Color(0xFF63798D) -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.theme 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import androidx.compose.foundation.isSystemInDarkTheme 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.darkColorScheme 8 | import androidx.compose.material3.dynamicDarkColorScheme 9 | import androidx.compose.material3.dynamicLightColorScheme 10 | import androidx.compose.material3.lightColorScheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.SideEffect 13 | import androidx.compose.ui.graphics.toArgb 14 | import androidx.compose.ui.platform.LocalContext 15 | import androidx.compose.ui.platform.LocalView 16 | import androidx.core.view.WindowCompat 17 | 18 | private val DarkColorScheme = darkColorScheme( 19 | primary = Purple80, 20 | secondary = PurpleGrey80, 21 | tertiary = Pink80 22 | ) 23 | 24 | private val LightColorScheme = lightColorScheme( 25 | primary = Purple40, 26 | secondary = PurpleGrey40, 27 | tertiary = Pink40 28 | 29 | /* Other default colors to override 30 | background = Color(0xFFFFFBFE), 31 | surface = Color(0xFFFFFBFE), 32 | onPrimary = Color.White, 33 | onSecondary = Color.White, 34 | onTertiary = Color.White, 35 | onBackground = Color(0xFF1C1B1F), 36 | onSurface = Color(0xFF1C1B1F), 37 | */ 38 | ) 39 | 40 | @Composable 41 | fun JetPlaygroundTheme( 42 | darkTheme: Boolean = isSystemInDarkTheme(), 43 | // Dynamic color is available on Android 12+ 44 | dynamicColor: Boolean = true, 45 | content: @Composable () -> Unit 46 | ) { 47 | val colorScheme = when { 48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { 49 | val context = LocalContext.current 50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) 51 | } 52 | 53 | darkTheme -> DarkColorScheme 54 | else -> LightColorScheme 55 | } 56 | val view = LocalView.current 57 | if (!view.isInEditMode) { 58 | SideEffect { 59 | val window = (view.context as Activity).window 60 | window.statusBarColor = colorScheme.primary.toArgb() 61 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme 62 | } 63 | } 64 | 65 | MaterialTheme( 66 | colorScheme = colorScheme, 67 | typography = Typography, 68 | content = content 69 | ) 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/appmason/jetplayground/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.appmason.jetplayground.ui.theme 2 | 3 | import androidx.compose.material3.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | bodyLarge = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp, 15 | lineHeight = 24.sp, 16 | letterSpacing = 0.5.sp 17 | ) 18 | /* Other default text styles to override 19 | titleLarge = TextStyle( 20 | fontFamily = FontFamily.Default, 21 | fontWeight = FontWeight.Normal, 22 | fontSize = 22.sp, 23 | lineHeight = 28.sp, 24 | letterSpacing = 0.sp 25 | ), 26 | labelSmall = TextStyle( 27 | fontFamily = FontFamily.Default, 28 | fontWeight = FontWeight.Medium, 29 | fontSize = 11.sp, 30 | lineHeight = 16.sp, 31 | letterSpacing = 0.5.sp 32 | ) 33 | */ 34 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_back.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pushpalroy/ComposeOtpVerify/e57c00881dac850888b245f01ce31dde1684e09d/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/raw/loading.json: -------------------------------------------------------------------------------- 1 | { 2 | "v": "4.8.0", 3 | "meta": { 4 | "g": "LottieFiles AE 3.5.3", 5 | "a": "", 6 | "k": "", 7 | "d": "", 8 | "tc": "" 9 | }, 10 | "fr": 30, 11 | "ip": 0, 12 | "op": 56, 13 | "w": 500, 14 | "h": 500, 15 | "nm": "Check Mark", 16 | "ddd": 0, 17 | "assets": [], 18 | "layers": [ 19 | { 20 | "ddd": 0, 21 | "ind": 1, 22 | "ty": 4, 23 | "nm": "line 2", 24 | "sr": 1, 25 | "ks": { 26 | "o": { 27 | "a": 0, 28 | "k": 100, 29 | "ix": 11 30 | }, 31 | "r": { 32 | "a": 0, 33 | "k": 0, 34 | "ix": 10 35 | }, 36 | "p": { 37 | "a": 0, 38 | "k": [ 39 | 250, 40 | 250, 41 | 0 42 | ], 43 | "ix": 2 44 | }, 45 | "a": { 46 | "a": 0, 47 | "k": [ 48 | 133.203, 49 | 133.203, 50 | 0 51 | ], 52 | "ix": 1 53 | }, 54 | "s": { 55 | "a": 0, 56 | "k": [ 57 | 100, 58 | 100, 59 | 100 60 | ], 61 | "ix": 6 62 | } 63 | }, 64 | "ao": 0, 65 | "shapes": [ 66 | { 67 | "ty": "gr", 68 | "it": [ 69 | { 70 | "ind": 0, 71 | "ty": "sh", 72 | "ix": 1, 73 | "ks": { 74 | "a": 0, 75 | "k": { 76 | "i": [ 77 | [ 78 | 67.153, 79 | 0 80 | ], 81 | [ 82 | 0, 83 | 67.153 84 | ], 85 | [ 86 | -67.153, 87 | 0 88 | ], 89 | [ 90 | 0, 91 | -67.153 92 | ] 93 | ], 94 | "o": [ 95 | [ 96 | -67.153, 97 | 0 98 | ], 99 | [ 100 | 0, 101 | -67.153 102 | ], 103 | [ 104 | 67.153, 105 | 0 106 | ], 107 | [ 108 | 0, 109 | 67.153 110 | ] 111 | ], 112 | "v": [ 113 | [ 114 | 0, 115 | 121.785 116 | ], 117 | [ 118 | -121.786, 119 | -0.001 120 | ], 121 | [ 122 | 0, 123 | -121.785 124 | ], 125 | [ 126 | 121.786, 127 | -0.001 128 | ] 129 | ], 130 | "c": true 131 | }, 132 | "ix": 2 133 | }, 134 | "nm": "Path 1", 135 | "mn": "ADBE Vector Shape - Group", 136 | "hd": false 137 | }, 138 | { 139 | "ty": "st", 140 | "c": { 141 | "a": 0, 142 | "k": [ 143 | 0.043137254902, 144 | 0.160784313725, 145 | 0.658823529412, 146 | 1 147 | ], 148 | "ix": 3 149 | }, 150 | "o": { 151 | "a": 0, 152 | "k": 100, 153 | "ix": 4 154 | }, 155 | "w": { 156 | "a": 0, 157 | "k": 4.567, 158 | "ix": 5 159 | }, 160 | "lc": 1, 161 | "lj": 1, 162 | "ml": 10, 163 | "bm": 0, 164 | "nm": "Stroke 1", 165 | "mn": "ADBE Vector Graphic - Stroke", 166 | "hd": false 167 | }, 168 | { 169 | "ty": "tr", 170 | "p": { 171 | "a": 0, 172 | "k": [ 173 | 133.203, 174 | 133.203 175 | ], 176 | "ix": 2 177 | }, 178 | "a": { 179 | "a": 0, 180 | "k": [ 181 | 0, 182 | 0 183 | ], 184 | "ix": 1 185 | }, 186 | "s": { 187 | "a": 0, 188 | "k": [ 189 | 100, 190 | 100 191 | ], 192 | "ix": 3 193 | }, 194 | "r": { 195 | "a": 0, 196 | "k": 0, 197 | "ix": 6 198 | }, 199 | "o": { 200 | "a": 0, 201 | "k": 100, 202 | "ix": 7 203 | }, 204 | "sk": { 205 | "a": 0, 206 | "k": 0, 207 | "ix": 4 208 | }, 209 | "sa": { 210 | "a": 0, 211 | "k": 0, 212 | "ix": 5 213 | }, 214 | "nm": "Transform" 215 | } 216 | ], 217 | "nm": "Group 1", 218 | "np": 2, 219 | "cix": 2, 220 | "bm": 0, 221 | "ix": 1, 222 | "mn": "ADBE Vector Group", 223 | "hd": false 224 | }, 225 | { 226 | "ty": "tm", 227 | "s": { 228 | "a": 1, 229 | "k": [ 230 | { 231 | "i": { 232 | "x": [ 233 | 0.667 234 | ], 235 | "y": [ 236 | 1 237 | ] 238 | }, 239 | "o": { 240 | "x": [ 241 | 0.333 242 | ], 243 | "y": [ 244 | 0 245 | ] 246 | }, 247 | "t": 15.015, 248 | "s": [ 249 | 0 250 | ] 251 | }, 252 | { 253 | "t": 25.025512817505, 254 | "s": [ 255 | 100 256 | ] 257 | } 258 | ], 259 | "ix": 1 260 | }, 261 | "e": { 262 | "a": 1, 263 | "k": [ 264 | { 265 | "i": { 266 | "x": [ 267 | 0.667 268 | ], 269 | "y": [ 270 | 1 271 | ] 272 | }, 273 | "o": { 274 | "x": [ 275 | 0.333 276 | ], 277 | "y": [ 278 | 0 279 | ] 280 | }, 281 | "t": 5.005, 282 | "s": [ 283 | 0 284 | ] 285 | }, 286 | { 287 | "t": 15.014770630005, 288 | "s": [ 289 | 100 290 | ] 291 | } 292 | ], 293 | "ix": 2 294 | }, 295 | "o": { 296 | "a": 0, 297 | "k": 0, 298 | "ix": 3 299 | }, 300 | "m": 1, 301 | "ix": 2, 302 | "nm": "Trim Paths 1", 303 | "mn": "ADBE Vector Filter - Trim", 304 | "hd": false 305 | } 306 | ], 307 | "ip": 5.00500500500501, 308 | "op": 58.0580580580581, 309 | "st": 5.00500500500501, 310 | "bm": 0 311 | }, 312 | { 313 | "ddd": 0, 314 | "ind": 2, 315 | "ty": 4, 316 | "nm": "line 1", 317 | "sr": 1, 318 | "ks": { 319 | "o": { 320 | "a": 0, 321 | "k": 100, 322 | "ix": 11 323 | }, 324 | "r": { 325 | "a": 0, 326 | "k": 0, 327 | "ix": 10 328 | }, 329 | "p": { 330 | "a": 0, 331 | "k": [ 332 | 250, 333 | 250, 334 | 0 335 | ], 336 | "ix": 2 337 | }, 338 | "a": { 339 | "a": 0, 340 | "k": [ 341 | 126.5, 342 | 126.5, 343 | 0 344 | ], 345 | "ix": 1 346 | }, 347 | "s": { 348 | "a": 0, 349 | "k": [ 350 | 100, 351 | 100, 352 | 100 353 | ], 354 | "ix": 6 355 | } 356 | }, 357 | "ao": 0, 358 | "shapes": [ 359 | { 360 | "ty": "gr", 361 | "it": [ 362 | { 363 | "ind": 0, 364 | "ty": "sh", 365 | "ix": 1, 366 | "ks": { 367 | "a": 0, 368 | "k": { 369 | "i": [ 370 | [ 371 | 60.103, 372 | 0 373 | ], 374 | [ 375 | 0, 376 | 60.103 377 | ], 378 | [ 379 | -60.103, 380 | 0 381 | ], 382 | [ 383 | 0, 384 | -60.103 385 | ] 386 | ], 387 | "o": [ 388 | [ 389 | -60.103, 390 | 0 391 | ], 392 | [ 393 | 0, 394 | -60.103 395 | ], 396 | [ 397 | 60.103, 398 | 0 399 | ], 400 | [ 401 | 0, 402 | 60.103 403 | ] 404 | ], 405 | "v": [ 406 | [ 407 | 0, 408 | 109 409 | ], 410 | [ 411 | -109, 412 | 0 413 | ], 414 | [ 415 | 0, 416 | -109 417 | ], 418 | [ 419 | 109, 420 | 0 421 | ] 422 | ], 423 | "c": true 424 | }, 425 | "ix": 2 426 | }, 427 | "nm": "Path 1", 428 | "mn": "ADBE Vector Shape - Group", 429 | "hd": false 430 | }, 431 | { 432 | "ty": "st", 433 | "c": { 434 | "a": 0, 435 | "k": [ 436 | 0.043137254902, 437 | 0.160784313725, 438 | 0.658823529412, 439 | 1 440 | ], 441 | "ix": 3 442 | }, 443 | "o": { 444 | "a": 0, 445 | "k": 100, 446 | "ix": 4 447 | }, 448 | "w": { 449 | "a": 0, 450 | "k": 7, 451 | "ix": 5 452 | }, 453 | "lc": 1, 454 | "lj": 1, 455 | "ml": 10, 456 | "bm": 0, 457 | "nm": "Stroke 1", 458 | "mn": "ADBE Vector Graphic - Stroke", 459 | "hd": false 460 | }, 461 | { 462 | "ty": "tr", 463 | "p": { 464 | "a": 0, 465 | "k": [ 466 | 126.5, 467 | 126.5 468 | ], 469 | "ix": 2 470 | }, 471 | "a": { 472 | "a": 0, 473 | "k": [ 474 | 0, 475 | 0 476 | ], 477 | "ix": 1 478 | }, 479 | "s": { 480 | "a": 0, 481 | "k": [ 482 | 100, 483 | 100 484 | ], 485 | "ix": 3 486 | }, 487 | "r": { 488 | "a": 0, 489 | "k": 0, 490 | "ix": 6 491 | }, 492 | "o": { 493 | "a": 0, 494 | "k": 100, 495 | "ix": 7 496 | }, 497 | "sk": { 498 | "a": 0, 499 | "k": 0, 500 | "ix": 4 501 | }, 502 | "sa": { 503 | "a": 0, 504 | "k": 0, 505 | "ix": 5 506 | }, 507 | "nm": "Transform" 508 | } 509 | ], 510 | "nm": "Group 1", 511 | "np": 2, 512 | "cix": 2, 513 | "bm": 0, 514 | "ix": 1, 515 | "mn": "ADBE Vector Group", 516 | "hd": false 517 | }, 518 | { 519 | "ty": "tm", 520 | "s": { 521 | "a": 1, 522 | "k": [ 523 | { 524 | "i": { 525 | "x": [ 526 | 0.667 527 | ], 528 | "y": [ 529 | 1 530 | ] 531 | }, 532 | "o": { 533 | "x": [ 534 | 0.333 535 | ], 536 | "y": [ 537 | 0 538 | ] 539 | }, 540 | "t": 10.01, 541 | "s": [ 542 | 0 543 | ] 544 | }, 545 | { 546 | "t": 20.0205078125, 547 | "s": [ 548 | 100 549 | ] 550 | } 551 | ], 552 | "ix": 1 553 | }, 554 | "e": { 555 | "a": 1, 556 | "k": [ 557 | { 558 | "i": { 559 | "x": [ 560 | 0.667 561 | ], 562 | "y": [ 563 | 1 564 | ] 565 | }, 566 | "o": { 567 | "x": [ 568 | 0.333 569 | ], 570 | "y": [ 571 | 0 572 | ] 573 | }, 574 | "t": 0, 575 | "s": [ 576 | 0 577 | ] 578 | }, 579 | { 580 | "t": 10.009765625, 581 | "s": [ 582 | 100 583 | ] 584 | } 585 | ], 586 | "ix": 2 587 | }, 588 | "o": { 589 | "a": 0, 590 | "k": 0, 591 | "ix": 3 592 | }, 593 | "m": 1, 594 | "ix": 2, 595 | "nm": "Trim Paths 1", 596 | "mn": "ADBE Vector Filter - Trim", 597 | "hd": false 598 | } 599 | ], 600 | "ip": 0, 601 | "op": 58.0580580580581, 602 | "st": 0, 603 | "bm": 0 604 | }, 605 | { 606 | "ddd": 0, 607 | "ind": 3, 608 | "ty": 4, 609 | "nm": "check mark", 610 | "sr": 1, 611 | "ks": { 612 | "o": { 613 | "a": 0, 614 | "k": 100, 615 | "ix": 11 616 | }, 617 | "r": { 618 | "a": 0, 619 | "k": 0, 620 | "ix": 10 621 | }, 622 | "p": { 623 | "a": 0, 624 | "k": [ 625 | 247.728, 626 | 250, 627 | 0 628 | ], 629 | "ix": 2 630 | }, 631 | "a": { 632 | "a": 0, 633 | "k": [ 634 | 93.963, 635 | 79.058, 636 | 0 637 | ], 638 | "ix": 1 639 | }, 640 | "s": { 641 | "a": 1, 642 | "k": [ 643 | { 644 | "i": { 645 | "x": [ 646 | 0.667, 647 | 0.667, 648 | 0.667 649 | ], 650 | "y": [ 651 | 1, 652 | 1, 653 | 1 654 | ] 655 | }, 656 | "o": { 657 | "x": [ 658 | 0.333, 659 | 0.333, 660 | 0.333 661 | ], 662 | "y": [ 663 | 0, 664 | 0, 665 | 0 666 | ] 667 | }, 668 | "t": 25, 669 | "s": [ 670 | 0, 671 | 0, 672 | 100 673 | ] 674 | }, 675 | { 676 | "i": { 677 | "x": [ 678 | 0.667, 679 | 0.667, 680 | 0.667 681 | ], 682 | "y": [ 683 | 1, 684 | 1, 685 | 1 686 | ] 687 | }, 688 | "o": { 689 | "x": [ 690 | 0.333, 691 | 0.333, 692 | 0.333 693 | ], 694 | "y": [ 695 | 0, 696 | 0, 697 | 0 698 | ] 699 | }, 700 | "t": 35, 701 | "s": [ 702 | 120, 703 | 120, 704 | 100 705 | ] 706 | }, 707 | { 708 | "i": { 709 | "x": [ 710 | 0.667, 711 | 0.667, 712 | 0.667 713 | ], 714 | "y": [ 715 | 1, 716 | 1, 717 | 1 718 | ] 719 | }, 720 | "o": { 721 | "x": [ 722 | 0.167, 723 | 0.167, 724 | 0.167 725 | ], 726 | "y": [ 727 | 0, 728 | 0, 729 | 0 730 | ] 731 | }, 732 | "t": 40, 733 | "s": [ 734 | 100, 735 | 100, 736 | 100 737 | ] 738 | }, 739 | { 740 | "i": { 741 | "x": [ 742 | 0.833, 743 | 0.833, 744 | 0.833 745 | ], 746 | "y": [ 747 | 1, 748 | 1, 749 | 1 750 | ] 751 | }, 752 | "o": { 753 | "x": [ 754 | 0.167, 755 | 0.167, 756 | 0.167 757 | ], 758 | "y": [ 759 | 0, 760 | 0, 761 | 0 762 | ] 763 | }, 764 | "t": 45, 765 | "s": [ 766 | 100, 767 | 100, 768 | 100 769 | ] 770 | }, 771 | { 772 | "t": 55, 773 | "s": [ 774 | 0, 775 | 0, 776 | 100 777 | ] 778 | } 779 | ], 780 | "ix": 6 781 | } 782 | }, 783 | "ao": 0, 784 | "shapes": [ 785 | { 786 | "ty": "gr", 787 | "it": [ 788 | { 789 | "ind": 0, 790 | "ty": "sh", 791 | "ix": 1, 792 | "ks": { 793 | "a": 0, 794 | "k": { 795 | "i": [ 796 | [ 797 | 0, 798 | 0 799 | ], 800 | [ 801 | 0, 802 | 0 803 | ], 804 | [ 805 | 0, 806 | 0 807 | ] 808 | ], 809 | "o": [ 810 | [ 811 | 0, 812 | 0 813 | ], 814 | [ 815 | 0, 816 | 0 817 | ], 818 | [ 819 | 0, 820 | 0 821 | ] 822 | ], 823 | "v": [ 824 | [ 825 | -53.61, 826 | 8.434 827 | ], 828 | [ 829 | -21.259, 830 | 38.706 831 | ], 832 | [ 833 | 53.61, 834 | -38.706 835 | ] 836 | ], 837 | "c": false 838 | }, 839 | "ix": 2 840 | }, 841 | "nm": "Path 1", 842 | "mn": "ADBE Vector Shape - Group", 843 | "hd": false 844 | }, 845 | { 846 | "ty": "st", 847 | "c": { 848 | "a": 0, 849 | "k": [ 850 | 1, 851 | 1, 852 | 1, 853 | 1 854 | ], 855 | "ix": 3 856 | }, 857 | "o": { 858 | "a": 0, 859 | "k": 100, 860 | "ix": 4 861 | }, 862 | "w": { 863 | "a": 0, 864 | "k": 16.141, 865 | "ix": 5 866 | }, 867 | "lc": 2, 868 | "lj": 2, 869 | "bm": 0, 870 | "nm": "Stroke 1", 871 | "mn": "ADBE Vector Graphic - Stroke", 872 | "hd": false 873 | }, 874 | { 875 | "ty": "tr", 876 | "p": { 877 | "a": 0, 878 | "k": [ 879 | 93.963, 880 | 79.058 881 | ], 882 | "ix": 2 883 | }, 884 | "a": { 885 | "a": 0, 886 | "k": [ 887 | 0, 888 | 0 889 | ], 890 | "ix": 1 891 | }, 892 | "s": { 893 | "a": 0, 894 | "k": [ 895 | 100, 896 | 100 897 | ], 898 | "ix": 3 899 | }, 900 | "r": { 901 | "a": 0, 902 | "k": 0, 903 | "ix": 6 904 | }, 905 | "o": { 906 | "a": 0, 907 | "k": 100, 908 | "ix": 7 909 | }, 910 | "sk": { 911 | "a": 0, 912 | "k": 0, 913 | "ix": 4 914 | }, 915 | "sa": { 916 | "a": 0, 917 | "k": 0, 918 | "ix": 5 919 | }, 920 | "nm": "Transform" 921 | } 922 | ], 923 | "nm": "Group 1", 924 | "np": 2, 925 | "cix": 2, 926 | "bm": 0, 927 | "ix": 1, 928 | "mn": "ADBE Vector Group", 929 | "hd": false 930 | } 931 | ], 932 | "ip": 10, 933 | "op": 68.0580580580581, 934 | "st": 10, 935 | "bm": 0 936 | }, 937 | { 938 | "ddd": 0, 939 | "ind": 4, 940 | "ty": 4, 941 | "nm": "circle", 942 | "sr": 1, 943 | "ks": { 944 | "o": { 945 | "a": 0, 946 | "k": 100, 947 | "ix": 11 948 | }, 949 | "r": { 950 | "a": 0, 951 | "k": 0, 952 | "ix": 10 953 | }, 954 | "p": { 955 | "a": 0, 956 | "k": [ 957 | 250, 958 | 250, 959 | 0 960 | ], 961 | "ix": 2 962 | }, 963 | "a": { 964 | "a": 0, 965 | "k": [ 966 | 98.25, 967 | 98.25, 968 | 0 969 | ], 970 | "ix": 1 971 | }, 972 | "s": { 973 | "a": 1, 974 | "k": [ 975 | { 976 | "i": { 977 | "x": [ 978 | 0.667, 979 | 0.667, 980 | 0.667 981 | ], 982 | "y": [ 983 | 1, 984 | 1, 985 | 1 986 | ] 987 | }, 988 | "o": { 989 | "x": [ 990 | 0.333, 991 | 0.333, 992 | 0.333 993 | ], 994 | "y": [ 995 | 0, 996 | 0, 997 | 0 998 | ] 999 | }, 1000 | "t": 15, 1001 | "s": [ 1002 | 0, 1003 | 0, 1004 | 100 1005 | ] 1006 | }, 1007 | { 1008 | "i": { 1009 | "x": [ 1010 | 0.667, 1011 | 0.667, 1012 | 0.667 1013 | ], 1014 | "y": [ 1015 | 1, 1016 | 1, 1017 | 1 1018 | ] 1019 | }, 1020 | "o": { 1021 | "x": [ 1022 | 0.333, 1023 | 0.333, 1024 | 0.333 1025 | ], 1026 | "y": [ 1027 | 0, 1028 | 0, 1029 | 0 1030 | ] 1031 | }, 1032 | "t": 25, 1033 | "s": [ 1034 | 115, 1035 | 115, 1036 | 100 1037 | ] 1038 | }, 1039 | { 1040 | "i": { 1041 | "x": [ 1042 | 0.667, 1043 | 0.667, 1044 | 0.667 1045 | ], 1046 | "y": [ 1047 | 1, 1048 | 1, 1049 | 1 1050 | ] 1051 | }, 1052 | "o": { 1053 | "x": [ 1054 | 0.167, 1055 | 0.167, 1056 | 0.167 1057 | ], 1058 | "y": [ 1059 | 0, 1060 | 0, 1061 | 0 1062 | ] 1063 | }, 1064 | "t": 30, 1065 | "s": [ 1066 | 100, 1067 | 100, 1068 | 100 1069 | ] 1070 | }, 1071 | { 1072 | "i": { 1073 | "x": [ 1074 | 0.833, 1075 | 0.833, 1076 | 0.833 1077 | ], 1078 | "y": [ 1079 | 1, 1080 | 1, 1081 | 1 1082 | ] 1083 | }, 1084 | "o": { 1085 | "x": [ 1086 | 0.167, 1087 | 0.167, 1088 | 0.167 1089 | ], 1090 | "y": [ 1091 | 0, 1092 | 0, 1093 | 0 1094 | ] 1095 | }, 1096 | "t": 45, 1097 | "s": [ 1098 | 100, 1099 | 100, 1100 | 100 1101 | ] 1102 | }, 1103 | { 1104 | "t": 55, 1105 | "s": [ 1106 | 0, 1107 | 0, 1108 | 100 1109 | ] 1110 | } 1111 | ], 1112 | "ix": 6 1113 | } 1114 | }, 1115 | "ao": 0, 1116 | "shapes": [ 1117 | { 1118 | "ty": "gr", 1119 | "it": [ 1120 | { 1121 | "ind": 0, 1122 | "ty": "sh", 1123 | "ix": 1, 1124 | "ks": { 1125 | "a": 0, 1126 | "k": { 1127 | "i": [ 1128 | [ 1129 | 0, 1130 | -54.124 1131 | ], 1132 | [ 1133 | 54.124, 1134 | 0 1135 | ], 1136 | [ 1137 | 0, 1138 | 54.124 1139 | ], 1140 | [ 1141 | -54.124, 1142 | 0 1143 | ] 1144 | ], 1145 | "o": [ 1146 | [ 1147 | 0, 1148 | 54.124 1149 | ], 1150 | [ 1151 | -54.124, 1152 | 0 1153 | ], 1154 | [ 1155 | 0, 1156 | -54.124 1157 | ], 1158 | [ 1159 | 54.124, 1160 | 0 1161 | ] 1162 | ], 1163 | "v": [ 1164 | [ 1165 | 98, 1166 | 0 1167 | ], 1168 | [ 1169 | 0, 1170 | 98 1171 | ], 1172 | [ 1173 | -98, 1174 | 0 1175 | ], 1176 | [ 1177 | 0, 1178 | -98 1179 | ] 1180 | ], 1181 | "c": true 1182 | }, 1183 | "ix": 2 1184 | }, 1185 | "nm": "Path 1", 1186 | "mn": "ADBE Vector Shape - Group", 1187 | "hd": false 1188 | }, 1189 | { 1190 | "ty": "fl", 1191 | "c": { 1192 | "a": 0, 1193 | "k": [ 1194 | 0, 1195 | 0.784313785329, 1196 | 0.521568627451, 1197 | 1 1198 | ], 1199 | "ix": 4 1200 | }, 1201 | "o": { 1202 | "a": 0, 1203 | "k": 100, 1204 | "ix": 5 1205 | }, 1206 | "r": 1, 1207 | "bm": 0, 1208 | "nm": "Fill 1", 1209 | "mn": "ADBE Vector Graphic - Fill", 1210 | "hd": false 1211 | }, 1212 | { 1213 | "ty": "tr", 1214 | "p": { 1215 | "a": 0, 1216 | "k": [ 1217 | 98.25, 1218 | 98.25 1219 | ], 1220 | "ix": 2 1221 | }, 1222 | "a": { 1223 | "a": 0, 1224 | "k": [ 1225 | 0, 1226 | 0 1227 | ], 1228 | "ix": 1 1229 | }, 1230 | "s": { 1231 | "a": 0, 1232 | "k": [ 1233 | 100, 1234 | 100 1235 | ], 1236 | "ix": 3 1237 | }, 1238 | "r": { 1239 | "a": 0, 1240 | "k": 0, 1241 | "ix": 6 1242 | }, 1243 | "o": { 1244 | "a": 0, 1245 | "k": 100, 1246 | "ix": 7 1247 | }, 1248 | "sk": { 1249 | "a": 0, 1250 | "k": 0, 1251 | "ix": 4 1252 | }, 1253 | "sa": { 1254 | "a": 0, 1255 | "k": 0, 1256 | "ix": 5 1257 | }, 1258 | "nm": "Transform" 1259 | } 1260 | ], 1261 | "nm": "Group 1", 1262 | "np": 2, 1263 | "cix": 2, 1264 | "bm": 0, 1265 | "ix": 1, 1266 | "mn": "ADBE Vector Group", 1267 | "hd": false 1268 | } 1269 | ], 1270 | "ip": 0, 1271 | "op": 58.0580580580581, 1272 | "st": 0, 1273 | "bm": 0 1274 | }, 1275 | { 1276 | "ddd": 0, 1277 | "ind": 5, 1278 | "ty": 4, 1279 | "nm": "circles", 1280 | "sr": 1, 1281 | "ks": { 1282 | "o": { 1283 | "a": 1, 1284 | "k": [ 1285 | { 1286 | "i": { 1287 | "x": [ 1288 | 0.667 1289 | ], 1290 | "y": [ 1291 | 1 1292 | ] 1293 | }, 1294 | "o": { 1295 | "x": [ 1296 | 0.333 1297 | ], 1298 | "y": [ 1299 | 0 1300 | ] 1301 | }, 1302 | "t": 50, 1303 | "s": [ 1304 | 100 1305 | ] 1306 | }, 1307 | { 1308 | "t": 55, 1309 | "s": [ 1310 | 0 1311 | ] 1312 | } 1313 | ], 1314 | "ix": 11 1315 | }, 1316 | "r": { 1317 | "a": 0, 1318 | "k": 0, 1319 | "ix": 10 1320 | }, 1321 | "p": { 1322 | "a": 0, 1323 | "k": [ 1324 | 248.685, 1325 | 246.851, 1326 | 0 1327 | ], 1328 | "ix": 2 1329 | }, 1330 | "a": { 1331 | "a": 0, 1332 | "k": [ 1333 | 123.082, 1334 | 121.196, 1335 | 0 1336 | ], 1337 | "ix": 1 1338 | }, 1339 | "s": { 1340 | "a": 1, 1341 | "k": [ 1342 | { 1343 | "i": { 1344 | "x": [ 1345 | 0.667, 1346 | 0.667, 1347 | 0.667 1348 | ], 1349 | "y": [ 1350 | 1, 1351 | 1, 1352 | 1 1353 | ] 1354 | }, 1355 | "o": { 1356 | "x": [ 1357 | 0.333, 1358 | 0.333, 1359 | 0.333 1360 | ], 1361 | "y": [ 1362 | 0, 1363 | 0, 1364 | 0 1365 | ] 1366 | }, 1367 | "t": 35, 1368 | "s": [ 1369 | 0, 1370 | 0, 1371 | 100 1372 | ] 1373 | }, 1374 | { 1375 | "i": { 1376 | "x": [ 1377 | 0.667, 1378 | 0.667, 1379 | 0.667 1380 | ], 1381 | "y": [ 1382 | 1, 1383 | 1, 1384 | 1 1385 | ] 1386 | }, 1387 | "o": { 1388 | "x": [ 1389 | 0.333, 1390 | 0.333, 1391 | 0.333 1392 | ], 1393 | "y": [ 1394 | 0, 1395 | 0, 1396 | 0 1397 | ] 1398 | }, 1399 | "t": 50, 1400 | "s": [ 1401 | 100, 1402 | 100, 1403 | 100 1404 | ] 1405 | }, 1406 | { 1407 | "t": 55, 1408 | "s": [ 1409 | 120, 1410 | 120, 1411 | 100 1412 | ] 1413 | } 1414 | ], 1415 | "ix": 6 1416 | } 1417 | }, 1418 | "ao": 0, 1419 | "shapes": [ 1420 | { 1421 | "ty": "gr", 1422 | "it": [ 1423 | { 1424 | "ind": 0, 1425 | "ty": "sh", 1426 | "ix": 1, 1427 | "ks": { 1428 | "a": 0, 1429 | "k": { 1430 | "i": [ 1431 | [ 1432 | 0, 1433 | -1.964 1434 | ], 1435 | [ 1436 | 1.965, 1437 | 0 1438 | ], 1439 | [ 1440 | 0, 1441 | 1.964 1442 | ], 1443 | [ 1444 | -1.964, 1445 | 0 1446 | ] 1447 | ], 1448 | "o": [ 1449 | [ 1450 | 0, 1451 | 1.964 1452 | ], 1453 | [ 1454 | -1.964, 1455 | 0 1456 | ], 1457 | [ 1458 | 0, 1459 | -1.964 1460 | ], 1461 | [ 1462 | 1.965, 1463 | 0 1464 | ] 1465 | ], 1466 | "v": [ 1467 | [ 1468 | 3.557, 1469 | 0 1470 | ], 1471 | [ 1472 | -0.001, 1473 | 3.556 1474 | ], 1475 | [ 1476 | -3.557, 1477 | 0 1478 | ], 1479 | [ 1480 | -0.001, 1481 | -3.556 1482 | ] 1483 | ], 1484 | "c": true 1485 | }, 1486 | "ix": 2 1487 | }, 1488 | "nm": "Path 1", 1489 | "mn": "ADBE Vector Shape - Group", 1490 | "hd": false 1491 | }, 1492 | { 1493 | "ty": "fl", 1494 | "c": { 1495 | "a": 0, 1496 | "k": [ 1497 | 1, 1498 | 0.674509803922, 1499 | 0.384313755409, 1500 | 1 1501 | ], 1502 | "ix": 4 1503 | }, 1504 | "o": { 1505 | "a": 0, 1506 | "k": 100, 1507 | "ix": 5 1508 | }, 1509 | "r": 1, 1510 | "bm": 0, 1511 | "nm": "Fill 1", 1512 | "mn": "ADBE Vector Graphic - Fill", 1513 | "hd": false 1514 | }, 1515 | { 1516 | "ty": "tr", 1517 | "p": { 1518 | "a": 0, 1519 | "k": [ 1520 | 65.54, 1521 | 32.998 1522 | ], 1523 | "ix": 2 1524 | }, 1525 | "a": { 1526 | "a": 0, 1527 | "k": [ 1528 | 0, 1529 | 0 1530 | ], 1531 | "ix": 1 1532 | }, 1533 | "s": { 1534 | "a": 0, 1535 | "k": [ 1536 | 100, 1537 | 100 1538 | ], 1539 | "ix": 3 1540 | }, 1541 | "r": { 1542 | "a": 0, 1543 | "k": 0, 1544 | "ix": 6 1545 | }, 1546 | "o": { 1547 | "a": 0, 1548 | "k": 100, 1549 | "ix": 7 1550 | }, 1551 | "sk": { 1552 | "a": 0, 1553 | "k": 0, 1554 | "ix": 4 1555 | }, 1556 | "sa": { 1557 | "a": 0, 1558 | "k": 0, 1559 | "ix": 5 1560 | }, 1561 | "nm": "Transform" 1562 | } 1563 | ], 1564 | "nm": "Group 1", 1565 | "np": 2, 1566 | "cix": 2, 1567 | "bm": 0, 1568 | "ix": 1, 1569 | "mn": "ADBE Vector Group", 1570 | "hd": false 1571 | }, 1572 | { 1573 | "ty": "gr", 1574 | "it": [ 1575 | { 1576 | "ind": 0, 1577 | "ty": "sh", 1578 | "ix": 1, 1579 | "ks": { 1580 | "a": 0, 1581 | "k": { 1582 | "i": [ 1583 | [ 1584 | 0, 1585 | -5.855 1586 | ], 1587 | [ 1588 | 5.855, 1589 | 0 1590 | ], 1591 | [ 1592 | 0, 1593 | 5.855 1594 | ], 1595 | [ 1596 | -5.856, 1597 | 0 1598 | ] 1599 | ], 1600 | "o": [ 1601 | [ 1602 | 0, 1603 | 5.855 1604 | ], 1605 | [ 1606 | -5.856, 1607 | 0 1608 | ], 1609 | [ 1610 | 0, 1611 | -5.855 1612 | ], 1613 | [ 1614 | 5.855, 1615 | 0 1616 | ] 1617 | ], 1618 | "v": [ 1619 | [ 1620 | 10.602, 1621 | 0 1622 | ], 1623 | [ 1624 | 0, 1625 | 10.602 1626 | ], 1627 | [ 1628 | -10.602, 1629 | 0 1630 | ], 1631 | [ 1632 | 0, 1633 | -10.602 1634 | ] 1635 | ], 1636 | "c": true 1637 | }, 1638 | "ix": 2 1639 | }, 1640 | "nm": "Path 1", 1641 | "mn": "ADBE Vector Shape - Group", 1642 | "hd": false 1643 | }, 1644 | { 1645 | "ty": "fl", 1646 | "c": { 1647 | "a": 0, 1648 | "k": [ 1649 | 1, 1650 | 0.674509803922, 1651 | 0.384313755409, 1652 | 1 1653 | ], 1654 | "ix": 4 1655 | }, 1656 | "o": { 1657 | "a": 0, 1658 | "k": 100, 1659 | "ix": 5 1660 | }, 1661 | "r": 1, 1662 | "bm": 0, 1663 | "nm": "Fill 1", 1664 | "mn": "ADBE Vector Graphic - Fill", 1665 | "hd": false 1666 | }, 1667 | { 1668 | "ty": "tr", 1669 | "p": { 1670 | "a": 0, 1671 | "k": [ 1672 | 233.331, 1673 | 79.874 1674 | ], 1675 | "ix": 2 1676 | }, 1677 | "a": { 1678 | "a": 0, 1679 | "k": [ 1680 | 0, 1681 | 0 1682 | ], 1683 | "ix": 1 1684 | }, 1685 | "s": { 1686 | "a": 0, 1687 | "k": [ 1688 | 100, 1689 | 100 1690 | ], 1691 | "ix": 3 1692 | }, 1693 | "r": { 1694 | "a": 0, 1695 | "k": 0, 1696 | "ix": 6 1697 | }, 1698 | "o": { 1699 | "a": 0, 1700 | "k": 100, 1701 | "ix": 7 1702 | }, 1703 | "sk": { 1704 | "a": 0, 1705 | "k": 0, 1706 | "ix": 4 1707 | }, 1708 | "sa": { 1709 | "a": 0, 1710 | "k": 0, 1711 | "ix": 5 1712 | }, 1713 | "nm": "Transform" 1714 | } 1715 | ], 1716 | "nm": "Group 2", 1717 | "np": 2, 1718 | "cix": 2, 1719 | "bm": 0, 1720 | "ix": 2, 1721 | "mn": "ADBE Vector Group", 1722 | "hd": false 1723 | }, 1724 | { 1725 | "ty": "gr", 1726 | "it": [ 1727 | { 1728 | "ind": 0, 1729 | "ty": "sh", 1730 | "ix": 1, 1731 | "ks": { 1732 | "a": 0, 1733 | "k": { 1734 | "i": [ 1735 | [ 1736 | 0, 1737 | -7.129 1738 | ], 1739 | [ 1740 | 7.129, 1741 | 0 1742 | ], 1743 | [ 1744 | 0, 1745 | 7.129 1746 | ], 1747 | [ 1748 | -7.129, 1749 | 0 1750 | ] 1751 | ], 1752 | "o": [ 1753 | [ 1754 | 0, 1755 | 7.129 1756 | ], 1757 | [ 1758 | -7.129, 1759 | 0 1760 | ], 1761 | [ 1762 | 0, 1763 | -7.129 1764 | ], 1765 | [ 1766 | 7.129, 1767 | 0 1768 | ] 1769 | ], 1770 | "v": [ 1771 | [ 1772 | 12.908, 1773 | 0 1774 | ], 1775 | [ 1776 | 0, 1777 | 12.908 1778 | ], 1779 | [ 1780 | -12.908, 1781 | 0 1782 | ], 1783 | [ 1784 | 0, 1785 | -12.908 1786 | ] 1787 | ], 1788 | "c": true 1789 | }, 1790 | "ix": 2 1791 | }, 1792 | "nm": "Path 1", 1793 | "mn": "ADBE Vector Shape - Group", 1794 | "hd": false 1795 | }, 1796 | { 1797 | "ty": "fl", 1798 | "c": { 1799 | "a": 0, 1800 | "k": [ 1801 | 0.305882352941, 1802 | 0.84313731474, 1803 | 1, 1804 | 1 1805 | ], 1806 | "ix": 4 1807 | }, 1808 | "o": { 1809 | "a": 0, 1810 | "k": 100, 1811 | "ix": 5 1812 | }, 1813 | "r": 1, 1814 | "bm": 0, 1815 | "nm": "Fill 1", 1816 | "mn": "ADBE Vector Graphic - Fill", 1817 | "hd": false 1818 | }, 1819 | { 1820 | "ty": "tr", 1821 | "p": { 1822 | "a": 0, 1823 | "k": [ 1824 | 13.158, 1825 | 79.874 1826 | ], 1827 | "ix": 2 1828 | }, 1829 | "a": { 1830 | "a": 0, 1831 | "k": [ 1832 | 0, 1833 | 0 1834 | ], 1835 | "ix": 1 1836 | }, 1837 | "s": { 1838 | "a": 0, 1839 | "k": [ 1840 | 100, 1841 | 100 1842 | ], 1843 | "ix": 3 1844 | }, 1845 | "r": { 1846 | "a": 0, 1847 | "k": 0, 1848 | "ix": 6 1849 | }, 1850 | "o": { 1851 | "a": 0, 1852 | "k": 100, 1853 | "ix": 7 1854 | }, 1855 | "sk": { 1856 | "a": 0, 1857 | "k": 0, 1858 | "ix": 4 1859 | }, 1860 | "sa": { 1861 | "a": 0, 1862 | "k": 0, 1863 | "ix": 5 1864 | }, 1865 | "nm": "Transform" 1866 | } 1867 | ], 1868 | "nm": "Group 3", 1869 | "np": 2, 1870 | "cix": 2, 1871 | "bm": 0, 1872 | "ix": 3, 1873 | "mn": "ADBE Vector Group", 1874 | "hd": false 1875 | }, 1876 | { 1877 | "ty": "gr", 1878 | "it": [ 1879 | { 1880 | "ind": 0, 1881 | "ty": "sh", 1882 | "ix": 1, 1883 | "ks": { 1884 | "a": 0, 1885 | "k": { 1886 | "i": [ 1887 | [ 1888 | 0, 1889 | -3.475 1890 | ], 1891 | [ 1892 | 3.475, 1893 | 0 1894 | ], 1895 | [ 1896 | 0, 1897 | 3.475 1898 | ], 1899 | [ 1900 | -3.474, 1901 | 0 1902 | ] 1903 | ], 1904 | "o": [ 1905 | [ 1906 | 0, 1907 | 3.475 1908 | ], 1909 | [ 1910 | -3.474, 1911 | 0 1912 | ], 1913 | [ 1914 | 0, 1915 | -3.475 1916 | ], 1917 | [ 1918 | 3.475, 1919 | 0 1920 | ] 1921 | ], 1922 | "v": [ 1923 | [ 1924 | 6.292, 1925 | 0 1926 | ], 1927 | [ 1928 | -0.001, 1929 | 6.292 1930 | ], 1931 | [ 1932 | -6.292, 1933 | 0 1934 | ], 1935 | [ 1936 | -0.001, 1937 | -6.292 1938 | ] 1939 | ], 1940 | "c": true 1941 | }, 1942 | "ix": 2 1943 | }, 1944 | "nm": "Path 1", 1945 | "mn": "ADBE Vector Shape - Group", 1946 | "hd": false 1947 | }, 1948 | { 1949 | "ty": "fl", 1950 | "c": { 1951 | "a": 0, 1952 | "k": [ 1953 | 0.305882352941, 1954 | 0.84313731474, 1955 | 1, 1956 | 1 1957 | ], 1958 | "ix": 4 1959 | }, 1960 | "o": { 1961 | "a": 0, 1962 | "k": 100, 1963 | "ix": 5 1964 | }, 1965 | "r": 1, 1966 | "bm": 0, 1967 | "nm": "Fill 1", 1968 | "mn": "ADBE Vector Graphic - Fill", 1969 | "hd": false 1970 | }, 1971 | { 1972 | "ty": "tr", 1973 | "p": { 1974 | "a": 0, 1975 | "k": [ 1976 | 239.623, 1977 | 146.646 1978 | ], 1979 | "ix": 2 1980 | }, 1981 | "a": { 1982 | "a": 0, 1983 | "k": [ 1984 | 0, 1985 | 0 1986 | ], 1987 | "ix": 1 1988 | }, 1989 | "s": { 1990 | "a": 0, 1991 | "k": [ 1992 | 100, 1993 | 100 1994 | ], 1995 | "ix": 3 1996 | }, 1997 | "r": { 1998 | "a": 0, 1999 | "k": 0, 2000 | "ix": 6 2001 | }, 2002 | "o": { 2003 | "a": 0, 2004 | "k": 100, 2005 | "ix": 7 2006 | }, 2007 | "sk": { 2008 | "a": 0, 2009 | "k": 0, 2010 | "ix": 4 2011 | }, 2012 | "sa": { 2013 | "a": 0, 2014 | "k": 0, 2015 | "ix": 5 2016 | }, 2017 | "nm": "Transform" 2018 | } 2019 | ], 2020 | "nm": "Group 4", 2021 | "np": 2, 2022 | "cix": 2, 2023 | "bm": 0, 2024 | "ix": 4, 2025 | "mn": "ADBE Vector Group", 2026 | "hd": false 2027 | }, 2028 | { 2029 | "ty": "gr", 2030 | "it": [ 2031 | { 2032 | "ind": 0, 2033 | "ty": "sh", 2034 | "ix": 1, 2035 | "ks": { 2036 | "a": 0, 2037 | "k": { 2038 | "i": [ 2039 | [ 2040 | 0, 2041 | -1.782 2042 | ], 2043 | [ 2044 | 1.782, 2045 | 0 2046 | ], 2047 | [ 2048 | 0, 2049 | 1.782 2050 | ], 2051 | [ 2052 | -1.782, 2053 | 0 2054 | ] 2055 | ], 2056 | "o": [ 2057 | [ 2058 | 0, 2059 | 1.782 2060 | ], 2061 | [ 2062 | -1.782, 2063 | 0 2064 | ], 2065 | [ 2066 | 0, 2067 | -1.782 2068 | ], 2069 | [ 2070 | 1.782, 2071 | 0 2072 | ] 2073 | ], 2074 | "v": [ 2075 | [ 2076 | 3.227, 2077 | 0 2078 | ], 2079 | [ 2080 | 0, 2081 | 3.227 2082 | ], 2083 | [ 2084 | -3.227, 2085 | 0 2086 | ], 2087 | [ 2088 | 0, 2089 | -3.227 2090 | ] 2091 | ], 2092 | "c": true 2093 | }, 2094 | "ix": 2 2095 | }, 2096 | "nm": "Path 1", 2097 | "mn": "ADBE Vector Shape - Group", 2098 | "hd": false 2099 | }, 2100 | { 2101 | "ty": "fl", 2102 | "c": { 2103 | "a": 0, 2104 | "k": [ 2105 | 0.392156892664, 2106 | 1, 2107 | 0.662745098039, 2108 | 1 2109 | ], 2110 | "ix": 4 2111 | }, 2112 | "o": { 2113 | "a": 0, 2114 | "k": 100, 2115 | "ix": 5 2116 | }, 2117 | "r": 1, 2118 | "bm": 0, 2119 | "nm": "Fill 1", 2120 | "mn": "ADBE Vector Graphic - Fill", 2121 | "hd": false 2122 | }, 2123 | { 2124 | "ty": "tr", 2125 | "p": { 2126 | "a": 0, 2127 | "k": [ 2128 | 16.716, 2129 | 159.823 2130 | ], 2131 | "ix": 2 2132 | }, 2133 | "a": { 2134 | "a": 0, 2135 | "k": [ 2136 | 0, 2137 | 0 2138 | ], 2139 | "ix": 1 2140 | }, 2141 | "s": { 2142 | "a": 0, 2143 | "k": [ 2144 | 100, 2145 | 100 2146 | ], 2147 | "ix": 3 2148 | }, 2149 | "r": { 2150 | "a": 0, 2151 | "k": 0, 2152 | "ix": 6 2153 | }, 2154 | "o": { 2155 | "a": 0, 2156 | "k": 100, 2157 | "ix": 7 2158 | }, 2159 | "sk": { 2160 | "a": 0, 2161 | "k": 0, 2162 | "ix": 4 2163 | }, 2164 | "sa": { 2165 | "a": 0, 2166 | "k": 0, 2167 | "ix": 5 2168 | }, 2169 | "nm": "Transform" 2170 | } 2171 | ], 2172 | "nm": "Group 5", 2173 | "np": 2, 2174 | "cix": 2, 2175 | "bm": 0, 2176 | "ix": 5, 2177 | "mn": "ADBE Vector Group", 2178 | "hd": false 2179 | }, 2180 | { 2181 | "ty": "gr", 2182 | "it": [ 2183 | { 2184 | "ind": 0, 2185 | "ty": "sh", 2186 | "ix": 1, 2187 | "ks": { 2188 | "a": 0, 2189 | "k": { 2190 | "i": [ 2191 | [ 2192 | 0, 2193 | -5.758 2194 | ], 2195 | [ 2196 | 5.758, 2197 | 0 2198 | ], 2199 | [ 2200 | 0, 2201 | 5.758 2202 | ], 2203 | [ 2204 | -5.759, 2205 | 0 2206 | ] 2207 | ], 2208 | "o": [ 2209 | [ 2210 | 0, 2211 | 5.758 2212 | ], 2213 | [ 2214 | -5.759, 2215 | 0 2216 | ], 2217 | [ 2218 | 0, 2219 | -5.758 2220 | ], 2221 | [ 2222 | 5.758, 2223 | 0 2224 | ] 2225 | ], 2226 | "v": [ 2227 | [ 2228 | 10.427, 2229 | 0 2230 | ], 2231 | [ 2232 | 0, 2233 | 10.426 2234 | ], 2235 | [ 2236 | -10.427, 2237 | 0 2238 | ], 2239 | [ 2240 | 0, 2241 | -10.426 2242 | ] 2243 | ], 2244 | "c": true 2245 | }, 2246 | "ix": 2 2247 | }, 2248 | "nm": "Path 1", 2249 | "mn": "ADBE Vector Shape - Group", 2250 | "hd": false 2251 | }, 2252 | { 2253 | "ty": "fl", 2254 | "c": { 2255 | "a": 0, 2256 | "k": [ 2257 | 0.392156892664, 2258 | 1, 2259 | 0.662745098039, 2260 | 1 2261 | ], 2262 | "ix": 4 2263 | }, 2264 | "o": { 2265 | "a": 0, 2266 | "k": 100, 2267 | "ix": 5 2268 | }, 2269 | "r": 1, 2270 | "bm": 0, 2271 | "nm": "Fill 1", 2272 | "mn": "ADBE Vector Graphic - Fill", 2273 | "hd": false 2274 | }, 2275 | { 2276 | "ty": "tr", 2277 | "p": { 2278 | "a": 0, 2279 | "k": [ 2280 | 208.393, 2281 | 211.092 2282 | ], 2283 | "ix": 2 2284 | }, 2285 | "a": { 2286 | "a": 0, 2287 | "k": [ 2288 | 0, 2289 | 0 2290 | ], 2291 | "ix": 1 2292 | }, 2293 | "s": { 2294 | "a": 0, 2295 | "k": [ 2296 | 100, 2297 | 100 2298 | ], 2299 | "ix": 3 2300 | }, 2301 | "r": { 2302 | "a": 0, 2303 | "k": 0, 2304 | "ix": 6 2305 | }, 2306 | "o": { 2307 | "a": 0, 2308 | "k": 100, 2309 | "ix": 7 2310 | }, 2311 | "sk": { 2312 | "a": 0, 2313 | "k": 0, 2314 | "ix": 4 2315 | }, 2316 | "sa": { 2317 | "a": 0, 2318 | "k": 0, 2319 | "ix": 5 2320 | }, 2321 | "nm": "Transform" 2322 | } 2323 | ], 2324 | "nm": "Group 6", 2325 | "np": 2, 2326 | "cix": 2, 2327 | "bm": 0, 2328 | "ix": 6, 2329 | "mn": "ADBE Vector Group", 2330 | "hd": false 2331 | }, 2332 | { 2333 | "ty": "gr", 2334 | "it": [ 2335 | { 2336 | "ind": 0, 2337 | "ty": "sh", 2338 | "ix": 1, 2339 | "ks": { 2340 | "a": 0, 2341 | "k": { 2342 | "i": [ 2343 | [ 2344 | 0, 2345 | -2.054 2346 | ], 2347 | [ 2348 | 2.054, 2349 | 0 2350 | ], 2351 | [ 2352 | 0, 2353 | 2.054 2354 | ], 2355 | [ 2356 | -2.053, 2357 | 0 2358 | ] 2359 | ], 2360 | "o": [ 2361 | [ 2362 | 0, 2363 | 2.054 2364 | ], 2365 | [ 2366 | -2.053, 2367 | 0 2368 | ], 2369 | [ 2370 | 0, 2371 | -2.054 2372 | ], 2373 | [ 2374 | 2.054, 2375 | 0 2376 | ] 2377 | ], 2378 | "v": [ 2379 | [ 2380 | 3.719, 2381 | 0 2382 | ], 2383 | [ 2384 | -0.001, 2385 | 3.719 2386 | ], 2387 | [ 2388 | -3.719, 2389 | 0 2390 | ], 2391 | [ 2392 | -0.001, 2393 | -3.719 2394 | ] 2395 | ], 2396 | "c": true 2397 | }, 2398 | "ix": 2 2399 | }, 2400 | "nm": "Path 1", 2401 | "mn": "ADBE Vector Shape - Group", 2402 | "hd": false 2403 | }, 2404 | { 2405 | "ty": "fl", 2406 | "c": { 2407 | "a": 0, 2408 | "k": [ 2409 | 0.929411824544, 2410 | 0.243137269862, 2411 | 0.243137269862, 2412 | 1 2413 | ], 2414 | "ix": 4 2415 | }, 2416 | "o": { 2417 | "a": 0, 2418 | "k": 100, 2419 | "ix": 5 2420 | }, 2421 | "r": 1, 2422 | "bm": 0, 2423 | "nm": "Fill 1", 2424 | "mn": "ADBE Vector Graphic - Fill", 2425 | "hd": false 2426 | }, 2427 | { 2428 | "ty": "tr", 2429 | "p": { 2430 | "a": 0, 2431 | "k": [ 2432 | 187.583, 2433 | 32.998 2434 | ], 2435 | "ix": 2 2436 | }, 2437 | "a": { 2438 | "a": 0, 2439 | "k": [ 2440 | 0, 2441 | 0 2442 | ], 2443 | "ix": 1 2444 | }, 2445 | "s": { 2446 | "a": 0, 2447 | "k": [ 2448 | 100, 2449 | 100 2450 | ], 2451 | "ix": 3 2452 | }, 2453 | "r": { 2454 | "a": 0, 2455 | "k": 0, 2456 | "ix": 6 2457 | }, 2458 | "o": { 2459 | "a": 0, 2460 | "k": 100, 2461 | "ix": 7 2462 | }, 2463 | "sk": { 2464 | "a": 0, 2465 | "k": 0, 2466 | "ix": 4 2467 | }, 2468 | "sa": { 2469 | "a": 0, 2470 | "k": 0, 2471 | "ix": 5 2472 | }, 2473 | "nm": "Transform" 2474 | } 2475 | ], 2476 | "nm": "Group 7", 2477 | "np": 2, 2478 | "cix": 2, 2479 | "bm": 0, 2480 | "ix": 7, 2481 | "mn": "ADBE Vector Group", 2482 | "hd": false 2483 | }, 2484 | { 2485 | "ty": "gr", 2486 | "it": [ 2487 | { 2488 | "ind": 0, 2489 | "ty": "sh", 2490 | "ix": 1, 2491 | "ks": { 2492 | "a": 0, 2493 | "k": { 2494 | "i": [ 2495 | [ 2496 | 0, 2497 | -6.803 2498 | ], 2499 | [ 2500 | 6.802, 2501 | 0 2502 | ], 2503 | [ 2504 | 0, 2505 | 6.803 2506 | ], 2507 | [ 2508 | -6.803, 2509 | 0 2510 | ] 2511 | ], 2512 | "o": [ 2513 | [ 2514 | 0, 2515 | 6.803 2516 | ], 2517 | [ 2518 | -6.803, 2519 | 0 2520 | ], 2521 | [ 2522 | 0, 2523 | -6.803 2524 | ], 2525 | [ 2526 | 6.802, 2527 | 0 2528 | ] 2529 | ], 2530 | "v": [ 2531 | [ 2532 | 12.318, 2533 | 0 2534 | ], 2535 | [ 2536 | 0.001, 2537 | 12.317 2538 | ], 2539 | [ 2540 | -12.318, 2541 | 0 2542 | ], 2543 | [ 2544 | 0.001, 2545 | -12.317 2546 | ] 2547 | ], 2548 | "c": true 2549 | }, 2550 | "ix": 2 2551 | }, 2552 | "nm": "Path 1", 2553 | "mn": "ADBE Vector Shape - Group", 2554 | "hd": false 2555 | }, 2556 | { 2557 | "ty": "fl", 2558 | "c": { 2559 | "a": 0, 2560 | "k": [ 2561 | 0.929411824544, 2562 | 0.243137269862, 2563 | 0.243137269862, 2564 | 1 2565 | ], 2566 | "ix": 4 2567 | }, 2568 | "o": { 2569 | "a": 0, 2570 | "k": 100, 2571 | "ix": 5 2572 | }, 2573 | "r": 1, 2574 | "bm": 0, 2575 | "nm": "Fill 1", 2576 | "mn": "ADBE Vector Graphic - Fill", 2577 | "hd": false 2578 | }, 2579 | { 2580 | "ty": "tr", 2581 | "p": { 2582 | "a": 0, 2583 | "k": [ 2584 | 55.34, 2585 | 221.518 2586 | ], 2587 | "ix": 2 2588 | }, 2589 | "a": { 2590 | "a": 0, 2591 | "k": [ 2592 | 0, 2593 | 0 2594 | ], 2595 | "ix": 1 2596 | }, 2597 | "s": { 2598 | "a": 0, 2599 | "k": [ 2600 | 100, 2601 | 100 2602 | ], 2603 | "ix": 3 2604 | }, 2605 | "r": { 2606 | "a": 0, 2607 | "k": 0, 2608 | "ix": 6 2609 | }, 2610 | "o": { 2611 | "a": 0, 2612 | "k": 100, 2613 | "ix": 7 2614 | }, 2615 | "sk": { 2616 | "a": 0, 2617 | "k": 0, 2618 | "ix": 4 2619 | }, 2620 | "sa": { 2621 | "a": 0, 2622 | "k": 0, 2623 | "ix": 5 2624 | }, 2625 | "nm": "Transform" 2626 | } 2627 | ], 2628 | "nm": "Group 8", 2629 | "np": 2, 2630 | "cix": 2, 2631 | "bm": 0, 2632 | "ix": 8, 2633 | "mn": "ADBE Vector Group", 2634 | "hd": false 2635 | }, 2636 | { 2637 | "ty": "gr", 2638 | "it": [ 2639 | { 2640 | "ind": 0, 2641 | "ty": "sh", 2642 | "ix": 1, 2643 | "ks": { 2644 | "a": 0, 2645 | "k": { 2646 | "i": [ 2647 | [ 2648 | 0, 2649 | -5.582 2650 | ], 2651 | [ 2652 | 5.582, 2653 | 0 2654 | ], 2655 | [ 2656 | 0, 2657 | 5.582 2658 | ], 2659 | [ 2660 | -5.583, 2661 | 0 2662 | ] 2663 | ], 2664 | "o": [ 2665 | [ 2666 | 0, 2667 | 5.582 2668 | ], 2669 | [ 2670 | -5.583, 2671 | 0 2672 | ], 2673 | [ 2674 | 0, 2675 | -5.582 2676 | ], 2677 | [ 2678 | 5.582, 2679 | 0 2680 | ] 2681 | ], 2682 | "v": [ 2683 | [ 2684 | 10.107, 2685 | 0 2686 | ], 2687 | [ 2688 | 0, 2689 | 10.107 2690 | ], 2691 | [ 2692 | -10.108, 2693 | 0 2694 | ], 2695 | [ 2696 | 0, 2697 | -10.107 2698 | ] 2699 | ], 2700 | "c": true 2701 | }, 2702 | "ix": 2 2703 | }, 2704 | "nm": "Path 1", 2705 | "mn": "ADBE Vector Shape - Group", 2706 | "hd": false 2707 | }, 2708 | { 2709 | "ty": "fl", 2710 | "c": { 2711 | "a": 0, 2712 | "k": [ 2713 | 0.490196108351, 2714 | 0.294117647059, 2715 | 0.752941236309, 2716 | 1 2717 | ], 2718 | "ix": 4 2719 | }, 2720 | "o": { 2721 | "a": 0, 2722 | "k": 100, 2723 | "ix": 5 2724 | }, 2725 | "r": 1, 2726 | "bm": 0, 2727 | "nm": "Fill 1", 2728 | "mn": "ADBE Vector Graphic - Fill", 2729 | "hd": false 2730 | }, 2731 | { 2732 | "ty": "tr", 2733 | "p": { 2734 | "a": 0, 2735 | "k": [ 2736 | 128.624, 2737 | 10.357 2738 | ], 2739 | "ix": 2 2740 | }, 2741 | "a": { 2742 | "a": 0, 2743 | "k": [ 2744 | 0, 2745 | 0 2746 | ], 2747 | "ix": 1 2748 | }, 2749 | "s": { 2750 | "a": 0, 2751 | "k": [ 2752 | 100, 2753 | 100 2754 | ], 2755 | "ix": 3 2756 | }, 2757 | "r": { 2758 | "a": 0, 2759 | "k": 0, 2760 | "ix": 6 2761 | }, 2762 | "o": { 2763 | "a": 0, 2764 | "k": 100, 2765 | "ix": 7 2766 | }, 2767 | "sk": { 2768 | "a": 0, 2769 | "k": 0, 2770 | "ix": 4 2771 | }, 2772 | "sa": { 2773 | "a": 0, 2774 | "k": 0, 2775 | "ix": 5 2776 | }, 2777 | "nm": "Transform" 2778 | } 2779 | ], 2780 | "nm": "Group 9", 2781 | "np": 2, 2782 | "cix": 2, 2783 | "bm": 0, 2784 | "ix": 9, 2785 | "mn": "ADBE Vector Group", 2786 | "hd": false 2787 | }, 2788 | { 2789 | "ty": "gr", 2790 | "it": [ 2791 | { 2792 | "ind": 0, 2793 | "ty": "sh", 2794 | "ix": 1, 2795 | "ks": { 2796 | "a": 0, 2797 | "k": { 2798 | "i": [ 2799 | [ 2800 | 0, 2801 | -2.903 2802 | ], 2803 | [ 2804 | 2.903, 2805 | 0 2806 | ], 2807 | [ 2808 | 0, 2809 | 2.903 2810 | ], 2811 | [ 2812 | -2.903, 2813 | 0 2814 | ] 2815 | ], 2816 | "o": [ 2817 | [ 2818 | 0, 2819 | 2.903 2820 | ], 2821 | [ 2822 | -2.903, 2823 | 0 2824 | ], 2825 | [ 2826 | 0, 2827 | -2.903 2828 | ], 2829 | [ 2830 | 2.903, 2831 | 0 2832 | ] 2833 | ], 2834 | "v": [ 2835 | [ 2836 | 5.257, 2837 | 0 2838 | ], 2839 | [ 2840 | -0.001, 2841 | 5.257 2842 | ], 2843 | [ 2844 | -5.257, 2845 | 0 2846 | ], 2847 | [ 2848 | -0.001, 2849 | -5.257 2850 | ] 2851 | ], 2852 | "c": true 2853 | }, 2854 | "ix": 2 2855 | }, 2856 | "nm": "Path 1", 2857 | "mn": "ADBE Vector Shape - Group", 2858 | "hd": false 2859 | }, 2860 | { 2861 | "ty": "fl", 2862 | "c": { 2863 | "a": 0, 2864 | "k": [ 2865 | 0.490196108351, 2866 | 0.294117647059, 2867 | 0.752941236309, 2868 | 1 2869 | ], 2870 | "ix": 4 2871 | }, 2872 | "o": { 2873 | "a": 0, 2874 | "k": 100, 2875 | "ix": 5 2876 | }, 2877 | "r": 1, 2878 | "bm": 0, 2879 | "nm": "Fill 1", 2880 | "mn": "ADBE Vector Graphic - Fill", 2881 | "hd": false 2882 | }, 2883 | { 2884 | "ty": "tr", 2885 | "p": { 2886 | "a": 0, 2887 | "k": [ 2888 | 135.955, 2889 | 236.884 2890 | ], 2891 | "ix": 2 2892 | }, 2893 | "a": { 2894 | "a": 0, 2895 | "k": [ 2896 | 0, 2897 | 0 2898 | ], 2899 | "ix": 1 2900 | }, 2901 | "s": { 2902 | "a": 0, 2903 | "k": [ 2904 | 100, 2905 | 100 2906 | ], 2907 | "ix": 3 2908 | }, 2909 | "r": { 2910 | "a": 0, 2911 | "k": 0, 2912 | "ix": 6 2913 | }, 2914 | "o": { 2915 | "a": 0, 2916 | "k": 100, 2917 | "ix": 7 2918 | }, 2919 | "sk": { 2920 | "a": 0, 2921 | "k": 0, 2922 | "ix": 4 2923 | }, 2924 | "sa": { 2925 | "a": 0, 2926 | "k": 0, 2927 | "ix": 5 2928 | }, 2929 | "nm": "Transform" 2930 | } 2931 | ], 2932 | "nm": "Group 10", 2933 | "np": 2, 2934 | "cix": 2, 2935 | "bm": 0, 2936 | "ix": 10, 2937 | "mn": "ADBE Vector Group", 2938 | "hd": false 2939 | } 2940 | ], 2941 | "ip": 0, 2942 | "op": 58.0580580580581, 2943 | "st": 0, 2944 | "bm": 0 2945 | } 2946 | ], 2947 | "markers": [] 2948 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ComposeOtpVerify 3 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |