diff --git a/app/build.gradle b/app/build.gradle index 0b82abe..d797dc5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ android { defaultConfig { applicationId "com.steve_md.smartmkulima" - minSdk 21 + minSdk 23 targetSdk 34 versionCode 1 versionName "1.0" @@ -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' @@ -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' @@ -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' @@ -147,4 +145,8 @@ dependencies { // Step View implementation 'com.github.shuhart:stepview:1.5.1' + + // Biometric authentication + implementation "androidx.biometric:biometric:1.1.0" + } \ No newline at end of file diff --git a/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/auth/SignInDetailsWithEmailFragment.kt b/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/auth/SignInDetailsWithEmailFragment.kt index b17db3f..67028ea 100644 --- a/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/auth/SignInDetailsWithEmailFragment.kt +++ b/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/auth/SignInDetailsWithEmailFragment.kt @@ -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 @@ -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 @@ -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 } @@ -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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/main/HomeDashboardFragment.kt b/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/main/HomeDashboardFragment.kt index 4144250..df62cee 100644 --- a/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/main/HomeDashboardFragment.kt +++ b/app/src/main/java/com/steve_md/smartmkulima/ui/fragments/main/HomeDashboardFragment.kt @@ -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!!) diff --git a/app/src/main/res/drawable/baseline_fingerprint_24.xml b/app/src/main/res/drawable/baseline_fingerprint_24.xml new file mode 100644 index 0000000..00e63e2 --- /dev/null +++ b/app/src/main/res/drawable/baseline_fingerprint_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_sign_in_details_with_email.xml b/app/src/main/res/layout/fragment_sign_in_details_with_email.xml index 066e71f..e40d9ee 100644 --- a/app/src/main/res/layout/fragment_sign_in_details_with_email.xml +++ b/app/src/main/res/layout/fragment_sign_in_details_with_email.xml @@ -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"> @@ -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" @@ -162,7 +165,7 @@ + + + +