Skip to content

Commit

Permalink
Merge pull request #67 from MuindiStephen/66-task-biometric-auth-as-a…
Browse files Browse the repository at this point in the history
…nother-option-and-an-alternative-to-normal-email-password-signin-fingerprint-recognition

66 task biometric auth as another option and an alternative to normal email password signin fingerprint recognition
  • Loading branch information
MuindiStephen committed Jun 24, 2024
2 parents b1223b4 + 254974a commit f7bd0ac
Show file tree
Hide file tree
Showing 5 changed files with 214 additions and 17 deletions.
32 changes: 17 additions & 15 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ android {

defaultConfig {
applicationId "com.steve_md.smartmkulima"
minSdk 21
minSdk 23
targetSdk 34
versionCode 1
versionName "1.0"
Expand Down Expand Up @@ -54,24 +54,22 @@ android {
dependencies {

implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'com.google.firebase:firebase-auth-ktx:22.3.1'
implementation 'com.google.firebase:firebase-database-ktx:20.3.0'
implementation 'com.google.firebase:firebase-storage-ktx:20.3.0'
implementation 'com.google.firebase:firebase-firestore-ktx:24.10.2'
def nav_version = "2.7.7"
def lifecycle_version = "2.7.0"
implementation 'com.google.firebase:firebase-auth-ktx:23.0.0'
implementation 'com.google.firebase:firebase-database-ktx:21.0.0'
implementation 'com.google.firebase:firebase-storage-ktx:21.0.0'
implementation 'com.google.firebase:firebase-firestore-ktx:25.0.0'
def timber_version = "5.0.1"
def room_version = "2.6.1"


// Ktx
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.core:core-ktx:1.13.1'

// AppCompat
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.appcompat:appcompat:1.7.0'

// Material
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.12.0'

// Constraint Layout
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
Expand All @@ -82,15 +80,15 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

// Navigation Components
implementation("androidx.navigation:navigation-fragment-ktx:$nav_version")
implementation("androidx.navigation:navigation-ui-ktx:$nav_version")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")


// Alternatively - just LiveData
implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.2")

// Alternatively - just ViewModel
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2")

// Dagger - Hilt
implementation 'com.google.dagger:hilt-android:2.50'
Expand Down Expand Up @@ -126,7 +124,7 @@ dependencies {

// Maps - Dependency
implementation 'com.google.android.gms:play-services-maps:18.2.0'
implementation 'com.google.android.gms:play-services-location:21.1.0'
implementation 'com.google.android.gms:play-services-location:21.3.0'

// Lottie Animation
implementation 'com.airbnb.android:lottie:6.0.0'
Expand All @@ -147,4 +145,8 @@ dependencies {

// Step View
implementation 'com.github.shuhart:stepview:1.5.1'

// Biometric authentication
implementation "androidx.biometric:biometric:1.1.0"

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package com.steve_md.smartmkulima.ui.fragments.auth

import android.annotation.SuppressLint
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
Expand All @@ -16,8 +26,10 @@ import com.steve_md.smartmkulima.utils.EventObserver
import com.steve_md.smartmkulima.utils.displaySnackBar
import com.steve_md.smartmkulima.utils.hideKeyboard
import com.steve_md.smartmkulima.utils.snackBar
import com.steve_md.smartmkulima.utils.toast
import com.steve_md.smartmkulima.viewmodel.AuthViewModel
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber


@AndroidEntryPoint
Expand All @@ -31,12 +43,155 @@ class SignInDetailsWithEmailFragment : Fragment() {

private val authViewModel: AuthViewModel by viewModels()

private lateinit var biometricPrompt: BiometricPrompt

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)


}

private fun authenticate() {

/**
* Check whether the Authentication is available
* @param biometricManager is present, is enrolled
*/
val biometricManager = BiometricManager.from(requireContext())
when(biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)) {
BiometricManager.BIOMETRIC_SUCCESS -> {
Timber.tag("Biometric").e("Authenticated using biometrics")
val prompt = createBiometricPrompt()
prompt.authenticate(createPromptInfo())
}
BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> {
Timber.tag(requireActivity().toString())
.e("onCreate: Biometric features are currently unavailable.")
}

BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
// The user didn't enroll in biometrics that your app accepts, prompt them to enroll in it
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val enrollIntent =
Intent(Settings.ACTION_BIOMETRIC_ENROLL).apply {
putExtra(
Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED,
BIOMETRIC_STRONG or DEVICE_CREDENTIAL
)
}
startActivityForResult(enrollIntent, REQUEST_CODE)
}
}

BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
Timber.d("Biometric features hardware is missing")
}

BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {

}

BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {

}

BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {

}
}
}

private fun setUpBinding() {
binding.fingerprintToPress.setOnClickListener {
authenticate()
}
}

private fun createPromptInfo(): BiometricPrompt.PromptInfo =
BiometricPrompt.PromptInfo.Builder()
.setTitle("Login")
.setSubtitle("Log in to your Agri-Sasa account")
.setDescription("Please authenticate using biometrics")
.setNegativeButtonText("CANCEL")
.setAllowedAuthenticators(BIOMETRIC_STRONG)
.setConfirmationRequired(false)
.build()


/**
*
* Display biometric prompt
* @param biometricPrompt
*/
private fun createBiometricPrompt(): BiometricPrompt {
val executor = ContextCompat.getMainExecutor(requireContext())

val callBack = object : BiometricPrompt.AuthenticationCallback() {
@SuppressLint("TimberArgCount")
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
Timber.tag(this@SignInDetailsWithEmailFragment.toString())
.d("$errorCode :: $errString")

if (errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) {
loginWithEmailPasswordOtherwise()
} else if (errorCode == BiometricPrompt.ERROR_NO_BIOMETRICS) {
snackBar("Device does not support Fingerprint biometrics")
loginWithEmailPasswordOtherwise()
} else if (errorCode == BiometricPrompt.ERROR_CANCELED) {
loginWithEmailPasswordOtherwise()
} else if (errorCode == BiometricPrompt.ERROR_SECURITY_UPDATE_REQUIRED) {
snackBar("Security violation")
loginWithEmailPasswordOtherwise()
} else if (errorCode == BiometricPrompt.ERROR_TIMEOUT) {
snackBar("Fingerprint failed. try again or login using email instead")
} else {
Timber.tag(this.toString())
.e("%s%s", "%s || ", "onAuthenticationError: %s", errorCode, errString)
}

}

override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Timber.tag(this.toString()).d("Authentication was successful")
toast("Authenticated with biometrics successfully")

navigateHome()
// showEncryptedMessage(result.cryptoObject)
}

override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Timber.tag(this.toString()).d("Authentication failed for an unknown reason")
toast("Unknown authentication error")
}

}

