mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
first version of the feature, wip still
This commit is contained in:
parent
32d9cb1b39
commit
d8a55a0297
@ -22,17 +22,9 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
|
|
||||||
typealias OnDialogCancelled = suspend () -> Unit
|
typealias OnDialogCancelled = suspend () -> Unit
|
||||||
|
|
||||||
enum class DialogTitle(val value: Int) {
|
|
||||||
TapKey(0),
|
|
||||||
OperationSuccessful(1),
|
|
||||||
OperationFailed(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
class DialogManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) {
|
class DialogManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) {
|
||||||
private val channel =
|
private val channel =
|
||||||
MethodChannel(messenger, "com.yubico.authenticator.channel.dialog")
|
MethodChannel(messenger, "com.yubico.authenticator.channel.dialog")
|
||||||
@ -48,40 +40,13 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showDialog(
|
fun showDialog(cancelled: OnDialogCancelled?) {
|
||||||
dialogTitle: DialogTitle,
|
|
||||||
dialogDescriptionId: Int,
|
|
||||||
cancelled: OnDialogCancelled?
|
|
||||||
) {
|
|
||||||
onCancelled = cancelled
|
onCancelled = cancelled
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
channel.invoke(
|
channel.invoke("show", null)
|
||||||
"show",
|
|
||||||
Json.encodeToString(
|
|
||||||
mapOf(
|
|
||||||
"title" to dialogTitle.value,
|
|
||||||
"description" to dialogDescriptionId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateDialogState(
|
|
||||||
dialogTitle: DialogTitle,
|
|
||||||
dialogDescriptionId: Int? = null,
|
|
||||||
) {
|
|
||||||
channel.invoke(
|
|
||||||
"state",
|
|
||||||
Json.encodeToString(
|
|
||||||
mapOf(
|
|
||||||
"title" to dialogTitle.value,
|
|
||||||
"description" to dialogDescriptionId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun closeDialog() {
|
suspend fun closeDialog() {
|
||||||
channel.invoke("close", NULL)
|
channel.invoke("close", NULL)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package com.yubico.authenticator
|
package com.yubico.authenticator
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
@ -355,13 +354,14 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
try {
|
try {
|
||||||
it.processYubiKey(device)
|
it.processYubiKey(device)
|
||||||
if (device is NfcYubiKeyDevice) {
|
if (device is NfcYubiKeyDevice) {
|
||||||
|
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED)
|
||||||
device.remove {
|
device.remove {
|
||||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY)
|
appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED)
|
||||||
logger.error("Error processing YubiKey in AppContextManager", e)
|
logger.error("Error processing YubiKey in AppContextManager", e)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -441,6 +441,7 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
oathViewModel,
|
oathViewModel,
|
||||||
dialogManager,
|
dialogManager,
|
||||||
appPreferences,
|
appPreferences,
|
||||||
|
appMethodChannel,
|
||||||
nfcActivityListener
|
nfcActivityListener
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Yubico.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.yubico.authenticator.fido
|
|
||||||
|
|
||||||
const val dialogDescriptionFidoIndex = 200
|
|
||||||
|
|
||||||
enum class FidoActionDescription(private val value: Int) {
|
|
||||||
Reset(0),
|
|
||||||
Unlock(1),
|
|
||||||
SetPin(2),
|
|
||||||
DeleteCredential(3),
|
|
||||||
DeleteFingerprint(4),
|
|
||||||
RenameFingerprint(5),
|
|
||||||
RegisterFingerprint(6),
|
|
||||||
EnableEnterpriseAttestation(7),
|
|
||||||
ActionFailure(8);
|
|
||||||
|
|
||||||
val id: Int
|
|
||||||
get() = value + dialogDescriptionFidoIndex
|
|
||||||
}
|
|
@ -17,7 +17,6 @@
|
|||||||
package com.yubico.authenticator.fido
|
package com.yubico.authenticator.fido
|
||||||
|
|
||||||
import com.yubico.authenticator.DialogManager
|
import com.yubico.authenticator.DialogManager
|
||||||
import com.yubico.authenticator.DialogTitle
|
|
||||||
import com.yubico.authenticator.device.DeviceManager
|
import com.yubico.authenticator.device.DeviceManager
|
||||||
import com.yubico.authenticator.fido.data.YubiKitFidoSession
|
import com.yubico.authenticator.fido.data.YubiKitFidoSession
|
||||||
import com.yubico.authenticator.yubikit.withConnection
|
import com.yubico.authenticator.yubikit.withConnection
|
||||||
@ -48,12 +47,9 @@ class FidoConnectionHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> useSession(
|
suspend fun <T> useSession(action: (YubiKitFidoSession) -> T): T {
|
||||||
actionDescription: FidoActionDescription,
|
|
||||||
action: (YubiKitFidoSession) -> T
|
|
||||||
): T {
|
|
||||||
return deviceManager.withKey(
|
return deviceManager.withKey(
|
||||||
onNfc = { useSessionNfc(actionDescription,action) },
|
onNfc = { useSessionNfc(action) },
|
||||||
onUsb = { useSessionUsb(it, action) })
|
onUsb = { useSessionUsb(it, action) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +60,7 @@ class FidoConnectionHelper(
|
|||||||
block(YubiKitFidoSession(it))
|
block(YubiKitFidoSession(it))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> useSessionNfc(
|
suspend fun <T> useSessionNfc(block: (YubiKitFidoSession) -> T): T {
|
||||||
actionDescription: FidoActionDescription,
|
|
||||||
block: (YubiKitFidoSession) -> T
|
|
||||||
): T {
|
|
||||||
try {
|
try {
|
||||||
val result = suspendCoroutine { outer ->
|
val result = suspendCoroutine { outer ->
|
||||||
pendingAction = {
|
pendingAction = {
|
||||||
@ -75,11 +68,8 @@ class FidoConnectionHelper(
|
|||||||
block.invoke(it.value)
|
block.invoke(it.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dialogManager.showDialog(
|
dialogManager.showDialog {
|
||||||
DialogTitle.TapKey,
|
logger.debug("Cancelled dialog")
|
||||||
actionDescription.id
|
|
||||||
) {
|
|
||||||
logger.debug("Cancelled Dialog {}", actionDescription.name)
|
|
||||||
pendingAction?.invoke(Result.failure(CancellationException()))
|
pendingAction?.invoke(Result.failure(CancellationException()))
|
||||||
pendingAction = null
|
pendingAction = null
|
||||||
}
|
}
|
||||||
|
@ -343,7 +343,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unlock(pin: CharArray): String =
|
private suspend fun unlock(pin: CharArray): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.Unlock) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val clientPin =
|
val clientPin =
|
||||||
@ -380,7 +380,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun setPin(pin: CharArray?, newPin: CharArray): String =
|
private suspend fun setPin(pin: CharArray?, newPin: CharArray): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.SetPin) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
try {
|
try {
|
||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
@ -428,7 +428,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteCredential(rpId: String, credentialId: String): String =
|
private suspend fun deleteCredential(rpId: String, credentialId: String): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.DeleteCredential) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
|
|
||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
@ -476,7 +476,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteFingerprint(templateId: String): String =
|
private suspend fun deleteFingerprint(templateId: String): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.DeleteFingerprint) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
|
|
||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
@ -501,7 +501,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun renameFingerprint(templateId: String, name: String): String =
|
private suspend fun renameFingerprint(templateId: String, name: String): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.RenameFingerprint) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
|
|
||||||
val clientPin =
|
val clientPin =
|
||||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||||
@ -531,7 +531,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun registerFingerprint(name: String?): String =
|
private suspend fun registerFingerprint(name: String?): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.RegisterFingerprint) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
state?.cancel()
|
state?.cancel()
|
||||||
state = CommandState()
|
state = CommandState()
|
||||||
val clientPin =
|
val clientPin =
|
||||||
@ -607,7 +607,7 @@ class FidoManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun enableEnterpriseAttestation(): String =
|
private suspend fun enableEnterpriseAttestation(): String =
|
||||||
connectionHelper.useSession(FidoActionDescription.EnableEnterpriseAttestation) { fidoSession ->
|
connectionHelper.useSession { fidoSession ->
|
||||||
try {
|
try {
|
||||||
val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)
|
val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)
|
||||||
val clientPin = ClientPin(fidoSession, uvAuthProtocol)
|
val clientPin = ClientPin(fidoSession, uvAuthProtocol)
|
||||||
|
@ -211,7 +211,7 @@ class FidoResetHelper(
|
|||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
fidoViewModel.updateResetState(FidoResetState.Touch)
|
fidoViewModel.updateResetState(FidoResetState.Touch)
|
||||||
try {
|
try {
|
||||||
connectionHelper.useSessionNfc(FidoActionDescription.Reset) { fidoSession ->
|
connectionHelper.useSessionNfc { fidoSession ->
|
||||||
doReset(fidoSession)
|
doReset(fidoSession)
|
||||||
continuation.resume(Unit)
|
continuation.resume(Unit)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package com.yubico.authenticator.management
|
package com.yubico.authenticator.management
|
||||||
|
|
||||||
import com.yubico.authenticator.DialogManager
|
import com.yubico.authenticator.DialogManager
|
||||||
import com.yubico.authenticator.DialogTitle
|
|
||||||
import com.yubico.authenticator.device.DeviceManager
|
import com.yubico.authenticator.device.DeviceManager
|
||||||
import com.yubico.authenticator.yubikit.withConnection
|
import com.yubico.authenticator.yubikit.withConnection
|
||||||
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||||
@ -63,10 +62,7 @@ class ManagementConnectionHelper(
|
|||||||
block.invoke(it.value)
|
block.invoke(it.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
dialogManager.showDialog(
|
dialogManager.showDialog {
|
||||||
DialogTitle.TapKey,
|
|
||||||
actionDescription.id
|
|
||||||
) {
|
|
||||||
logger.debug("Cancelled Dialog {}", actionDescription.name)
|
logger.debug("Cancelled Dialog {}", actionDescription.name)
|
||||||
action?.invoke(Result.failure(CancellationException()))
|
action?.invoke(Result.failure(CancellationException()))
|
||||||
action = null
|
action = null
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2023 Yubico.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.yubico.authenticator.oath
|
|
||||||
|
|
||||||
const val dialogDescriptionOathIndex = 100
|
|
||||||
|
|
||||||
enum class OathActionDescription(private val value: Int) {
|
|
||||||
Reset(0),
|
|
||||||
Unlock(1),
|
|
||||||
SetPassword(2),
|
|
||||||
UnsetPassword(3),
|
|
||||||
AddAccount(4),
|
|
||||||
RenameAccount(5),
|
|
||||||
DeleteAccount(6),
|
|
||||||
CalculateCode(7),
|
|
||||||
ActionFailure(8),
|
|
||||||
AddMultipleAccounts(9);
|
|
||||||
|
|
||||||
val id: Int
|
|
||||||
get() = value + dialogDescriptionOathIndex
|
|
||||||
}
|
|
@ -58,6 +58,7 @@ import com.yubico.yubikit.core.smartcard.SmartCardProtocol
|
|||||||
import com.yubico.yubikit.core.util.Result
|
import com.yubico.yubikit.core.util.Result
|
||||||
import com.yubico.yubikit.management.Capability
|
import com.yubico.yubikit.management.Capability
|
||||||
import com.yubico.yubikit.oath.CredentialData
|
import com.yubico.yubikit.oath.CredentialData
|
||||||
|
import com.yubico.yubikit.support.DeviceUtil
|
||||||
import io.flutter.plugin.common.BinaryMessenger
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
@ -65,6 +66,7 @@ import kotlinx.serialization.encodeToString
|
|||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
import java.util.TimerTask
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
@ -78,6 +80,7 @@ class OathManager(
|
|||||||
private val oathViewModel: OathViewModel,
|
private val oathViewModel: OathViewModel,
|
||||||
private val dialogManager: DialogManager,
|
private val dialogManager: DialogManager,
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
|
private val appMethodChannel: MainActivity.AppMethodChannel,
|
||||||
private val nfcActivityListener: NfcActivityListener
|
private val nfcActivityListener: NfcActivityListener
|
||||||
) : AppContextManager(), DeviceListener {
|
) : AppContextManager(), DeviceListener {
|
||||||
|
|
||||||
@ -214,24 +217,33 @@ class OathManager(
|
|||||||
coroutineScope.cancel()
|
coroutineScope.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showProcessingTimerTask: TimerTask? = null
|
||||||
|
|
||||||
override suspend fun processYubiKey(device: YubiKeyDevice) {
|
override suspend fun processYubiKey(device: YubiKeyDevice) {
|
||||||
try {
|
try {
|
||||||
|
if (device is NfcYubiKeyDevice) {
|
||||||
|
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED)
|
||||||
|
}
|
||||||
|
|
||||||
device.withConnection<SmartCardConnection, Unit> { connection ->
|
device.withConnection<SmartCardConnection, Unit> { connection ->
|
||||||
val session = getOathSession(connection)
|
val session = getOathSession(connection)
|
||||||
val previousId = oathViewModel.currentSession()?.deviceId
|
val previousId = oathViewModel.currentSession()?.deviceId
|
||||||
if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
|
if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
|
||||||
// Run any pending action
|
// Either run a pending action, or just refresh codes
|
||||||
pendingAction?.let { action ->
|
if (pendingAction != null) {
|
||||||
action.invoke(Result.success(session))
|
pendingAction?.let { action ->
|
||||||
pendingAction = null
|
action.invoke(Result.success(session))
|
||||||
}
|
pendingAction = null
|
||||||
|
}
|
||||||
// Refresh codes
|
} else {
|
||||||
if (!session.isLocked) {
|
// Refresh codes
|
||||||
try {
|
if (!session.isLocked) {
|
||||||
oathViewModel.updateCredentials(calculateOathCodes(session))
|
try {
|
||||||
} catch (error: Exception) {
|
oathViewModel.updateCredentials(calculateOathCodes(session))
|
||||||
logger.error("Failed to refresh codes", error)
|
} catch (error: Exception) {
|
||||||
|
logger.error("Failed to refresh codes", error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -261,6 +273,7 @@ class OathManager(
|
|||||||
} else {
|
} else {
|
||||||
// Awaiting an action for a different device? Fail it and stop processing.
|
// Awaiting an action for a different device? Fail it and stop processing.
|
||||||
action.invoke(Result.failure(IllegalStateException("Wrong deviceId")))
|
action.invoke(Result.failure(IllegalStateException("Wrong deviceId")))
|
||||||
|
showProcessingTimerTask?.cancel()
|
||||||
return@withConnection
|
return@withConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -281,11 +294,14 @@ class OathManager(
|
|||||||
supportedCapabilities = oathCapabilities
|
supportedCapabilities = oathCapabilities
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
showProcessingTimerTask?.cancel()
|
||||||
return@withConnection
|
return@withConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showProcessingTimerTask?.cancel()
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Successfully read Oath session info (and credentials if unlocked) from connected key"
|
"Successfully read Oath session info (and credentials if unlocked) from connected key"
|
||||||
)
|
)
|
||||||
@ -294,10 +310,12 @@ class OathManager(
|
|||||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED)
|
||||||
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
|
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
|
||||||
logger.error("Failed to connect to CCID: ", e)
|
logger.error("Failed to connect to CCID: ", e)
|
||||||
// Clear any cached OATH state
|
// Clear any cached OATH state
|
||||||
oathViewModel.clearSession()
|
oathViewModel.clearSession()
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +326,7 @@ class OathManager(
|
|||||||
val credentialData: CredentialData =
|
val credentialData: CredentialData =
|
||||||
CredentialData.parseUri(URI.create(uri))
|
CredentialData.parseUri(URI.create(uri))
|
||||||
addToAny = true
|
addToAny = true
|
||||||
return useOathSessionNfc(OathActionDescription.AddAccount) { session ->
|
return useOathSessionNfc { session ->
|
||||||
// We need to check for duplicates here since we haven't yet read the credentials
|
// We need to check for duplicates here since we haven't yet read the credentials
|
||||||
if (session.credentials.any { it.id.contentEquals(credentialData.id) }) {
|
if (session.credentials.any { it.id.contentEquals(credentialData.id) }) {
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
@ -338,7 +356,7 @@ class OathManager(
|
|||||||
logger.trace("Adding following accounts: {}", uris)
|
logger.trace("Adding following accounts: {}", uris)
|
||||||
|
|
||||||
addToAny = true
|
addToAny = true
|
||||||
return useOathSession(OathActionDescription.AddMultipleAccounts) { session ->
|
return useOathSession { session ->
|
||||||
var successCount = 0
|
var successCount = 0
|
||||||
for (index in uris.indices) {
|
for (index in uris.indices) {
|
||||||
|
|
||||||
@ -370,7 +388,7 @@ class OathManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun reset(): String =
|
private suspend fun reset(): String =
|
||||||
useOathSession(OathActionDescription.Reset, updateDeviceInfo = true) {
|
useOathSession(updateDeviceInfo = true) {
|
||||||
// note, it is ok to reset locked session
|
// note, it is ok to reset locked session
|
||||||
it.reset()
|
it.reset()
|
||||||
keyManager.removeKey(it.deviceId)
|
keyManager.removeKey(it.deviceId)
|
||||||
@ -382,7 +400,7 @@ class OathManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unlock(password: String, remember: Boolean): String =
|
private suspend fun unlock(password: String, remember: Boolean): String =
|
||||||
useOathSession(OathActionDescription.Unlock) {
|
useOathSession {
|
||||||
val accessKey = it.deriveAccessKey(password.toCharArray())
|
val accessKey = it.deriveAccessKey(password.toCharArray())
|
||||||
keyManager.addKey(it.deviceId, accessKey, remember)
|
keyManager.addKey(it.deviceId, accessKey, remember)
|
||||||
|
|
||||||
@ -390,11 +408,7 @@ class OathManager(
|
|||||||
val remembered = keyManager.isRemembered(it.deviceId)
|
val remembered = keyManager.isRemembered(it.deviceId)
|
||||||
if (unlocked) {
|
if (unlocked) {
|
||||||
oathViewModel.setSessionState(Session(it, remembered))
|
oathViewModel.setSessionState(Session(it, remembered))
|
||||||
|
oathViewModel.updateCredentials(calculateOathCodes(it))
|
||||||
// fetch credentials after unlocking only if the YubiKey is connected over USB
|
|
||||||
if (deviceManager.isUsbKeyConnected()) {
|
|
||||||
oathViewModel.updateCredentials(calculateOathCodes(it))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonSerializer.encodeToString(mapOf("unlocked" to unlocked, "remembered" to remembered))
|
jsonSerializer.encodeToString(mapOf("unlocked" to unlocked, "remembered" to remembered))
|
||||||
@ -405,7 +419,6 @@ class OathManager(
|
|||||||
newPassword: String,
|
newPassword: String,
|
||||||
): String =
|
): String =
|
||||||
useOathSession(
|
useOathSession(
|
||||||
OathActionDescription.SetPassword,
|
|
||||||
unlock = false,
|
unlock = false,
|
||||||
updateDeviceInfo = true
|
updateDeviceInfo = true
|
||||||
) { session ->
|
) { session ->
|
||||||
@ -427,7 +440,7 @@ class OathManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun unsetPassword(currentPassword: String): String =
|
private suspend fun unsetPassword(currentPassword: String): String =
|
||||||
useOathSession(OathActionDescription.UnsetPassword, unlock = false) { session ->
|
useOathSession(unlock = false) { session ->
|
||||||
if (session.isAccessKeySet) {
|
if (session.isAccessKeySet) {
|
||||||
// test current password sent by the user
|
// test current password sent by the user
|
||||||
if (session.unlock(currentPassword.toCharArray())) {
|
if (session.unlock(currentPassword.toCharArray())) {
|
||||||
@ -459,7 +472,7 @@ class OathManager(
|
|||||||
uri: String,
|
uri: String,
|
||||||
requireTouch: Boolean,
|
requireTouch: Boolean,
|
||||||
): String =
|
): String =
|
||||||
useOathSession(OathActionDescription.AddAccount) { session ->
|
useOathSession { session ->
|
||||||
val credentialData: CredentialData =
|
val credentialData: CredentialData =
|
||||||
CredentialData.parseUri(URI.create(uri))
|
CredentialData.parseUri(URI.create(uri))
|
||||||
|
|
||||||
@ -480,21 +493,30 @@ class OathManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun renameAccount(uri: String, name: String, issuer: String?): String =
|
private suspend fun renameAccount(uri: String, name: String, issuer: String?): String =
|
||||||
useOathSession(OathActionDescription.RenameAccount) { session ->
|
useOathSession { session ->
|
||||||
val credential = getOathCredential(session, uri)
|
val credential = getCredential(uri)
|
||||||
val renamedCredential =
|
val renamed = Credential(
|
||||||
Credential(session.renameCredential(credential, name, issuer), session.deviceId)
|
session.renameCredential(credential, name, issuer),
|
||||||
oathViewModel.renameCredential(
|
session.deviceId
|
||||||
Credential(credential, session.deviceId),
|
|
||||||
renamedCredential
|
|
||||||
)
|
)
|
||||||
|
|
||||||
jsonSerializer.encodeToString(renamedCredential)
|
oathViewModel.renameCredential(
|
||||||
|
Credential(credential, session.deviceId),
|
||||||
|
renamed
|
||||||
|
)
|
||||||
|
|
||||||
|
// // simulate long taking op
|
||||||
|
// val renamedCredential = credential
|
||||||
|
// logger.debug("simulate error")
|
||||||
|
// Thread.sleep(3000)
|
||||||
|
// throw IOException("Test exception")
|
||||||
|
|
||||||
|
jsonSerializer.encodeToString(renamed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteAccount(credentialId: String): String =
|
private suspend fun deleteAccount(credentialId: String): String =
|
||||||
useOathSession(OathActionDescription.DeleteAccount) { session ->
|
useOathSession { session ->
|
||||||
val credential = getOathCredential(session, credentialId)
|
val credential = getCredential(credentialId)
|
||||||
session.deleteCredential(credential)
|
session.deleteCredential(credential)
|
||||||
oathViewModel.removeCredential(Credential(credential, session.deviceId))
|
oathViewModel.removeCredential(Credential(credential, session.deviceId))
|
||||||
NULL
|
NULL
|
||||||
@ -546,8 +568,8 @@ class OathManager(
|
|||||||
|
|
||||||
|
|
||||||
private suspend fun calculate(credentialId: String): String =
|
private suspend fun calculate(credentialId: String): String =
|
||||||
useOathSession(OathActionDescription.CalculateCode) { session ->
|
useOathSession { session ->
|
||||||
val credential = getOathCredential(session, credentialId)
|
val credential = getCredential(credentialId)
|
||||||
|
|
||||||
val code = Code.from(calculateCode(session, credential))
|
val code = Code.from(calculateCode(session, credential))
|
||||||
oathViewModel.updateCode(
|
oathViewModel.updateCode(
|
||||||
@ -649,31 +671,43 @@ class OathManager(
|
|||||||
return session.calculateCodes(timestamp).map { (credential, code) ->
|
return session.calculateCodes(timestamp).map { (credential, code) ->
|
||||||
Pair(
|
Pair(
|
||||||
Credential(credential, session.deviceId),
|
Credential(credential, session.deviceId),
|
||||||
Code.from(if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) {
|
Code.from(
|
||||||
session.calculateSteamCode(credential, timestamp)
|
if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) {
|
||||||
} else if (credential.isTouchRequired && bypassTouch) {
|
session.calculateSteamCode(credential, timestamp)
|
||||||
session.calculateCode(credential, timestamp)
|
} else if (credential.isTouchRequired && bypassTouch) {
|
||||||
} else {
|
session.calculateCode(credential, timestamp)
|
||||||
code
|
} else {
|
||||||
})
|
code
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}.toMap()
|
}.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getCredential(id: String): YubiKitCredential {
|
||||||
|
val credential =
|
||||||
|
oathViewModel.credentials.value?.find { it.credential.id == id }?.credential
|
||||||
|
|
||||||
|
if (credential == null || credential.data == null) {
|
||||||
|
logger.debug("Failed to find credential with id: {}", id)
|
||||||
|
throw Exception("Failed to find account")
|
||||||
|
}
|
||||||
|
|
||||||
|
return credential.data
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun <T> useOathSession(
|
private suspend fun <T> useOathSession(
|
||||||
oathActionDescription: OathActionDescription,
|
|
||||||
unlock: Boolean = true,
|
unlock: Boolean = true,
|
||||||
updateDeviceInfo: Boolean = false,
|
updateDeviceInfo: Boolean = false,
|
||||||
action: (YubiKitOathSession) -> T
|
action: (YubiKitOathSession) -> T
|
||||||
): T {
|
): T {
|
||||||
|
|
||||||
// callers can decide whether the session should be unlocked first
|
// callers can decide whether the session should be unlocked first
|
||||||
unlockOnConnect.set(unlock)
|
unlockOnConnect.set(unlock)
|
||||||
// callers can request whether device info should be updated after session operation
|
// callers can request whether device info should be updated after session operation
|
||||||
this@OathManager.updateDeviceInfo.set(updateDeviceInfo)
|
this@OathManager.updateDeviceInfo.set(updateDeviceInfo)
|
||||||
return deviceManager.withKey(
|
return deviceManager.withKey(
|
||||||
onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) },
|
onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) },
|
||||||
onNfc = { useOathSessionNfc(oathActionDescription, action) }
|
onNfc = { useOathSessionNfc(action) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -690,50 +724,42 @@ class OathManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <T> useOathSessionNfc(
|
private suspend fun <T> useOathSessionNfc(
|
||||||
oathActionDescription: OathActionDescription,
|
|
||||||
block: (YubiKitOathSession) -> T
|
block: (YubiKitOathSession) -> T
|
||||||
): T {
|
): T {
|
||||||
try {
|
var firstShow = true
|
||||||
val result = suspendCoroutine { outer ->
|
while (true) { // loop until success or cancel
|
||||||
pendingAction = {
|
try {
|
||||||
outer.resumeWith(runCatching {
|
val result = suspendCoroutine { outer ->
|
||||||
block.invoke(it.value)
|
pendingAction = {
|
||||||
})
|
outer.resumeWith(runCatching {
|
||||||
}
|
val session = it.value // this can throw CancellationException
|
||||||
dialogManager.showDialog(DialogTitle.TapKey, oathActionDescription.id) {
|
nfcActivityListener.onChange(NfcActivityState.PROCESSING_STARTED)
|
||||||
logger.debug("Cancelled Dialog {}", oathActionDescription.name)
|
block.invoke(session)
|
||||||
pendingAction?.invoke(Result.failure(CancellationException()))
|
})
|
||||||
pendingAction = null
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED)
|
|
||||||
dialogManager.updateDialogState(
|
|
||||||
dialogTitle = DialogTitle.OperationSuccessful
|
|
||||||
)
|
|
||||||
// TODO: This delays the closing of the dialog, but also the return value
|
|
||||||
delay(1500)
|
|
||||||
return result
|
|
||||||
} catch (cancelled: CancellationException) {
|
|
||||||
throw cancelled
|
|
||||||
} catch (error: Throwable) {
|
|
||||||
nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED)
|
|
||||||
dialogManager.updateDialogState(
|
|
||||||
dialogTitle = DialogTitle.OperationFailed,
|
|
||||||
dialogDescriptionId = OathActionDescription.ActionFailure.id
|
|
||||||
)
|
|
||||||
// TODO: This delays the closing of the dialog, but also the return value
|
|
||||||
delay(1500)
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
dialogManager.closeDialog()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getOathCredential(session: YubiKitOathSession, credentialId: String) =
|
if (firstShow) {
|
||||||
// we need to use oathSession.calculateCodes() to get proper Credential.touchRequired value
|
dialogManager.showDialog {
|
||||||
session.calculateCodes().map { e -> e.key }.firstOrNull { credential ->
|
logger.debug("Cancelled dialog")
|
||||||
(credential != null) && credential.id.asString() == credentialId
|
pendingAction?.invoke(Result.failure(CancellationException()))
|
||||||
} ?: throw Exception("Failed to find account")
|
pendingAction = null
|
||||||
|
}
|
||||||
|
firstShow = false
|
||||||
|
}
|
||||||
|
// here the coroutine is suspended and waits till pendingAction is
|
||||||
|
// invoked - the pending action result will resume this coroutine
|
||||||
|
}
|
||||||
|
nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED)
|
||||||
|
return result
|
||||||
|
} catch (cancelled: CancellationException) {
|
||||||
|
throw cancelled
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("Exception during action: ", e)
|
||||||
|
nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} // while
|
||||||
|
}
|
||||||
|
|
||||||
override fun onConnected(device: YubiKeyDevice) {
|
override fun onConnected(device: YubiKeyDevice) {
|
||||||
refreshJob?.cancel()
|
refreshJob?.cancel()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2023 Yubico.
|
* Copyright (C) 2023-2024 Yubico.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -35,9 +35,10 @@ data class Credential(
|
|||||||
@SerialName("name")
|
@SerialName("name")
|
||||||
val accountName: String,
|
val accountName: String,
|
||||||
@SerialName("touch_required")
|
@SerialName("touch_required")
|
||||||
val touchRequired: Boolean
|
val touchRequired: Boolean,
|
||||||
|
@kotlinx.serialization.Transient
|
||||||
|
val data: YubiKitCredential? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(credential: YubiKitCredential, deviceId: String) : this(
|
constructor(credential: YubiKitCredential, deviceId: String) : this(
|
||||||
deviceId = deviceId,
|
deviceId = deviceId,
|
||||||
id = credential.id.asString(),
|
id = credential.id.asString(),
|
||||||
@ -48,7 +49,8 @@ data class Credential(
|
|||||||
period = credential.period,
|
period = credential.period,
|
||||||
issuer = credential.issuer,
|
issuer = credential.issuer,
|
||||||
accountName = credential.accountName,
|
accountName = credential.accountName,
|
||||||
touchRequired = credential.isTouchRequired
|
touchRequired = credential.isTouchRequired,
|
||||||
|
data = credential
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean =
|
override fun equals(other: Any?): Boolean =
|
||||||
|
@ -19,6 +19,7 @@ package com.yubico.authenticator.yubikit
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.nfc.NfcAdapter
|
import android.nfc.NfcAdapter
|
||||||
import android.nfc.Tag
|
import android.nfc.Tag
|
||||||
|
import com.yubico.authenticator.yubikit.NfcActivityListener
|
||||||
|
|
||||||
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
|
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
|
||||||
import com.yubico.yubikit.android.transport.nfc.NfcDispatcher
|
import com.yubico.yubikit.android.transport.nfc.NfcDispatcher
|
||||||
@ -51,7 +52,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp
|
|||||||
nfcConfiguration,
|
nfcConfiguration,
|
||||||
TagInterceptor(listener, handler)
|
TagInterceptor(listener, handler)
|
||||||
)
|
)
|
||||||
listener.onChange(NfcActivityState.READY)
|
//listener.onChange(NfcActivityState.READY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disable(activity: Activity) {
|
override fun disable(activity: Activity) {
|
||||||
@ -68,7 +69,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp
|
|||||||
private val logger = LoggerFactory.getLogger(TagInterceptor::class.java)
|
private val logger = LoggerFactory.getLogger(TagInterceptor::class.java)
|
||||||
|
|
||||||
override fun onTag(tag: Tag) {
|
override fun onTag(tag: Tag) {
|
||||||
listener.onChange(NfcActivityState.PROCESSING_STARTED)
|
//listener.onChange(NfcActivityState.PROCESSING_STARTED)
|
||||||
logger.debug("forwarding tag")
|
logger.debug("forwarding tag")
|
||||||
tagHandler.onTag(tag)
|
tagHandler.onTag(tag)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ pluginManagement {
|
|||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
id "com.android.application" version "8.5.0" apply false
|
id "com.android.application" version '8.5.2' apply false
|
||||||
id "org.jetbrains.kotlin.android" version "2.0.0" apply false
|
id "org.jetbrains.kotlin.android" version "2.0.0" apply false
|
||||||
id "org.jetbrains.kotlin.plugin.serialization" version "2.0.0" apply false
|
id "org.jetbrains.kotlin.plugin.serialization" version "2.0.0" apply false
|
||||||
id "com.google.android.gms.oss-licenses-plugin" version "0.10.6" apply false
|
id "com.google.android.gms.oss-licenses-plugin" version "0.10.6" apply false
|
||||||
|
@ -32,17 +32,18 @@ import '../../exception/no_data_exception.dart';
|
|||||||
import '../../exception/platform_exception_decoder.dart';
|
import '../../exception/platform_exception_decoder.dart';
|
||||||
import '../../fido/models.dart';
|
import '../../fido/models.dart';
|
||||||
import '../../fido/state.dart';
|
import '../../fido/state.dart';
|
||||||
|
import '../tap_request_dialog.dart';
|
||||||
|
|
||||||
final _log = Logger('android.fido.state');
|
final _log = Logger('android.fido.state');
|
||||||
|
|
||||||
const _methods = MethodChannel('android.fido.methods');
|
|
||||||
|
|
||||||
final androidFidoStateProvider = AsyncNotifierProvider.autoDispose
|
final androidFidoStateProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<FidoStateNotifier, FidoState, DevicePath>(_FidoStateNotifier.new);
|
.family<FidoStateNotifier, FidoState, DevicePath>(_FidoStateNotifier.new);
|
||||||
|
|
||||||
class _FidoStateNotifier extends FidoStateNotifier {
|
class _FidoStateNotifier extends FidoStateNotifier {
|
||||||
final _events = const EventChannel('android.fido.sessionState');
|
final _events = const EventChannel('android.fido.sessionState');
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
late final _FidoMethodChannelNotifier fido =
|
||||||
|
ref.read(_fidoMethodsProvider.notifier);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<FidoState> build(DevicePath devicePath) async {
|
FutureOr<FidoState> build(DevicePath devicePath) async {
|
||||||
@ -79,7 +80,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
});
|
});
|
||||||
|
|
||||||
controller.onCancel = () async {
|
controller.onCancel = () async {
|
||||||
await _methods.invokeMethod('cancelReset');
|
await fido.cancelReset();
|
||||||
if (!controller.isClosed) {
|
if (!controller.isClosed) {
|
||||||
await subscription.cancel();
|
await subscription.cancel();
|
||||||
}
|
}
|
||||||
@ -87,7 +88,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
|
|
||||||
controller.onListen = () async {
|
controller.onListen = () async {
|
||||||
try {
|
try {
|
||||||
await _methods.invokeMethod('reset');
|
await fido.reset();
|
||||||
await controller.sink.close();
|
await controller.sink.close();
|
||||||
ref.invalidateSelf();
|
ref.invalidateSelf();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -102,13 +103,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<PinResult> setPin(String newPin, {String? oldPin}) async {
|
Future<PinResult> setPin(String newPin, {String? oldPin}) async {
|
||||||
try {
|
try {
|
||||||
final response = jsonDecode(await _methods.invokeMethod(
|
final response = jsonDecode(await fido.setPin(newPin, oldPin: oldPin));
|
||||||
'setPin',
|
|
||||||
{
|
|
||||||
'pin': oldPin,
|
|
||||||
'newPin': newPin,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
if (response['success'] == true) {
|
if (response['success'] == true) {
|
||||||
_log.debug('FIDO PIN set/change successful');
|
_log.debug('FIDO PIN set/change successful');
|
||||||
return PinResult.success();
|
return PinResult.success();
|
||||||
@ -134,10 +129,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<PinResult> unlock(String pin) async {
|
Future<PinResult> unlock(String pin) async {
|
||||||
try {
|
try {
|
||||||
final response = jsonDecode(await _methods.invokeMethod(
|
final response = jsonDecode(await fido.unlock(pin));
|
||||||
'unlock',
|
|
||||||
{'pin': pin},
|
|
||||||
));
|
|
||||||
|
|
||||||
if (response['success'] == true) {
|
if (response['success'] == true) {
|
||||||
_log.debug('FIDO applet unlocked');
|
_log.debug('FIDO applet unlocked');
|
||||||
@ -165,9 +157,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> enableEnterpriseAttestation() async {
|
Future<void> enableEnterpriseAttestation() async {
|
||||||
try {
|
try {
|
||||||
final response = jsonDecode(await _methods.invokeMethod(
|
final response = jsonDecode(await fido.enableEnterpriseAttestation());
|
||||||
'enableEnterpriseAttestation',
|
|
||||||
));
|
|
||||||
|
|
||||||
if (response['success'] == true) {
|
if (response['success'] == true) {
|
||||||
_log.debug('Enterprise attestation enabled');
|
_log.debug('Enterprise attestation enabled');
|
||||||
@ -193,6 +183,8 @@ final androidFingerprintProvider = AsyncNotifierProvider.autoDispose
|
|||||||
class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
||||||
final _events = const EventChannel('android.fido.fingerprints');
|
final _events = const EventChannel('android.fido.fingerprints');
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
late final _FidoMethodChannelNotifier fido =
|
||||||
|
ref.read(_fidoMethodsProvider.notifier);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<List<Fingerprint>> build(DevicePath devicePath) async {
|
FutureOr<List<Fingerprint>> build(DevicePath devicePath) async {
|
||||||
@ -243,15 +235,14 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
|||||||
controller.onCancel = () async {
|
controller.onCancel = () async {
|
||||||
if (!controller.isClosed) {
|
if (!controller.isClosed) {
|
||||||
_log.debug('Cancelling fingerprint registration');
|
_log.debug('Cancelling fingerprint registration');
|
||||||
await _methods.invokeMethod('cancelRegisterFingerprint');
|
await fido.cancelFingerprintRegistration();
|
||||||
await registerFpSub.cancel();
|
await registerFpSub.cancel();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
controller.onListen = () async {
|
controller.onListen = () async {
|
||||||
try {
|
try {
|
||||||
final registerFpResult =
|
final registerFpResult = await fido.registerFingerprint(name);
|
||||||
await _methods.invokeMethod('registerFingerprint', {'name': name});
|
|
||||||
|
|
||||||
_log.debug('Finished registerFingerprint with: $registerFpResult');
|
_log.debug('Finished registerFingerprint with: $registerFpResult');
|
||||||
|
|
||||||
@ -286,13 +277,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
|||||||
Future<Fingerprint> renameFingerprint(
|
Future<Fingerprint> renameFingerprint(
|
||||||
Fingerprint fingerprint, String name) async {
|
Fingerprint fingerprint, String name) async {
|
||||||
try {
|
try {
|
||||||
final renameFingerprintResponse = jsonDecode(await _methods.invokeMethod(
|
final renameFingerprintResponse =
|
||||||
'renameFingerprint',
|
jsonDecode(await fido.renameFingerprint(fingerprint, name));
|
||||||
{
|
|
||||||
'templateId': fingerprint.templateId,
|
|
||||||
'name': name,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
if (renameFingerprintResponse['success'] == true) {
|
if (renameFingerprintResponse['success'] == true) {
|
||||||
_log.debug('FIDO rename fingerprint succeeded');
|
_log.debug('FIDO rename fingerprint succeeded');
|
||||||
@ -316,12 +302,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> deleteFingerprint(Fingerprint fingerprint) async {
|
Future<void> deleteFingerprint(Fingerprint fingerprint) async {
|
||||||
try {
|
try {
|
||||||
final deleteFingerprintResponse = jsonDecode(await _methods.invokeMethod(
|
final deleteFingerprintResponse =
|
||||||
'deleteFingerprint',
|
jsonDecode(await fido.deleteFingerprint(fingerprint));
|
||||||
{
|
|
||||||
'templateId': fingerprint.templateId,
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
if (deleteFingerprintResponse['success'] == true) {
|
if (deleteFingerprintResponse['success'] == true) {
|
||||||
_log.debug('FIDO delete fingerprint succeeded');
|
_log.debug('FIDO delete fingerprint succeeded');
|
||||||
@ -348,6 +330,8 @@ final androidCredentialProvider = AsyncNotifierProvider.autoDispose
|
|||||||
class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
||||||
final _events = const EventChannel('android.fido.credentials');
|
final _events = const EventChannel('android.fido.credentials');
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
late final _FidoMethodChannelNotifier fido =
|
||||||
|
ref.read(_fidoMethodsProvider.notifier);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<List<FidoCredential>> build(DevicePath devicePath) async {
|
FutureOr<List<FidoCredential>> build(DevicePath devicePath) async {
|
||||||
@ -371,13 +355,7 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> deleteCredential(FidoCredential credential) async {
|
Future<void> deleteCredential(FidoCredential credential) async {
|
||||||
try {
|
try {
|
||||||
await _methods.invokeMethod(
|
await fido.deleteCredential(credential);
|
||||||
'deleteCredential',
|
|
||||||
{
|
|
||||||
'rpId': credential.rpId,
|
|
||||||
'credentialId': credential.credentialId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} on PlatformException catch (pe) {
|
} on PlatformException catch (pe) {
|
||||||
var decodedException = pe.decode();
|
var decodedException = pe.decode();
|
||||||
if (decodedException is CancellationException) {
|
if (decodedException is CancellationException) {
|
||||||
@ -388,3 +366,88 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _fidoMethodsProvider = NotifierProvider<_FidoMethodChannelNotifier, void>(
|
||||||
|
() => _FidoMethodChannelNotifier());
|
||||||
|
|
||||||
|
class _FidoMethodChannelNotifier extends MethodChannelNotifier {
|
||||||
|
_FidoMethodChannelNotifier()
|
||||||
|
: super(const MethodChannel('android.fido.methods'));
|
||||||
|
late final l10n = ref.read(l10nProvider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {}
|
||||||
|
|
||||||
|
Future<dynamic> deleteCredential(FidoCredential credential) async =>
|
||||||
|
invoke('deleteCredential', {
|
||||||
|
'callArgs': {
|
||||||
|
'rpId': credential.rpId,
|
||||||
|
'credentialId': credential.credentialId
|
||||||
|
},
|
||||||
|
'operationName': l10n.s_nfc_dialog_fido_delete_credential,
|
||||||
|
'operationProcessing':
|
||||||
|
l10n.s_nfc_dialog_fido_delete_credential_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_fido_delete_credential_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_fido_delete_credential_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> cancelReset() async => invoke('cancelReset');
|
||||||
|
|
||||||
|
Future<dynamic> reset() async => invoke('reset', {
|
||||||
|
'operationName': l10n.s_nfc_dialog_fido_reset,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_fido_reset_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_fido_reset_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_fido_reset_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> setPin(String newPin, {String? oldPin}) async =>
|
||||||
|
invoke('setPin', {
|
||||||
|
'callArgs': {'pin': oldPin, 'newPin': newPin},
|
||||||
|
'operationName': oldPin != null
|
||||||
|
? l10n.s_nfc_dialog_fido_change_pin
|
||||||
|
: l10n.s_nfc_dialog_fido_set_pin,
|
||||||
|
'operationProcessing': oldPin != null
|
||||||
|
? l10n.s_nfc_dialog_fido_change_pin_processing
|
||||||
|
: l10n.s_nfc_dialog_fido_set_pin_processing,
|
||||||
|
'operationSuccess': oldPin != null
|
||||||
|
? l10n.s_nfc_dialog_fido_change_pin_success
|
||||||
|
: l10n.s_nfc_dialog_fido_set_pin_success,
|
||||||
|
'operationFailure': oldPin != null
|
||||||
|
? l10n.s_nfc_dialog_fido_change_pin_failure
|
||||||
|
: l10n.s_nfc_dialog_fido_set_pin_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> unlock(String pin) async => invoke('unlock', {
|
||||||
|
'callArgs': {'pin': pin},
|
||||||
|
'operationName': l10n.s_nfc_dialog_fido_unlock,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_fido_unlock_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_fido_unlock_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_fido_unlock_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> enableEnterpriseAttestation() async =>
|
||||||
|
invoke('enableEnterpriseAttestation');
|
||||||
|
|
||||||
|
Future<dynamic> registerFingerprint(String? name) async =>
|
||||||
|
invoke('registerFingerprint', {
|
||||||
|
'callArgs': {'name': name}
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> cancelFingerprintRegistration() async =>
|
||||||
|
invoke('cancelRegisterFingerprint');
|
||||||
|
|
||||||
|
Future<dynamic> renameFingerprint(
|
||||||
|
Fingerprint fingerprint, String name) async =>
|
||||||
|
invoke('renameFingerprint', {
|
||||||
|
'callArgs': {'templateId': fingerprint.templateId, 'name': name},
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> deleteFingerprint(Fingerprint fingerprint) async =>
|
||||||
|
invoke('deleteFingerprint', {
|
||||||
|
'callArgs': {'templateId': fingerprint.templateId},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -43,6 +43,7 @@ import 'oath/state.dart';
|
|||||||
import 'qr_scanner/qr_scanner_provider.dart';
|
import 'qr_scanner/qr_scanner_provider.dart';
|
||||||
import 'state.dart';
|
import 'state.dart';
|
||||||
import 'tap_request_dialog.dart';
|
import 'tap_request_dialog.dart';
|
||||||
|
import 'views/nfc/nfc_activity_command_listener.dart';
|
||||||
import 'window_state_provider.dart';
|
import 'window_state_provider.dart';
|
||||||
|
|
||||||
Future<Widget> initialize() async {
|
Future<Widget> initialize() async {
|
||||||
@ -106,6 +107,8 @@ Future<Widget> initialize() async {
|
|||||||
child: DismissKeyboard(
|
child: DismissKeyboard(
|
||||||
child: YubicoAuthenticatorApp(page: Consumer(
|
child: YubicoAuthenticatorApp(page: Consumer(
|
||||||
builder: (context, ref, child) {
|
builder: (context, ref, child) {
|
||||||
|
ref.read(nfcActivityCommandListener).startListener(context);
|
||||||
|
|
||||||
Timer.run(() {
|
Timer.run(() {
|
||||||
ref.read(featureFlagProvider.notifier)
|
ref.read(featureFlagProvider.notifier)
|
||||||
// TODO: Load feature flags from file/config?
|
// TODO: Load feature flags from file/config?
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022-2023 Yubico.
|
* Copyright (C) 2022-2024 Yubico.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -35,11 +35,10 @@ import '../../exception/no_data_exception.dart';
|
|||||||
import '../../exception/platform_exception_decoder.dart';
|
import '../../exception/platform_exception_decoder.dart';
|
||||||
import '../../oath/models.dart';
|
import '../../oath/models.dart';
|
||||||
import '../../oath/state.dart';
|
import '../../oath/state.dart';
|
||||||
|
import '../tap_request_dialog.dart';
|
||||||
|
|
||||||
final _log = Logger('android.oath.state');
|
final _log = Logger('android.oath.state');
|
||||||
|
|
||||||
const _methods = MethodChannel('android.oath.methods');
|
|
||||||
|
|
||||||
final androidOathStateProvider = AsyncNotifierProvider.autoDispose
|
final androidOathStateProvider = AsyncNotifierProvider.autoDispose
|
||||||
.family<OathStateNotifier, OathState, DevicePath>(
|
.family<OathStateNotifier, OathState, DevicePath>(
|
||||||
_AndroidOathStateNotifier.new);
|
_AndroidOathStateNotifier.new);
|
||||||
@ -47,6 +46,8 @@ final androidOathStateProvider = AsyncNotifierProvider.autoDispose
|
|||||||
class _AndroidOathStateNotifier extends OathStateNotifier {
|
class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||||
final _events = const EventChannel('android.oath.sessionState');
|
final _events = const EventChannel('android.oath.sessionState');
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
late _OathMethodChannelNotifier oath =
|
||||||
|
ref.watch(_oathMethodsProvider.notifier);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<OathState> build(DevicePath arg) {
|
FutureOr<OathState> build(DevicePath arg) {
|
||||||
@ -75,7 +76,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
// await ref
|
// await ref
|
||||||
// .read(androidAppContextHandler)
|
// .read(androidAppContextHandler)
|
||||||
// .switchAppContext(Application.accounts);
|
// .switchAppContext(Application.accounts);
|
||||||
await _methods.invokeMethod('reset');
|
await oath.reset();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_log.debug('Calling reset failed with exception: $e');
|
_log.debug('Calling reset failed with exception: $e');
|
||||||
}
|
}
|
||||||
@ -84,8 +85,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<(bool, bool)> unlock(String password, {bool remember = false}) async {
|
Future<(bool, bool)> unlock(String password, {bool remember = false}) async {
|
||||||
try {
|
try {
|
||||||
final unlockResponse = jsonDecode(await _methods.invokeMethod(
|
final unlockResponse =
|
||||||
'unlock', {'password': password, 'remember': remember}));
|
jsonDecode(await oath.unlock(password, remember: remember));
|
||||||
_log.debug('applet unlocked');
|
_log.debug('applet unlocked');
|
||||||
|
|
||||||
final unlocked = unlockResponse['unlocked'] == true;
|
final unlocked = unlockResponse['unlocked'] == true;
|
||||||
@ -106,8 +107,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<bool> setPassword(String? current, String password) async {
|
Future<bool> setPassword(String? current, String password) async {
|
||||||
try {
|
try {
|
||||||
await _methods.invokeMethod(
|
await oath.setPassword(current, password);
|
||||||
'setPassword', {'current': current, 'password': password});
|
|
||||||
return true;
|
return true;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_log.debug('Calling set password failed with exception: $e');
|
_log.debug('Calling set password failed with exception: $e');
|
||||||
@ -118,7 +118,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<bool> unsetPassword(String current) async {
|
Future<bool> unsetPassword(String current) async {
|
||||||
try {
|
try {
|
||||||
await _methods.invokeMethod('unsetPassword', {'current': current});
|
await oath.unsetPassword(current);
|
||||||
return true;
|
return true;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_log.debug('Calling unset password failed with exception: $e');
|
_log.debug('Calling unset password failed with exception: $e');
|
||||||
@ -129,7 +129,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> forgetPassword() async {
|
Future<void> forgetPassword() async {
|
||||||
try {
|
try {
|
||||||
await _methods.invokeMethod('forgetPassword');
|
await oath.forgetPassword();
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_log.debug('Calling forgetPassword failed with exception: $e');
|
_log.debug('Calling forgetPassword failed with exception: $e');
|
||||||
}
|
}
|
||||||
@ -161,12 +161,10 @@ Exception _decodeAddAccountException(PlatformException platformException) {
|
|||||||
|
|
||||||
final addCredentialToAnyProvider =
|
final addCredentialToAnyProvider =
|
||||||
Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async {
|
Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async {
|
||||||
|
final oath = ref.watch(_oathMethodsProvider.notifier);
|
||||||
try {
|
try {
|
||||||
String resultString = await _methods.invokeMethod(
|
String resultString = await oath.addAccountToAny(credentialUri,
|
||||||
'addAccountToAny', {
|
requireTouch: requireTouch);
|
||||||
'uri': credentialUri.toString(),
|
|
||||||
'requireTouch': requireTouch
|
|
||||||
});
|
|
||||||
|
|
||||||
var result = jsonDecode(resultString);
|
var result = jsonDecode(resultString);
|
||||||
return OathCredential.fromJson(result['credential']);
|
return OathCredential.fromJson(result['credential']);
|
||||||
@ -177,17 +175,13 @@ final addCredentialToAnyProvider =
|
|||||||
|
|
||||||
final addCredentialsToAnyProvider = Provider(
|
final addCredentialsToAnyProvider = Provider(
|
||||||
(ref) => (List<String> credentialUris, List<bool> touchRequired) async {
|
(ref) => (List<String> credentialUris, List<bool> touchRequired) async {
|
||||||
|
final oath = ref.read(_oathMethodsProvider.notifier);
|
||||||
try {
|
try {
|
||||||
_log.debug(
|
_log.debug(
|
||||||
'Calling android with ${credentialUris.length} credentials to be added');
|
'Calling android with ${credentialUris.length} credentials to be added');
|
||||||
|
|
||||||
String resultString = await _methods.invokeMethod(
|
String resultString =
|
||||||
'addAccountsToAny',
|
await oath.addAccounts(credentialUris, touchRequired);
|
||||||
{
|
|
||||||
'uris': credentialUris,
|
|
||||||
'requireTouch': touchRequired,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
_log.debug('Call result: $resultString');
|
_log.debug('Call result: $resultString');
|
||||||
var result = jsonDecode(resultString);
|
var result = jsonDecode(resultString);
|
||||||
@ -218,6 +212,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
final WithContext _withContext;
|
final WithContext _withContext;
|
||||||
final Ref _ref;
|
final Ref _ref;
|
||||||
late StreamSubscription _sub;
|
late StreamSubscription _sub;
|
||||||
|
late _OathMethodChannelNotifier oath =
|
||||||
|
_ref.read(_oathMethodsProvider.notifier);
|
||||||
|
|
||||||
_AndroidCredentialListNotifier(this._withContext, this._ref) : super() {
|
_AndroidCredentialListNotifier(this._withContext, this._ref) : super() {
|
||||||
_sub = _events.receiveBroadcastStream().listen((event) {
|
_sub = _events.receiveBroadcastStream().listen((event) {
|
||||||
@ -264,8 +260,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final resultJson = await _methods
|
final resultJson = await oath.calculate(credential);
|
||||||
.invokeMethod('calculate', {'credentialId': credential.id});
|
|
||||||
_log.debug('Calculate', resultJson);
|
_log.debug('Calculate', resultJson);
|
||||||
return OathCode.fromJson(jsonDecode(resultJson));
|
return OathCode.fromJson(jsonDecode(resultJson));
|
||||||
} on PlatformException catch (pe) {
|
} on PlatformException catch (pe) {
|
||||||
@ -280,9 +275,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
Future<OathCredential> addAccount(Uri credentialUri,
|
Future<OathCredential> addAccount(Uri credentialUri,
|
||||||
{bool requireTouch = false}) async {
|
{bool requireTouch = false}) async {
|
||||||
try {
|
try {
|
||||||
String resultString = await _methods.invokeMethod('addAccount',
|
String resultString =
|
||||||
{'uri': credentialUri.toString(), 'requireTouch': requireTouch});
|
await oath.addAccount(credentialUri, requireTouch: requireTouch);
|
||||||
|
|
||||||
var result = jsonDecode(resultString);
|
var result = jsonDecode(resultString);
|
||||||
return OathCredential.fromJson(result['credential']);
|
return OathCredential.fromJson(result['credential']);
|
||||||
} on PlatformException catch (pe) {
|
} on PlatformException catch (pe) {
|
||||||
@ -294,9 +288,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
Future<OathCredential> renameAccount(
|
Future<OathCredential> renameAccount(
|
||||||
OathCredential credential, String? issuer, String name) async {
|
OathCredential credential, String? issuer, String name) async {
|
||||||
try {
|
try {
|
||||||
final response = await _methods.invokeMethod('renameAccount',
|
final response = await oath.renameAccount(credential, issuer, name);
|
||||||
{'credentialId': credential.id, 'name': name, 'issuer': issuer});
|
|
||||||
|
|
||||||
_log.debug('Rename response: $response');
|
_log.debug('Rename response: $response');
|
||||||
|
|
||||||
var responseJson = jsonDecode(response);
|
var responseJson = jsonDecode(response);
|
||||||
@ -311,11 +303,149 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
|||||||
@override
|
@override
|
||||||
Future<void> deleteAccount(OathCredential credential) async {
|
Future<void> deleteAccount(OathCredential credential) async {
|
||||||
try {
|
try {
|
||||||
await _methods
|
await oath.deleteAccount(credential);
|
||||||
.invokeMethod('deleteAccount', {'credentialId': credential.id});
|
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
_log.debug('Received exception: $e');
|
var decoded = e.decode();
|
||||||
throw e.decode();
|
if (decoded is CancellationException) {
|
||||||
|
_log.debug('Account delete was cancelled.');
|
||||||
|
} else {
|
||||||
|
_log.debug('Received exception: $e');
|
||||||
|
}
|
||||||
|
|
||||||
|
throw decoded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _oathMethodsProvider = NotifierProvider<_OathMethodChannelNotifier, void>(
|
||||||
|
() => _OathMethodChannelNotifier());
|
||||||
|
|
||||||
|
class _OathMethodChannelNotifier extends MethodChannelNotifier {
|
||||||
|
_OathMethodChannelNotifier()
|
||||||
|
: super(const MethodChannel('android.oath.methods'));
|
||||||
|
late final l10n = ref.read(l10nProvider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {}
|
||||||
|
|
||||||
|
Future<dynamic> reset() async => invoke('reset', {
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_reset,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_reset_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_reset_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_reset_failure
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> unlock(String password, {bool remember = false}) async =>
|
||||||
|
invoke('unlock', {
|
||||||
|
'callArgs': {'password': password, 'remember': remember},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_unlock,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_unlock_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_unlock_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_unlock_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> setPassword(String? current, String password) async =>
|
||||||
|
invoke('setPassword', {
|
||||||
|
'callArgs': {'current': current, 'password': password},
|
||||||
|
'operationName': current != null
|
||||||
|
? l10n.s_nfc_dialog_oath_change_password
|
||||||
|
: l10n.s_nfc_dialog_oath_set_password,
|
||||||
|
'operationProcessing': current != null
|
||||||
|
? l10n.s_nfc_dialog_oath_change_password_processing
|
||||||
|
: l10n.s_nfc_dialog_oath_set_password_processing,
|
||||||
|
'operationSuccess': current != null
|
||||||
|
? l10n.s_nfc_dialog_oath_change_password_success
|
||||||
|
: l10n.s_nfc_dialog_oath_set_password_success,
|
||||||
|
'operationFailure': current != null
|
||||||
|
? l10n.s_nfc_dialog_oath_change_password_failure
|
||||||
|
: l10n.s_nfc_dialog_oath_set_password_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> unsetPassword(String current) async =>
|
||||||
|
invoke('unsetPassword', {
|
||||||
|
'callArgs': {'current': current},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_remove_password,
|
||||||
|
'operationProcessing':
|
||||||
|
l10n.s_nfc_dialog_oath_remove_password_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_remove_password_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_remove_password_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> forgetPassword() async => invoke('forgetPassword');
|
||||||
|
|
||||||
|
Future<dynamic> calculate(OathCredential credential) async =>
|
||||||
|
invoke('calculate', {
|
||||||
|
'callArgs': {'credentialId': credential.id},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_calculate_code,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_calculate_code_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_calculate_code_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_calculate_code_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> addAccount(Uri credentialUri,
|
||||||
|
{bool requireTouch = false}) async =>
|
||||||
|
invoke('addAccount', {
|
||||||
|
'callArgs': {
|
||||||
|
'uri': credentialUri.toString(),
|
||||||
|
'requireTouch': requireTouch
|
||||||
|
},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_add_account,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> addAccounts(
|
||||||
|
List<String> credentialUris, List<bool> touchRequired) async =>
|
||||||
|
invoke('addAccountsToAny', {
|
||||||
|
'callArgs': {
|
||||||
|
'uris': credentialUris,
|
||||||
|
'requireTouch': touchRequired,
|
||||||
|
},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_add_multiple_accounts,
|
||||||
|
'operationProcessing':
|
||||||
|
l10n.s_nfc_dialog_oath_add_multiple_accounts_processing,
|
||||||
|
'operationSuccess':
|
||||||
|
l10n.s_nfc_dialog_oath_add_multiple_accounts_success,
|
||||||
|
'operationFailure':
|
||||||
|
l10n.s_nfc_dialog_oath_add_multiple_accounts_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> addAccountToAny(Uri credentialUri,
|
||||||
|
{bool requireTouch = false}) async =>
|
||||||
|
invoke('addAccountToAny', {
|
||||||
|
'callArgs': {
|
||||||
|
'uri': credentialUri.toString(),
|
||||||
|
'requireTouch': requireTouch
|
||||||
|
},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_add_account,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_add_account_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_add_account_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_add_account_failure,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> deleteAccount(OathCredential credential) async =>
|
||||||
|
invoke('deleteAccount', {
|
||||||
|
'callArgs': {'credentialId': credential.id},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_delete_account,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_delete_account_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_delete_account_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_delete_account_failure,
|
||||||
|
'showSuccess': true
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<dynamic> renameAccount(
|
||||||
|
OathCredential credential, String? issuer, String name) async =>
|
||||||
|
invoke('renameAccount', {
|
||||||
|
'callArgs': {
|
||||||
|
'credentialId': credential.id,
|
||||||
|
'name': name,
|
||||||
|
'issuer': issuer
|
||||||
|
},
|
||||||
|
'operationName': l10n.s_nfc_dialog_oath_rename_account,
|
||||||
|
'operationProcessing': l10n.s_nfc_dialog_oath_rename_account_processing,
|
||||||
|
'operationSuccess': l10n.s_nfc_dialog_oath_rename_account_success,
|
||||||
|
'operationFailure': l10n.s_nfc_dialog_oath_rename_account_failure,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -15,113 +15,132 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../app/models.dart';
|
||||||
import '../app/state.dart';
|
import '../app/state.dart';
|
||||||
import '../app/views/user_interaction.dart';
|
import '../widgets/pulsing.dart';
|
||||||
import 'views/nfc/nfc_activity_widget.dart';
|
import 'state.dart';
|
||||||
|
import 'views/nfc/nfc_activity_overlay.dart';
|
||||||
|
|
||||||
const _channel = MethodChannel('com.yubico.authenticator.channel.dialog');
|
const _channel = MethodChannel('com.yubico.authenticator.channel.dialog');
|
||||||
|
|
||||||
// _DDesc contains id of title resource for the dialog
|
final androidDialogProvider =
|
||||||
enum _DTitle {
|
NotifierProvider<_DialogProvider, int>(_DialogProvider.new);
|
||||||
tapKey,
|
|
||||||
operationSuccessful,
|
|
||||||
operationFailed,
|
|
||||||
invalid;
|
|
||||||
|
|
||||||
static _DTitle fromId(int? id) =>
|
class _DialogProvider extends Notifier<int> {
|
||||||
const {
|
Timer? processingTimer;
|
||||||
0: _DTitle.tapKey,
|
bool explicitAction = false;
|
||||||
1: _DTitle.operationSuccessful,
|
|
||||||
2: _DTitle.operationFailed
|
|
||||||
}[id] ??
|
|
||||||
_DTitle.invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
// _DDesc contains action description in the dialog
|
@override
|
||||||
enum _DDesc {
|
int build() {
|
||||||
// oath descriptions
|
final l10n = ref.read(l10nProvider);
|
||||||
oathResetApplet,
|
ref.listen(androidNfcActivityProvider, (previous, current) {
|
||||||
oathUnlockSession,
|
final notifier = ref.read(nfcActivityCommandNotifier.notifier);
|
||||||
oathSetPassword,
|
|
||||||
oathUnsetPassword,
|
|
||||||
oathAddAccount,
|
|
||||||
oathRenameAccount,
|
|
||||||
oathDeleteAccount,
|
|
||||||
oathCalculateCode,
|
|
||||||
oathActionFailure,
|
|
||||||
oathAddMultipleAccounts,
|
|
||||||
// FIDO descriptions
|
|
||||||
fidoResetApplet,
|
|
||||||
fidoUnlockSession,
|
|
||||||
fidoSetPin,
|
|
||||||
fidoDeleteCredential,
|
|
||||||
fidoDeleteFingerprint,
|
|
||||||
fidoRenameFingerprint,
|
|
||||||
fidoRegisterFingerprint,
|
|
||||||
fidoEnableEnterpriseAttestation,
|
|
||||||
fidoActionFailure,
|
|
||||||
// Others
|
|
||||||
invalid;
|
|
||||||
|
|
||||||
static const int dialogDescriptionOathIndex = 100;
|
if (!explicitAction) {
|
||||||
static const int dialogDescriptionFidoIndex = 200;
|
// setup properties for ad-hoc action
|
||||||
|
ref.read(nfcActivityWidgetNotifier.notifier).setDialogProperties(
|
||||||
|
operationProcessing: l10n.s_nfc_dialog_read_key,
|
||||||
|
operationFailure: l10n.s_nfc_dialog_read_key_failure,
|
||||||
|
showSuccess: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static _DDesc fromId(int? id) =>
|
final properties = ref.read(nfcActivityWidgetNotifier);
|
||||||
const {
|
|
||||||
dialogDescriptionOathIndex + 0: oathResetApplet,
|
|
||||||
dialogDescriptionOathIndex + 1: oathUnlockSession,
|
|
||||||
dialogDescriptionOathIndex + 2: oathSetPassword,
|
|
||||||
dialogDescriptionOathIndex + 3: oathUnsetPassword,
|
|
||||||
dialogDescriptionOathIndex + 4: oathAddAccount,
|
|
||||||
dialogDescriptionOathIndex + 5: oathRenameAccount,
|
|
||||||
dialogDescriptionOathIndex + 6: oathDeleteAccount,
|
|
||||||
dialogDescriptionOathIndex + 7: oathCalculateCode,
|
|
||||||
dialogDescriptionOathIndex + 8: oathActionFailure,
|
|
||||||
dialogDescriptionOathIndex + 9: oathAddMultipleAccounts,
|
|
||||||
dialogDescriptionFidoIndex + 0: fidoResetApplet,
|
|
||||||
dialogDescriptionFidoIndex + 1: fidoUnlockSession,
|
|
||||||
dialogDescriptionFidoIndex + 2: fidoSetPin,
|
|
||||||
dialogDescriptionFidoIndex + 3: fidoDeleteCredential,
|
|
||||||
dialogDescriptionFidoIndex + 4: fidoDeleteFingerprint,
|
|
||||||
dialogDescriptionFidoIndex + 5: fidoRenameFingerprint,
|
|
||||||
dialogDescriptionFidoIndex + 6: fidoRegisterFingerprint,
|
|
||||||
dialogDescriptionFidoIndex + 7: fidoEnableEnterpriseAttestation,
|
|
||||||
dialogDescriptionFidoIndex + 8: fidoActionFailure,
|
|
||||||
}[id] ??
|
|
||||||
_DDesc.invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
final androidDialogProvider = Provider<_DialogProvider>(
|
debugPrint('XXX now it is: $current');
|
||||||
(ref) {
|
switch (current) {
|
||||||
return _DialogProvider(ref.watch(withContextProvider));
|
case NfcActivity.processingStarted:
|
||||||
},
|
processingTimer?.cancel();
|
||||||
);
|
|
||||||
|
|
||||||
class _DialogProvider {
|
debugPrint('XXX explicit action: $explicitAction');
|
||||||
final WithContext _withContext;
|
final timeout = explicitAction ? 300 : 200;
|
||||||
final Widget _icon = const NfcActivityWidget(width: 64, height: 64);
|
|
||||||
UserInteractionController? _controller;
|
processingTimer = Timer(Duration(milliseconds: timeout), () {
|
||||||
|
if (!explicitAction) {
|
||||||
|
// show the widget
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: NfcActivityWidgetActionShowWidget(
|
||||||
|
child: _NfcActivityWidgetView(
|
||||||
|
title: properties.operationProcessing,
|
||||||
|
subtitle: '',
|
||||||
|
inProgress: true,
|
||||||
|
))));
|
||||||
|
} else {
|
||||||
|
// the processing view will only be shown if the timer is still active
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: NfcActivityWidgetActionSetWidgetData(
|
||||||
|
child: _NfcActivityWidgetView(
|
||||||
|
title: properties.operationProcessing,
|
||||||
|
subtitle: l10n.s_nfc_dialog_hold_key,
|
||||||
|
inProgress: true,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case NfcActivity.processingFinished:
|
||||||
|
explicitAction = false; // next action might not be explicit
|
||||||
|
processingTimer?.cancel();
|
||||||
|
if (properties.showSuccess ?? false) {
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: NfcActivityWidgetActionSetWidgetData(
|
||||||
|
child: NfcActivityClosingCountdownWidgetView(
|
||||||
|
closeInSec: 5,
|
||||||
|
child: _NfcActivityWidgetView(
|
||||||
|
title: properties.operationSuccess,
|
||||||
|
subtitle: l10n.s_nfc_dialog_remove_key,
|
||||||
|
inProgress: false,
|
||||||
|
),
|
||||||
|
))));
|
||||||
|
} else {
|
||||||
|
// directly hide
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NfcActivity.processingInterrupted:
|
||||||
|
explicitAction = false; // next action might not be explicit
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: NfcActivityWidgetActionSetWidgetData(
|
||||||
|
child: _NfcActivityWidgetView(
|
||||||
|
title: properties.operationFailure,
|
||||||
|
inProgress: false,
|
||||||
|
))));
|
||||||
|
break;
|
||||||
|
case NfcActivity.notActive:
|
||||||
|
debugPrint('Received not handled notActive');
|
||||||
|
break;
|
||||||
|
case NfcActivity.ready:
|
||||||
|
debugPrint('Received not handled ready');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
_DialogProvider(this._withContext) {
|
|
||||||
_channel.setMethodCallHandler((call) async {
|
_channel.setMethodCallHandler((call) async {
|
||||||
final args = jsonDecode(call.arguments);
|
final notifier = ref.read(nfcActivityCommandNotifier.notifier);
|
||||||
|
final properties = ref.read(nfcActivityWidgetNotifier);
|
||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'close':
|
|
||||||
_closeDialog();
|
|
||||||
break;
|
|
||||||
case 'show':
|
case 'show':
|
||||||
await _showDialog(args['title'], args['description']);
|
explicitAction = true;
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: NfcActivityWidgetActionShowWidget(
|
||||||
|
child: _NfcActivityWidgetView(
|
||||||
|
title: l10n.s_nfc_dialog_tap_for(
|
||||||
|
properties.operationName ?? '[OPERATION NAME MISSING]'),
|
||||||
|
subtitle: '',
|
||||||
|
inProgress: false,
|
||||||
|
))));
|
||||||
break;
|
break;
|
||||||
case 'state':
|
|
||||||
await _updateDialogState(args['title'], args['description']);
|
case 'close':
|
||||||
|
notifier.update(NfcActivityWidgetCommand(
|
||||||
|
action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0)));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw PlatformException(
|
throw PlatformException(
|
||||||
code: 'NotImplemented',
|
code: 'NotImplemented',
|
||||||
@ -129,71 +148,112 @@ class _DialogProvider {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _closeDialog() {
|
void cancelDialog() async {
|
||||||
_controller?.close();
|
debugPrint('Cancelled dialog');
|
||||||
_controller = null;
|
explicitAction = false;
|
||||||
|
await _channel.invokeMethod('cancel');
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getTitle(BuildContext context, int? titleId) {
|
Future<void> waitForDialogClosed() async {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final completer = Completer();
|
||||||
return switch (_DTitle.fromId(titleId)) {
|
|
||||||
_DTitle.tapKey => l10n.l_nfc_dialog_tap_key,
|
|
||||||
_DTitle.operationSuccessful => l10n.s_nfc_dialog_operation_success,
|
|
||||||
_DTitle.operationFailed => l10n.s_nfc_dialog_operation_failed,
|
|
||||||
_ => ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getDialogDescription(BuildContext context, int? descriptionId) {
|
Timer.periodic(
|
||||||
final l10n = AppLocalizations.of(context)!;
|
const Duration(milliseconds: 200),
|
||||||
return switch (_DDesc.fromId(descriptionId)) {
|
(timer) {
|
||||||
_DDesc.oathResetApplet => l10n.s_nfc_dialog_oath_reset,
|
if (!ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) {
|
||||||
_DDesc.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock,
|
timer.cancel();
|
||||||
_DDesc.oathSetPassword => l10n.s_nfc_dialog_oath_set_password,
|
completer.complete();
|
||||||
_DDesc.oathUnsetPassword => l10n.s_nfc_dialog_oath_unset_password,
|
}
|
||||||
_DDesc.oathAddAccount => l10n.s_nfc_dialog_oath_add_account,
|
},
|
||||||
_DDesc.oathRenameAccount => l10n.s_nfc_dialog_oath_rename_account,
|
);
|
||||||
_DDesc.oathDeleteAccount => l10n.s_nfc_dialog_oath_delete_account,
|
|
||||||
_DDesc.oathCalculateCode => l10n.s_nfc_dialog_oath_calculate_code,
|
|
||||||
_DDesc.oathActionFailure => l10n.s_nfc_dialog_oath_failure,
|
|
||||||
_DDesc.oathAddMultipleAccounts =>
|
|
||||||
l10n.s_nfc_dialog_oath_add_multiple_accounts,
|
|
||||||
_DDesc.fidoResetApplet => l10n.s_nfc_dialog_fido_reset,
|
|
||||||
_DDesc.fidoUnlockSession => l10n.s_nfc_dialog_fido_unlock,
|
|
||||||
_DDesc.fidoSetPin => l10n.l_nfc_dialog_fido_set_pin,
|
|
||||||
_DDesc.fidoDeleteCredential => l10n.s_nfc_dialog_fido_delete_credential,
|
|
||||||
_DDesc.fidoDeleteFingerprint => l10n.s_nfc_dialog_fido_delete_fingerprint,
|
|
||||||
_DDesc.fidoRenameFingerprint => l10n.s_nfc_dialog_fido_rename_fingerprint,
|
|
||||||
_DDesc.fidoActionFailure => l10n.s_nfc_dialog_fido_failure,
|
|
||||||
_ => ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateDialogState(int? title, int? description) async {
|
await completer.future;
|
||||||
await _withContext((context) async {
|
}
|
||||||
_controller?.updateContent(
|
}
|
||||||
title: _getTitle(context, title),
|
|
||||||
description: _getDialogDescription(context, description),
|
class _NfcActivityWidgetView extends StatelessWidget {
|
||||||
icon: (_DDesc.fromId(description) != _DDesc.oathActionFailure)
|
final bool inProgress;
|
||||||
? _icon
|
final String? title;
|
||||||
: const Icon(Icons.warning_amber_rounded, size: 64),
|
final String? subtitle;
|
||||||
);
|
|
||||||
});
|
const _NfcActivityWidgetView(
|
||||||
}
|
{required this.title, this.subtitle, this.inProgress = false});
|
||||||
|
|
||||||
Future<void> _showDialog(int title, int description) async {
|
@override
|
||||||
_controller = await _withContext((context) async {
|
Widget build(BuildContext context) {
|
||||||
return promptUserInteraction(
|
return Padding(
|
||||||
context,
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
title: _getTitle(context, title),
|
child: Column(
|
||||||
description: _getDialogDescription(context, description),
|
children: [
|
||||||
icon: _icon,
|
Text(title ?? 'Missing title',
|
||||||
onCancel: () {
|
textAlign: TextAlign.center,
|
||||||
_channel.invokeMethod('cancel');
|
style: Theme.of(context).textTheme.titleLarge),
|
||||||
},
|
const SizedBox(height: 8),
|
||||||
);
|
if (subtitle != null)
|
||||||
});
|
Text(subtitle!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
inProgress
|
||||||
|
? const Pulsing(child: Icon(Symbols.contactless, size: 64))
|
||||||
|
: const Icon(Symbols.contactless, size: 64),
|
||||||
|
const SizedBox(height: 24)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MethodChannelHelper {
|
||||||
|
final ProviderRef _ref;
|
||||||
|
final MethodChannel _channel;
|
||||||
|
|
||||||
|
const MethodChannelHelper(this._ref, this._channel);
|
||||||
|
|
||||||
|
Future<dynamic> invoke(String method,
|
||||||
|
{String? operationName,
|
||||||
|
String? operationSuccess,
|
||||||
|
String? operationProcessing,
|
||||||
|
String? operationFailure,
|
||||||
|
bool? showSuccess,
|
||||||
|
Map<String, dynamic> arguments = const {}}) async {
|
||||||
|
final notifier = _ref.read(nfcActivityWidgetNotifier.notifier);
|
||||||
|
notifier.setDialogProperties(
|
||||||
|
operationName: operationName,
|
||||||
|
operationProcessing: operationProcessing,
|
||||||
|
operationSuccess: operationSuccess,
|
||||||
|
operationFailure: operationFailure,
|
||||||
|
showSuccess: showSuccess);
|
||||||
|
|
||||||
|
final result = await _channel.invokeMethod(method, arguments);
|
||||||
|
await _ref.read(androidDialogProvider.notifier).waitForDialogClosed();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MethodChannelNotifier extends Notifier<void> {
|
||||||
|
final MethodChannel _channel;
|
||||||
|
|
||||||
|
MethodChannelNotifier(this._channel);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void build() {}
|
||||||
|
|
||||||
|
Future<dynamic> invoke(String name,
|
||||||
|
[Map<String, dynamic> params = const {}]) async {
|
||||||
|
final notifier = ref.read(nfcActivityWidgetNotifier.notifier);
|
||||||
|
notifier.setDialogProperties(
|
||||||
|
operationName: params['operationName'],
|
||||||
|
operationProcessing: params['operationProcessing'],
|
||||||
|
operationSuccess: params['operationSuccess'],
|
||||||
|
operationFailure: params['operationFailure'],
|
||||||
|
showSuccess: params['showSuccess']);
|
||||||
|
|
||||||
|
final result = await _channel.invokeMethod(name, params['callArgs']);
|
||||||
|
await ref.read(androidDialogProvider.notifier).waitForDialogClosed();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
84
lib/android/views/nfc/nfc_activity_command_listener.dart
Normal file
84
lib/android/views/nfc/nfc_activity_command_listener.dart
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Yubico.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../../../app/models.dart';
|
||||||
|
import '../../tap_request_dialog.dart';
|
||||||
|
import 'nfc_activity_overlay.dart';
|
||||||
|
|
||||||
|
final nfcActivityCommandListener = Provider<_NfcActivityCommandListener>(
|
||||||
|
(ref) => _NfcActivityCommandListener(ref));
|
||||||
|
|
||||||
|
class _NfcActivityCommandListener {
|
||||||
|
final ProviderRef _ref;
|
||||||
|
ProviderSubscription<NfcActivityWidgetAction>? listener;
|
||||||
|
|
||||||
|
_NfcActivityCommandListener(this._ref);
|
||||||
|
|
||||||
|
void startListener(BuildContext context) {
|
||||||
|
debugPrint('XXX Started listener');
|
||||||
|
listener?.close();
|
||||||
|
listener = _ref.listen(nfcActivityCommandNotifier.select((c) => c.action),
|
||||||
|
(previous, action) {
|
||||||
|
debugPrint(
|
||||||
|
'XXX Change in command for Overlay: $previous -> $action in context: $context');
|
||||||
|
switch (action) {
|
||||||
|
case (NfcActivityWidgetActionShowWidget a):
|
||||||
|
_show(context, a.child);
|
||||||
|
break;
|
||||||
|
case (NfcActivityWidgetActionSetWidgetData a):
|
||||||
|
_ref.read(nfcActivityWidgetNotifier.notifier).update(a.child);
|
||||||
|
break;
|
||||||
|
case (NfcActivityWidgetActionHideWidget _):
|
||||||
|
_hide(context);
|
||||||
|
break;
|
||||||
|
case (NfcActivityWidgetActionCancelWidget _):
|
||||||
|
_ref.read(androidDialogProvider.notifier).cancelDialog();
|
||||||
|
_hide(context);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _show(BuildContext context, Widget child) async {
|
||||||
|
final widgetNotifier = _ref.read(nfcActivityWidgetNotifier.notifier);
|
||||||
|
widgetNotifier.update(child);
|
||||||
|
if (!_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) {
|
||||||
|
widgetNotifier.setShowing(true);
|
||||||
|
final result = await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return const NfcBottomSheet();
|
||||||
|
});
|
||||||
|
|
||||||
|
debugPrint('XXX result is: $result');
|
||||||
|
if (result == null) {
|
||||||
|
// the modal sheet was cancelled by Back button, close button or dismiss
|
||||||
|
_ref.read(androidDialogProvider.notifier).cancelDialog();
|
||||||
|
}
|
||||||
|
widgetNotifier.setShowing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _hide(BuildContext context) {
|
||||||
|
if (_ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) {
|
||||||
|
Navigator.of(context).pop('AFTER OP');
|
||||||
|
_ref.read(nfcActivityWidgetNotifier.notifier).setShowing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
172
lib/android/views/nfc/nfc_activity_overlay.dart
Normal file
172
lib/android/views/nfc/nfc_activity_overlay.dart
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
import '../../../app/models.dart';
|
||||||
|
import '../../state.dart';
|
||||||
|
|
||||||
|
final nfcActivityCommandNotifier = NotifierProvider<
|
||||||
|
_NfcActivityWidgetCommandNotifier,
|
||||||
|
NfcActivityWidgetCommand>(_NfcActivityWidgetCommandNotifier.new);
|
||||||
|
|
||||||
|
class _NfcActivityWidgetCommandNotifier
|
||||||
|
extends Notifier<NfcActivityWidgetCommand> {
|
||||||
|
@override
|
||||||
|
NfcActivityWidgetCommand build() {
|
||||||
|
return NfcActivityWidgetCommand(action: const NfcActivityWidgetAction());
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(NfcActivityWidgetCommand command) {
|
||||||
|
state = command;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final nfcActivityWidgetNotifier =
|
||||||
|
NotifierProvider<_NfcActivityWidgetNotifier, NfcActivityWidgetState>(
|
||||||
|
_NfcActivityWidgetNotifier.new);
|
||||||
|
|
||||||
|
class NfcActivityClosingCountdownWidgetView extends ConsumerStatefulWidget {
|
||||||
|
final int closeInSec;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const NfcActivityClosingCountdownWidgetView(
|
||||||
|
{super.key, required this.child, this.closeInSec = 3});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<NfcActivityClosingCountdownWidgetView> createState() =>
|
||||||
|
_NfcActivityClosingCountdownWidgetViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NfcActivityClosingCountdownWidgetViewState
|
||||||
|
extends ConsumerState<NfcActivityClosingCountdownWidgetView> {
|
||||||
|
late int counter;
|
||||||
|
late Timer? timer;
|
||||||
|
bool shouldHide = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ref.listen(androidNfcActivityProvider, (previous, current) {
|
||||||
|
if (current == NfcActivity.ready) {
|
||||||
|
timer?.cancel();
|
||||||
|
hideNow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.loose,
|
||||||
|
children: [
|
||||||
|
Center(child: widget.child),
|
||||||
|
Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
|
child: counter > 0
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text('Closing in $counter'),
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
counter = widget.closeInSec;
|
||||||
|
timer = Timer(const Duration(seconds: 1), onTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onTimer() async {
|
||||||
|
timer?.cancel();
|
||||||
|
setState(() {
|
||||||
|
counter--;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (counter > 0) {
|
||||||
|
timer = Timer(const Duration(seconds: 1), onTimer);
|
||||||
|
} else {
|
||||||
|
hideNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideNow() {
|
||||||
|
debugPrint('XXX closing because have to!');
|
||||||
|
ref.read(nfcActivityCommandNotifier.notifier).update(
|
||||||
|
NfcActivityWidgetCommand(
|
||||||
|
action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NfcActivityWidgetNotifier extends Notifier<NfcActivityWidgetState> {
|
||||||
|
@override
|
||||||
|
NfcActivityWidgetState build() {
|
||||||
|
return NfcActivityWidgetState(isShowing: false, child: const SizedBox());
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Widget child) {
|
||||||
|
state = state.copyWith(child: child);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShowing(bool value) {
|
||||||
|
state = state.copyWith(isShowing: value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDialogProperties(
|
||||||
|
{String? operationName,
|
||||||
|
String? operationProcessing,
|
||||||
|
String? operationSuccess,
|
||||||
|
String? operationFailure,
|
||||||
|
bool? showSuccess}) {
|
||||||
|
state = state.copyWith(
|
||||||
|
operationName: operationName,
|
||||||
|
operationProcessing: operationProcessing,
|
||||||
|
operationSuccess: operationSuccess,
|
||||||
|
operationFailure: operationFailure,
|
||||||
|
showSuccess: showSuccess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcBottomSheet extends ConsumerWidget {
|
||||||
|
const NfcBottomSheet({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final widget = ref.watch(nfcActivityWidgetNotifier.select((s) => s.child));
|
||||||
|
final showCloseButton = ref.watch(
|
||||||
|
nfcActivityWidgetNotifier.select((s) => s.showCloseButton ?? false));
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (showCloseButton) const SizedBox(height: 8),
|
||||||
|
if (showCloseButton)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: const Icon(Symbols.close, fill: 1, size: 24))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (showCloseButton) const SizedBox(height: 16),
|
||||||
|
if (!showCloseButton) const SizedBox(height: 48),
|
||||||
|
widget,
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -170,3 +170,46 @@ class _ColorConverter implements JsonConverter<Color?, int?> {
|
|||||||
@override
|
@override
|
||||||
int? toJson(Color? object) => object?.value;
|
int? toJson(Color? object) => object?.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NfcActivityWidgetAction {
|
||||||
|
const NfcActivityWidgetAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcActivityWidgetActionShowWidget extends NfcActivityWidgetAction {
|
||||||
|
final Widget child;
|
||||||
|
const NfcActivityWidgetActionShowWidget({required this.child});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcActivityWidgetActionHideWidget extends NfcActivityWidgetAction {
|
||||||
|
final int timeoutMs;
|
||||||
|
const NfcActivityWidgetActionHideWidget({required this.timeoutMs});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcActivityWidgetActionCancelWidget extends NfcActivityWidgetAction {
|
||||||
|
const NfcActivityWidgetActionCancelWidget();
|
||||||
|
}
|
||||||
|
|
||||||
|
class NfcActivityWidgetActionSetWidgetData extends NfcActivityWidgetAction {
|
||||||
|
final Widget child;
|
||||||
|
const NfcActivityWidgetActionSetWidgetData({required this.child});
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class NfcActivityWidgetState with _$NfcActivityWidgetState {
|
||||||
|
factory NfcActivityWidgetState(
|
||||||
|
{required bool isShowing,
|
||||||
|
required Widget child,
|
||||||
|
bool? showCloseButton,
|
||||||
|
bool? showSuccess,
|
||||||
|
String? operationName,
|
||||||
|
String? operationProcessing,
|
||||||
|
String? operationSuccess,
|
||||||
|
String? operationFailure}) = _NfcActivityWidgetState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class NfcActivityWidgetCommand with _$NfcActivityWidgetCommand {
|
||||||
|
factory NfcActivityWidgetCommand({
|
||||||
|
@Default(NfcActivityWidgetAction()) NfcActivityWidgetAction action,
|
||||||
|
}) = _NfcActivityWidgetCommand;
|
||||||
|
}
|
||||||
|
@ -1346,3 +1346,410 @@ abstract class _KeyCustomization implements KeyCustomization {
|
|||||||
_$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith =>
|
_$$KeyCustomizationImplCopyWith<_$KeyCustomizationImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$NfcActivityWidgetState {
|
||||||
|
bool get isShowing => throw _privateConstructorUsedError;
|
||||||
|
Widget get child => throw _privateConstructorUsedError;
|
||||||
|
bool? get showCloseButton => throw _privateConstructorUsedError;
|
||||||
|
bool? get showSuccess => throw _privateConstructorUsedError;
|
||||||
|
String? get operationName => throw _privateConstructorUsedError;
|
||||||
|
String? get operationProcessing => throw _privateConstructorUsedError;
|
||||||
|
String? get operationSuccess => throw _privateConstructorUsedError;
|
||||||
|
String? get operationFailure => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$NfcActivityWidgetStateCopyWith<NfcActivityWidgetState> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $NfcActivityWidgetStateCopyWith<$Res> {
|
||||||
|
factory $NfcActivityWidgetStateCopyWith(NfcActivityWidgetState value,
|
||||||
|
$Res Function(NfcActivityWidgetState) then) =
|
||||||
|
_$NfcActivityWidgetStateCopyWithImpl<$Res, NfcActivityWidgetState>;
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{bool isShowing,
|
||||||
|
Widget child,
|
||||||
|
bool? showCloseButton,
|
||||||
|
bool? showSuccess,
|
||||||
|
String? operationName,
|
||||||
|
String? operationProcessing,
|
||||||
|
String? operationSuccess,
|
||||||
|
String? operationFailure});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$NfcActivityWidgetStateCopyWithImpl<$Res,
|
||||||
|
$Val extends NfcActivityWidgetState>
|
||||||
|
implements $NfcActivityWidgetStateCopyWith<$Res> {
|
||||||
|
_$NfcActivityWidgetStateCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? isShowing = null,
|
||||||
|
Object? child = null,
|
||||||
|
Object? showCloseButton = freezed,
|
||||||
|
Object? showSuccess = freezed,
|
||||||
|
Object? operationName = freezed,
|
||||||
|
Object? operationProcessing = freezed,
|
||||||
|
Object? operationSuccess = freezed,
|
||||||
|
Object? operationFailure = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
isShowing: null == isShowing
|
||||||
|
? _value.isShowing
|
||||||
|
: isShowing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
child: null == child
|
||||||
|
? _value.child
|
||||||
|
: child // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Widget,
|
||||||
|
showCloseButton: freezed == showCloseButton
|
||||||
|
? _value.showCloseButton
|
||||||
|
: showCloseButton // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
showSuccess: freezed == showSuccess
|
||||||
|
? _value.showSuccess
|
||||||
|
: showSuccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
operationName: freezed == operationName
|
||||||
|
? _value.operationName
|
||||||
|
: operationName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationProcessing: freezed == operationProcessing
|
||||||
|
? _value.operationProcessing
|
||||||
|
: operationProcessing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationSuccess: freezed == operationSuccess
|
||||||
|
? _value.operationSuccess
|
||||||
|
: operationSuccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationFailure: freezed == operationFailure
|
||||||
|
? _value.operationFailure
|
||||||
|
: operationFailure // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$NfcActivityWidgetStateImplCopyWith<$Res>
|
||||||
|
implements $NfcActivityWidgetStateCopyWith<$Res> {
|
||||||
|
factory _$$NfcActivityWidgetStateImplCopyWith(
|
||||||
|
_$NfcActivityWidgetStateImpl value,
|
||||||
|
$Res Function(_$NfcActivityWidgetStateImpl) then) =
|
||||||
|
__$$NfcActivityWidgetStateImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call(
|
||||||
|
{bool isShowing,
|
||||||
|
Widget child,
|
||||||
|
bool? showCloseButton,
|
||||||
|
bool? showSuccess,
|
||||||
|
String? operationName,
|
||||||
|
String? operationProcessing,
|
||||||
|
String? operationSuccess,
|
||||||
|
String? operationFailure});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$NfcActivityWidgetStateImplCopyWithImpl<$Res>
|
||||||
|
extends _$NfcActivityWidgetStateCopyWithImpl<$Res,
|
||||||
|
_$NfcActivityWidgetStateImpl>
|
||||||
|
implements _$$NfcActivityWidgetStateImplCopyWith<$Res> {
|
||||||
|
__$$NfcActivityWidgetStateImplCopyWithImpl(
|
||||||
|
_$NfcActivityWidgetStateImpl _value,
|
||||||
|
$Res Function(_$NfcActivityWidgetStateImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? isShowing = null,
|
||||||
|
Object? child = null,
|
||||||
|
Object? showCloseButton = freezed,
|
||||||
|
Object? showSuccess = freezed,
|
||||||
|
Object? operationName = freezed,
|
||||||
|
Object? operationProcessing = freezed,
|
||||||
|
Object? operationSuccess = freezed,
|
||||||
|
Object? operationFailure = freezed,
|
||||||
|
}) {
|
||||||
|
return _then(_$NfcActivityWidgetStateImpl(
|
||||||
|
isShowing: null == isShowing
|
||||||
|
? _value.isShowing
|
||||||
|
: isShowing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
child: null == child
|
||||||
|
? _value.child
|
||||||
|
: child // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Widget,
|
||||||
|
showCloseButton: freezed == showCloseButton
|
||||||
|
? _value.showCloseButton
|
||||||
|
: showCloseButton // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
showSuccess: freezed == showSuccess
|
||||||
|
? _value.showSuccess
|
||||||
|
: showSuccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool?,
|
||||||
|
operationName: freezed == operationName
|
||||||
|
? _value.operationName
|
||||||
|
: operationName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationProcessing: freezed == operationProcessing
|
||||||
|
? _value.operationProcessing
|
||||||
|
: operationProcessing // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationSuccess: freezed == operationSuccess
|
||||||
|
? _value.operationSuccess
|
||||||
|
: operationSuccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
operationFailure: freezed == operationFailure
|
||||||
|
? _value.operationFailure
|
||||||
|
: operationFailure // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$NfcActivityWidgetStateImpl implements _NfcActivityWidgetState {
|
||||||
|
_$NfcActivityWidgetStateImpl(
|
||||||
|
{required this.isShowing,
|
||||||
|
required this.child,
|
||||||
|
this.showCloseButton,
|
||||||
|
this.showSuccess,
|
||||||
|
this.operationName,
|
||||||
|
this.operationProcessing,
|
||||||
|
this.operationSuccess,
|
||||||
|
this.operationFailure});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final bool isShowing;
|
||||||
|
@override
|
||||||
|
final Widget child;
|
||||||
|
@override
|
||||||
|
final bool? showCloseButton;
|
||||||
|
@override
|
||||||
|
final bool? showSuccess;
|
||||||
|
@override
|
||||||
|
final String? operationName;
|
||||||
|
@override
|
||||||
|
final String? operationProcessing;
|
||||||
|
@override
|
||||||
|
final String? operationSuccess;
|
||||||
|
@override
|
||||||
|
final String? operationFailure;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NfcActivityWidgetState(isShowing: $isShowing, child: $child, showCloseButton: $showCloseButton, showSuccess: $showSuccess, operationName: $operationName, operationProcessing: $operationProcessing, operationSuccess: $operationSuccess, operationFailure: $operationFailure)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$NfcActivityWidgetStateImpl &&
|
||||||
|
(identical(other.isShowing, isShowing) ||
|
||||||
|
other.isShowing == isShowing) &&
|
||||||
|
(identical(other.child, child) || other.child == child) &&
|
||||||
|
(identical(other.showCloseButton, showCloseButton) ||
|
||||||
|
other.showCloseButton == showCloseButton) &&
|
||||||
|
(identical(other.showSuccess, showSuccess) ||
|
||||||
|
other.showSuccess == showSuccess) &&
|
||||||
|
(identical(other.operationName, operationName) ||
|
||||||
|
other.operationName == operationName) &&
|
||||||
|
(identical(other.operationProcessing, operationProcessing) ||
|
||||||
|
other.operationProcessing == operationProcessing) &&
|
||||||
|
(identical(other.operationSuccess, operationSuccess) ||
|
||||||
|
other.operationSuccess == operationSuccess) &&
|
||||||
|
(identical(other.operationFailure, operationFailure) ||
|
||||||
|
other.operationFailure == operationFailure));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
runtimeType,
|
||||||
|
isShowing,
|
||||||
|
child,
|
||||||
|
showCloseButton,
|
||||||
|
showSuccess,
|
||||||
|
operationName,
|
||||||
|
operationProcessing,
|
||||||
|
operationSuccess,
|
||||||
|
operationFailure);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl>
|
||||||
|
get copyWith => __$$NfcActivityWidgetStateImplCopyWithImpl<
|
||||||
|
_$NfcActivityWidgetStateImpl>(this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _NfcActivityWidgetState implements NfcActivityWidgetState {
|
||||||
|
factory _NfcActivityWidgetState(
|
||||||
|
{required final bool isShowing,
|
||||||
|
required final Widget child,
|
||||||
|
final bool? showCloseButton,
|
||||||
|
final bool? showSuccess,
|
||||||
|
final String? operationName,
|
||||||
|
final String? operationProcessing,
|
||||||
|
final String? operationSuccess,
|
||||||
|
final String? operationFailure}) = _$NfcActivityWidgetStateImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get isShowing;
|
||||||
|
@override
|
||||||
|
Widget get child;
|
||||||
|
@override
|
||||||
|
bool? get showCloseButton;
|
||||||
|
@override
|
||||||
|
bool? get showSuccess;
|
||||||
|
@override
|
||||||
|
String? get operationName;
|
||||||
|
@override
|
||||||
|
String? get operationProcessing;
|
||||||
|
@override
|
||||||
|
String? get operationSuccess;
|
||||||
|
@override
|
||||||
|
String? get operationFailure;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$NfcActivityWidgetStateImplCopyWith<_$NfcActivityWidgetStateImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$NfcActivityWidgetCommand {
|
||||||
|
NfcActivityWidgetAction get action => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
$NfcActivityWidgetCommandCopyWith<NfcActivityWidgetCommand> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $NfcActivityWidgetCommandCopyWith<$Res> {
|
||||||
|
factory $NfcActivityWidgetCommandCopyWith(NfcActivityWidgetCommand value,
|
||||||
|
$Res Function(NfcActivityWidgetCommand) then) =
|
||||||
|
_$NfcActivityWidgetCommandCopyWithImpl<$Res, NfcActivityWidgetCommand>;
|
||||||
|
@useResult
|
||||||
|
$Res call({NfcActivityWidgetAction action});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$NfcActivityWidgetCommandCopyWithImpl<$Res,
|
||||||
|
$Val extends NfcActivityWidgetCommand>
|
||||||
|
implements $NfcActivityWidgetCommandCopyWith<$Res> {
|
||||||
|
_$NfcActivityWidgetCommandCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? action = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
action: null == action
|
||||||
|
? _value.action
|
||||||
|
: action // ignore: cast_nullable_to_non_nullable
|
||||||
|
as NfcActivityWidgetAction,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$NfcActivityWidgetCommandImplCopyWith<$Res>
|
||||||
|
implements $NfcActivityWidgetCommandCopyWith<$Res> {
|
||||||
|
factory _$$NfcActivityWidgetCommandImplCopyWith(
|
||||||
|
_$NfcActivityWidgetCommandImpl value,
|
||||||
|
$Res Function(_$NfcActivityWidgetCommandImpl) then) =
|
||||||
|
__$$NfcActivityWidgetCommandImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({NfcActivityWidgetAction action});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$NfcActivityWidgetCommandImplCopyWithImpl<$Res>
|
||||||
|
extends _$NfcActivityWidgetCommandCopyWithImpl<$Res,
|
||||||
|
_$NfcActivityWidgetCommandImpl>
|
||||||
|
implements _$$NfcActivityWidgetCommandImplCopyWith<$Res> {
|
||||||
|
__$$NfcActivityWidgetCommandImplCopyWithImpl(
|
||||||
|
_$NfcActivityWidgetCommandImpl _value,
|
||||||
|
$Res Function(_$NfcActivityWidgetCommandImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? action = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$NfcActivityWidgetCommandImpl(
|
||||||
|
action: null == action
|
||||||
|
? _value.action
|
||||||
|
: action // ignore: cast_nullable_to_non_nullable
|
||||||
|
as NfcActivityWidgetAction,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$NfcActivityWidgetCommandImpl implements _NfcActivityWidgetCommand {
|
||||||
|
_$NfcActivityWidgetCommandImpl(
|
||||||
|
{this.action = const NfcActivityWidgetAction()});
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final NfcActivityWidgetAction action;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'NfcActivityWidgetCommand(action: $action)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$NfcActivityWidgetCommandImpl &&
|
||||||
|
(identical(other.action, action) || other.action == action));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, action);
|
||||||
|
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl>
|
||||||
|
get copyWith => __$$NfcActivityWidgetCommandImplCopyWithImpl<
|
||||||
|
_$NfcActivityWidgetCommandImpl>(this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _NfcActivityWidgetCommand implements NfcActivityWidgetCommand {
|
||||||
|
factory _NfcActivityWidgetCommand({final NfcActivityWidgetAction action}) =
|
||||||
|
_$NfcActivityWidgetCommandImpl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
NfcActivityWidgetAction get action;
|
||||||
|
@override
|
||||||
|
@JsonKey(ignore: true)
|
||||||
|
_$$NfcActivityWidgetCommandImplCopyWith<_$NfcActivityWidgetCommandImpl>
|
||||||
|
get copyWith => throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
@ -885,28 +885,95 @@
|
|||||||
"l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen",
|
"l_launch_app_on_usb_off": "Andere Anwendungen können den YubiKey über USB nutzen",
|
||||||
"s_allow_screenshots": "Bildschirmfotos erlauben",
|
"s_allow_screenshots": "Bildschirmfotos erlauben",
|
||||||
|
|
||||||
"l_nfc_dialog_tap_key": "Halten Sie Ihren Schlüssel dagegen",
|
"@_ndef_oath_actions": {},
|
||||||
"s_nfc_dialog_operation_success": "Erfolgreich",
|
"s_nfc_dialog_oath_reset": null,
|
||||||
"s_nfc_dialog_operation_failed": "Fehlgeschlagen",
|
"s_nfc_dialog_oath_reset_processing": null,
|
||||||
|
"s_nfc_dialog_oath_reset_success": null,
|
||||||
|
"s_nfc_dialog_oath_reset_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_oath_reset": "Aktion: OATH-Anwendung zurücksetzen",
|
"s_nfc_dialog_oath_unlock": null,
|
||||||
"s_nfc_dialog_oath_unlock": "Aktion: OATH-Anwendung entsperren",
|
"s_nfc_dialog_oath_unlock_processing": null,
|
||||||
"s_nfc_dialog_oath_set_password": "Aktion: OATH-Passwort setzen",
|
"s_nfc_dialog_oath_unlock_success": null,
|
||||||
"s_nfc_dialog_oath_unset_password": "Aktion: OATH-Passwort entfernen",
|
"s_nfc_dialog_oath_unlock_failure": null,
|
||||||
"s_nfc_dialog_oath_add_account": "Aktion: neues Konto hinzufügen",
|
|
||||||
"s_nfc_dialog_oath_rename_account": "Aktion: Konto umbenennen",
|
|
||||||
"s_nfc_dialog_oath_delete_account": "Aktion: Konto löschen",
|
|
||||||
"s_nfc_dialog_oath_calculate_code": "Aktion: OATH-Code berechnen",
|
|
||||||
"s_nfc_dialog_oath_failure": "OATH-Operation fehlgeschlagen",
|
|
||||||
"s_nfc_dialog_oath_add_multiple_accounts": "Aktion: mehrere Konten hinzufügen",
|
|
||||||
|
|
||||||
"s_nfc_dialog_fido_reset": "Aktion: FIDO-Anwendung zurücksetzen",
|
"s_nfc_dialog_oath_set_password": null,
|
||||||
"s_nfc_dialog_fido_unlock": "Aktion: FIDO-Anwendung entsperren",
|
"s_nfc_dialog_oath_change_password": null,
|
||||||
"l_nfc_dialog_fido_set_pin": "Aktion: FIDO-PIN setzen oder ändern",
|
"s_nfc_dialog_oath_set_password_processing": null,
|
||||||
"s_nfc_dialog_fido_delete_credential": "Aktion: Passkey löschen",
|
"s_nfc_dialog_oath_change_password_processing": null,
|
||||||
"s_nfc_dialog_fido_delete_fingerprint": "Aktion: Fingerabdruck löschen",
|
"s_nfc_dialog_oath_set_password_success": null,
|
||||||
"s_nfc_dialog_fido_rename_fingerprint": "Aktion: Fingerabdruck umbenennen",
|
"s_nfc_dialog_oath_change_password_success": null,
|
||||||
"s_nfc_dialog_fido_failure": "FIDO-Operation fehlgeschlagen",
|
"s_nfc_dialog_oath_set_password_failure": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_remove_password": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_account": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_rename_account": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_delete_account": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_calculate_code": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_processing": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_success": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_fido_actions": {},
|
||||||
|
"s_nfc_dialog_fido_reset": null,
|
||||||
|
"s_nfc_dialog_fido_reset_processing": null,
|
||||||
|
"s_nfc_dialog_fido_reset_success": null,
|
||||||
|
"s_nfc_dialog_fido_reset_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_unlock": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_processing": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_success": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_set_pin": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_change_pin": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_delete_credential": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_processing": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_success": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_operations": {},
|
||||||
|
"s_nfc_dialog_tap_for": null,
|
||||||
|
"@s_nfc_dialog_tap_for": {
|
||||||
|
"placeholders": {
|
||||||
|
"operation": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"s_nfc_dialog_read_key": null,
|
||||||
|
"s_nfc_dialog_read_key_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_hold_key": null,
|
||||||
|
"s_nfc_dialog_remove_key": null,
|
||||||
|
|
||||||
"@_ndef": {},
|
"@_ndef": {},
|
||||||
"p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.",
|
"p_ndef_set_otp": "OTP-Code wurde erfolgreich von Ihrem YubiKey in die Zwischenablage kopiert.",
|
||||||
|
@ -885,28 +885,95 @@
|
|||||||
"l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB",
|
"l_launch_app_on_usb_off": "Other apps can use the YubiKey over USB",
|
||||||
"s_allow_screenshots": "Allow screenshots",
|
"s_allow_screenshots": "Allow screenshots",
|
||||||
|
|
||||||
"l_nfc_dialog_tap_key": "Tap and hold your key",
|
"@_ndef_oath_actions": {},
|
||||||
"s_nfc_dialog_operation_success": "Success",
|
"s_nfc_dialog_oath_reset": "reset Accounts",
|
||||||
"s_nfc_dialog_operation_failed": "Failed",
|
"s_nfc_dialog_oath_reset_processing": "Reset in progress",
|
||||||
|
"s_nfc_dialog_oath_reset_success": "Accounts reset",
|
||||||
|
"s_nfc_dialog_oath_reset_failure": "Failed to reset accounts",
|
||||||
|
|
||||||
"s_nfc_dialog_oath_reset": "Action: reset OATH application",
|
"s_nfc_dialog_oath_unlock": "unlock",
|
||||||
"s_nfc_dialog_oath_unlock": "Action: unlock OATH application",
|
"s_nfc_dialog_oath_unlock_processing": "Unlocking",
|
||||||
"s_nfc_dialog_oath_set_password": "Action: set OATH password",
|
"s_nfc_dialog_oath_unlock_success": "Accounts unlocked",
|
||||||
"s_nfc_dialog_oath_unset_password": "Action: remove OATH password",
|
"s_nfc_dialog_oath_unlock_failure": "Failed to unlock",
|
||||||
"s_nfc_dialog_oath_add_account": "Action: add new account",
|
|
||||||
"s_nfc_dialog_oath_rename_account": "Action: rename account",
|
|
||||||
"s_nfc_dialog_oath_delete_account": "Action: delete account",
|
|
||||||
"s_nfc_dialog_oath_calculate_code": "Action: calculate OATH code",
|
|
||||||
"s_nfc_dialog_oath_failure": "OATH operation failed",
|
|
||||||
"s_nfc_dialog_oath_add_multiple_accounts": "Action: add multiple accounts",
|
|
||||||
|
|
||||||
"s_nfc_dialog_fido_reset": "Action: reset FIDO application",
|
"s_nfc_dialog_oath_set_password": "set password",
|
||||||
"s_nfc_dialog_fido_unlock": "Action: unlock FIDO application",
|
"s_nfc_dialog_oath_change_password": "change password",
|
||||||
"l_nfc_dialog_fido_set_pin": "Action: set or change the FIDO PIN",
|
"s_nfc_dialog_oath_set_password_processing": "Setting password",
|
||||||
"s_nfc_dialog_fido_delete_credential": "Action: delete Passkey",
|
"s_nfc_dialog_oath_change_password_processing": "Changing password",
|
||||||
"s_nfc_dialog_fido_delete_fingerprint": "Action: delete fingerprint",
|
"s_nfc_dialog_oath_set_password_success": "Password set",
|
||||||
"s_nfc_dialog_fido_rename_fingerprint": "Action: rename fingerprint",
|
"s_nfc_dialog_oath_change_password_success": "Password changed",
|
||||||
"s_nfc_dialog_fido_failure": "FIDO operation failed",
|
"s_nfc_dialog_oath_set_password_failure": "Failed to set password",
|
||||||
|
"s_nfc_dialog_oath_change_password_failure": "Failed to change password",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_remove_password": "remove password",
|
||||||
|
"s_nfc_dialog_oath_remove_password_processing": "Removing password",
|
||||||
|
"s_nfc_dialog_oath_remove_password_success": "Password removed",
|
||||||
|
"s_nfc_dialog_oath_remove_password_failure": "Failed to remove password",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_account": "add account",
|
||||||
|
"s_nfc_dialog_oath_add_account_processing": "Adding account",
|
||||||
|
"s_nfc_dialog_oath_add_account_success": "Account added",
|
||||||
|
"s_nfc_dialog_oath_add_account_failure": "Failed to add account",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_rename_account": "rename account",
|
||||||
|
"s_nfc_dialog_oath_rename_account_processing": "Renaming account",
|
||||||
|
"s_nfc_dialog_oath_rename_account_success": "Account renamed",
|
||||||
|
"s_nfc_dialog_oath_rename_account_failure": "Failed to rename account",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_delete_account": "delete account",
|
||||||
|
"s_nfc_dialog_oath_delete_account_processing": "Deleting account",
|
||||||
|
"s_nfc_dialog_oath_delete_account_success": "Account deleted",
|
||||||
|
"s_nfc_dialog_oath_delete_account_failure": "Failed to delete account",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_calculate_code": "calculate code",
|
||||||
|
"s_nfc_dialog_oath_calculate_code_processing": "Calculating",
|
||||||
|
"s_nfc_dialog_oath_calculate_code_success": "Code calculated",
|
||||||
|
"s_nfc_dialog_oath_calculate_code_failure": "Failed to calculate code",
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts": "add selected accounts",
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_processing": "Adding accounts",
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_success": "Accounts added",
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_failure": "Failed to add accounts",
|
||||||
|
|
||||||
|
"@_ndef_fido_actions": {},
|
||||||
|
"s_nfc_dialog_fido_reset": "reset FIDO application",
|
||||||
|
"s_nfc_dialog_fido_reset_processing": "Resetting FIDO",
|
||||||
|
"s_nfc_dialog_fido_reset_success": "FIDO reset",
|
||||||
|
"s_nfc_dialog_fido_reset_failure": "FIDO reset failed",
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_unlock": "unlock",
|
||||||
|
"s_nfc_dialog_fido_unlock_processing": "Unlocking",
|
||||||
|
"s_nfc_dialog_fido_unlock_success": "unlocked",
|
||||||
|
"s_nfc_dialog_fido_unlock_failure": "Failed to unlock",
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_set_pin": "set PIN",
|
||||||
|
"s_nfc_dialog_fido_set_pin_processing": "Setting PIN",
|
||||||
|
"s_nfc_dialog_fido_set_pin_success": "PIN set",
|
||||||
|
"s_nfc_dialog_fido_set_pin_failure": "Failure setting PIN",
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_change_pin": "change PIN",
|
||||||
|
"s_nfc_dialog_fido_change_pin_processing": "Changing PIN",
|
||||||
|
"s_nfc_dialog_fido_change_pin_success": "PIN changed",
|
||||||
|
"s_nfc_dialog_fido_change_pin_failure": "Failure changing PIN",
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_delete_credential": "delete passkey",
|
||||||
|
"s_nfc_dialog_fido_delete_credential_processing": "Deleting passkey",
|
||||||
|
"s_nfc_dialog_fido_delete_credential_success": "Passkey deleted",
|
||||||
|
"s_nfc_dialog_fido_delete_credential_failure": "Failed to delete passkey",
|
||||||
|
|
||||||
|
"@_ndef_operations": {},
|
||||||
|
"s_nfc_dialog_tap_for": "Tap YubiKey to {operation}",
|
||||||
|
"@s_nfc_dialog_tap_for": {
|
||||||
|
"placeholders": {
|
||||||
|
"operation": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"s_nfc_dialog_read_key": "Reading YubiKey",
|
||||||
|
"s_nfc_dialog_read_key_failure": "Failed to read YubiKey, try again",
|
||||||
|
|
||||||
|
"s_nfc_dialog_hold_key": "Hold YubiKey",
|
||||||
|
"s_nfc_dialog_remove_key": "You can remove YubiKey",
|
||||||
|
|
||||||
"@_ndef": {},
|
"@_ndef": {},
|
||||||
"p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.",
|
"p_ndef_set_otp": "Successfully copied OTP code from YubiKey to clipboard.",
|
||||||
|
@ -885,28 +885,95 @@
|
|||||||
"l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB",
|
"l_launch_app_on_usb_off": "D'autres applications peuvent utiliser la YubiKey en USB",
|
||||||
"s_allow_screenshots": "Autoriser captures d'écran",
|
"s_allow_screenshots": "Autoriser captures d'écran",
|
||||||
|
|
||||||
"l_nfc_dialog_tap_key": "Appuyez et maintenez votre clé",
|
"@_ndef_oath_actions": {},
|
||||||
"s_nfc_dialog_operation_success": "Succès",
|
"s_nfc_dialog_oath_reset": null,
|
||||||
"s_nfc_dialog_operation_failed": "Échec",
|
"s_nfc_dialog_oath_reset_processing": null,
|
||||||
|
"s_nfc_dialog_oath_reset_success": null,
|
||||||
|
"s_nfc_dialog_oath_reset_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_oath_reset": "Action\u00a0: réinitialiser applet OATH",
|
"s_nfc_dialog_oath_unlock": null,
|
||||||
"s_nfc_dialog_oath_unlock": "Action\u00a0: débloquer applet OATH",
|
"s_nfc_dialog_oath_unlock_processing": null,
|
||||||
"s_nfc_dialog_oath_set_password": "Action\u00a0: définir mot de passe OATH",
|
"s_nfc_dialog_oath_unlock_success": null,
|
||||||
"s_nfc_dialog_oath_unset_password": "Action\u00a0: supprimer mot de passe OATH",
|
"s_nfc_dialog_oath_unlock_failure": null,
|
||||||
"s_nfc_dialog_oath_add_account": "Action\u00a0: ajouter nouveau compte",
|
|
||||||
"s_nfc_dialog_oath_rename_account": "Action\u00a0: renommer compte",
|
|
||||||
"s_nfc_dialog_oath_delete_account": "Action\u00a0: supprimer compte",
|
|
||||||
"s_nfc_dialog_oath_calculate_code": "Action\u00a0: calculer code OATH",
|
|
||||||
"s_nfc_dialog_oath_failure": "Opération OATH impossible",
|
|
||||||
"s_nfc_dialog_oath_add_multiple_accounts": "Action\u00a0: ajouter plusieurs comptes",
|
|
||||||
|
|
||||||
"s_nfc_dialog_fido_reset": "Action : réinitialiser l'application FIDO",
|
"s_nfc_dialog_oath_set_password": null,
|
||||||
"s_nfc_dialog_fido_unlock": "Action : déverrouiller l'application FIDO",
|
"s_nfc_dialog_oath_change_password": null,
|
||||||
"l_nfc_dialog_fido_set_pin": "Action : définir ou modifier le code PIN FIDO",
|
"s_nfc_dialog_oath_set_password_processing": null,
|
||||||
"s_nfc_dialog_fido_delete_credential": "Action : supprimer le Passkey",
|
"s_nfc_dialog_oath_change_password_processing": null,
|
||||||
"s_nfc_dialog_fido_delete_fingerprint": "Action : supprimer l'empreinte digitale",
|
"s_nfc_dialog_oath_set_password_success": null,
|
||||||
"s_nfc_dialog_fido_rename_fingerprint": "Action : renommer l'empreinte digitale",
|
"s_nfc_dialog_oath_change_password_success": null,
|
||||||
"s_nfc_dialog_fido_failure": "Échec de l'opération FIDO",
|
"s_nfc_dialog_oath_set_password_failure": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_remove_password": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_account": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_rename_account": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_delete_account": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_calculate_code": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_processing": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_success": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_fido_actions": {},
|
||||||
|
"s_nfc_dialog_fido_reset": null,
|
||||||
|
"s_nfc_dialog_fido_reset_processing": null,
|
||||||
|
"s_nfc_dialog_fido_reset_success": null,
|
||||||
|
"s_nfc_dialog_fido_reset_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_unlock": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_processing": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_success": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_set_pin": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_change_pin": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_delete_credential": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_processing": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_success": null,
|
||||||
|
"s_nfc_dialog_fido_delete_credential_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_operations": {},
|
||||||
|
"s_nfc_dialog_tap_for": null,
|
||||||
|
"@s_nfc_dialog_tap_for": {
|
||||||
|
"placeholders": {
|
||||||
|
"operation": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"s_nfc_dialog_read_key": null,
|
||||||
|
"s_nfc_dialog_read_key_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_hold_key": null,
|
||||||
|
"s_nfc_dialog_remove_key": null,
|
||||||
|
|
||||||
"@_ndef": {},
|
"@_ndef": {},
|
||||||
"p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.",
|
"p_ndef_set_otp": "Code OTP copié de la YubiKey dans le presse-papiers.",
|
||||||
|
@ -885,28 +885,95 @@
|
|||||||
"l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます",
|
"l_launch_app_on_usb_off": "他のアプリがUSB経由でYubiKeyを使用できます",
|
||||||
"s_allow_screenshots": "スクリーンショットを許可",
|
"s_allow_screenshots": "スクリーンショットを許可",
|
||||||
|
|
||||||
"l_nfc_dialog_tap_key": "キーをタップして長押しします",
|
"@_ndef_oath_actions": {},
|
||||||
"s_nfc_dialog_operation_success": "成功",
|
|
||||||
"s_nfc_dialog_operation_failed": "失敗",
|
|
||||||
|
|
||||||
"s_nfc_dialog_oath_reset": "アクション:OATHアプレットをリセット",
|
"s_nfc_dialog_oath_reset": "アクション:OATHアプレットをリセット",
|
||||||
"s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除",
|
"s_nfc_dialog_oath_reset_processing": null,
|
||||||
"s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定",
|
"s_nfc_dialog_oath_reset_success": null,
|
||||||
"s_nfc_dialog_oath_unset_password": "アクション:OATHパスワードを削除",
|
"s_nfc_dialog_oath_reset_failure": null,
|
||||||
"s_nfc_dialog_oath_add_account": "アクション:新しいアカウントを追加",
|
|
||||||
"s_nfc_dialog_oath_rename_account": "アクション:アカウント名を変更",
|
|
||||||
"s_nfc_dialog_oath_delete_account": "アクション:アカウントを削除",
|
|
||||||
"s_nfc_dialog_oath_calculate_code": "アクション:OATHコードを計算",
|
|
||||||
"s_nfc_dialog_oath_failure": "OATH操作が失敗しました",
|
|
||||||
"s_nfc_dialog_oath_add_multiple_accounts": "アクション:複数アカウントを追加",
|
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除",
|
||||||
|
"s_nfc_dialog_oath_unlock_processing": null,
|
||||||
|
"s_nfc_dialog_oath_unlock_success": null,
|
||||||
|
"s_nfc_dialog_oath_unlock_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定",
|
||||||
|
"s_nfc_dialog_oath_change_password": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_failure": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_remove_password": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_account": "アクション:新しいアカウントを追加",
|
||||||
|
"s_nfc_dialog_oath_add_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_rename_account": "アクション:アカウント名を変更",
|
||||||
|
"s_nfc_dialog_oath_rename_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_delete_account": "アクション:アカウントを削除",
|
||||||
|
"s_nfc_dialog_oath_delete_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_calculate_code": "アクション:OATHコードを計算",
|
||||||
|
"s_nfc_dialog_oath_calculate_code_processing": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_success": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts": "アクション:複数アカウントを追加",
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_fido_actions": {},
|
||||||
"s_nfc_dialog_fido_reset": "アクション: FIDOアプリケーションをリセット",
|
"s_nfc_dialog_fido_reset": "アクション: FIDOアプリケーションをリセット",
|
||||||
|
"s_nfc_dialog_fido_reset_processing": null,
|
||||||
|
"s_nfc_dialog_fido_reset_success": null,
|
||||||
|
"s_nfc_dialog_fido_reset_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する",
|
"s_nfc_dialog_fido_unlock": "アクション:FIDOアプリケーションのロックを解除する",
|
||||||
"l_nfc_dialog_fido_set_pin": "アクション:FIDOのPINの設定または変更",
|
"s_nfc_dialog_fido_unlock_processing": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_success": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_set_pin": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_change_pin": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_fido_delete_credential": "アクション: パスキーを削除",
|
"s_nfc_dialog_fido_delete_credential": "アクション: パスキーを削除",
|
||||||
"s_nfc_dialog_fido_delete_fingerprint": "アクション: 指紋の削除",
|
"s_nfc_dialog_fido_delete_credential_processing": null,
|
||||||
"s_nfc_dialog_fido_rename_fingerprint": "アクション: 指紋の名前を変更する",
|
"s_nfc_dialog_fido_delete_credential_success": null,
|
||||||
"s_nfc_dialog_fido_failure": "FIDO操作に失敗しました",
|
"s_nfc_dialog_fido_delete_credential_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_operations": {},
|
||||||
|
"s_nfc_dialog_tap_for": null,
|
||||||
|
"@s_nfc_dialog_tap_for": {
|
||||||
|
"placeholders": {
|
||||||
|
"operation": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"s_nfc_dialog_read_key": null,
|
||||||
|
"s_nfc_dialog_read_key_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_hold_key": null,
|
||||||
|
"s_nfc_dialog_remove_key": null,
|
||||||
|
|
||||||
"@_ndef": {},
|
"@_ndef": {},
|
||||||
"p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。",
|
"p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。",
|
||||||
|
@ -885,28 +885,95 @@
|
|||||||
"l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB",
|
"l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB",
|
||||||
"s_allow_screenshots": "Zezwalaj na zrzuty ekranu",
|
"s_allow_screenshots": "Zezwalaj na zrzuty ekranu",
|
||||||
|
|
||||||
"l_nfc_dialog_tap_key": null,
|
"@_ndef_oath_actions": {},
|
||||||
"s_nfc_dialog_operation_success": "Powodzenie",
|
"s_nfc_dialog_oath_reset": null,
|
||||||
"s_nfc_dialog_operation_failed": "Niepowodzenie",
|
"s_nfc_dialog_oath_reset_processing": null,
|
||||||
|
"s_nfc_dialog_oath_reset_success": null,
|
||||||
|
"s_nfc_dialog_oath_reset_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_oath_reset": "Działanie: resetuj aplet OATH",
|
"s_nfc_dialog_oath_unlock": null,
|
||||||
"s_nfc_dialog_oath_unlock": "Działanie: odblokuj aplet OATH",
|
"s_nfc_dialog_oath_unlock_processing": null,
|
||||||
"s_nfc_dialog_oath_set_password": "Działanie: ustaw hasło OATH",
|
"s_nfc_dialog_oath_unlock_success": null,
|
||||||
"s_nfc_dialog_oath_unset_password": "Działanie: usuń hasło OATH",
|
"s_nfc_dialog_oath_unlock_failure": null,
|
||||||
"s_nfc_dialog_oath_add_account": "Działanie: dodaj nowe konto",
|
|
||||||
"s_nfc_dialog_oath_rename_account": "Działanie: zmień nazwę konta",
|
|
||||||
"s_nfc_dialog_oath_delete_account": "Działanie: usuń konto",
|
|
||||||
"s_nfc_dialog_oath_calculate_code": "Działanie: oblicz kod OATH",
|
|
||||||
"s_nfc_dialog_oath_failure": "Operacja OATH nie powiodła się",
|
|
||||||
"s_nfc_dialog_oath_add_multiple_accounts": "Działanie: dodawanie wielu kont",
|
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_set_password": null,
|
||||||
|
"s_nfc_dialog_oath_change_password": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_set_password_failure": null,
|
||||||
|
"s_nfc_dialog_oath_change_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_remove_password": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_processing": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_success": null,
|
||||||
|
"s_nfc_dialog_oath_remove_password_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_account": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_rename_account": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_rename_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_delete_account": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_processing": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_success": null,
|
||||||
|
"s_nfc_dialog_oath_delete_account_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_calculate_code": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_processing": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_success": null,
|
||||||
|
"s_nfc_dialog_oath_calculate_code_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_processing": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_success": null,
|
||||||
|
"s_nfc_dialog_oath_add_multiple_accounts_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_fido_actions": {},
|
||||||
"s_nfc_dialog_fido_reset": null,
|
"s_nfc_dialog_fido_reset": null,
|
||||||
|
"s_nfc_dialog_fido_reset_processing": null,
|
||||||
|
"s_nfc_dialog_fido_reset_success": null,
|
||||||
|
"s_nfc_dialog_fido_reset_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_fido_unlock": null,
|
"s_nfc_dialog_fido_unlock": null,
|
||||||
"l_nfc_dialog_fido_set_pin": null,
|
"s_nfc_dialog_fido_unlock_processing": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_success": null,
|
||||||
|
"s_nfc_dialog_fido_unlock_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_set_pin": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_set_pin_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_fido_change_pin": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_processing": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_success": null,
|
||||||
|
"s_nfc_dialog_fido_change_pin_failure": null,
|
||||||
|
|
||||||
"s_nfc_dialog_fido_delete_credential": null,
|
"s_nfc_dialog_fido_delete_credential": null,
|
||||||
"s_nfc_dialog_fido_delete_fingerprint": null,
|
"s_nfc_dialog_fido_delete_credential_processing": null,
|
||||||
"s_nfc_dialog_fido_rename_fingerprint": null,
|
"s_nfc_dialog_fido_delete_credential_success": null,
|
||||||
"s_nfc_dialog_fido_failure": null,
|
"s_nfc_dialog_fido_delete_credential_failure": null,
|
||||||
|
|
||||||
|
"@_ndef_operations": {},
|
||||||
|
"s_nfc_dialog_tap_for": null,
|
||||||
|
"@s_nfc_dialog_tap_for": {
|
||||||
|
"placeholders": {
|
||||||
|
"operation": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"s_nfc_dialog_read_key": null,
|
||||||
|
"s_nfc_dialog_read_key_failure": null,
|
||||||
|
|
||||||
|
"s_nfc_dialog_hold_key": null,
|
||||||
|
"s_nfc_dialog_remove_key": null,
|
||||||
|
|
||||||
"@_ndef": {},
|
"@_ndef": {},
|
||||||
"p_ndef_set_otp": "OTP zostało skopiowane do schowka.",
|
"p_ndef_set_otp": "OTP zostało skopiowane do schowka.",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (C) 2022 Yubico.
|
* Copyright (C) 2021-2024 Yubico.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
const defaultPrimaryColor = Colors.lightGreen;
|
const defaultPrimaryColor = Colors.lightGreen;
|
||||||
|
|
||||||
@ -50,6 +51,9 @@ class AppTheme {
|
|||||||
fontFamily: 'Roboto',
|
fontFamily: 'Roboto',
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.dark,
|
||||||
|
statusBarColor: Colors.transparent),
|
||||||
),
|
),
|
||||||
listTileTheme: const ListTileThemeData(
|
listTileTheme: const ListTileThemeData(
|
||||||
// For alignment under menu button
|
// For alignment under menu button
|
||||||
@ -81,6 +85,9 @@ class AppTheme {
|
|||||||
scaffoldBackgroundColor: colorScheme.surface,
|
scaffoldBackgroundColor: colorScheme.surface,
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
|
statusBarIconBrightness: Brightness.light,
|
||||||
|
statusBarColor: Colors.transparent),
|
||||||
),
|
),
|
||||||
listTileTheme: const ListTileThemeData(
|
listTileTheme: const ListTileThemeData(
|
||||||
// For alignment under menu button
|
// For alignment under menu button
|
||||||
|
68
lib/widgets/pulsing.dart
Normal file
68
lib/widgets/pulsing.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Yubico.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class Pulsing extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const Pulsing({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Pulsing> createState() => _PulsingState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PulsingState extends State<Pulsing> with SingleTickerProviderStateMixin {
|
||||||
|
late final AnimationController controller;
|
||||||
|
late final Animation<double> animationScale;
|
||||||
|
|
||||||
|
late final CurvedAnimation curvedAnimation;
|
||||||
|
|
||||||
|
static const _duration = Duration(milliseconds: 400);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
child: Transform.scale(scale: animationScale.value, child: widget.child),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
controller = AnimationController(
|
||||||
|
duration: _duration,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
curvedAnimation = CurvedAnimation(
|
||||||
|
parent: controller, curve: Curves.easeIn, reverseCurve: Curves.easeOut);
|
||||||
|
animationScale = Tween<double>(
|
||||||
|
begin: 1.0,
|
||||||
|
end: 1.2,
|
||||||
|
).animate(curvedAnimation)
|
||||||
|
..addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.repeat(reverse: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user