Skip to content
Draft
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
6 changes: 6 additions & 0 deletions packages/local_auth/local_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 3.1.0

* Adds `requiresLocalizedReason` to determine if the current platform implementation
requires a localized reason for authentication.
* Make `localizedReason` parameter optional in `authenticate`

## 3.0.2

* Clarifies the `getAvailableBiometrics` documentation regarding iOS permission requirements.
Expand Down
5 changes: 5 additions & 0 deletions packages/local_auth/local_auth/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,8 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
local_auth_android: {path: ../../../../packages/local_auth/local_auth_android}
local_auth_platform_interface: {path: ../../../../packages/local_auth/local_auth_platform_interface}
8 changes: 6 additions & 2 deletions packages/local_auth/local_auth/lib/src/local_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class LocalAuthentication {
///
/// [localizedReason] is the message to show to user while prompting them
/// for authentication. This is typically along the lines of: 'Authenticate
/// to access MyApp.'. This must not be empty.
/// to access MyApp.'.
///
/// Provide [authMessages] if you want to customize messages in the dialogs.
///
Expand All @@ -46,7 +46,7 @@ class LocalAuthentication {
/// retry the authentication on foregrounding instead of failing with an error
/// on backgrounding.
Future<bool> authenticate({
required String localizedReason,
String? localizedReason,
Iterable<AuthMessages> authMessages = const <AuthMessages>[
IOSAuthMessages(),
AndroidAuthMessages(),
Expand Down Expand Up @@ -97,4 +97,8 @@ class LocalAuthentication {
/// permissions to use them.
Future<List<BiometricType>> getAvailableBiometrics() =>
LocalAuthPlatform.instance.getEnrolledBiometrics();

/// Returns true if the current platform implementation requires
/// a localized reason for authentication.
bool requiresLocalizedReason() => LocalAuthPlatform.instance.requiresLocalizedReason();
}
10 changes: 9 additions & 1 deletion packages/local_auth/local_auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: local_auth
description: Flutter plugin to allow local authentication via biometrics, passcode, pin, or pattern.
repository: https://github.com/flutter/packages/tree/main/packages/local_auth/local_auth
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
version: 3.0.2
version: 3.1.0

environment:
sdk: ^3.10.0
Expand Down Expand Up @@ -38,3 +38,11 @@ topics:
- authentication
- biometrics
- local-auth
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
local_auth_android: {path: ../../../packages/local_auth/local_auth_android}
local_auth_darwin: {path: ../../../packages/local_auth/local_auth_darwin}
local_auth_platform_interface: {path: ../../../packages/local_auth/local_auth_platform_interface}
local_auth_windows: {path: ../../../packages/local_auth/local_auth_windows}

17 changes: 16 additions & 1 deletion packages/local_auth/local_auth/test/local_auth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ void main() {
expect(result, true);
verify(mockLocalAuthPlatform.deviceSupportsBiometrics()).called(2);
});

test('requiresLocalizedReason calls platform implementation', () {
when(mockLocalAuthPlatform.requiresLocalizedReason()).thenReturn(true);
final bool result = localAuthentication.requiresLocalizedReason();
expect(result, true);
verify(mockLocalAuthPlatform.requiresLocalizedReason()).called(1);
});
}

class MockLocalAuthPlatform extends Mock
Expand All @@ -84,7 +91,7 @@ class MockLocalAuthPlatform extends Mock

@override
Future<bool> authenticate({
required String? localizedReason,
String? localizedReason,
required Iterable<AuthMessages>? authMessages,
AuthenticationOptions? options = const AuthenticationOptions(),
}) =>
Expand Down Expand Up @@ -129,4 +136,12 @@ class MockLocalAuthPlatform extends Mock
returnValue: Future<bool>.value(false),
)
as Future<bool>;

@override
bool requiresLocalizedReason() =>
super.noSuchMethod(
Invocation.method(#requiresLocalizedReason, <Object>[]),
returnValue: false,
)
as bool;
}
6 changes: 6 additions & 0 deletions packages/local_auth/local_auth_android/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 2.1.0

* Adds `requiresLocalizedReason` to determine if the current platform implementation
requires a localized reason for authentication.
* Make `localizedReason` parameter optional in `authenticate`.

## 2.0.9

* Migrates to Built-in Kotlin to support AGP 9.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2013 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Autogenerated from Pigeon (v26.2.3), do not edit directly.
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
// See also: https://pub.dev/packages/pigeon
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")

Expand Down Expand Up @@ -32,7 +32,36 @@ private object MessagesPigeonUtils {
}
}

fun doubleEquals(a: Double, b: Double): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
}