return BiometricPrompt(this.requireActivity(), executor, callBack)
}

private fun showEncryptedMessage(cryptoObject: BiometricPrompt.CryptoObject?) {

}

private fun loginWithEmailPasswordOtherwise() {

}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSignInDetailsWithEmailBinding.inflate(layoutInflater, container, false)
setUpBinding()

biometricPrompt = createBiometricPrompt()

authenticate()

return binding.root
}

Expand Down Expand Up @@ -98,4 +253,8 @@ class SignInDetailsWithEmailFragment : Fragment() {
//findNavController().navigate(R.id.action_signInDetailsWithEmailFragment_to_signInDetailsFragment2)
displaySnackBar("Feature is coming soon!")
}

companion object {
const val REQUEST_CODE = 1333
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ class HomeDashboardFragment : Fragment() {
firebaseAuth = FirebaseAuth.getInstance()

val userId = firebaseAuth!!.uid

if (userId == null) {
Timber.e("Firebase User ID is null")
return binding.root
}
val currentUserLogged = firebaseAuth!!.currentUser

databaseReference = FirebaseDatabase.getInstance().getReference("users").child(userId!!)
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/drawable/baseline_fingerprint_24.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:alpha="0.61" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">

<path android:fillColor="@android:color/white" android:pathData="M17.81,4.47c-0.08,0 -0.16,-0.02 -0.23,-0.06C15.66,3.42 14,3 12.01,3c-1.98,0 -3.86,0.47 -5.57,1.41 -0.24,0.13 -0.54,0.04 -0.68,-0.2 -0.13,-0.24 -0.04,-0.55 0.2,-0.68C7.82,2.52 9.86,2 12.01,2c2.13,0 3.99,0.47 6.03,1.52 0.25,0.13 0.34,0.43 0.21,0.67 -0.09,0.18 -0.26,0.28 -0.44,0.28zM3.5,9.72c-0.1,0 -0.2,-0.03 -0.29,-0.09 -0.23,-0.16 -0.28,-0.47 -0.12,-0.7 0.99,-1.4 2.25,-2.5 3.75,-3.27C9.98,4.04 14,4.03 17.15,5.65c1.5,0.77 2.76,1.86 3.75,3.25 0.16,0.22 0.11,0.54 -0.12,0.7 -0.23,0.16 -0.54,0.11 -0.7,-0.12 -0.9,-1.26 -2.04,-2.25 -3.39,-2.94 -2.87,-1.47 -6.54,-1.47 -9.4,0.01 -1.36,0.7 -2.5,1.7 -3.4,2.96 -0.08,0.14 -0.23,0.21 -0.39,0.21zM9.75,21.79c-0.13,0 -0.26,-0.05 -0.35,-0.15 -0.87,-0.87 -1.34,-1.43 -2.01,-2.64 -0.69,-1.23 -1.05,-2.73 -1.05,-4.34 0,-2.97 2.54,-5.39 5.66,-5.39s5.66,2.42 5.66,5.39c0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0,-2.42 -2.09,-4.39 -4.66,-4.39 -2.57,0 -4.66,1.97 -4.66,4.39 0,1.44 0.32,2.77 0.93,3.85 0.64,1.15 1.08,1.64 1.85,2.42 0.19,0.2 0.19,0.51 0,0.71 -0.11,0.1 -0.24,0.15 -0.37,0.15zM16.92,19.94c-1.19,0 -2.24,-0.3 -3.1,-0.89 -1.49,-1.01 -2.38,-2.65 -2.38,-4.39 0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0,1.41 0.72,2.74 1.94,3.56 0.71,0.48 1.54,0.71 2.54,0.71 0.24,0 0.64,-0.03 1.04,-0.1 0.27,-0.05 0.53,0.13 0.58,0.41 0.05,0.27 -0.13,0.53 -0.41,0.58 -0.57,0.11 -1.07,0.12 -1.21,0.12zM14.91,22c-0.04,0 -0.09,-0.01 -0.13,-0.02 -1.59,-0.44 -2.63,-1.03 -3.72,-2.1 -1.4,-1.39 -2.17,-3.24 -2.17,-5.22 0,-1.62 1.38,-2.94 3.08,-2.94 1.7,0 3.08,1.32 3.08,2.94 0,1.07 0.93,1.94 2.08,1.94s2.08,-0.87 2.08,-1.94c0,-3.77 -3.25,-6.83 -7.25,-6.83 -2.84,0 -5.44,1.58 -6.61,4.03 -0.39,0.81 -0.59,1.76 -0.59,2.8 0,0.78 0.07,2.01 0.67,3.61 0.1,0.26 -0.03,0.55 -0.29,0.64 -0.26,0.1 -0.55,-0.04 -0.64,-0.29 -0.49,-1.31 -0.73,-2.61 -0.73,-3.96 0,-1.2 0.23,-2.29 0.68,-3.24 1.33,-2.79 4.28,-4.6 7.51,-4.6 4.55,0 8.25,3.51 8.25,7.83 0,1.62 -1.38,2.94 -3.08,2.94s-3.08,-1.32 -3.08,-2.94c0,-1.07 -0.93,-1.94 -2.08,-1.94s-2.08,0.87 -2.08,1.94c0,1.71 0.66,3.31 1.87,4.51 0.95,0.94 1.86,1.46 3.27,1.85 0.27,0.07 0.42,0.35 0.35,0.61 -0.05,0.23 -0.26,0.38 -0.47,0.38z"/>

</vector>
30 changes: 28 additions & 2 deletions app/src/main/res/layout/fragment_sign_in_details_with_email.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@
app:endIconMode="clear_text"
android:textColor="@color/textColor"
app:endIconTint="@color/SilverGray"

app:layout_constraintEnd_toEndOf="@id/textView2"
app:layout_constraintStart_toStartOf="@id/textView2"
app:layout_constraintTop_toBottomOf="@id/textView2">

<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputLoginEmail"
android:fontFamily="@font/montserrat"
android:layout_width="match_parent"
android:textColor="@color/textColor"
android:layout_height="wrap_content"
Expand Down Expand Up @@ -124,6 +126,7 @@
android:inputType="textPassword"
android:textColor="@color/textColor"
android:id="@+id/inputLoginPassword"
android:fontFamily="@font/montserrat"
/>

</com.google.android.material.textfield.TextInputLayout>
Expand All @@ -134,7 +137,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="45dp"
android:layout_marginTop="150dp"
android:layout_marginTop="100dp"
android:layout_marginEnd="45dp"
android:backgroundTint="@color/main1"
android:fontFamily="@font/montserrat_bold"
Expand Down Expand Up @@ -162,7 +165,7 @@
<LinearLayout
android:id="@+id/linearLayout8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="30dp"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@id/signInWithEmailButton"
Expand Down Expand Up @@ -237,4 +240,27 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout8" />

<TextView
android:id="@+id/textViewFingerprint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/montserrat_medium"
android:text="Use fingerprint"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

<ImageView
android:id="@+id/fingerprintToPress"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginBottom="8dp"
android:scaleType="centerCrop"
android:src="@drawable/baseline_fingerprint_24"
app:layout_constraintBottom_toTopOf="@id/textViewFingerprint"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:contentDescription="fingerprint" />
</androidx.constraintlayout.widget.ConstraintLayout>

0 comments on commit f7bd0ac

Please sign in to comment.