Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

66 task biometric auth as another option and an alternative to normal email password signin fingerprint recognition #67

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
Loading