diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index bae407fa..31d04311 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -159,15 +159,15 @@ dependencies {
// Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
- implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
+ implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
- implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
+ implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
// Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1")
- implementation("com.google.android.material:material:1.10.0")
+ implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@@ -200,8 +200,10 @@ dependencies {
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
// Crash Reports (AcraApplication.kt)
- implementation("ch.acra:acra-core:5.11.2")
- implementation("ch.acra:acra-toast:5.11.2")
+ implementation("ch.acra:acra-core:5.11.3")
+ implementation("ch.acra:acra-toast:5.11.3")
+
+ implementation ("androidx.biometric:biometric:1.2.0-alpha05")
// UI Stuff
implementation("com.facebook.shimmer:shimmer:0.5.0") // Shimmering Effect (Loading Skeleton)
@@ -224,8 +226,8 @@ dependencies {
Level 25 or Less. */
// Downloading & Networking
- implementation("androidx.work:work-runtime:2.8.1")
- implementation("androidx.work:work-runtime-ktx:2.8.1")
+ implementation("androidx.work:work-runtime:2.9.0")
+ implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("com.github.Blatzar:NiceHttp:0.4.4") // HTTP Lib
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e665c3bc..d1d6dba6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,6 +15,8 @@
+
+
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index 2819ab98..71d6e952 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -126,6 +126,9 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isTruePhone
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
@@ -1068,6 +1071,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+
+
+
val errorFile = filesDir.resolve("last_error")
if (errorFile.exists() && errorFile.isFile) {
lastError = errorFile.readText(Charset.defaultCharset())
@@ -1151,6 +1157,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings())
+ val authEnabled = settingsManager.getBoolean(
+ getString(R.string.biometric_enabled_key), false)
+
+ if (isTruePhone() && authEnabled) {
+ BiometricAuthenticator.initializeBiometrics(this@MainActivity)
+ BiometricAuthenticator.checkBiometricAvailability(this@MainActivity)
+ BiometricAuthenticator.biometricPrompt.authenticate(promptInfo)
+ }
+
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
main {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
new file mode 100644
index 00000000..0759277d
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
@@ -0,0 +1,132 @@
+package com.lagradost.cloudstream3.utils
+
+import android.content.Context
+import android.os.Build
+import android.util.Log
+import android.widget.Toast
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
+import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
+import androidx.biometric.BiometricPrompt
+import androidx.core.content.ContextCompat
+import com.lagradost.cloudstream3.AcraApplication.Companion.context
+import com.lagradost.cloudstream3.MainActivity
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
+
+object BiometricAuthenticator {
+
+ private lateinit var biometricManager: BiometricManager
+ lateinit var biometricPrompt: BiometricPrompt
+ lateinit var promptInfo: BiometricPrompt.PromptInfo
+
+ fun initializeBiometrics(activity: MainActivity) {
+ val executor = ContextCompat.getMainExecutor(activity)
+ biometricManager = BiometricManager.from(activity)
+
+ biometricPrompt = BiometricPrompt(activity, executor,
+ object : BiometricPrompt.AuthenticationCallback() {
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ Toast.makeText(context?.applicationContext, "Authentication error: $errString", Toast.LENGTH_SHORT).show()
+ // Handle error as needed
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ Toast.makeText(context?.applicationContext, "Authentication succeeded!", Toast.LENGTH_SHORT).show()
+ // Handle authentication success
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ Toast.makeText(context?.applicationContext, "Authentication failed", Toast.LENGTH_SHORT).show()
+ // Handle authentication failure
+ }
+ })
+
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Biometric login for my app")
+ .setSubtitle("Log in using your biometric credential")
+ //.setNegativeButtonText("Use account password")
+ .setAllowedAuthenticators(BIOMETRIC_WEAK or BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
+ .build()
+ }
+
+ fun checkBiometricAvailability(context: Context) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ when (biometricManager.canAuthenticate(BIOMETRIC_WEAK or BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
+ BiometricManager.BIOMETRIC_SUCCESS ->
+ Toast.makeText(
+ context,
+ "Biometric authentication is available",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
+ Toast.makeText(
+ context,
+ "This device doesn't support biometric authentication",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
+ Toast.makeText(
+ context,
+ "Biometric authentication is currently unavailable",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
+ Toast.makeText(
+ context,
+ "No biometric credentials are enrolled",
+ Toast.LENGTH_SHORT
+ ).show()
+
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
+ TODO()
+ }
+
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
+ TODO()
+ }
+
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
+ TODO()
+ }
+ }
+ }
+
+ else {
+ when (biometricManager.canAuthenticate(BIOMETRIC_WEAK or BIOMETRIC_STRONG)) {
+ BiometricManager.BIOMETRIC_SUCCESS ->
+ Log.d("Cs3Auth", "App can authenticate using biometrics.")
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
+ Log.e("Cs3Auth", "No biometric features available on this device.")
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
+ Log.e("Cs3Auth", "Biometric features are currently unavailable.")
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
+ Log.e("Cs3Auth", "Biometric features are currently unavailable.")
+
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> {
+ TODO()
+ }
+
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> {
+ TODO()
+ }
+
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> {
+ TODO()
+ }
+ }
+ }
+ }
+
+ fun isTruePhone(): Boolean {
+ return !isTrueTvSettings() && !isTvSettings() && context?.isEmulatorSettings() != true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_fingerprint.xml b/app/src/main/res/drawable/ic_fingerprint.xml
new file mode 100644
index 00000000..5c96e5a5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ce660a67..8e9140ec 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -67,6 +67,7 @@
enable_nsfw_on_providers_key
skip_startup_account_select_key
enable_skip_op_from_database
+ biometric_key
%d %s | %s
%s • %s
@@ -733,4 +734,5 @@
Logged in as %s
Skip account selection at startup
Use Default Account
+ Use Fingerprint Authentication
diff --git a/app/src/main/res/xml/settings_account.xml b/app/src/main/res/xml/settings_account.xml
index ec882088..f31c205c 100644
--- a/app/src/main/res/xml/settings_account.xml
+++ b/app/src/main/res/xml/settings_account.xml
@@ -1,11 +1,5 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file