fun floatEquals(a: Float, b: Float): Boolean {
// Normalize -0.0 to 0.0 and handle NaN equality.
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
}

fun doubleHash(d: Double): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (d == 0.0) 0.0 else d
val bits = java.lang.Double.doubleToLongBits(normalized)
return (bits xor (bits ushr 32)).toInt()
}

fun floatHash(f: Float): Int {
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
val normalized = if (f == 0.0f) 0.0f else f
return java.lang.Float.floatToIntBits(normalized)
}

fun deepEquals(a: Any?, b: Any?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
if (a is ByteArray && b is ByteArray) {
return a.contentEquals(b)
}
Expand All @@ -43,20 +72,109 @@ private object MessagesPigeonUtils {
return a.contentEquals(b)
}
if (a is DoubleArray && b is DoubleArray) {
return a.contentEquals(b)
if (a.size != b.size) return false
for (i in a.indices) {
if (!doubleEquals(a[i], b[i])) return false
}
return true
}
if (a is FloatArray && b is FloatArray) {
if (a.size != b.size) return false
for (i in a.indices) {
if (!floatEquals(a[i], b[i])) return false
}
return true
}
if (a is Array<*> && b is Array<*>) {
return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
if (a.size != b.size) return false
for (i in a.indices) {
if (!deepEquals(a[i], b[i])) return false
}
return true
}
if (a is List<*> && b is List<*>) {
return a.size == b.size && a.indices.all { deepEquals(a[it], b[it]) }
if (a.size != b.size) return false
val iterA = a.iterator()
val iterB = b.iterator()
while (iterA.hasNext() && iterB.hasNext()) {
if (!deepEquals(iterA.next(), iterB.next())) return false
}
return true
}
if (a is Map<*, *> && b is Map<*, *>) {
return a.size == b.size &&
a.all { (b as Map<Any?, Any?>).contains(it.key) && deepEquals(it.value, b[it.key]) }
if (a.size != b.size) return false
for (entry in a) {
val key = entry.key
var found = false
for (bEntry in b) {
if (deepEquals(key, bEntry.key)) {
if (deepEquals(entry.value, bEntry.value)) {
found = true
break
} else {
return false
}
}
}
if (!found) return false
}
return true
}
if (a is Double && b is Double) {
return doubleEquals(a, b)
}
if (a is Float && b is Float) {
return floatEquals(a, b)
}
return a == b
}

fun deepHash(value: Any?): Int {
return when (value) {
null -> 0
is ByteArray -> value.contentHashCode()
is IntArray -> value.contentHashCode()
is LongArray -> value.contentHashCode()
is DoubleArray -> {
var result = 1
for (item in value) {
result = 31 * result + doubleHash(item)
}
result
}
is FloatArray -> {
var result = 1
for (item in value) {
result = 31 * result + floatHash(item)
}
result
}
is Array<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is List<*> -> {
var result = 1
for (item in value) {
result = 31 * result + deepHash(item)
}
result
}
is Map<*, *> -> {
var result = 0
for (entry in value) {
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
}
result
}
is Double -> doubleHash(value)
is Float -> floatHash(value)
else -> value.hashCode()
}
}
}

