├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── WalkthroughActivity.iml ├── app ├── .DS_Store ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── instamobile │ │ └── ui │ │ └── fragment │ │ └── onBoarding │ │ └── walkthroughactivity │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── instamobile │ │ │ └── firebaseStarterKit │ │ │ ├── adapter │ │ │ ├── BindingAdapter.kt │ │ │ └── SliderAdapter.kt │ │ │ ├── model │ │ │ ├── SlideContent.kt │ │ │ └── UserModel.kt │ │ │ ├── ui │ │ │ ├── activity │ │ │ │ └── host │ │ │ │ │ └── HostActivity.kt │ │ │ └── fragment │ │ │ │ ├── authHost │ │ │ │ ├── AuthFragment.kt │ │ │ │ └── AuthViewModel.kt │ │ │ │ ├── home │ │ │ │ └── HomeFragment.kt │ │ │ │ ├── login │ │ │ │ ├── LoginFragment.kt │ │ │ │ └── LoginViewModel.kt │ │ │ │ ├── onBoarding │ │ │ │ ├── OnBoarding.kt │ │ │ │ └── OnBoardingViewModel.kt │ │ │ │ └── signUp │ │ │ │ ├── SignUpFragment.kt │ │ │ │ └── SignUpViewModel.kt │ │ │ └── utils │ │ │ ├── AppLifecycleHandler.kt │ │ │ ├── FirestoreUtil.kt │ │ │ ├── MyApplication.kt │ │ │ ├── Prefs.kt │ │ │ └── TabLayoutMediator.kt │ └── res │ │ ├── drawable-hdpi │ │ └── instamobile_logo.png │ │ ├── drawable-mdpi │ │ └── instamobile_logo.png │ │ ├── drawable-xhdpi │ │ └── instamobile_logo.png │ │ ├── drawable-xxhdpi │ │ └── instamobile_logo.png │ │ ├── drawable-xxxhdpi │ │ └── instamobile_logo.png │ │ ├── drawable │ │ ├── btn_background.xml │ │ ├── disabled_rounded_blue_button.xml │ │ ├── ic_blue_button.xml │ │ ├── ic_camera.xml │ │ ├── ic_chat.xml │ │ ├── ic_fast.xml │ │ ├── ic_firebase.xml │ │ ├── ic_kotlin.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_logout.xml │ │ ├── ic_save_time.xml │ │ ├── ic_white_button.xml │ │ ├── launch_screen.xml │ │ ├── placeholder.jpg │ │ ├── rounded_blue_button.xml │ │ ├── rounded_border.xml │ │ ├── sign_up_button_selector.xml │ │ ├── tab_indicator_default.xml │ │ ├── tab_indicator_selected.xml │ │ └── tab_selector.xml │ │ ├── layout │ │ ├── activity_host.xml │ │ ├── drawer_header_layout.xml │ │ ├── fragment_auth.xml │ │ ├── fragment_home.xml │ │ ├── fragment_login.xml │ │ ├── fragment_on_boarding.xml │ │ ├── fragment_sign_up.xml │ │ └── slide.xml │ │ ├── menu │ │ └── drawer_menu.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 │ │ ├── navigation │ │ └── main_graph.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── instamobile │ └── ui │ └── fragment │ └── onBoarding │ └── walkthroughactivity │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties └── settings.gradle /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Instamobile 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Firebase - Android Starter Project 2 | 3 | Android Starter Project with Authentication, Firestore & Storage Integration. 4 | 5 |

6 | 7 | Kotlin Firebase 8 | 9 |

