├── .gitignore ├── .idea ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── mayandro │ │ └── firebasephoneauth │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── mayandro │ │ │ └── firebasephoneauth │ │ │ ├── BaseApplication.kt │ │ │ ├── di │ │ │ ├── app │ │ │ │ ├── component │ │ │ │ │ └── AppComponent.kt │ │ │ │ ├── module │ │ │ │ │ ├── ActivityBindingModule.kt │ │ │ │ │ ├── AppModule.kt │ │ │ │ │ ├── NetworkModule.kt │ │ │ │ │ ├── PersistanceModule.kt │ │ │ │ │ └── ViewModelFactoryModule.kt │ │ │ │ ├── qualifiers │ │ │ │ │ ├── ActivityContext.kt │ │ │ │ │ └── ApplicationContext.kt │ │ │ │ ├── scope │ │ │ │ │ ├── ActivityScoped.kt │ │ │ │ │ ├── AppScoped.kt │ │ │ │ │ └── FragmentScoped.kt │ │ │ │ └── utils │ │ │ │ │ ├── ViewModelFactory.kt │ │ │ │ │ └── ViewModelKey.kt │ │ │ ├── auth │ │ │ │ ├── AuthActivityModule.kt │ │ │ │ └── FragmentBindingModule.kt │ │ │ └── home │ │ │ │ └── HomeActivityModule.kt │ │ │ ├── ui │ │ │ ├── auth │ │ │ │ ├── AuthActivity.kt │ │ │ │ ├── AuthActivityViewInteractor.kt │ │ │ │ ├── AuthActivityViewModel.kt │ │ │ │ ├── adapter │ │ │ │ │ └── AuthPagerAdapter.kt │ │ │ │ └── fragments │ │ │ │ │ ├── OtpVerificationFragment.kt │ │ │ │ │ └── PhoneVerificationFragment.kt │ │ │ ├── base │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── BaseViewModel.kt │ │ │ │ └── ViewInteractor.kt │ │ │ ├── custom_view │ │ │ │ └── OtpEditText.kt │ │ │ └── home │ │ │ │ ├── HomeActivity.kt │ │ │ │ └── HomeActivityViewModel.kt │ │ │ └── utils │ │ │ ├── AppConstants.kt │ │ │ ├── ConnectionProviderManager.kt │ │ │ ├── FireBaseAuthProvider.kt │ │ │ ├── ResourceProvider.kt │ │ │ └── SharedPreferenceManager.kt │ └── res │ │ ├── drawable-hdpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable-ldpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable-mdpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable-xxhdpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable-xxxhdpi │ │ ├── otp.png │ │ └── phone.png │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout-land │ │ ├── fragment_otp_verification.xml │ │ └── fragment_phone_verification.xml │ │ ├── layout │ │ ├── activity_auth.xml │ │ ├── activity_home.xml │ │ ├── fragment_otp_verification.xml │ │ └── fragment_phone_verification.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── mayandro │ └── firebasephoneauth │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle ├── dependency.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | xmlns:android 17 | 18 | ^$ 19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | xmlns:.* 28 | 29 | ^$ 30 | 31 | 32 | BY_NAME 33 | 34 |
35 |
36 | 37 | 38 | 39 | .*:id 40 | 41 | http://schemas.android.com/apk/res/android 42 | 43 | 44 | 45 |
46 |
47 | 48 | 49 | 50 | .*:name 51 | 52 | http://schemas.android.com/apk/res/android 53 | 54 | 55 | 56 |
57 |
58 | 59 | 60 | 61 | name 62 | 63 | ^$ 64 | 65 | 66 | 67 |
68 |
69 | 70 | 71 | 72 | style 73 | 74 | ^$ 75 | 76 | 77 | 78 |
79 |
80 | 81 | 82 | 83 | .* 84 | 85 | ^$ 86 | 87 | 88 | BY_NAME 89 | 90 |
91 |
92 | 93 | 94 | 95 | .* 96 | 97 | http://schemas.android.com/apk/res/android 98 | 99 | 100 | ANDROID_ATTRIBUTE_ORDER 101 | 102 |
103 |
104 | 105 | 106 | 107 | .* 108 | 109 | .* 110 | 111 | 112 | BY_NAME 113 | 114 |
115 |
116 |
117 |
118 | 119 | 121 |
122 |
-------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidFirebaseAuthCleanArch 2 | Android Application using Architecture Componenets in Kotlin demonstrating Firebase Phone Auth. Application uses Dagger Android, Firebase Auth, Google Services, Material Design, Kotlin, Arch Components and Dagger Android. 3 | 4 | # Design Credits 5 | https://www.uplabs.com/posts/verification-interaction 6 | 7 | # Implemented Design 8 | ![Screenshot_2019-12-18-18-08-36-706_com mayandro firebasephoneauth](https://user-images.githubusercontent.com/16761273/71108513-8e6bf300-21c3-11ea-9b40-ce4cb463862d.png) 9 | ![Screenshot_2019-12-18-18-08-57-127_com mayandro firebasephoneauth](https://user-images.githubusercontent.com/16761273/71108514-8f048980-21c3-11ea-8948-56da88fde964.png) 10 | 11 | # Like if it is useful :) 12 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | apply plugin: 'kotlin-kapt' 8 | 9 | apply plugin: "androidx.navigation.safeargs.kotlin" 10 | 11 | apply plugin: 'com.google.gms.google-services' 12 | 13 | android { 14 | compileSdkVersion versions.compileSdkVersion 15 | buildToolsVersion versions.buildToolVersion 16 | 17 | defaultConfig { 18 | applicationId "com.mayandro.firebasephoneauth" 19 | minSdkVersion versions.minSdkVersion 20 | targetSdkVersion versions.targetSdkVersion 21 | versionCode 1 22 | versionName "1.0" 23 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 24 | vectorDrawables.useSupportLibrary = true 25 | } 26 | buildTypes { 27 | release { 28 | minifyEnabled false 29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 30 | } 31 | } 32 | dataBinding { 33 | enabled = true 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation fileTree(dir: 'libs', include: ['*.jar']) 39 | 40 | testImplementation 'junit:junit:4.12' 41 | androidTestImplementation 'androidx.test:runner:1.2.0' 42 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 43 | 44 | // Kotlin 45 | implementation deps.kotlin.kotlinSDK 46 | implementation 'androidx.core:core-ktx:1.1.0' 47 | 48 | // Coroutine 49 | implementation deps.kotlin.coroutine 50 | 51 | // App dependencies 52 | implementation deps.androidx.appcompat 53 | implementation deps.androidx.constraintlayout 54 | implementation deps.androidx.recyclerview 55 | implementation deps.androidx.cardview 56 | implementation deps.androidx.viewpager2 57 | 58 | // Timber 59 | implementation deps.logger.timber 60 | 61 | // design 62 | implementation deps.design.material 63 | 64 | // Architecture Components 65 | implementation deps.arch.extensions 66 | implementation deps.arch.viewmodel 67 | kapt deps.arch.compiler 68 | 69 | // Room 70 | implementation deps.database.room 71 | kapt deps.database.compiler 72 | 73 | //Navigation 74 | implementation deps.arch.navigationui 75 | implementation deps.arch.navigationfragment 76 | 77 | // Retrofit 2 78 | implementation deps.network.retrofit 79 | implementation deps.network.retrofitgson 80 | implementation deps.network.retrofitrx 81 | implementation deps.network.okhttplogger 82 | 83 | // Glide 84 | implementation deps.imageLib.glide 85 | 86 | // Databinding compiler 87 | kapt deps.binding.databinding 88 | 89 | // Rx Plugins 90 | implementation deps.rx.permissions 91 | 92 | // JetPack Shared Preference Plugins 93 | implementation deps.sharedpreferences.preference 94 | 95 | // Dagger 2 96 | implementation deps.dagger.dagger 97 | implementation deps.dagger.daggerSupport 98 | kapt deps.dagger.daggerCompiler 99 | kapt deps.dagger.daggerProcessor 100 | 101 | //Phone 102 | implementation deps.playservicesauth.playservicesauth 103 | implementation deps.playservicesauth.playservicesauthapiphone 104 | 105 | //Firebase 106 | implementation deps.firebase.auth 107 | implementation deps.firebase.analytics 108 | } 109 | -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1039434398181", 4 | "firebase_url": "https://fir-authdemo-77fa0.firebaseio.com", 5 | "project_id": "fir-authdemo-77fa0", 6 | "storage_bucket": "fir-authdemo-77fa0.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1039434398181:android:3a574dc2490bb42ce3d70c", 12 | "android_client_info": { 13 | "package_name": "com.mayandro.firebasephoneauth" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1039434398181-oodccjco314ur1groav6f3g0mddd6moo.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.mayandro.firebasephoneauth", 22 | "certificate_hash": "82f836a84ad7a59699e1a2560dbbf1ac25acc875" 23 | } 24 | }, 25 | { 26 | "client_id": "1039434398181-sgrgq998bv3nc2re7l3kal420aavb06s.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyDM8Pe3U5a0qx7A-sZpaBVbDNAeITo3jd4" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "1039434398181-sgrgq998bv3nc2re7l3kal420aavb06s.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | ], 47 | "configuration_version": "1" 48 | } -------------------------------------------------------------------------------- /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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/mayandro/firebasephoneauth/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.runner.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.mayandro.firebasephoneauth", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/BaseApplication.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth 2 | 3 | import com.mayandro.firebasephoneauth.di.app.component.DaggerAppComponent 4 | import dagger.android.AndroidInjector 5 | import dagger.android.DaggerApplication 6 | import timber.log.Timber 7 | 8 | class BaseApplication : DaggerApplication() { 9 | 10 | override fun applicationInjector(): AndroidInjector { 11 | return DaggerAppComponent.builder().application(this).build() 12 | } 13 | 14 | override fun onCreate() { 15 | super.onCreate() 16 | if (BuildConfig.DEBUG) { 17 | Timber.plant(Timber.DebugTree()) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/component/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.component 2 | 3 | import android.app.Application 4 | import com.mayandro.firebasephoneauth.BaseApplication 5 | import com.mayandro.firebasephoneauth.di.app.module.* 6 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 7 | import dagger.BindsInstance 8 | import dagger.Component 9 | import dagger.android.AndroidInjector 10 | import dagger.android.support.AndroidSupportInjectionModule 11 | 12 | @AppScoped 13 | @Component(modules = [ 14 | AndroidSupportInjectionModule::class, 15 | ActivityBindingModule::class, 16 | AppModule::class, 17 | ViewModelFactoryModule::class, 18 | NetworkModule::class, 19 | PersistanceModule::class 20 | ]) 21 | interface AppComponent : AndroidInjector { 22 | @Component.Builder 23 | interface Builder { 24 | @BindsInstance 25 | fun application(application: Application): Builder 26 | 27 | fun build(): AppComponent 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/module/ActivityBindingModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.module 2 | 3 | import com.mayandro.firebasephoneauth.di.app.scope.ActivityScoped 4 | import com.mayandro.firebasephoneauth.di.auth.AuthActivityModule 5 | import com.mayandro.firebasephoneauth.di.auth.FragmentBindingModule 6 | import com.mayandro.firebasephoneauth.di.home.HomeActivityModule 7 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivity 8 | import com.mayandro.firebasephoneauth.ui.home.HomeActivity 9 | import dagger.Module 10 | import dagger.android.ContributesAndroidInjector 11 | 12 | @Module 13 | abstract class ActivityBindingModule { 14 | @ActivityScoped 15 | @ContributesAndroidInjector(modules = [AuthActivityModule::class, FragmentBindingModule::class]) 16 | internal abstract fun bindAuthActivity(): AuthActivity 17 | 18 | @ActivityScoped 19 | @ContributesAndroidInjector(modules = [HomeActivityModule::class]) 20 | internal abstract fun bindGoalsActivity(): HomeActivity 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/module/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.module 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 5 | import dagger.Module 6 | import dagger.Provides 7 | 8 | 9 | @Module 10 | class AppModule { 11 | 12 | @Provides 13 | @AppScoped 14 | internal fun provideFirebaseAuth(): FirebaseAuth { 15 | return FirebaseAuth.getInstance() 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/module/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.module 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import dagger.Module 6 | import android.net.ConnectivityManager 7 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 8 | import dagger.Provides 9 | 10 | 11 | 12 | @Module 13 | class NetworkModule { 14 | 15 | @Provides 16 | @AppScoped 17 | internal fun provideConnectivityManager(context: Application): ConnectivityManager { 18 | return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/module/PersistanceModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.module 2 | 3 | import android.app.Application 4 | import android.content.SharedPreferences 5 | import androidx.preference.PreferenceManager 6 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 7 | import dagger.Module 8 | import dagger.Provides 9 | 10 | @Module 11 | class PersistanceModule { 12 | 13 | @Provides 14 | @AppScoped 15 | internal fun provideSharedPreference(context: Application): SharedPreferences { 16 | return PreferenceManager.getDefaultSharedPreferences(context) 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/module/ViewModelFactoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.module 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 6 | import com.mayandro.firebasephoneauth.di.app.utils.ViewModelFactory 7 | import com.mayandro.firebasephoneauth.di.app.utils.ViewModelKey 8 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivityViewModel 9 | import com.mayandro.firebasephoneauth.ui.home.HomeActivityViewModel 10 | import dagger.Binds 11 | import dagger.Module 12 | import dagger.multibindings.IntoMap 13 | 14 | @Module 15 | abstract class ViewModelFactoryModule { 16 | 17 | @Binds 18 | @AppScoped 19 | internal abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory 20 | 21 | @Binds 22 | @IntoMap 23 | @ViewModelKey(AuthActivityViewModel::class) 24 | abstract fun bindAuthActivityViewModel(authActivityViewModel: AuthActivityViewModel): ViewModel 25 | 26 | @Binds 27 | @IntoMap 28 | @ViewModelKey(HomeActivityViewModel::class) 29 | abstract fun bindGoalActivityViewModel(goalActivityViewModel: HomeActivityViewModel): ViewModel 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/qualifiers/ActivityContext.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.qualifiers 2 | 3 | import javax.inject.Qualifier 4 | 5 | 6 | @Retention @Qualifier annotation class ActivityContext 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/qualifiers/ApplicationContext.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.qualifiers 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Retention @Qualifier annotation class ApplicationContext 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/scope/ActivityScoped.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.scope 2 | 3 | import java.lang.annotation.RetentionPolicy 4 | import javax.inject.Scope 5 | 6 | @Scope 7 | annotation class ActivityScoped -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/scope/AppScoped.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.scope 2 | 3 | import java.lang.annotation.RetentionPolicy 4 | import javax.inject.Scope 5 | 6 | @Scope 7 | annotation class AppScoped -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/scope/FragmentScoped.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.scope 2 | 3 | import javax.inject.Scope 4 | 5 | @Scope annotation class FragmentScoped -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/utils/ViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.utils 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 6 | import javax.inject.Inject 7 | import javax.inject.Provider 8 | 9 | @AppScoped 10 | class ViewModelFactory @Inject 11 | constructor(private val creators: Map, @JvmSuppressWildcards Provider>) : ViewModelProvider.Factory { 12 | 13 | override fun create(modelClass: Class): T { 14 | var creator: Provider? = creators[modelClass] 15 | if (creator == null) { 16 | for ((key, value) in creators) { 17 | if (modelClass.isAssignableFrom(key)) { 18 | creator = value 19 | break 20 | } 21 | } 22 | } 23 | if (creator == null) { 24 | throw IllegalArgumentException("unknown model class " + modelClass) 25 | } 26 | try { 27 | @Suppress("UNCHECKED_CAST") 28 | return creator.get() as T 29 | } catch (e: Exception) { 30 | throw RuntimeException(e) 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/app/utils/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.app.utils 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | @Target( 8 | AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER 9 | ) 10 | @Retention(AnnotationRetention.RUNTIME) 11 | @MapKey 12 | annotation class ViewModelKey(val value: KClass) -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/auth/AuthActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.auth 2 | 3 | import com.mayandro.firebasephoneauth.di.app.scope.ActivityScoped 4 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivity 5 | import com.mayandro.firebasephoneauth.ui.auth.adapter.AuthPagerAdapter 6 | import com.mayandro.firebasephoneauth.utils.BaseResourceProvider 7 | import com.mayandro.firebasephoneauth.utils.ResourceProvider 8 | import dagger.Module 9 | import dagger.Provides 10 | 11 | 12 | @Module 13 | class AuthActivityModule { 14 | @Provides 15 | @ActivityScoped 16 | fun provideResourceProvider(context: AuthActivity): BaseResourceProvider { 17 | return ResourceProvider(context) 18 | } 19 | 20 | @Provides 21 | @ActivityScoped 22 | fun provideAuthPagerAdapter(activity: AuthActivity): AuthPagerAdapter { 23 | return AuthPagerAdapter(activity) 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/auth/FragmentBindingModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.auth 2 | 3 | import com.mayandro.firebasephoneauth.di.app.scope.FragmentScoped 4 | import com.mayandro.firebasephoneauth.ui.auth.fragments.OtpVerificationFragment 5 | import com.mayandro.firebasephoneauth.ui.auth.fragments.PhoneVerificationFragment 6 | import dagger.Module 7 | import dagger.android.ContributesAndroidInjector 8 | 9 | @Module 10 | abstract class FragmentBindingModule { 11 | @FragmentScoped 12 | @ContributesAndroidInjector 13 | internal abstract fun bindOtpVerificationFragment(): OtpVerificationFragment 14 | 15 | @FragmentScoped 16 | @ContributesAndroidInjector 17 | internal abstract fun bindPhoneVerificationFragment(): PhoneVerificationFragment 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/di/home/HomeActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.di.home 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.mayandro.firebasephoneauth.di.app.scope.ActivityScoped 6 | import com.mayandro.firebasephoneauth.ui.home.HomeActivity 7 | import com.mayandro.firebasephoneauth.utils.BaseResourceProvider 8 | import com.mayandro.firebasephoneauth.utils.ResourceProvider 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.android.support.DaggerAppCompatActivity 12 | 13 | 14 | @Module 15 | class HomeActivityModule { 16 | 17 | @Provides 18 | @ActivityScoped 19 | fun provideActivityContext(activity: HomeActivity): Context { 20 | return activity 21 | } 22 | 23 | @Provides 24 | @ActivityScoped 25 | fun provideActivity(homeActivity: HomeActivity): DaggerAppCompatActivity { 26 | return homeActivity 27 | } 28 | 29 | @Provides 30 | @ActivityScoped 31 | fun provideResourceProvider(context: Application): BaseResourceProvider { 32 | return ResourceProvider(context) 33 | } 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/AuthActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth 2 | 3 | import android.app.Activity 4 | import android.content.* 5 | import android.os.Bundle 6 | import android.widget.Toast 7 | import androidx.lifecycle.Observer 8 | import com.google.android.gms.auth.api.credentials.Credential 9 | import com.google.android.gms.auth.api.credentials.Credentials 10 | import com.google.android.gms.auth.api.credentials.HintRequest 11 | import com.google.firebase.auth.FirebaseAuth 12 | import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException 13 | import com.google.firebase.auth.PhoneAuthCredential 14 | import com.mayandro.firebasephoneauth.R 15 | import com.mayandro.firebasephoneauth.databinding.ActivityAuthBinding 16 | import com.mayandro.firebasephoneauth.ui.auth.adapter.AuthPagerAdapter 17 | import com.mayandro.firebasephoneauth.ui.base.BaseActivity 18 | import com.mayandro.firebasephoneauth.ui.home.HomeActivity 19 | import javax.inject.Inject 20 | 21 | import com.google.android.gms.auth.api.phone.SmsRetriever 22 | import com.google.android.gms.common.api.CommonStatusCodes 23 | import com.google.android.gms.common.api.Status 24 | import timber.log.Timber 25 | 26 | class AuthActivity : BaseActivity(), 27 | AuthActivityViewInteractor { 28 | 29 | companion object { 30 | private const val CREDENTIAL_PICKER_REQUEST = 1 31 | private const val SMS_CONSENT_REQUEST = 2 32 | 33 | fun getIntent(context: Context): Intent { 34 | return Intent(context, AuthActivity::class.java) 35 | } 36 | } 37 | 38 | override fun getViewModelClass(): Class = 39 | AuthActivityViewModel::class.java 40 | 41 | override fun layoutId(): Int = R.layout.activity_auth 42 | 43 | @Inject 44 | lateinit var authPagerAdapter: AuthPagerAdapter 45 | 46 | @Inject 47 | lateinit var firebaseAuth: FirebaseAuth 48 | 49 | private val smsVerificationReceiver = object : BroadcastReceiver() { 50 | override fun onReceive(context: Context, intent: Intent) { 51 | if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) { 52 | val extras = intent.extras 53 | val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as Status 54 | 55 | when (smsRetrieverStatus.statusCode) { 56 | CommonStatusCodes.SUCCESS -> { 57 | val consentIntent = extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT) 58 | try { 59 | startActivityForResult(consentIntent, SMS_CONSENT_REQUEST) 60 | } catch (e: ActivityNotFoundException) { 61 | showSnackBar(e.message?: "Something went wrong") 62 | } 63 | } 64 | CommonStatusCodes.TIMEOUT -> { 65 | // Time out occurred, handle the error. 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | override fun onCreate(savedInstanceState: Bundle?) { 73 | super.onCreate(savedInstanceState) 74 | 75 | viewModel.viewInteractor = this 76 | 77 | val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION) 78 | registerReceiver(smsVerificationReceiver, intentFilter) 79 | 80 | setUpPager() 81 | 82 | if (savedInstanceState == null) { 83 | requestHint() 84 | } 85 | 86 | viewModel.pagerPagePosition.observe( 87 | this, Observer { value -> 88 | binding.authViewPager.currentItem = value ?: 0 89 | } 90 | ) 91 | } 92 | 93 | private fun setUpPager() { 94 | authPagerAdapter.setCount(2) 95 | binding.authViewPager.adapter = authPagerAdapter 96 | binding.authViewPager.isUserInputEnabled = false 97 | } 98 | 99 | private fun requestHint() { 100 | val hintRequest = HintRequest.Builder() 101 | .setPhoneNumberIdentifierSupported(true) 102 | .build() 103 | val credentialsClient = Credentials.getClient(this) 104 | val intent = credentialsClient.getHintPickerIntent(hintRequest) 105 | startIntentSenderForResult( 106 | intent.intentSender, 107 | CREDENTIAL_PICKER_REQUEST, 108 | null, 0, 0, 0 109 | ) 110 | } 111 | 112 | override fun startSMSListener() { 113 | val smsRetrieverClient = SmsRetriever.getClient(this) 114 | val task = smsRetrieverClient.startSmsUserConsent(null) 115 | task.addOnSuccessListener { 116 | Toast.makeText(this, "SMS Retriever starts", Toast.LENGTH_LONG).show() 117 | } 118 | task.addOnFailureListener { 119 | Toast.makeText(this, "Error", Toast.LENGTH_LONG).show() 120 | } 121 | } 122 | 123 | 124 | public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 125 | super.onActivityResult(requestCode, resultCode, data) 126 | when (requestCode) { 127 | CREDENTIAL_PICKER_REQUEST -> 128 | if (resultCode == Activity.RESULT_OK && data != null) { 129 | val credential = data.getParcelableExtra(Credential.EXTRA_KEY) 130 | viewModel.selectedPhoneNumber.value = credential?.id 131 | } 132 | 133 | SMS_CONSENT_REQUEST -> 134 | if (resultCode == Activity.RESULT_OK && data != null) { 135 | val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE) 136 | val oneTimeCode = message?.substring(0, 6) 137 | Timber.d("AuthActivity.onActivityResult message $oneTimeCode") 138 | viewModel.selectedOtpNumber.value = oneTimeCode?.trim() 139 | } 140 | } 141 | } 142 | 143 | override fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) { 144 | firebaseAuth.signInWithCredential(credential) 145 | .addOnCompleteListener( 146 | this 147 | ) { 148 | if (it.isSuccessful) { 149 | goToGoalActivity() 150 | } else { 151 | // Show Error 152 | if (it.exception is FirebaseAuthInvalidCredentialsException) { 153 | // The verification code entered was invalid 154 | showSnackBar(it.exception?.message?: "Verification Failed") 155 | } else { 156 | showSnackBar("Verification Failed") 157 | } 158 | } 159 | } 160 | } 161 | 162 | override fun onBackPressed() { 163 | when (binding.authViewPager.currentItem) { 164 | AuthActivityViewModel.PHONE_VERIFICATION_PAGE -> super.onBackPressed() 165 | AuthActivityViewModel.OTP_VERIFICATION_PAGE -> { 166 | binding.authViewPager.currentItem = AuthActivityViewModel.PHONE_VERIFICATION_PAGE 167 | viewModel.clearCountdownTick() 168 | } 169 | else -> super.onBackPressed() 170 | } 171 | } 172 | 173 | override fun goToGoalActivity() { 174 | startActivity(HomeActivity.getIntent(this)) 175 | finish() 176 | } 177 | 178 | override fun showSnackBarMessage(message: String) { 179 | showSnackBar(message) 180 | } 181 | 182 | override fun onDestroy() { 183 | unregisterReceiver(smsVerificationReceiver) 184 | super.onDestroy() 185 | } 186 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/AuthActivityViewInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth 2 | 3 | import com.google.firebase.auth.PhoneAuthCredential 4 | import com.mayandro.firebasephoneauth.ui.base.ViewInteractor 5 | 6 | interface AuthActivityViewInteractor: ViewInteractor { 7 | 8 | fun showSnackBarMessage(message: String) 9 | 10 | fun goToGoalActivity() 11 | 12 | fun startSMSListener() 13 | 14 | fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/AuthActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth 2 | 3 | import android.os.Handler 4 | import androidx.lifecycle.MutableLiveData 5 | import com.google.firebase.auth.PhoneAuthCredential 6 | import com.google.firebase.auth.PhoneAuthProvider 7 | import com.mayandro.firebasephoneauth.ui.base.BaseViewModel 8 | import com.mayandro.firebasephoneauth.utils.FireBaseAuthProvider 9 | import com.mayandro.firebasephoneauth.utils.PhoneCallbacksListener 10 | import timber.log.Timber 11 | import java.util.concurrent.TimeUnit 12 | import javax.inject.Inject 13 | 14 | 15 | class AuthActivityViewModel @Inject constructor(val fireBaseAuthProvider: FireBaseAuthProvider) : 16 | BaseViewModel(), PhoneCallbacksListener { 17 | 18 | companion object { 19 | val PHONE_VERIFICATION_PAGE = 0 20 | val OTP_VERIFICATION_PAGE = 1 21 | private const val RESEND_WAIT_MILLIS: Long = 30000 22 | private const val TICK_INTERVAL_MILLIS: Long = 1000 23 | 24 | } 25 | 26 | init { 27 | fireBaseAuthProvider.setPhoneCallbacksListener(this) 28 | if (fireBaseAuthProvider.isUserVerified()) { 29 | viewInteractor?.goToGoalActivity() 30 | } 31 | } 32 | 33 | var selectedPhoneNumber = MutableLiveData() 34 | var selectedOtpNumber = MutableLiveData() 35 | 36 | var pagerPagePosition = MutableLiveData() 37 | 38 | private var millisUntilFinished = RESEND_WAIT_MILLIS 39 | private val resendCodeLooper: Handler = Handler() 40 | private val resendCodeCountdown = Runnable { processCountdownTick() } 41 | var showResendCodeText = MutableLiveData() 42 | 43 | var phone:String = "" 44 | 45 | fun sendOtpToPhone(phone: String) { 46 | this.phone = phone 47 | viewInteractor?.startSMSListener() 48 | fireBaseAuthProvider.sendVerificationCode(phone) 49 | } 50 | 51 | fun verifyOtp(otp: String) { 52 | viewInteractor?.signInWithPhoneAuthCredential(fireBaseAuthProvider.verifyVerificationCode(otp)) 53 | } 54 | 55 | fun resendOtp() { 56 | viewInteractor?.startSMSListener() 57 | fireBaseAuthProvider.resendCode(phone) 58 | resetCountdownTick() 59 | } 60 | 61 | fun checkIfPhoneIsValid(phone: String): Boolean { 62 | return phone.let { 63 | !it.isBlank() && (it.length > 10) 64 | } 65 | } 66 | 67 | fun checkIfOtpIsValid(otp: String): Boolean { 68 | return otp.let { 69 | !it.isBlank() && (it.length == 6) 70 | } 71 | } 72 | 73 | fun processCountdownTick() { 74 | millisUntilFinished -= TICK_INTERVAL_MILLIS 75 | when { 76 | millisUntilFinished <= 0 -> { 77 | showResendCodeText.value = true 78 | } 79 | else -> { 80 | showResendCodeText.value = false 81 | resendCodeLooper.postDelayed(resendCodeCountdown, TICK_INTERVAL_MILLIS) 82 | } 83 | } 84 | } 85 | 86 | private fun resetCountdownTick() { 87 | showResendCodeText.value = false 88 | millisUntilFinished = RESEND_WAIT_MILLIS 89 | resendCodeLooper.postDelayed(resendCodeCountdown, TICK_INTERVAL_MILLIS) 90 | } 91 | 92 | fun clearCountdownTick() { 93 | resendCodeLooper.removeCallbacks(resendCodeCountdown) 94 | } 95 | 96 | override fun onVerificationCompleted() { 97 | viewInteractor?.showSnackBarMessage("Verification Completed") 98 | viewInteractor?.goToGoalActivity() 99 | } 100 | 101 | override fun onVerificationCodeDetected(code: String) { 102 | Timber.d("AuthActivityViewModel onReceive: success $code") 103 | selectedOtpNumber.value = code 104 | } 105 | 106 | override fun onVerificationFailed(message: String) { 107 | Timber.d(message) 108 | viewInteractor?.showSnackBarMessage(message) 109 | } 110 | 111 | override fun onCodeSent( 112 | verificationId: String?, 113 | token: PhoneAuthProvider.ForceResendingToken? 114 | ) { 115 | viewInteractor?.showSnackBarMessage("OTP has sent") 116 | pagerPagePosition.value = OTP_VERIFICATION_PAGE 117 | } 118 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/adapter/AuthPagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth.adapter 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.fragment.app.FragmentActivity 5 | import androidx.viewpager2.adapter.FragmentStateAdapter 6 | import com.mayandro.firebasephoneauth.ui.auth.fragments.OtpVerificationFragment 7 | import com.mayandro.firebasephoneauth.ui.auth.fragments.PhoneVerificationFragment 8 | 9 | class AuthPagerAdapter ( 10 | activity: FragmentActivity 11 | ) : FragmentStateAdapter(activity) { 12 | 13 | private var mTabCount = 0 14 | 15 | fun setCount(count: Int) { 16 | mTabCount = count 17 | } 18 | 19 | override fun getItemCount(): Int { 20 | return mTabCount 21 | } 22 | 23 | override fun createFragment(position: Int): Fragment { 24 | return when (position) { 25 | 0 -> PhoneVerificationFragment.newInstance() 26 | 1 -> OtpVerificationFragment.newInstance() 27 | else -> PhoneVerificationFragment.newInstance() 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/fragments/OtpVerificationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth.fragments 2 | 3 | import android.os.Bundle 4 | import android.text.Spannable 5 | import android.text.SpannableStringBuilder 6 | import android.text.style.ForegroundColorSpan 7 | import android.view.View 8 | import androidx.lifecycle.Observer 9 | import com.mayandro.firebasephoneauth.R 10 | import com.mayandro.firebasephoneauth.databinding.FragmentOtpVerificationBinding 11 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivityViewModel 12 | import com.mayandro.firebasephoneauth.ui.base.BaseFragment 13 | 14 | 15 | class OtpVerificationFragment: BaseFragment() { 16 | 17 | companion object { 18 | fun newInstance(): OtpVerificationFragment { 19 | val args = Bundle() 20 | val fragment = OtpVerificationFragment() 21 | fragment.arguments = args 22 | return fragment 23 | } 24 | } 25 | 26 | override fun getViewModelClass(): Class = AuthActivityViewModel::class.java 27 | 28 | override fun layoutId(): Int = R.layout.fragment_otp_verification 29 | 30 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 31 | super.onViewCreated(view, savedInstanceState) 32 | 33 | viewModel.processCountdownTick() 34 | 35 | val text: String = resources.getString(R.string.otp_auth_subtitle, viewModel.phone) 36 | binding.textViewSubtitleOtpAuth.text = text 37 | 38 | 39 | val spannable = SpannableStringBuilder("Didn't received OTP? Resend") 40 | spannable.setSpan( 41 | ForegroundColorSpan(resources.getColor(R.color.primary_700)), 42 | spannable.indexOf("Resend"), spannable.length, 43 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 44 | binding.textViewTicker.text = spannable 45 | 46 | viewModel.selectedOtpNumber.observe( 47 | requireActivity(), 48 | Observer { value -> 49 | binding.otpEditText.setText(value ?: "") 50 | } 51 | ) 52 | 53 | binding.buttonVerifyOtp.setOnClickListener { 54 | if (viewModel.checkIfOtpIsValid(binding.otpEditText.text.toString())) { 55 | viewModel.verifyOtp(binding.otpEditText.text.toString()) 56 | } else { 57 | showSnackBar("Invalid Otp: Please enter correct OTP") 58 | } 59 | } 60 | 61 | viewModel.showResendCodeText.observe( 62 | requireActivity(), 63 | Observer { value -> 64 | when(value) { 65 | true -> binding.textViewTicker.visibility = View.VISIBLE 66 | false -> binding.textViewTicker.visibility = View.GONE 67 | else -> binding.textViewTicker.visibility = View.GONE 68 | } 69 | } 70 | ) 71 | 72 | binding.textViewTicker.setOnClickListener { 73 | viewModel.resendOtp() 74 | } 75 | } 76 | 77 | override fun onDestroy() { 78 | super.onDestroy() 79 | viewModel.clearCountdownTick() 80 | } 81 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/auth/fragments/PhoneVerificationFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.auth.fragments 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import androidx.lifecycle.Observer 6 | import com.mayandro.firebasephoneauth.R 7 | import com.mayandro.firebasephoneauth.databinding.FragmentPhoneVerificationBinding 8 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivityViewModel 9 | import com.mayandro.firebasephoneauth.ui.base.BaseFragment 10 | 11 | 12 | class PhoneVerificationFragment : BaseFragment() { 13 | 14 | companion object { 15 | fun newInstance(): PhoneVerificationFragment { 16 | val args = Bundle() 17 | val fragment = PhoneVerificationFragment() 18 | fragment.arguments = args 19 | return fragment 20 | } 21 | } 22 | 23 | override fun getViewModelClass(): Class = AuthActivityViewModel::class.java 24 | 25 | override fun layoutId(): Int = R.layout.fragment_phone_verification 26 | 27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 28 | super.onViewCreated(view, savedInstanceState) 29 | 30 | viewModel.selectedPhoneNumber.observe( 31 | requireActivity(), 32 | Observer { value -> 33 | binding.textInputEditTextPhone.setText(value ?: "") 34 | }) 35 | 36 | binding.buttonVerifyPhone.setOnClickListener { 37 | activity?.hideKeyboard() 38 | if (viewModel.checkIfPhoneIsValid(binding.textInputEditTextPhone.text.toString())) { 39 | viewModel.sendOtpToPhone(binding.textInputEditTextPhone.text.toString()) 40 | } else { 41 | binding.textInputLayoutPhone.error = "Invalid Phone: Please enter phone number with country code" 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.base 2 | 3 | import android.os.Bundle 4 | import androidx.annotation.LayoutRes 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.databinding.ViewDataBinding 7 | import androidx.lifecycle.ViewModel 8 | import androidx.lifecycle.ViewModelProvider 9 | import androidx.lifecycle.ViewModelProviders 10 | import com.google.android.material.snackbar.Snackbar 11 | import dagger.android.support.DaggerAppCompatActivity 12 | import javax.inject.Inject 13 | 14 | abstract class BaseActivity : DaggerAppCompatActivity() { 15 | @Inject 16 | lateinit var viewModelFactory: ViewModelProvider.Factory 17 | 18 | protected lateinit var binding: B 19 | 20 | lateinit var viewModel: VM 21 | 22 | @LayoutRes 23 | protected abstract fun layoutId(): Int 24 | 25 | abstract fun getViewModelClass(): Class 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | // Bind the view and bind the viewModel to layout 30 | bindContentView(layoutId()) 31 | } 32 | 33 | private fun bindContentView(layoutId: Int) { 34 | binding = DataBindingUtil.setContentView(this, layoutId) 35 | viewModel = ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass()) 36 | } 37 | 38 | fun showSnackBar(message: String) { 39 | Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.base 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.view.inputmethod.InputMethodManager 10 | import androidx.annotation.LayoutRes 11 | import androidx.databinding.DataBindingUtil 12 | import androidx.databinding.ViewDataBinding 13 | import androidx.lifecycle.ViewModel 14 | import androidx.lifecycle.ViewModelProvider 15 | import androidx.lifecycle.ViewModelProviders 16 | import com.google.android.material.snackbar.Snackbar 17 | import dagger.android.support.DaggerFragment 18 | import javax.inject.Inject 19 | 20 | abstract class BaseFragment : DaggerFragment() { 21 | @Inject 22 | lateinit var viewModelFactory: ViewModelProvider.Factory 23 | 24 | protected lateinit var binding: B 25 | 26 | lateinit var viewModel: VM 27 | 28 | @LayoutRes 29 | protected abstract fun layoutId(): Int 30 | 31 | abstract fun getViewModelClass(): Class 32 | 33 | override fun onCreateView( 34 | inflater: LayoutInflater, 35 | container: ViewGroup?, 36 | savedInstanceState: Bundle? 37 | ): View? { 38 | 39 | binding = DataBindingUtil.inflate( 40 | inflater, 41 | layoutId(), 42 | container, 43 | false 44 | ) 45 | 46 | viewModel = activity?.run { 47 | ViewModelProviders.of(this, viewModelFactory).get(getViewModelClass()) 48 | } ?: throw Exception("Invalid Activity") 49 | 50 | return binding.root 51 | } 52 | 53 | fun Activity.hideKeyboard() { 54 | hideKeyboard(currentFocus ?: View(this)) 55 | } 56 | 57 | private fun Context.hideKeyboard(view: View) { 58 | val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager 59 | inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0) 60 | } 61 | 62 | fun showSnackBar(message: String) { 63 | Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show() 64 | } 65 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/base/BaseViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.base 2 | 3 | import androidx.lifecycle.ViewModel 4 | import io.reactivex.disposables.CompositeDisposable 5 | 6 | 7 | abstract class BaseViewModel: ViewModel() { 8 | 9 | private val mCompositeDisposable: CompositeDisposable? = null 10 | 11 | var viewInteractor: VI? = null 12 | set 13 | 14 | override fun onCleared() { 15 | mCompositeDisposable?.dispose() 16 | super.onCleared() 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/base/ViewInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.base 2 | 3 | interface ViewInteractor -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/custom_view/OtpEditText.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.custom_view 2 | 3 | import android.content.Context 4 | import android.content.res.ColorStateList 5 | import android.graphics.Canvas 6 | import android.graphics.Color 7 | import android.graphics.Paint 8 | import android.util.AttributeSet 9 | import android.util.TypedValue 10 | import android.view.ActionMode 11 | import android.view.Menu 12 | import android.view.MenuItem 13 | import androidx.appcompat.widget.AppCompatEditText 14 | import com.mayandro.firebasephoneauth.R 15 | 16 | class OtpEditText : AppCompatEditText { 17 | private var mSpace = 24f //24 dp by default, space between the lines 18 | private var mCharSize = 0f 19 | private var mNumChars = 4f 20 | private var mLineSpacing = 8f //8dp by default, height of the text from our lines 21 | private var mMaxLength = 4 22 | private var mClickListener: OnClickListener? = null 23 | private var mLineStroke = 1f //1dp by default 24 | private var mLineStrokeSelected = 2f //2dp by default 25 | private var mLinesPaint: Paint? = null 26 | var mStates = arrayOf( 27 | intArrayOf(android.R.attr.state_selected), 28 | intArrayOf(android.R.attr.state_focused), 29 | intArrayOf(-android.R.attr.state_focused) 30 | ) 31 | var mColors = intArrayOf( 32 | Color.GREEN, 33 | Color.BLACK, 34 | Color.GRAY 35 | ) 36 | var mColorStates = ColorStateList(mStates, mColors) 37 | 38 | constructor(context: Context?) : super(context) 39 | constructor(context: Context, attrs: AttributeSet) : super( 40 | context, 41 | attrs 42 | ) { 43 | init(context, attrs) 44 | } 45 | 46 | constructor( 47 | context: Context, 48 | attrs: AttributeSet, 49 | defStyleAttr: Int 50 | ) : super(context, attrs, defStyleAttr) { 51 | init(context, attrs) 52 | } 53 | 54 | private fun init( 55 | context: Context, 56 | attrs: AttributeSet 57 | ) { 58 | val multi = context.resources.displayMetrics.density 59 | mLineStroke *= multi 60 | mLineStrokeSelected *= multi 61 | mLinesPaint = Paint(paint) 62 | mLinesPaint!!.strokeWidth = mLineStroke 63 | if (!isInEditMode) { 64 | val outValue = TypedValue() 65 | context.theme.resolveAttribute( 66 | R.attr.colorControlActivated, 67 | outValue, true 68 | ) 69 | val colorActivated = outValue.data 70 | mColors[0] = colorActivated 71 | context.theme.resolveAttribute( 72 | R.attr.colorPrimaryDark, 73 | outValue, true 74 | ) 75 | val colorDark = outValue.data 76 | mColors[1] = colorDark 77 | context.theme.resolveAttribute( 78 | R.attr.colorControlHighlight, 79 | outValue, true 80 | ) 81 | val colorHighlight = outValue.data 82 | mColors[2] = colorHighlight 83 | } 84 | setBackgroundResource(0) 85 | mSpace *= multi //convert to pixels for our density 86 | mLineSpacing *= multi //convert to pixels for our density 87 | mMaxLength = attrs.getAttributeIntValue( 88 | XML_NAMESPACE_ANDROID, 89 | "maxLength", 90 | 4 91 | ) 92 | mNumChars = mMaxLength.toFloat() 93 | //Disable copy paste 94 | super.setCustomSelectionActionModeCallback(object : ActionMode.Callback { 95 | override fun onPrepareActionMode( 96 | mode: ActionMode, 97 | menu: Menu 98 | ): Boolean { 99 | return false 100 | } 101 | 102 | override fun onDestroyActionMode(mode: ActionMode) {} 103 | override fun onCreateActionMode( 104 | mode: ActionMode, 105 | menu: Menu 106 | ): Boolean { 107 | return false 108 | } 109 | 110 | override fun onActionItemClicked( 111 | mode: ActionMode, 112 | item: MenuItem 113 | ): Boolean { 114 | return false 115 | } 116 | }) 117 | // When tapped, move cursor to end of text. 118 | super.setOnClickListener { v -> 119 | setSelection(text!!.length) 120 | if (mClickListener != null) { 121 | mClickListener!!.onClick(v) 122 | } 123 | } 124 | } 125 | 126 | override fun setOnClickListener(l: OnClickListener?) { 127 | mClickListener = l 128 | } 129 | 130 | override fun setCustomSelectionActionModeCallback(actionModeCallback: ActionMode.Callback) { 131 | throw RuntimeException("setCustomSelectionActionModeCallback() not supported.") 132 | } 133 | 134 | override fun onDraw(canvas: Canvas) { //super.onDraw(canvas); 135 | val availableWidth = width - paddingRight - paddingLeft 136 | mCharSize = if (mSpace < 0) { 137 | availableWidth / (mNumChars * 2 - 1) 138 | } else { 139 | (availableWidth - mSpace * (mNumChars - 1)) / mNumChars 140 | } 141 | var startX = paddingLeft 142 | val bottom = height - paddingBottom 143 | //Text Width 144 | val text = text 145 | val textLength = text!!.length 146 | val textWidths = FloatArray(textLength) 147 | paint.getTextWidths(getText(), 0, textLength, textWidths) 148 | var i = 0 149 | while (i < mNumChars) { 150 | updateColorForLines(i == textLength) 151 | canvas.drawLine( 152 | startX.toFloat(), 153 | bottom.toFloat(), 154 | startX + mCharSize, 155 | bottom.toFloat(), 156 | mLinesPaint!! 157 | ) 158 | if (getText()!!.length > i) { 159 | val middle = startX + mCharSize / 2 160 | canvas.drawText( 161 | text, 162 | i, 163 | i + 1, 164 | middle - textWidths[0] / 2, 165 | bottom - mLineSpacing, 166 | paint 167 | ) 168 | } 169 | if (mSpace < 0) { 170 | startX = (startX + mCharSize * 2).toInt() 171 | } else { 172 | startX = (startX + mCharSize + mSpace).toInt() 173 | } 174 | i++ 175 | } 176 | } 177 | 178 | private fun getColorForState(vararg states: Int): Int { 179 | return mColorStates.getColorForState(states, Color.GRAY) 180 | } 181 | 182 | /** 183 | * @param next Is the current char the next character to be input? 184 | */ 185 | private fun updateColorForLines(next: Boolean) { 186 | if (isFocused) { 187 | mLinesPaint!!.strokeWidth = mLineStrokeSelected 188 | mLinesPaint!!.color = getColorForState(android.R.attr.state_focused) 189 | if (next) { 190 | mLinesPaint!!.color = getColorForState(android.R.attr.state_selected) 191 | } 192 | } else { 193 | mLinesPaint!!.strokeWidth = mLineStroke 194 | mLinesPaint!!.color = getColorForState(-android.R.attr.state_focused) 195 | } 196 | } 197 | 198 | companion object { 199 | const val XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android" 200 | } 201 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/home/HomeActivity.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.home 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.mayandro.firebasephoneauth.R 8 | import com.mayandro.firebasephoneauth.databinding.ActivityHomeBinding 9 | import com.mayandro.firebasephoneauth.ui.auth.AuthActivity 10 | import com.mayandro.firebasephoneauth.ui.base.BaseActivity 11 | 12 | class HomeActivity: BaseActivity() { 13 | 14 | 15 | companion object { 16 | fun getIntent(context: Context): Intent { 17 | return Intent(context, HomeActivity::class.java) 18 | } 19 | } 20 | override fun getViewModelClass(): Class = HomeActivityViewModel::class.java 21 | 22 | override fun layoutId(): Int = R.layout.activity_home 23 | 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | binding.button.setOnClickListener { 28 | FirebaseAuth.getInstance().signOut() 29 | startActivity(AuthActivity.getIntent(this)) 30 | finish() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/ui/home/HomeActivityViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.ui.home 2 | 3 | import androidx.lifecycle.ViewModel 4 | import timber.log.Timber 5 | import javax.inject.Inject 6 | 7 | class HomeActivityViewModel @Inject constructor(): ViewModel() { 8 | //@Inject lateinit var onlineCheckerImpl: ConnectionProviderManager 9 | 10 | override fun onCleared() { 11 | super.onCleared() 12 | Timber.d("unsubscribeFromDataStore()") 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/utils/AppConstants.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.utils.utils 2 | 3 | object AppConstants { 4 | val BASE_URL = "http://api.icndb.com" 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/utils/ConnectionProviderManager.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.utils 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.NetworkCapabilities 5 | import android.os.Build 6 | import com.mayandro.firebasephoneauth.di.app.scope.AppScoped 7 | import javax.inject.Inject 8 | 9 | interface OnlineChecker { 10 | fun isOnline(): Boolean 11 | } 12 | 13 | @AppScoped 14 | class ConnectionProviderManager @Inject constructor(private val connectivityManager: ConnectivityManager) : 15 | OnlineChecker { 16 | 17 | override fun isOnline(): Boolean { 18 | 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 20 | val network = connectivityManager.activeNetwork ?: return false 21 | val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false 22 | return when { 23 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true 24 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true 25 | //for other device how are able to connect with Ethernet 26 | activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true 27 | else -> false 28 | } 29 | } else { 30 | val networkInfo = connectivityManager.activeNetworkInfo ?: return false 31 | return networkInfo.isConnected 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/utils/FireBaseAuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.utils 2 | 3 | import com.google.android.gms.tasks.TaskExecutors 4 | import com.google.firebase.FirebaseException 5 | import com.google.firebase.FirebaseTooManyRequestsException 6 | import com.google.firebase.auth.FirebaseAuth 7 | import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException 8 | import com.google.firebase.auth.PhoneAuthCredential 9 | import com.google.firebase.auth.PhoneAuthProvider 10 | import timber.log.Timber 11 | import java.util.* 12 | import java.util.concurrent.TimeUnit 13 | import javax.inject.Inject 14 | 15 | class FireBaseAuthProvider @Inject constructor(val firebaseAuth: FirebaseAuth) { 16 | 17 | private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken 18 | private lateinit var verificationId: String 19 | 20 | private lateinit var phoneCallbacksListener:PhoneCallbacksListener 21 | 22 | fun setPhoneCallbacksListener(listner: PhoneCallbacksListener) { 23 | this.phoneCallbacksListener = listner 24 | } 25 | 26 | init { 27 | firebaseAuth.setLanguageCode(Locale.getDefault().language) 28 | } 29 | 30 | private val callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks = 31 | object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { 32 | override fun onVerificationCompleted(phoneAuthCredential: PhoneAuthCredential) { 33 | val code = phoneAuthCredential.smsCode 34 | if (code != null) { 35 | phoneCallbacksListener.onVerificationCodeDetected(code) 36 | } 37 | } 38 | 39 | override fun onVerificationFailed(e: FirebaseException) { 40 | when (e) { 41 | is FirebaseAuthInvalidCredentialsException -> { 42 | // Invalid request 43 | phoneCallbacksListener.onVerificationFailed(e.message?:" ") 44 | } 45 | is FirebaseTooManyRequestsException -> { 46 | // The SMS quota for the project has been exceeded 47 | phoneCallbacksListener.onVerificationFailed(e.message?:" ") 48 | } 49 | else -> { 50 | phoneCallbacksListener.onVerificationFailed(e.message?:" ") 51 | } 52 | } 53 | Timber.d("FireBaseAuthProvider.onVerificationFailed e() ${e.message}") 54 | } 55 | 56 | override fun onCodeSent( 57 | s: String, 58 | forceResendingToken: PhoneAuthProvider.ForceResendingToken 59 | ) { 60 | super.onCodeSent(s, forceResendingToken) 61 | verificationId = s 62 | resendToken = forceResendingToken 63 | phoneCallbacksListener.onCodeSent(s, forceResendingToken) 64 | } 65 | } 66 | 67 | fun sendVerificationCode(phone: String) { 68 | PhoneAuthProvider.getInstance().verifyPhoneNumber( 69 | phone.trim(), 70 | 30, 71 | TimeUnit.SECONDS, 72 | TaskExecutors.MAIN_THREAD, 73 | callbacks 74 | ) 75 | } 76 | 77 | fun verifyVerificationCode(code: String): PhoneAuthCredential { 78 | return PhoneAuthProvider.getCredential(verificationId, code) 79 | } 80 | 81 | fun resendCode(phone: String) { 82 | PhoneAuthProvider.getInstance().verifyPhoneNumber(phone, 30, TimeUnit.SECONDS, TaskExecutors.MAIN_THREAD, callbacks, resendToken) 83 | } 84 | 85 | fun isUserVerified(): Boolean { 86 | return firebaseAuth.currentUser != null 87 | } 88 | } 89 | 90 | interface PhoneCallbacksListener { 91 | fun onVerificationCompleted() 92 | fun onVerificationCodeDetected(code: String) 93 | fun onVerificationFailed(message: String) 94 | fun onCodeSent( 95 | verificationId: String?, 96 | token: PhoneAuthProvider.ForceResendingToken? 97 | ) 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/utils/ResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.utils 2 | 3 | import android.content.Context 4 | import androidx.annotation.StringRes 5 | 6 | interface BaseResourceProvider { 7 | fun getString(@StringRes id: Int): String 8 | fun getString(@StringRes resId: Int, vararg formatArgs: Any): String 9 | } 10 | 11 | class ResourceProvider(private val context: Context) : BaseResourceProvider { 12 | 13 | override fun getString(@StringRes id: Int): String { 14 | return context.getString(id) 15 | } 16 | 17 | override fun getString(@StringRes resId: Int, vararg formatArgs: Any): String { 18 | return context.getString(resId, formatArgs) 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/mayandro/firebasephoneauth/utils/SharedPreferenceManager.kt: -------------------------------------------------------------------------------- 1 | package com.mayandro.firebasephoneauth.utils 2 | 3 | import android.content.SharedPreferences 4 | import javax.inject.Inject 5 | 6 | class SharedPreferenceManager @Inject constructor(private val sharedPreferences: SharedPreferences) { 7 | 8 | companion object { 9 | private const val PREFS_KEY_USER_VISITED_ONBOARDING = "user_visited_onboarding" 10 | } 11 | 12 | var userVisistedOnBoarding: Boolean 13 | get(): Boolean { 14 | return sharedPreferences.getBoolean(PREFS_KEY_USER_VISITED_ONBOARDING, false) 15 | } 16 | set(value) { 17 | sharedPreferences.edit().putBoolean(PREFS_KEY_USER_VISITED_ONBOARDING, value).apply() 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-hdpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-hdpi/phone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-ldpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-ldpi/phone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-mdpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-mdpi/phone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xhdpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xhdpi/phone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xxhdpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xxhdpi/phone.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/otp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xxxhdpi/otp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/may-andro/firebase-phone-verification-for-android/2f86cfddf4903aa5ca694563332150853d7068e8/app/src/main/res/drawable-xxxhdpi/phone.png -------------------------------------------------------------------------------- /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/layout-land/fragment_otp_verification.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 26 | 27 | 41 | 42 | 54 | 55 | 71 | 72 | 84 | 85 | 96 | 97 | 103 | 104 | 113 | 114 | -------------------------------------------------------------------------------- /app/src/main/res/layout-land/fragment_phone_verification.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 25 | 26 | 40 | 41 | 54 | 55 | 66 | 67 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 97 | 98 | 104 | 105 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_auth.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 11 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 |