/**
Expand All @@ -70,7 +188,7 @@ class FlutterError(
val code: String,
override val message: String? = null,
val details: Any? = null
) : Throwable()
) : RuntimeException()

/** Possible outcomes of an authentication attempt. */
enum class AuthResultCode(val raw: Int) {
Expand Down Expand Up @@ -140,14 +258,14 @@ enum class AuthClassification(val raw: Int) {
* Generated class from Pigeon that represents data sent in messages.
*/
data class AuthStrings(
val reason: String,
val reason: String? = null,
val signInHint: String,
val cancelButton: String,
val signInTitle: String
) {
companion object {
fun fromList(pigeonVar_list: List<Any?>): AuthStrings {
val reason = pigeonVar_list[0] as String
val reason = pigeonVar_list[0] as String?
val signInHint = pigeonVar_list[1] as String
val cancelButton = pigeonVar_list[2] as String
val signInTitle = pigeonVar_list[3] as String
Expand All @@ -165,16 +283,27 @@ data class AuthStrings(
}

override fun equals(other: Any?): Boolean {
if (other !is AuthStrings) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return MessagesPigeonUtils.deepEquals(toList(), other.toList())
val other = other as AuthStrings
return MessagesPigeonUtils.deepEquals(this.reason, other.reason) &&
MessagesPigeonUtils.deepEquals(this.signInHint, other.signInHint) &&
MessagesPigeonUtils.deepEquals(this.cancelButton, other.cancelButton) &&
MessagesPigeonUtils.deepEquals(this.signInTitle, other.signInTitle)
}

override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.reason)
result = 31 * result + MessagesPigeonUtils.deepHash(this.signInHint)
result = 31 * result + MessagesPigeonUtils.deepHash(this.cancelButton)
result = 31 * result + MessagesPigeonUtils.deepHash(this.signInTitle)
return result
}
}

/**
Expand Down Expand Up @@ -204,16 +333,23 @@ data class AuthResult(
}

override fun equals(other: Any?): Boolean {
if (other !is AuthResult) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return MessagesPigeonUtils.deepEquals(toList(), other.toList())
val other = other as AuthResult
return MessagesPigeonUtils.deepEquals(this.code, other.code) &&
MessagesPigeonUtils.deepEquals(this.errorMessage, other.errorMessage)
}

override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.code)
result = 31 * result + MessagesPigeonUtils.deepHash(this.errorMessage)
return result
}
}

/** Generated class from Pigeon that represents data sent in messages. */
Expand All @@ -240,16 +376,25 @@ data class AuthOptions(
}

override fun equals(other: Any?): Boolean {
if (other !is AuthOptions) {
if (other == null || other.javaClass != javaClass) {
return false
}
if (this === other) {
return true
}
return MessagesPigeonUtils.deepEquals(toList(), other.toList())
val other = other as AuthOptions
return MessagesPigeonUtils.deepEquals(this.biometricOnly, other.biometricOnly) &&
MessagesPigeonUtils.deepEquals(this.sensitiveTransaction, other.sensitiveTransaction) &&
MessagesPigeonUtils.deepEquals(this.sticky, other.sticky)
}

override fun hashCode(): Int = toList().hashCode()
override fun hashCode(): Int {
var result = javaClass.hashCode()
result = 31 * result + MessagesPigeonUtils.deepHash(this.biometricOnly)
result = 31 * result + MessagesPigeonUtils.deepHash(this.sensitiveTransaction)
result = 31 * result + MessagesPigeonUtils.deepHash(this.sticky)
return result
}
}

private open class MessagesPigeonCodec : StandardMessageCodec() {
Expand Down
4 changes: 4 additions & 0 deletions packages/local_auth/local_auth_android/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ dev_dependencies:

flutter:
uses-material-design: true
# FOR TESTING AND INITIAL REVIEW ONLY. DO NOT MERGE.
# See https://github.com/flutter/flutter/blob/master/docs/ecosystem/contributing/README.md#changing-federated-plugins
dependency_overrides:
local_auth_platform_interface: {path: ../../../../packages/local_auth/local_auth_platform_interface}
Loading