10 | 11 | This Kotlin project for Android is an open-source boilerplate project with Firebase integrations for: 12 | * User Authentication 13 | * Firestore Database 14 | * Firebase Storage 15 | * Login with Facebook 16 | * Login with Google 17 | * Login with Email & Password 18 | * Profile Photo Upload 19 | * Walkthrough Onboarding Flow 20 | 21 | ## Getting Started 22 | * Clone this repository 23 | * Download google-service.json from your Firebase Console 24 | * Place google-service.json file in the app/ folder 25 | * Build & Run the app in Android Studio 26 | 27 | Save a couple of days of development by bootstrapping your new mobile app with this Kotlin Firebase starter kit for Android. 28 | 29 | Coded with ❤️️ by Instakotlin. 30 | -------------------------------------------------------------------------------- /WalkthroughActivity.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/.DS_Store -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 48 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'androidx.navigation.safeargs' 6 | 7 | apply plugin: 'kotlin-android-extensions' 8 | 9 | android { 10 | compileSdkVersion 29 11 | buildToolsVersion "29.0.2" 12 | defaultConfig { 13 | applicationId "com.instamobile.firebaseStarterKit" 14 | minSdkVersion 19 15 | targetSdkVersion 29 16 | versionCode 1 17 | multiDexEnabled true 18 | versionName "1.0" 19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 20 | } 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = "1.8" 33 | } 34 | dataBinding { 35 | enabled = true 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation fileTree(dir: 'libs', include: ['*.jar']) 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | implementation 'androidx.appcompat:appcompat:1.1.0' 43 | implementation 'androidx.core:core-ktx:1.2.0-beta01' 44 | implementation 'com.google.android.material:material:1.0.0' 45 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 46 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 47 | testImplementation 'junit:junit:4.12' 48 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 49 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 50 | 51 | //viewpager2 for RTL support 52 | implementation "androidx.viewpager2:viewpager2:1.0.0-rc01" 53 | //databinding 54 | kapt 'androidx.databinding:databinding-compiler:3.5.2' 55 | //multidex 56 | implementation 'androidx.multidex:multidex:2.0.1' 57 | 58 | //nav 59 | implementation 'androidx.navigation:navigation-fragment-ktx:2.2.0-rc01' 60 | implementation 'androidx.navigation:navigation-ui-ktx:2.2.0-rc01' 61 | 62 | //core 63 | implementation 'androidx.core:core-ktx:1.2.0-beta01' 64 | 65 | //view model 66 | implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' 67 | //facebook password-less login 68 | implementation 'com.facebook.android:facebook-android-sdk:5.0.0' 69 | // circleImageView 70 | implementation 'de.hdodenhof:circleimageview:3.0.1' 71 | 72 | //Picasso 73 | implementation 'com.squareup.picasso:picasso:2.71828' 74 | 75 | //firebase 76 | implementation 'com.google.firebase:firebase-core:17.2.1' 77 | implementation 'com.google.firebase:firebase-auth:19.1.0' 78 | implementation 'com.google.firebase:firebase-database:19.2.0' 79 | implementation 'com.google.firebase:firebase-firestore:21.2.1' 80 | implementation 'com.google.firebase:firebase-storage:19.1.0' 81 | implementation 'com.google.firebase:firebase-analytics:17.2.1' 82 | //google auth 83 | implementation 'com.google.android.gms:play-services-auth:17.0.0' 84 | 85 | } 86 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /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/instamobile/ui/fragment/onBoarding/walkthroughactivity/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.ui.fragment.onBoarding.walkthroughactivity 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("com.instamobile.ui.fragment.onBoarding.walkthroughactivity", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/adapter/BindingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.adapter 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import androidx.databinding.BindingAdapter 7 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.R 8 | import com.squareup.picasso.Picasso 9 | 10 | @BindingAdapter("hideIfFalse") 11 | fun hideIfFalse(view: View, boolean: Boolean) { 12 | if (boolean) view.visibility = View.VISIBLE else view.visibility = View.GONE 13 | } 14 | 15 | @BindingAdapter("hideIfEmpty") 16 | fun hideIfEmpty(textView: TextView, error: String?) { 17 | if (error != null) { 18 | if (error.isEmpty()) { 19 | textView.visibility = View.INVISIBLE 20 | } else { 21 | textView.visibility = View.VISIBLE 22 | textView.text = error 23 | } 24 | } 25 | } 26 | 27 | @BindingAdapter("setImage") 28 | fun setImage(imageView: ImageView, url: String?) { 29 | if (!url.isNullOrEmpty()) { 30 | Picasso.get().load(url).placeholder(R.drawable.placeholder).into(imageView) 31 | } else { 32 | imageView.setImageResource(R.drawable.placeholder) 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/adapter/SliderAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.adapter 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.instamobile.firebaseStarterKit.model.SlideContent 7 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.SlideBinding 8 | 9 | class SliderAdapter : RecyclerView.Adapter() { 10 | var list: List = listOf() 11 | 12 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OnBoardingViewHolder { 13 | val view = SlideBinding.inflate(LayoutInflater.from(parent.context), parent, false) 14 | return OnBoardingViewHolder(view) 15 | } 16 | 17 | override fun getItemCount(): Int { 18 | return list.size 19 | } 20 | 21 | override fun onBindViewHolder(holder: OnBoardingViewHolder, position: Int) { 22 | holder.bind(list[position]) 23 | } 24 | 25 | fun setItems(newList: List) { 26 | list = newList 27 | notifyDataSetChanged() 28 | } 29 | } 30 | 31 | class OnBoardingViewHolder(private val binding: SlideBinding) : 32 | RecyclerView.ViewHolder(binding.root) { 33 | 34 | fun bind(slideContent: SlideContent) { 35 | binding.content = slideContent 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/model/SlideContent.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.model 2 | 3 | import android.graphics.drawable.Drawable 4 | 5 | data class SlideContent(val image: Drawable, val header: String, val description: String) -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/model/UserModel.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.model 2 | 3 | data class UserModel( 4 | var userID: String = "", 5 | var profilePictureURL: String = "", 6 | var phoneNumber: String = "", 7 | var active: Boolean = false, 8 | var firstName: String = "", 9 | var lastName: String = "", 10 | var email: String = "", 11 | var fcmToken: String = "", 12 | var selected: Boolean = false, 13 | var userName: String = "" 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/activity/host/HostActivity.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.activity.host 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.View 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.drawerlayout.widget.DrawerLayout 8 | import androidx.navigation.NavController 9 | import androidx.navigation.fragment.NavHostFragment 10 | import androidx.navigation.ui.NavigationUI 11 | import androidx.navigation.ui.setupWithNavController 12 | import com.google.android.gms.auth.api.signin.GoogleSignIn 13 | import com.google.android.gms.auth.api.signin.GoogleSignInClient 14 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions 15 | import com.google.firebase.auth.FirebaseAuth 16 | import com.google.firebase.firestore.FirebaseFirestore 17 | import com.instamobile.firebaseStarterKit.model.UserModel 18 | import com.instamobile.firebaseStarterKit.utils.FirestoreUtil 19 | import com.instamobile.firebaseStarterKit.utils.MyApplication 20 | import com.instamobile.firebaseStarterKit.utils.Prefs 21 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.R 22 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.DrawerHeaderLayoutBinding 23 | import kotlinx.android.synthetic.main.activity_host.* 24 | 25 | class HostActivity : AppCompatActivity() { 26 | lateinit var googleSignInClient: GoogleSignInClient 27 | private lateinit var navController: NavController 28 | private val mAuth: FirebaseAuth = FirebaseAuth.getInstance() 29 | private val db: FirebaseFirestore = FirebaseFirestore.getInstance() 30 | private lateinit var drawerLayout: DrawerLayout 31 | private lateinit var navViewBinding: DrawerHeaderLayoutBinding 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | setTheme(R.style.AppTheme) 34 | super.onCreate(savedInstanceState) 35 | setContentView(R.layout.activity_host) 36 | val toolbar = customToolbar 37 | setSupportActionBar(toolbar) 38 | 39 | val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) 40 | .requestIdToken(getString(R.string.default_web_client_id)) 41 | .requestEmail() 42 | .build() 43 | googleSignInClient = GoogleSignIn.getClient(this, gso) 44 | 45 | drawerLayout = drawer_layout 46 | navViewBinding = DrawerHeaderLayoutBinding.inflate(layoutInflater, navView, true) 47 | val navHost = 48 | supportFragmentManager.findFragmentById(R.id.navHostFragment) as NavHostFragment 49 | navController = navHost.navController 50 | 51 | val navInflater = navController.navInflater 52 | 53 | val graph = navInflater.inflate(R.navigation.main_graph) 54 | 55 | navController.addOnDestinationChangedListener { _, destination, _ -> 56 | if (destination.id == R.id.onBoarding || 57 | destination.id == R.id.authFragment || 58 | destination.id == R.id.loginFragment || 59 | destination.id == R.id.signUpFragment 60 | ) { 61 | toolbar.visibility = View.GONE 62 | drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) 63 | } else { 64 | toolbar.visibility = View.VISIBLE 65 | drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) 66 | } 67 | } 68 | if (!Prefs.getInstance(this)!!.hasCompletedWalkthrough!!) { 69 | if (mAuth.currentUser == null) { 70 | graph.startDestination = R.id.authFragment 71 | } else { 72 | getUserData() 73 | graph.startDestination = R.id.homeFragment 74 | } 75 | } else { 76 | graph.startDestination = R.id.onBoarding 77 | 78 | } 79 | navController.graph = graph 80 | 81 | NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout) 82 | navView.setupWithNavController(navController) 83 | navView.setNavigationItemSelectedListener { 84 | it.isChecked 85 | drawerLayout.closeDrawers() 86 | when (it.itemId) { 87 | R.id.action_logout -> { 88 | MyApplication.currentUser!!.active = false 89 | FirestoreUtil.updateUser(MyApplication.currentUser!!) { 90 | mAuth.signOut() 91 | } 92 | googleSignInClient.signOut() 93 | MyApplication.currentUser = null 94 | navController.navigate(R.id.action_logout) 95 | } 96 | } 97 | true 98 | } 99 | } 100 | 101 | private fun getUserData() { 102 | 103 | val ref = db.collection("users").document(mAuth.currentUser!!.uid) 104 | 105 | ref.get().addOnSuccessListener { 106 | val userInfo = it.toObject(UserModel::class.java) 107 | navViewBinding.user = userInfo 108 | MyApplication.currentUser = userInfo 109 | MyApplication.currentUser!!.active = true 110 | FirestoreUtil.updateUser(MyApplication.currentUser!!) { 111 | } 112 | }.addOnFailureListener { 113 | val intent = Intent(this, MyApplication::class.java) 114 | startActivity(intent) 115 | finish() 116 | } 117 | } 118 | 119 | override fun onSupportNavigateUp(): Boolean { 120 | return NavigationUI.navigateUp(navController, drawerLayout) 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/authHost/AuthFragment.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.authHost 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.ViewModelProviders 11 | import androidx.navigation.fragment.findNavController 12 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.FragmentAuthBinding 13 | 14 | class AuthFragment : Fragment() { 15 | private lateinit var binding: FragmentAuthBinding 16 | private lateinit var viewModel: AuthViewModel 17 | override fun onCreateView( 18 | inflater: LayoutInflater, container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View? { 21 | viewModel = ViewModelProviders.of(this).get(AuthViewModel::class.java) 22 | binding = FragmentAuthBinding.inflate(inflater, container, false) 23 | binding.authViewModel = viewModel 24 | binding.lifecycleOwner = this 25 | 26 | viewModel.navigateToLogin.observe(this, Observer { 27 | if (it) { 28 | this.findNavController() 29 | .navigate(AuthFragmentDirections.actionAuthFragmentToLoginFragment()) 30 | viewModel.doneNavigationToLogin() 31 | } 32 | }) 33 | viewModel.navigateToSignUp.observe(this, Observer { 34 | if (it) { 35 | this.findNavController() 36 | .navigate(AuthFragmentDirections.actionAuthFragmentToSignUpFragment()) 37 | viewModel.doneNavigationToSignUp() 38 | } 39 | }) 40 | 41 | return binding.root 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/authHost/AuthViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.authHost 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | 7 | class AuthViewModel : ViewModel() { 8 | private val _navigateToLogin = MutableLiveData().apply { value = false } 9 | val navigateToLogin: LiveData 10 | get() = _navigateToLogin 11 | 12 | private val _navigateToSignUp = MutableLiveData().apply { value = false } 13 | val navigateToSignUp: LiveData 14 | get() = _navigateToSignUp 15 | 16 | fun startNavigationToLogin() { 17 | _navigateToLogin.value = true 18 | } 19 | 20 | fun doneNavigationToLogin() { 21 | _navigateToLogin.value = false 22 | } 23 | 24 | fun navigateToSignUp() { 25 | _navigateToSignUp.value = true 26 | } 27 | 28 | fun doneNavigationToSignUp() { 29 | _navigateToSignUp.value = false 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.home 2 | 3 | 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.Fragment 9 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.FragmentHomeBinding 10 | 11 | class HomeFragment : Fragment() { 12 | private lateinit var binding: FragmentHomeBinding 13 | override fun onCreateView( 14 | inflater: LayoutInflater, container: ViewGroup?, 15 | savedInstanceState: Bundle? 16 | ): View? { 17 | binding = FragmentHomeBinding.inflate(inflater, container, false) 18 | 19 | return binding.root 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/login/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.login 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.core.widget.doAfterTextChanged 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.ViewModelProviders 12 | import com.facebook.CallbackManager 13 | import com.facebook.FacebookCallback 14 | import com.facebook.FacebookException 15 | import com.facebook.login.LoginResult 16 | import com.instamobile.firebaseStarterKit.ui.activity.host.HostActivity 17 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.FragmentLoginBinding 18 | 19 | class LoginFragment : Fragment() { 20 | private val RC_SIGN_IN = 1 21 | private lateinit var mCallbackManager: CallbackManager 22 | private lateinit var binding: FragmentLoginBinding 23 | private lateinit var viewModel: LoginViewModel 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | mCallbackManager = CallbackManager.Factory.create() 29 | viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java) 30 | binding = FragmentLoginBinding.inflate(inflater, container, false) 31 | binding.loginViewModel = viewModel 32 | binding.lifecycleOwner = this 33 | 34 | setupListeners() 35 | setUpObservers() 36 | 37 | binding.btLoginFacebook.setPermissions("email") 38 | binding.btLoginFacebook.registerCallback(mCallbackManager, 39 | object : FacebookCallback { 40 | override fun onSuccess(result: LoginResult) { 41 | viewModel.handleFacebookToken(result) 42 | } 43 | 44 | override fun onCancel() { 45 | viewModel.onFinishLoading() 46 | } 47 | 48 | override fun onError(error: FacebookException?) { 49 | viewModel.onFinishLoading() 50 | viewModel.setError(error?.message!!) 51 | } 52 | }) 53 | 54 | return binding.root 55 | } 56 | 57 | private fun setupListeners() { 58 | binding.etEmail.doAfterTextChanged { email -> viewModel.username = email.toString() } 59 | binding.etPassword.doAfterTextChanged { pass -> viewModel.password = pass.toString() } 60 | binding.signInButton.setOnClickListener { 61 | signInWithGoogle() 62 | } 63 | } 64 | 65 | private fun setUpObservers() { 66 | viewModel.navigateToHome.observe(this, Observer { 67 | if (it) { 68 | startActivity(Intent(context, HostActivity::class.java)) 69 | (activity as HostActivity).finish() 70 | viewModel.doneHomeNavigation() 71 | } 72 | }) 73 | } 74 | 75 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 76 | super.onActivityResult(requestCode, resultCode, data) 77 | mCallbackManager.onActivityResult(requestCode, resultCode, data) 78 | if (requestCode == RC_SIGN_IN) { 79 | viewModel.handleGoogleSignInResult(data) 80 | } 81 | 82 | } 83 | 84 | private fun signInWithGoogle() { 85 | val signInIntent = (activity as HostActivity).googleSignInClient.signInIntent 86 | startActivityForResult(signInIntent, RC_SIGN_IN) 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.login 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import com.facebook.GraphRequest 9 | import com.facebook.login.LoginResult 10 | import com.google.android.gms.auth.api.signin.GoogleSignIn 11 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount 12 | import com.google.android.gms.common.api.ApiException 13 | import com.google.firebase.auth.FacebookAuthProvider 14 | import com.google.firebase.auth.FirebaseAuth 15 | import com.google.firebase.auth.FirebaseUser 16 | import com.google.firebase.auth.GoogleAuthProvider 17 | import com.google.firebase.firestore.FirebaseFirestore 18 | import com.instamobile.firebaseStarterKit.model.UserModel 19 | import com.instamobile.firebaseStarterKit.utils.FirestoreUtil 20 | import com.instamobile.firebaseStarterKit.utils.MyApplication 21 | import org.json.JSONObject 22 | 23 | class LoginViewModel : ViewModel() { 24 | private var mAuth: FirebaseAuth = FirebaseAuth.getInstance() 25 | private var db: FirebaseFirestore = FirebaseFirestore.getInstance() 26 | 27 | var username: String = "" 28 | set(value) { 29 | field = value 30 | validateInput() 31 | } 32 | 33 | var password: String = "" 34 | set(value) { 35 | field = value 36 | validateInput() 37 | } 38 | 39 | private val _buttonEnabled = MutableLiveData() 40 | val buttonEnabled: LiveData 41 | get() = _buttonEnabled 42 | 43 | private val _progress = MutableLiveData() 44 | val progress: LiveData 45 | get() = _progress 46 | 47 | private val _errorString = MutableLiveData() 48 | val errorString: LiveData 49 | get() = _errorString 50 | 51 | private fun validateInput() { 52 | _buttonEnabled.value = !(username.isEmpty() || password.isEmpty()) 53 | } 54 | 55 | private val _navigateToHome = MutableLiveData() 56 | val navigateToHome: LiveData 57 | get() = _navigateToHome 58 | 59 | fun login() { 60 | onStartLoading() 61 | mAuth.signInWithEmailAndPassword(username, password).addOnCompleteListener { 62 | if (it.isSuccessful) { 63 | getFirebaseUserData() 64 | } else { 65 | onFinishLoading() 66 | _errorString.value = it.exception?.message 67 | } 68 | } 69 | 70 | } 71 | 72 | private fun getFirebaseUserData() { 73 | val ref = db.collection("users").document(mAuth.currentUser!!.uid) 74 | ref.get().addOnSuccessListener { 75 | val userInfo = it.toObject(UserModel::class.java) 76 | MyApplication.currentUser = userInfo 77 | MyApplication.currentUser!!.active = true 78 | FirestoreUtil.updateUser(MyApplication.currentUser!!) { 79 | 80 | } 81 | onFinishLoading() 82 | startHomeNavigation() 83 | }.addOnFailureListener { 84 | onFinishLoading() 85 | _errorString.value = it.message 86 | } 87 | 88 | } 89 | 90 | fun handleFacebookToken(result: LoginResult) { 91 | onStartLoading() 92 | val credential = 93 | FacebookAuthProvider.getCredential(result.accessToken.token.toString()) 94 | mAuth.signInWithCredential(credential) 95 | .addOnCompleteListener { task -> 96 | if (task.isSuccessful) { 97 | val userId = task.result!!.user!!.uid 98 | db.collection("users").document(userId) 99 | .get() 100 | .addOnCompleteListener { 101 | if (it.isSuccessful) { 102 | val document = it.result 103 | 104 | if (document!!["email"] != null) { 105 | onFinishLoading() 106 | val userInfo = document.toObject(UserModel::class.java) 107 | MyApplication.currentUser = userInfo 108 | startHomeNavigation() 109 | } else { 110 | createUser(result) 111 | } 112 | } else { 113 | onFinishLoading() 114 | _errorString.value = it.exception?.message 115 | } 116 | } 117 | 118 | } else { 119 | onFinishLoading() 120 | _errorString.value = "Authentication failed." 121 | } 122 | } 123 | } 124 | 125 | private fun createUser(loginResult: LoginResult) { 126 | 127 | val userId = mAuth.currentUser!!.uid 128 | 129 | val request = GraphRequest.newMeRequest(loginResult.accessToken) { `object`, response -> 130 | try { 131 | 132 | val items = HashMap() 133 | items["email"] = `object`.get("email").toString() 134 | items["firstName"] = `object`.get("name").toString() 135 | items["lastName"] = "" 136 | items["userName"] = "" 137 | items["phoneNumber"] = "000000" 138 | items["userID"] = userId 139 | items["active"] = true 140 | // items["fcmToken"] = FirebaseInstanceId.getInstance().getToken().toString() 141 | 142 | val picture: JSONObject = `object`.get("picture") as JSONObject 143 | val data: JSONObject = picture.getJSONObject("data") 144 | val url = data.getString("url") 145 | items["profilePictureURL"] = url 146 | saveSocialUserToFirebase(mAuth.currentUser, items) 147 | } catch (e: Exception) { 148 | onFinishLoading() 149 | _errorString.value = e.message 150 | e.printStackTrace() 151 | } 152 | } 153 | 154 | val parameters = Bundle() 155 | parameters.putString("fields", "name,email,id,picture.type(large)") 156 | request.parameters = parameters 157 | request.executeAsync() 158 | } 159 | 160 | private fun saveSocialUserToFirebase( 161 | currentUser: FirebaseUser?, 162 | items: java.util.HashMap 163 | ) { 164 | db.collection("users").document(currentUser!!.uid).set(items).addOnSuccessListener { 165 | val userInfo = UserModel() 166 | userInfo.userID = currentUser.uid 167 | userInfo.email = items["email"].toString() 168 | userInfo.firstName = items["firstName"].toString() 169 | userInfo.lastName = items["lastName"].toString() 170 | userInfo.userName = items["userName"].toString() 171 | userInfo.profilePictureURL = items["profilePictureURL"].toString() 172 | userInfo.active = true 173 | // userInfo.fcmToken = items["fcmToken"].toString() 174 | MyApplication.currentUser = userInfo 175 | 176 | onFinishLoading() 177 | startHomeNavigation() 178 | }.addOnFailureListener { 179 | _errorString.value = it.message 180 | onFinishLoading() 181 | } 182 | 183 | } 184 | 185 | private fun startHomeNavigation() { 186 | _navigateToHome.value = true 187 | 188 | } 189 | 190 | fun doneHomeNavigation() { 191 | _navigateToHome.value = false 192 | } 193 | 194 | private fun onStartLoading() { 195 | _buttonEnabled.value = false 196 | _progress.value = true 197 | } 198 | 199 | fun onFinishLoading() { 200 | _buttonEnabled.value = true 201 | _progress.value = false 202 | } 203 | 204 | fun setError(error: String) { 205 | _errorString.value = error 206 | } 207 | 208 | fun handleGoogleSignInResult(data: Intent?) { 209 | val task = GoogleSignIn.getSignedInAccountFromIntent(data) 210 | try { 211 | val account = task.getResult(ApiException::class.java) 212 | firebaseAuthWithGoogle(account!!) 213 | } catch (e: ApiException) { 214 | e.printStackTrace() 215 | _errorString.value = e.message 216 | } 217 | } 218 | 219 | private fun firebaseAuthWithGoogle(account: GoogleSignInAccount) { 220 | onStartLoading() 221 | val credential = GoogleAuthProvider.getCredential(account.idToken, null) 222 | mAuth.signInWithCredential(credential).addOnCompleteListener { 223 | if (it.isSuccessful) { 224 | val userId = it.result!!.user!!.uid 225 | db.collection("users").document(userId) 226 | .get() 227 | .addOnCompleteListener { task -> 228 | if (task.isSuccessful) { 229 | val document = task.result 230 | 231 | if (document!!["email"] != null) { 232 | onFinishLoading() 233 | val userInfo = document.toObject(UserModel::class.java) 234 | MyApplication.currentUser = userInfo 235 | FirestoreUtil.updateUser(MyApplication.currentUser!!) { 236 | } 237 | startHomeNavigation() 238 | } else { 239 | createGoogleUser(account) 240 | } 241 | } else { 242 | onFinishLoading() 243 | _errorString.value = task.exception?.message 244 | } 245 | } 246 | 247 | } else { 248 | // If sign in fails, display a message to the user. 249 | onFinishLoading() 250 | _errorString.value = "Authentication failed." 251 | } 252 | } 253 | 254 | } 255 | 256 | private fun createGoogleUser(account: GoogleSignInAccount) { 257 | val userId = mAuth.currentUser!!.uid 258 | try { 259 | val items = HashMap() 260 | items["email"] = account.email!! 261 | items["firstName"] = account.displayName!! 262 | items["lastName"] = "" 263 | items["userName"] = "" 264 | items["phoneNumber"] = "000000" 265 | items["userID"] = userId 266 | items["active"] = true 267 | items["profilePictureURL"] = account.photoUrl.toString() 268 | saveSocialUserToFirebase(mAuth.currentUser, items) 269 | } catch (e: Exception) { 270 | onFinishLoading() 271 | _errorString.value = e.message 272 | e.printStackTrace() 273 | } 274 | } 275 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/onBoarding/OnBoarding.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.onBoarding 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.lifecycle.Observer 9 | import androidx.lifecycle.ViewModelProviders 10 | import androidx.navigation.fragment.findNavController 11 | import com.google.android.material.tabs.TabLayout 12 | import com.google.android.material.tabs.TabLayoutMediator 13 | import com.instamobile.firebaseStarterKit.adapter.SliderAdapter 14 | import com.instamobile.firebaseStarterKit.utils.Prefs 15 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.FragmentOnBoardingBinding 16 | import kotlinx.android.synthetic.main.fragment_on_boarding.* 17 | 18 | class OnBoarding : Fragment() { 19 | private var sliderAdapter: SliderAdapter = 20 | SliderAdapter() 21 | private lateinit var viewModel: OnBoardingViewModel 22 | private lateinit var binding: FragmentOnBoardingBinding 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | viewModel = ViewModelProviders.of(this).get(OnBoardingViewModel::class.java) 29 | binding = FragmentOnBoardingBinding.inflate(inflater, container, false) 30 | binding.onBoardingViewModel = viewModel 31 | binding.lifecycleOwner = this 32 | 33 | viewModel.dataSet.observe(this, Observer { 34 | viewPager2.adapter = sliderAdapter 35 | sliderAdapter.setItems(it) 36 | TabLayoutMediator( 37 | indicator, 38 | viewPager2, 39 | object : TabLayoutMediator.TabConfigurationStrategy { 40 | override fun onConfigureTab(tab: TabLayout.Tab, position: Int) { 41 | viewPager2.setCurrentItem(tab.position, true) 42 | } 43 | }).attach() 44 | }) 45 | viewModel.startNavigation.observe(this, Observer { 46 | if (it) { 47 | this.findNavController() 48 | .navigate(OnBoardingDirections.actionOnBoardingToAuthFragment()) 49 | Prefs.getInstance(context!!)!!.hasCompletedWalkthrough = false 50 | viewModel.doneNavigation() 51 | } 52 | }) 53 | binding.viewPager2.registerOnPageChangeCallback(viewModel.pagerCallBack) 54 | return binding.root 55 | } 56 | 57 | override fun onDestroyView() { 58 | super.onDestroyView() 59 | viewPager2.unregisterOnPageChangeCallback(viewModel.pagerCallBack) 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/onBoarding/OnBoardingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.onBoarding 2 | 3 | import android.app.Application 4 | import androidx.core.content.ContextCompat 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.LiveData 7 | import androidx.lifecycle.MutableLiveData 8 | import androidx.viewpager2.widget.ViewPager2 9 | import com.instamobile.firebaseStarterKit.model.SlideContent 10 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.R 11 | 12 | class OnBoardingViewModel(application: Application) : AndroidViewModel(application) { 13 | private val list = listOf( 14 | SlideContent( 15 | ContextCompat.getDrawable(application.applicationContext, R.drawable.ic_fast)!!, 16 | "Move Fast", 17 | "Use our starter kit in Kotlin to build your apps faster" 18 | ), 19 | SlideContent( 20 | ContextCompat.getDrawable(application.applicationContext, R.drawable.ic_kotlin)!!, 21 | "Learn Kotlin", 22 | "Learning Kotlin practically by working on a real project" 23 | ), 24 | SlideContent( 25 | ContextCompat.getDrawable(application.applicationContext, R.drawable.ic_firebase)!!, 26 | "Learn Firebase", 27 | "Learn how to use Firebase as a backend for your Kotlin app" 28 | ), 29 | SlideContent( 30 | ContextCompat.getDrawable(application.applicationContext, R.drawable.ic_save_time)!!, 31 | "Save Time", 32 | "Save a few days of development by starting with our app template" 33 | ) 34 | ) 35 | 36 | private val _dataSet = MutableLiveData>().apply { value = list } 37 | val dataSet: LiveData> 38 | get() = _dataSet 39 | 40 | private val _buttonVisiability = MutableLiveData().apply { value = false } 41 | val buttonVisiability: LiveData 42 | get() = _buttonVisiability 43 | 44 | val pagerCallBack = object : ViewPager2.OnPageChangeCallback() { 45 | override fun onPageSelected(position: Int) { 46 | _buttonVisiability.value = position == list.size - 1 47 | super.onPageSelected(position) 48 | } 49 | } 50 | private val _startNavigation = MutableLiveData().apply { value = false } 51 | val startNavigation: LiveData 52 | get() = _startNavigation 53 | 54 | fun navigateToAuth() { 55 | _startNavigation.value = true 56 | } 57 | 58 | fun doneNavigation() { 59 | _startNavigation.value = false 60 | } 61 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/signUp/SignUpFragment.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.signUp 2 | 3 | import android.Manifest 4 | import android.app.Activity 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.net.Uri 8 | import android.os.Build 9 | import android.os.Bundle 10 | import android.provider.MediaStore 11 | import android.view.LayoutInflater 12 | import android.view.View 13 | import android.view.ViewGroup 14 | import androidx.core.content.ContextCompat 15 | import androidx.core.widget.doAfterTextChanged 16 | import androidx.fragment.app.Fragment 17 | import androidx.lifecycle.Observer 18 | import androidx.lifecycle.ViewModelProviders 19 | import com.instamobile.firebaseStarterKit.ui.activity.host.HostActivity 20 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.R 21 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.databinding.FragmentSignUpBinding 22 | import com.squareup.picasso.Picasso 23 | 24 | 25 | class SignUpFragment : Fragment() { 26 | 27 | 28 | private val PERMISSION_CODE = 1 29 | private val PICK_IMAGE_CODE = 2 30 | private var imageUri: Uri? = null 31 | private lateinit var binding: FragmentSignUpBinding 32 | private lateinit var viewModel: SignUpViewModel 33 | override fun onCreateView( 34 | inflater: LayoutInflater, container: ViewGroup?, 35 | savedInstanceState: Bundle? 36 | ): View? { 37 | viewModel = ViewModelProviders.of(this).get(SignUpViewModel::class.java) 38 | binding = FragmentSignUpBinding.inflate(inflater, container, false) 39 | binding.signUpViewModel = viewModel 40 | binding.lifecycleOwner = this 41 | setupListeners() 42 | setupObservers() 43 | 44 | return binding.root 45 | } 46 | 47 | private fun setupListeners() { 48 | binding.etFullName.doAfterTextChanged { fullname -> 49 | viewModel.fullname = fullname.toString() 50 | } 51 | binding.etPhone.doAfterTextChanged { phone -> viewModel.phoneNumber = phone.toString() } 52 | binding.etEmail.doAfterTextChanged { email -> viewModel.email = email.toString() } 53 | binding.etPassword.doAfterTextChanged { password -> 54 | viewModel.password = password.toString() 55 | } 56 | binding.selectPhoto.setOnClickListener { 57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 58 | checkPermissions() 59 | } else { 60 | pickFromGallery() 61 | } 62 | } 63 | } 64 | 65 | private fun setupObservers() { 66 | viewModel.fullNameError.observe(this, Observer { 67 | if (it.isNotEmpty()) { 68 | binding.etFullName.error = it 69 | } 70 | }) 71 | viewModel.phoneNumberError.observe(this, Observer { 72 | if (it.isNotEmpty()) { 73 | binding.etPhone.error = it 74 | } 75 | }) 76 | viewModel.emailError.observe(this, Observer { 77 | if (it.isNotEmpty()) { 78 | binding.etEmail.error = it 79 | } 80 | }) 81 | viewModel.passwordError.observe(this, Observer { 82 | if (it.isNotEmpty()) { 83 | binding.etPassword.error = it 84 | } 85 | }) 86 | viewModel.navigateToHome.observe(this, Observer { 87 | if (it) { 88 | startActivity(Intent(context, HostActivity::class.java)) 89 | (activity as HostActivity).finish() 90 | viewModel.doneNavigating() 91 | } 92 | }) 93 | } 94 | 95 | override fun onRequestPermissionsResult( 96 | requestCode: Int, 97 | permissions: Array, 98 | grantResults: IntArray 99 | ) { 100 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 101 | if (requestCode == PERMISSION_CODE) { 102 | if (permissions[0] == Manifest.permission.READ_EXTERNAL_STORAGE) { 103 | if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { 104 | pickFromGallery() 105 | } 106 | } 107 | } 108 | } 109 | 110 | private fun checkPermissions() { 111 | if (ContextCompat.checkSelfPermission( 112 | context!!, 113 | Manifest.permission.READ_EXTERNAL_STORAGE 114 | ) != PackageManager.PERMISSION_GRANTED 115 | ) { 116 | requestPermissions( 117 | arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_CODE 118 | ) 119 | } else { 120 | pickFromGallery() 121 | } 122 | } 123 | 124 | private fun pickFromGallery() { 125 | val getIntent = Intent(Intent.ACTION_GET_CONTENT) 126 | getIntent.type = "image/*" 127 | 128 | val pickIntent = Intent( 129 | Intent.ACTION_PICK, 130 | MediaStore.Images.Media.EXTERNAL_CONTENT_URI 131 | ) 132 | pickIntent.type = "image/*" 133 | 134 | val chooserIntent = Intent.createChooser(getIntent, getString(R.string.select_image)) 135 | chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(pickIntent)) 136 | startActivityForResult(chooserIntent, PICK_IMAGE_CODE) 137 | } 138 | 139 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 140 | super.onActivityResult(requestCode, resultCode, data) 141 | if (requestCode == PICK_IMAGE_CODE && resultCode == Activity.RESULT_OK) { 142 | if (data != null) { 143 | imageUri = data.data 144 | viewModel.imageUri = imageUri 145 | Picasso.get().load(imageUri).placeholder(R.drawable.placeholder) 146 | .into(binding.ciAvatar) 147 | } 148 | } 149 | } 150 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/ui/fragment/signUp/SignUpViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.ui.fragment.signUp 2 | 3 | import android.app.Application 4 | import android.net.Uri 5 | import android.util.Log 6 | import android.util.Patterns 7 | import androidx.lifecycle.AndroidViewModel 8 | import androidx.lifecycle.LiveData 9 | import androidx.lifecycle.MutableLiveData 10 | import com.google.firebase.auth.FirebaseAuth 11 | import com.google.firebase.auth.FirebaseUser 12 | import com.google.firebase.firestore.FirebaseFirestore 13 | import com.google.firebase.storage.FirebaseStorage 14 | import com.instamobile.firebaseStarterKit.model.UserModel 15 | import com.instamobile.firebaseStarterKit.utils.MyApplication 16 | import com.instamobile.ui.fragment.onBoarding.walkthroughactivity.R 17 | 18 | class SignUpViewModel(private val myApplication: Application) : AndroidViewModel(myApplication) { 19 | private val auth = FirebaseAuth.getInstance() 20 | private val database = FirebaseFirestore.getInstance() 21 | 22 | var fullname: String = "" 23 | var phoneNumber: String = "" 24 | var email: String = "" 25 | var password: String = "" 26 | var imageUri: Uri? = null 27 | 28 | private val _fullNameError = MutableLiveData() 29 | val fullNameError: LiveData 30 | get() = _fullNameError 31 | 32 | private val _phoneNumberError = MutableLiveData() 33 | val phoneNumberError: LiveData 34 | get() = _phoneNumberError 35 | 36 | private val _emailError = MutableLiveData() 37 | val emailError: LiveData 38 | get() = _emailError 39 | 40 | private val _passwordError = MutableLiveData() 41 | val passwordError: LiveData 42 | get() = _passwordError 43 | 44 | private val _buttonEnabled = MutableLiveData().apply { value = true } 45 | val buttonEnabled: LiveData 46 | get() = _buttonEnabled 47 | 48 | private val _navigateToHome = MutableLiveData().apply { value = false } 49 | val navigateToHome: LiveData 50 | get() = _navigateToHome 51 | 52 | private val _signUpError = MutableLiveData() 53 | val signUpError: LiveData 54 | get() = _signUpError 55 | 56 | fun createUser() { 57 | when { 58 | fullname.isEmpty() -> _passwordError.value = 59 | myApplication.getString(R.string.name_required_error) 60 | phoneNumber.isEmpty() -> _phoneNumberError.value = 61 | myApplication.getString(R.string.phone_required_error) 62 | email.isEmpty() -> _emailError.value = 63 | myApplication.getString(R.string.email_required_error) 64 | password.isEmpty() -> _passwordError.value = 65 | myApplication.getString(R.string.password_required_error) 66 | !Patterns.PHONE.matcher(phoneNumber).matches() -> _phoneNumberError.value = 67 | myApplication.getString(R.string.malformed_phone_error) 68 | !Patterns.EMAIL_ADDRESS.matcher(email).matches() -> _emailError.value = 69 | myApplication.getString(R.string.malformed_email_error) 70 | password.length < 6 -> _passwordError.value = 71 | myApplication.getString(R.string.short_password_error) 72 | else -> { 73 | _buttonEnabled.value = false 74 | //create user without image 75 | 76 | auth.createUserWithEmailAndPassword(email, password).addOnSuccessListener { 77 | 78 | if (imageUri == null) { 79 | createUserWithoutImage() 80 | } else { 81 | createUserWithImage() 82 | } 83 | }.addOnFailureListener { 84 | it.printStackTrace() 85 | _signUpError.value = it.message 86 | _buttonEnabled.value = true 87 | 88 | } 89 | } 90 | } 91 | } 92 | 93 | private fun createUserWithoutImage() { 94 | val userid = auth.currentUser!!.uid 95 | val items = HashMap() 96 | items["email"] = email 97 | items["firstName"] = fullname 98 | items["lastName"] = "" 99 | items["userName"] = "" 100 | items["phoneNumber"] = phoneNumber 101 | items["userID"] = userid 102 | items["profilePictureURL"] = "" 103 | items["active"] = true 104 | saveUserToDatabase(auth.currentUser!!, items) 105 | 106 | } 107 | 108 | private fun createUserWithImage() { 109 | val data = FirebaseStorage.getInstance().reference 110 | val photoRef = data.child("images/" + auth.currentUser!!.uid + ".png") 111 | photoRef.putFile(this.imageUri!!).addOnProgressListener { 112 | }.continueWithTask { task -> 113 | if (!task.isSuccessful) { 114 | throw task.exception!! 115 | } 116 | photoRef.downloadUrl 117 | }.addOnSuccessListener { downloadUri -> 118 | 119 | val userId = auth.currentUser!!.uid 120 | val items = HashMap() 121 | items["email"] = email 122 | items["firstName"] = fullname 123 | items["lastName"] = "" 124 | items["userName"] = "" 125 | items["phoneNumber"] = phoneNumber 126 | items["userID"] = userId 127 | items["profilePictureURL"] = downloadUri.toString() 128 | items["active"] = true 129 | saveUserToDatabase(auth.currentUser!!, items) 130 | } 131 | } 132 | 133 | private fun saveUserToDatabase(user: FirebaseUser, items: HashMap) { 134 | 135 | database.collection("users").document(user.uid).set(items) 136 | .addOnSuccessListener { 137 | val userModel = UserModel() 138 | userModel.userID = user.uid 139 | userModel.email = items["email"].toString() 140 | userModel.firstName = items["firstName"].toString() 141 | userModel.lastName = items["lastName"].toString() 142 | userModel.userName = items["userName"].toString() 143 | userModel.profilePictureURL = items["profilePictureURL"].toString() 144 | userModel.active = true 145 | MyApplication.currentUser = userModel 146 | Log.d("SignUp state", "save user:success") 147 | _navigateToHome.value = true 148 | }.addOnFailureListener { 149 | _signUpError.value = it.message 150 | _buttonEnabled.value = true 151 | } 152 | } 153 | 154 | fun doneNavigating() { 155 | _navigateToHome.value = false 156 | } 157 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/utils/AppLifecycleHandler.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.utils 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.content.ComponentCallbacks2 6 | import android.content.res.Configuration 7 | import android.os.Bundle 8 | 9 | class AppLifecycleHandler(private val lifeCycleDelegate: LifeCycleDelegate) : 10 | Application.ActivityLifecycleCallbacks, 11 | ComponentCallbacks2 { 12 | 13 | private var appInForeground = false 14 | 15 | override fun onActivityPaused(p0: Activity?) {} 16 | 17 | override fun onActivityResumed(p0: Activity?) { 18 | if (!appInForeground) { 19 | appInForeground = true 20 | lifeCycleDelegate.onAppForegrounded() 21 | } 22 | } 23 | 24 | override fun onActivityStarted(p0: Activity?) { 25 | } 26 | 27 | override fun onActivityDestroyed(p0: Activity?) { 28 | } 29 | 30 | override fun onActivitySaveInstanceState(p0: Activity?, p1: Bundle?) { 31 | } 32 | 33 | override fun onActivityStopped(p0: Activity?) { 34 | } 35 | 36 | override fun onActivityCreated(p0: Activity?, p1: Bundle?) { 37 | } 38 | 39 | override fun onLowMemory() {} 40 | 41 | override fun onConfigurationChanged(p0: Configuration?) {} 42 | 43 | override fun onTrimMemory(level: Int) { 44 | if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { 45 | appInForeground = false 46 | lifeCycleDelegate.onAppBackgrounded() 47 | } 48 | } 49 | 50 | } 51 | 52 | interface LifeCycleDelegate { 53 | fun onAppBackgrounded() 54 | fun onAppForegrounded() 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/utils/FirestoreUtil.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.utils 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | import com.google.firebase.firestore.DocumentReference 5 | import com.google.firebase.firestore.FirebaseFirestore 6 | import com.instamobile.firebaseStarterKit.model.UserModel 7 | 8 | object FirestoreUtil { 9 | val firestoreInstance: FirebaseFirestore by lazy { FirebaseFirestore.getInstance() } 10 | 11 | private val currentUserDocRef: DocumentReference 12 | get() = firestoreInstance.document( 13 | "users/${FirebaseAuth.getInstance().currentUser?.uid 14 | ?: throw NullPointerException("UID is null.")}" 15 | ) 16 | 17 | fun updateUser(userModel: UserModel, onComplete: (String) -> Unit) { 18 | 19 | val task = currentUserDocRef.set(userModel) 20 | 21 | task.continueWith { 22 | if (it.isSuccessful) { 23 | onComplete("success") 24 | } 25 | }.addOnFailureListener { 26 | onComplete("failure") 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/instamobile/firebaseStarterKit/utils/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.instamobile.firebaseStarterKit.utils 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.util.Log 6 | import androidx.lifecycle.Lifecycle 7 | import androidx.lifecycle.LifecycleObserver 8 | import androidx.lifecycle.OnLifecycleEvent 9 | import androidx.lifecycle.ProcessLifecycleOwner 10 | import androidx.multidex.MultiDex 11 | import androidx.multidex.MultiDexApplication 12 | import com.google.firebase.auth.FirebaseAuth 13 | import com.instamobile.firebaseStarterKit.model.UserModel 14 | 15 | 16 | class MyApplication : MultiDexApplication(), LifeCycleDelegate { 17 | 18 | companion object { 19 | var currentUser: UserModel? = null 20 | } 21 | 22 | override fun onCreate() { 23 | super.onCreate() 24 | val lifeCycleHandler = AppLifecycleHandler(this) 25 | registerLifecycleHandler(lifeCycleHandler) 26 | } 27 | 28 | override fun attachBaseContext(context: Context) { 29 | super.attachBaseContext(context) 30 | MultiDex.install(this) 31 | } 32 | 33 | override fun onAppBackgrounded() { 34 | val mAuth: FirebaseAuth? = FirebaseAuth.getInstance() 35 | 36 | if (mAuth!!.currentUser != null && currentUser != null) { 37 | currentUser!!.active = false 38 | FirestoreUtil.updateUser(currentUser!!) { 39 | 40 | } 41 | } 42 | Log.d("Awww", "App in background") 43 | } 44 | 45 | override fun onAppForegrounded() { 46 | val mAuth: FirebaseAuth? = FirebaseAuth.getInstance() 47 | 48 | if (mAuth!!.currentUser != null && currentUser != null) { 49 | currentUser!!.active = true 50 | FirestoreUtil.updateUser(currentUser!!) { 51 | 52 | } 53 | } 54 | 55 | Log.d("Yeeey", "App in foreground") 56 | } 57 | 58 | private fun registerLifecycleHandler(lifeCycleHandler: AppLifecycleHandler) { 59 | registerActivityLifecycleCallbacks(lifeCycleHandler) 60 | registerComponentCallbacks(lifeCycleHandler) 61 | } 62 | } 63 | 64 | /** 65 | * If you are using Android Architecture Components you can use the ProcessLifecycleOwner 66 | * and LifecycleObserver like so (set this class to the app name in the manifest) 67 | * // ? = null 32 | private var attached: Boolean = false 33 | 34 | private var onPageChangeCallback: TabLayoutOnPageChangeCallback? = null 35 | private var onTabSelectedListener: TabLayout.OnTabSelectedListener? = null 36 | private var pagerAdapterObserver: RecyclerView.AdapterDataObserver? = null 37 | 38 | /** 39 | * A callback interface that must be implemented to set the text and styling of newly created 40 | * tabs. 41 | */ 42 | interface TabConfigurationStrategy { 43 | /** 44 | * Called to configure the tab for the page at the specified position. Typically calls [ ][TabLayout.Tab.setText], but any form of styling can be applied. 45 | * 46 | * @param tab The Tab which should be configured to represent the title of the item at the given 47 | * position in the data set. 48 | * @param position The position of the item within the adapter's data set. 49 | */ 50 | fun onConfigureTab(tab: TabLayout.Tab, position: Int) 51 | } 52 | 53 | constructor( 54 | tabLayout: TabLayout, 55 | viewPager: ViewPager2, 56 | tabConfigurationStrategy: TabConfigurationStrategy 57 | ) : this(tabLayout, viewPager, true, tabConfigurationStrategy) 58 | 59 | /** 60 | * Link the TabLayout and the ViewPager2 together. Must be called after ViewPager2 has an adapter 61 | * set. To be called on a new instance of TabLayoutMediator or if the ViewPager2's adapter 62 | * changes. 63 | * 64 | * @throws IllegalStateException If the mediator is already attached, or the ViewPager2 has no 65 | * adapter. 66 | */ 67 | fun attach() { 68 | check(!attached) { "TabLayoutMediator is already attached" } 69 | adapter = viewPager.adapter 70 | checkNotNull(adapter) { "TabLayoutMediator attached before ViewPager2 has an " + "adapter" } 71 | attached = true 72 | 73 | // Add our custom OnPageChangeCallback to the ViewPager 74 | onPageChangeCallback = TabLayoutOnPageChangeCallback(tabLayout) 75 | viewPager.registerOnPageChangeCallback(onPageChangeCallback!!) 76 | 77 | // Now we'll add a tab selected listener to set ViewPager's current item 78 | onTabSelectedListener = ViewPagerOnTabSelectedListener(viewPager) 79 | tabLayout.addOnTabSelectedListener(onTabSelectedListener!!) 80 | 81 | // Now we'll populate ourselves from the pager adapter, adding an observer if 82 | // autoRefresh is enabled 83 | if (autoRefresh) { 84 | // Register our observer on the new adapter 85 | pagerAdapterObserver = PagerAdapterObserver() 86 | adapter!!.registerAdapterDataObserver(pagerAdapterObserver!!) 87 | } 88 | 89 | populateTabsFromPagerAdapter() 90 | 91 | // Now update the scroll position to match the ViewPager's current item 92 | tabLayout.setScrollPosition(viewPager.currentItem, 0f, true) 93 | } 94 | 95 | /** 96 | * Unlink the TabLayout and the ViewPager. To be called on a stale TabLayoutMediator if a new one 97 | * is instantiated, to prevent holding on to a view that should be garbage collected. Also to be 98 | * called before [.attach] when a ViewPager2's adapter is changed. 99 | */ 100 | fun detach() { 101 | adapter!!.unregisterAdapterDataObserver(pagerAdapterObserver!!) 102 | tabLayout.removeOnTabSelectedListener(onTabSelectedListener!!) 103 | viewPager.unregisterOnPageChangeCallback(onPageChangeCallback!!) 104 | pagerAdapterObserver = null 105 | onTabSelectedListener = null 106 | onPageChangeCallback = null 107 | adapter = null 108 | attached = false 109 | } 110 | 111 | internal fun populateTabsFromPagerAdapter() { 112 | tabLayout.removeAllTabs() 113 | 114 | if (adapter != null) { 115 | val adapterCount = adapter!!.itemCount 116 | for (i in 0 until adapterCount) { 117 | val tab = tabLayout.newTab() 118 | tabConfigurationStrategy.onConfigureTab(tab, i) 119 | tabLayout.addTab(tab, false) 120 | } 121 | // Make sure we reflect the currently set ViewPager item 122 | if (adapterCount > 0) { 123 | val lastItem = tabLayout.tabCount - 1 124 | val currItem = Math.min(viewPager.currentItem, lastItem) 125 | if (currItem != tabLayout.selectedTabPosition) { 126 | tabLayout.selectTab(tabLayout.getTabAt(currItem)) 127 | } 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * A [ViewPager2.OnPageChangeCallback] class which contains the necessary calls back to the 134 | * provided [TabLayout] so that the tab position is kept in sync. 135 | * 136 | * 137 | * This class stores the provided TabLayout weakly, meaning that you can use [ ][ViewPager2.registerOnPageChangeCallback] without removing the 138 | * callback and not cause a leak. 139 | */ 140 | private class TabLayoutOnPageChangeCallback internal constructor(tabLayout: TabLayout) : 141 | ViewPager2.OnPageChangeCallback() { 142 | private val tabLayoutRef: WeakReference = WeakReference(tabLayout) 143 | private var previousScrollState: Int = 0 144 | private var scrollState: Int = 0 145 | 146 | init { 147 | reset() 148 | } 149 | 150 | override fun onPageScrollStateChanged(state: Int) { 151 | previousScrollState = scrollState 152 | scrollState = state 153 | } 154 | 155 | override fun onPageScrolled( 156 | position: Int, 157 | positionOffset: Float, 158 | positionOffsetPixels: Int 159 | ) { 160 | val tabLayout = tabLayoutRef.get() 161 | if (tabLayout != null) { 162 | // Only update the text selection if we're not settling, or we are settling after 163 | // being dragged 164 | val updateText = 165 | scrollState != SCROLL_STATE_SETTLING || previousScrollState == SCROLL_STATE_DRAGGING 166 | // Update the indicator if we're not settling after being idle. This is caused 167 | // from a setCurrentItem() call and will be handled by an animation from 168 | // onPageSelected() instead. 169 | val updateIndicator = 170 | !(scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE) 171 | tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator) 172 | } 173 | } 174 | 175 | override fun onPageSelected(position: Int) { 176 | val tabLayout = tabLayoutRef.get() 177 | if (tabLayout != null 178 | && tabLayout.selectedTabPosition != position 179 | && position < tabLayout.tabCount 180 | ) { 181 | // Select the tab, only updating the indicator if we're not being dragged/settled 182 | // (since onPageScrolled will handle that). 183 | val updateIndicator = 184 | scrollState == SCROLL_STATE_IDLE || scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE 185 | tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator) 186 | } 187 | } 188 | 189 | internal fun reset() { 190 | scrollState = SCROLL_STATE_IDLE 191 | previousScrollState = scrollState 192 | } 193 | } 194 | 195 | /** 196 | * A [TabLayout.OnTabSelectedListener] class which contains the necessary calls back to the 197 | * provided [ViewPager2] so that the tab position is kept in sync. 198 | */ 199 | private class ViewPagerOnTabSelectedListener internal constructor(private val viewPager: ViewPager2) : 200 | TabLayout.OnTabSelectedListener { 201 | 202 | override fun onTabSelected(tab: TabLayout.Tab) { 203 | viewPager.setCurrentItem(tab.position, true) 204 | } 205 | 206 | override fun onTabUnselected(tab: TabLayout.Tab) { 207 | // No-op 208 | } 209 | 210 | override fun onTabReselected(tab: TabLayout.Tab) { 211 | // No-op 212 | } 213 | } 214 | 215 | private inner class PagerAdapterObserver internal constructor() : 216 | RecyclerView.AdapterDataObserver() { 217 | 218 | override fun onChanged() { 219 | populateTabsFromPagerAdapter() 220 | } 221 | 222 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int) { 223 | populateTabsFromPagerAdapter() 224 | } 225 | 226 | override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) { 227 | populateTabsFromPagerAdapter() 228 | } 229 | 230 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 231 | populateTabsFromPagerAdapter() 232 | } 233 | 234 | override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { 235 | populateTabsFromPagerAdapter() 236 | } 237 | 238 | override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { 239 | populateTabsFromPagerAdapter() 240 | } 241 | } 242 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/instamobile_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable-hdpi/instamobile_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/instamobile_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable-mdpi/instamobile_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/instamobile_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable-xhdpi/instamobile_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/instamobile_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable-xxhdpi/instamobile_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/instamobile_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable-xxxhdpi/instamobile_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/disabled_rounded_blue_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_blue_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_camera.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chat.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_fast.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_firebase.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_kotlin.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /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_logout.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_white_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/launch_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instamobile/kotlin-firebase/7d4da154cb0981a19d6add5fe13afffa7f3cdbae/app/src/main/res/drawable/placeholder.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_blue_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rounded_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/sign_up_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_indicator_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_indicator_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/tab_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_host.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 14 | 15 | 23 | 24 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | 46 | 55 | -------------------------------------------------------------------------------- /app/src/main/res/layout/drawer_header_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 20 | 21 | 32 | 33 | 51 | 52 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_auth.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 33 | 34 | 52 | 53 | 69 | 70 |