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.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
typealias OnDialogCancelled = suspend () -> Unit
|
||||
|
||||
enum class DialogTitle(val value: Int) {
|
||||
TapKey(0),
|
||||
OperationSuccessful(1),
|
||||
OperationFailed(2)
|
||||
}
|
||||
|
||||
class DialogManager(messenger: BinaryMessenger, private val coroutineScope: CoroutineScope) {
|
||||
private val channel =
|
||||
MethodChannel(messenger, "com.yubico.authenticator.channel.dialog")
|
||||
@ -48,40 +40,13 @@ class DialogManager(messenger: BinaryMessenger, private val coroutineScope: Coro
|
||||
}
|
||||
}
|
||||
|
||||
fun showDialog(
|
||||
dialogTitle: DialogTitle,
|
||||
dialogDescriptionId: Int,
|
||||
cancelled: OnDialogCancelled?
|
||||
) {
|
||||
fun showDialog(cancelled: OnDialogCancelled?) {
|
||||
onCancelled = cancelled
|
||||
coroutineScope.launch {
|
||||
channel.invoke(
|
||||
"show",
|
||||
Json.encodeToString(
|
||||
mapOf(
|
||||
"title" to dialogTitle.value,
|
||||
"description" to dialogDescriptionId
|
||||
)
|
||||
)
|
||||
)
|
||||
channel.invoke("show", null)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateDialogState(
|
||||
dialogTitle: DialogTitle,
|
||||
dialogDescriptionId: Int? = null,
|
||||
) {
|
||||
channel.invoke(
|
||||
"state",
|
||||
Json.encodeToString(
|
||||
mapOf(
|
||||
"title" to dialogTitle.value,
|
||||
"description" to dialogDescriptionId
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun closeDialog() {
|
||||
channel.invoke("close", NULL)
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package com.yubico.authenticator
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@ -355,13 +354,14 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
try {
|
||||
it.processYubiKey(device)
|
||||
if (device is NfcYubiKeyDevice) {
|
||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_FINISHED)
|
||||
device.remove {
|
||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED)
|
||||
logger.error("Error processing YubiKey in AppContextManager", e)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -441,6 +441,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
oathViewModel,
|
||||
dialogManager,
|
||||
appPreferences,
|
||||
appMethodChannel,
|
||||
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
|
||||
|
||||
import com.yubico.authenticator.DialogManager
|
||||
import com.yubico.authenticator.DialogTitle
|
||||
import com.yubico.authenticator.device.DeviceManager
|
||||
import com.yubico.authenticator.fido.data.YubiKitFidoSession
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
@ -48,12 +47,9 @@ class FidoConnectionHelper(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun <T> useSession(
|
||||
actionDescription: FidoActionDescription,
|
||||
action: (YubiKitFidoSession) -> T
|
||||
): T {
|
||||
suspend fun <T> useSession(action: (YubiKitFidoSession) -> T): T {
|
||||
return deviceManager.withKey(
|
||||
onNfc = { useSessionNfc(actionDescription,action) },
|
||||
onNfc = { useSessionNfc(action) },
|
||||
onUsb = { useSessionUsb(it, action) })
|
||||
}
|
||||
|
||||
@ -64,10 +60,7 @@ class FidoConnectionHelper(
|
||||
block(YubiKitFidoSession(it))
|
||||
}
|
||||
|
||||
suspend fun <T> useSessionNfc(
|
||||
actionDescription: FidoActionDescription,
|
||||
block: (YubiKitFidoSession) -> T
|
||||
): T {
|
||||
suspend fun <T> useSessionNfc(block: (YubiKitFidoSession) -> T): T {
|
||||
try {
|
||||
val result = suspendCoroutine { outer ->
|
||||
pendingAction = {
|
||||
@ -75,11 +68,8 @@ class FidoConnectionHelper(
|
||||
block.invoke(it.value)
|
||||
})
|
||||
}
|
||||
dialogManager.showDialog(
|
||||
DialogTitle.TapKey,
|
||||
actionDescription.id
|
||||
) {
|
||||
logger.debug("Cancelled Dialog {}", actionDescription.name)
|
||||
dialogManager.showDialog {
|
||||
logger.debug("Cancelled dialog")
|
||||
pendingAction?.invoke(Result.failure(CancellationException()))
|
||||
pendingAction = null
|
||||
}
|
||||
|
@ -343,7 +343,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun unlock(pin: CharArray): String =
|
||||
connectionHelper.useSession(FidoActionDescription.Unlock) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
|
||||
try {
|
||||
val clientPin =
|
||||
@ -380,7 +380,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun setPin(pin: CharArray?, newPin: CharArray): String =
|
||||
connectionHelper.useSession(FidoActionDescription.SetPin) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
try {
|
||||
val clientPin =
|
||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||
@ -428,7 +428,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun deleteCredential(rpId: String, credentialId: String): String =
|
||||
connectionHelper.useSession(FidoActionDescription.DeleteCredential) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
|
||||
val clientPin =
|
||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||
@ -476,7 +476,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun deleteFingerprint(templateId: String): String =
|
||||
connectionHelper.useSession(FidoActionDescription.DeleteFingerprint) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
|
||||
val clientPin =
|
||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||
@ -501,7 +501,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun renameFingerprint(templateId: String, name: String): String =
|
||||
connectionHelper.useSession(FidoActionDescription.RenameFingerprint) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
|
||||
val clientPin =
|
||||
ClientPin(fidoSession, getPreferredPinUvAuthProtocol(fidoSession.cachedInfo))
|
||||
@ -531,7 +531,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun registerFingerprint(name: String?): String =
|
||||
connectionHelper.useSession(FidoActionDescription.RegisterFingerprint) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
state?.cancel()
|
||||
state = CommandState()
|
||||
val clientPin =
|
||||
@ -607,7 +607,7 @@ class FidoManager(
|
||||
}
|
||||
|
||||
private suspend fun enableEnterpriseAttestation(): String =
|
||||
connectionHelper.useSession(FidoActionDescription.EnableEnterpriseAttestation) { fidoSession ->
|
||||
connectionHelper.useSession { fidoSession ->
|
||||
try {
|
||||
val uvAuthProtocol = getPreferredPinUvAuthProtocol(fidoSession.cachedInfo)
|
||||
val clientPin = ClientPin(fidoSession, uvAuthProtocol)
|
||||
|
@ -211,7 +211,7 @@ class FidoResetHelper(
|
||||
coroutineScope.launch {
|
||||
fidoViewModel.updateResetState(FidoResetState.Touch)
|
||||
try {
|
||||
connectionHelper.useSessionNfc(FidoActionDescription.Reset) { fidoSession ->
|
||||
connectionHelper.useSessionNfc { fidoSession ->
|
||||
doReset(fidoSession)
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
package com.yubico.authenticator.management
|
||||
|
||||
import com.yubico.authenticator.DialogManager
|
||||
import com.yubico.authenticator.DialogTitle
|
||||
import com.yubico.authenticator.device.DeviceManager
|
||||
import com.yubico.authenticator.yubikit.withConnection
|
||||
import com.yubico.yubikit.android.transport.usb.UsbYubiKeyDevice
|
||||
@ -63,10 +62,7 @@ class ManagementConnectionHelper(
|
||||
block.invoke(it.value)
|
||||
})
|
||||
}
|
||||
dialogManager.showDialog(
|
||||
DialogTitle.TapKey,
|
||||
actionDescription.id
|
||||
) {
|
||||
dialogManager.showDialog {
|
||||
logger.debug("Cancelled Dialog {}", actionDescription.name)
|
||||
action?.invoke(Result.failure(CancellationException()))
|
||||
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.management.Capability
|
||||
import com.yubico.yubikit.oath.CredentialData
|
||||
import com.yubico.yubikit.support.DeviceUtil
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.*
|
||||
@ -65,6 +66,7 @@ import kotlinx.serialization.encodeToString
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.IOException
|
||||
import java.net.URI
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
@ -78,6 +80,7 @@ class OathManager(
|
||||
private val oathViewModel: OathViewModel,
|
||||
private val dialogManager: DialogManager,
|
||||
private val appPreferences: AppPreferences,
|
||||
private val appMethodChannel: MainActivity.AppMethodChannel,
|
||||
private val nfcActivityListener: NfcActivityListener
|
||||
) : AppContextManager(), DeviceListener {
|
||||
|
||||
@ -214,24 +217,33 @@ class OathManager(
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
var showProcessingTimerTask: TimerTask? = null
|
||||
|
||||
override suspend fun processYubiKey(device: YubiKeyDevice) {
|
||||
try {
|
||||
if (device is NfcYubiKeyDevice) {
|
||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED)
|
||||
}
|
||||
|
||||
device.withConnection<SmartCardConnection, Unit> { connection ->
|
||||
val session = getOathSession(connection)
|
||||
val previousId = oathViewModel.currentSession()?.deviceId
|
||||
if (session.deviceId == previousId && device is NfcYubiKeyDevice) {
|
||||
// Run any pending action
|
||||
pendingAction?.let { action ->
|
||||
action.invoke(Result.success(session))
|
||||
pendingAction = null
|
||||
}
|
||||
|
||||
// Refresh codes
|
||||
if (!session.isLocked) {
|
||||
try {
|
||||
oathViewModel.updateCredentials(calculateOathCodes(session))
|
||||
} catch (error: Exception) {
|
||||
logger.error("Failed to refresh codes", error)
|
||||
// Either run a pending action, or just refresh codes
|
||||
if (pendingAction != null) {
|
||||
pendingAction?.let { action ->
|
||||
action.invoke(Result.success(session))
|
||||
pendingAction = null
|
||||
}
|
||||
} else {
|
||||
// Refresh codes
|
||||
if (!session.isLocked) {
|
||||
try {
|
||||
oathViewModel.updateCredentials(calculateOathCodes(session))
|
||||
} catch (error: Exception) {
|
||||
logger.error("Failed to refresh codes", error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -261,6 +273,7 @@ class OathManager(
|
||||
} else {
|
||||
// Awaiting an action for a different device? Fail it and stop processing.
|
||||
action.invoke(Result.failure(IllegalStateException("Wrong deviceId")))
|
||||
showProcessingTimerTask?.cancel()
|
||||
return@withConnection
|
||||
}
|
||||
}
|
||||
@ -281,11 +294,14 @@ class OathManager(
|
||||
supportedCapabilities = oathCapabilities
|
||||
)
|
||||
)
|
||||
showProcessingTimerTask?.cancel()
|
||||
return@withConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showProcessingTimerTask?.cancel()
|
||||
logger.debug(
|
||||
"Successfully read Oath session info (and credentials if unlocked) from connected key"
|
||||
)
|
||||
@ -294,10 +310,12 @@ class OathManager(
|
||||
deviceManager.setDeviceInfo(getDeviceInfo(device))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_INTERRUPTED)
|
||||
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
|
||||
logger.error("Failed to connect to CCID: ", e)
|
||||
// Clear any cached OATH state
|
||||
oathViewModel.clearSession()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,7 +326,7 @@ class OathManager(
|
||||
val credentialData: CredentialData =
|
||||
CredentialData.parseUri(URI.create(uri))
|
||||
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
|
||||
if (session.credentials.any { it.id.contentEquals(credentialData.id) }) {
|
||||
throw IllegalArgumentException()
|
||||
@ -338,7 +356,7 @@ class OathManager(
|
||||
logger.trace("Adding following accounts: {}", uris)
|
||||
|
||||
addToAny = true
|
||||
return useOathSession(OathActionDescription.AddMultipleAccounts) { session ->
|
||||
return useOathSession { session ->
|
||||
var successCount = 0
|
||||
for (index in uris.indices) {
|
||||
|
||||
@ -370,7 +388,7 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun reset(): String =
|
||||
useOathSession(OathActionDescription.Reset, updateDeviceInfo = true) {
|
||||
useOathSession(updateDeviceInfo = true) {
|
||||
// note, it is ok to reset locked session
|
||||
it.reset()
|
||||
keyManager.removeKey(it.deviceId)
|
||||
@ -382,7 +400,7 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun unlock(password: String, remember: Boolean): String =
|
||||
useOathSession(OathActionDescription.Unlock) {
|
||||
useOathSession {
|
||||
val accessKey = it.deriveAccessKey(password.toCharArray())
|
||||
keyManager.addKey(it.deviceId, accessKey, remember)
|
||||
|
||||
@ -390,11 +408,7 @@ class OathManager(
|
||||
val remembered = keyManager.isRemembered(it.deviceId)
|
||||
if (unlocked) {
|
||||
oathViewModel.setSessionState(Session(it, remembered))
|
||||
|
||||
// fetch credentials after unlocking only if the YubiKey is connected over USB
|
||||
if (deviceManager.isUsbKeyConnected()) {
|
||||
oathViewModel.updateCredentials(calculateOathCodes(it))
|
||||
}
|
||||
oathViewModel.updateCredentials(calculateOathCodes(it))
|
||||
}
|
||||
|
||||
jsonSerializer.encodeToString(mapOf("unlocked" to unlocked, "remembered" to remembered))
|
||||
@ -405,7 +419,6 @@ class OathManager(
|
||||
newPassword: String,
|
||||
): String =
|
||||
useOathSession(
|
||||
OathActionDescription.SetPassword,
|
||||
unlock = false,
|
||||
updateDeviceInfo = true
|
||||
) { session ->
|
||||
@ -427,7 +440,7 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun unsetPassword(currentPassword: String): String =
|
||||
useOathSession(OathActionDescription.UnsetPassword, unlock = false) { session ->
|
||||
useOathSession(unlock = false) { session ->
|
||||
if (session.isAccessKeySet) {
|
||||
// test current password sent by the user
|
||||
if (session.unlock(currentPassword.toCharArray())) {
|
||||
@ -459,7 +472,7 @@ class OathManager(
|
||||
uri: String,
|
||||
requireTouch: Boolean,
|
||||
): String =
|
||||
useOathSession(OathActionDescription.AddAccount) { session ->
|
||||
useOathSession { session ->
|
||||
val credentialData: CredentialData =
|
||||
CredentialData.parseUri(URI.create(uri))
|
||||
|
||||
@ -480,21 +493,30 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun renameAccount(uri: String, name: String, issuer: String?): String =
|
||||
useOathSession(OathActionDescription.RenameAccount) { session ->
|
||||
val credential = getOathCredential(session, uri)
|
||||
val renamedCredential =
|
||||
Credential(session.renameCredential(credential, name, issuer), session.deviceId)
|
||||
oathViewModel.renameCredential(
|
||||
Credential(credential, session.deviceId),
|
||||
renamedCredential
|
||||
useOathSession { session ->
|
||||
val credential = getCredential(uri)
|
||||
val renamed = Credential(
|
||||
session.renameCredential(credential, name, issuer),
|
||||
session.deviceId
|
||||
)
|
||||
|
||||
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 =
|
||||
useOathSession(OathActionDescription.DeleteAccount) { session ->
|
||||
val credential = getOathCredential(session, credentialId)
|
||||
useOathSession { session ->
|
||||
val credential = getCredential(credentialId)
|
||||
session.deleteCredential(credential)
|
||||
oathViewModel.removeCredential(Credential(credential, session.deviceId))
|
||||
NULL
|
||||
@ -546,8 +568,8 @@ class OathManager(
|
||||
|
||||
|
||||
private suspend fun calculate(credentialId: String): String =
|
||||
useOathSession(OathActionDescription.CalculateCode) { session ->
|
||||
val credential = getOathCredential(session, credentialId)
|
||||
useOathSession { session ->
|
||||
val credential = getCredential(credentialId)
|
||||
|
||||
val code = Code.from(calculateCode(session, credential))
|
||||
oathViewModel.updateCode(
|
||||
@ -649,31 +671,43 @@ class OathManager(
|
||||
return session.calculateCodes(timestamp).map { (credential, code) ->
|
||||
Pair(
|
||||
Credential(credential, session.deviceId),
|
||||
Code.from(if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) {
|
||||
session.calculateSteamCode(credential, timestamp)
|
||||
} else if (credential.isTouchRequired && bypassTouch) {
|
||||
session.calculateCode(credential, timestamp)
|
||||
} else {
|
||||
code
|
||||
})
|
||||
Code.from(
|
||||
if (credential.isSteamCredential() && (!credential.isTouchRequired || bypassTouch)) {
|
||||
session.calculateSteamCode(credential, timestamp)
|
||||
} else if (credential.isTouchRequired && bypassTouch) {
|
||||
session.calculateCode(credential, timestamp)
|
||||
} else {
|
||||
code
|
||||
}
|
||||
)
|
||||
)
|
||||
}.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(
|
||||
oathActionDescription: OathActionDescription,
|
||||
unlock: Boolean = true,
|
||||
updateDeviceInfo: Boolean = false,
|
||||
action: (YubiKitOathSession) -> T
|
||||
): T {
|
||||
|
||||
// callers can decide whether the session should be unlocked first
|
||||
unlockOnConnect.set(unlock)
|
||||
// callers can request whether device info should be updated after session operation
|
||||
this@OathManager.updateDeviceInfo.set(updateDeviceInfo)
|
||||
return deviceManager.withKey(
|
||||
onUsb = { useOathSessionUsb(it, updateDeviceInfo, action) },
|
||||
onNfc = { useOathSessionNfc(oathActionDescription, action) }
|
||||
onNfc = { useOathSessionNfc(action) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -690,50 +724,42 @@ class OathManager(
|
||||
}
|
||||
|
||||
private suspend fun <T> useOathSessionNfc(
|
||||
oathActionDescription: OathActionDescription,
|
||||
block: (YubiKitOathSession) -> T
|
||||
): T {
|
||||
try {
|
||||
val result = suspendCoroutine { outer ->
|
||||
pendingAction = {
|
||||
outer.resumeWith(runCatching {
|
||||
block.invoke(it.value)
|
||||
})
|
||||
}
|
||||
dialogManager.showDialog(DialogTitle.TapKey, oathActionDescription.id) {
|
||||
logger.debug("Cancelled Dialog {}", oathActionDescription.name)
|
||||
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()
|
||||
}
|
||||
}
|
||||
var firstShow = true
|
||||
while (true) { // loop until success or cancel
|
||||
try {
|
||||
val result = suspendCoroutine { outer ->
|
||||
pendingAction = {
|
||||
outer.resumeWith(runCatching {
|
||||
val session = it.value // this can throw CancellationException
|
||||
nfcActivityListener.onChange(NfcActivityState.PROCESSING_STARTED)
|
||||
block.invoke(session)
|
||||
})
|
||||
}
|
||||
|
||||
private fun getOathCredential(session: YubiKitOathSession, credentialId: String) =
|
||||
// we need to use oathSession.calculateCodes() to get proper Credential.touchRequired value
|
||||
session.calculateCodes().map { e -> e.key }.firstOrNull { credential ->
|
||||
(credential != null) && credential.id.asString() == credentialId
|
||||
} ?: throw Exception("Failed to find account")
|
||||
if (firstShow) {
|
||||
dialogManager.showDialog {
|
||||
logger.debug("Cancelled dialog")
|
||||
pendingAction?.invoke(Result.failure(CancellationException()))
|
||||
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) {
|
||||
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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -35,9 +35,10 @@ data class Credential(
|
||||
@SerialName("name")
|
||||
val accountName: String,
|
||||
@SerialName("touch_required")
|
||||
val touchRequired: Boolean
|
||||
val touchRequired: Boolean,
|
||||
@kotlinx.serialization.Transient
|
||||
val data: YubiKitCredential? = null
|
||||
) {
|
||||
|
||||
constructor(credential: YubiKitCredential, deviceId: String) : this(
|
||||
deviceId = deviceId,
|
||||
id = credential.id.asString(),
|
||||
@ -48,7 +49,8 @@ data class Credential(
|
||||
period = credential.period,
|
||||
issuer = credential.issuer,
|
||||
accountName = credential.accountName,
|
||||
touchRequired = credential.isTouchRequired
|
||||
touchRequired = credential.isTouchRequired,
|
||||
data = credential
|
||||
)
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
|
@ -19,6 +19,7 @@ package com.yubico.authenticator.yubikit
|
||||
import android.app.Activity
|
||||
import android.nfc.NfcAdapter
|
||||
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.NfcDispatcher
|
||||
@ -51,7 +52,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp
|
||||
nfcConfiguration,
|
||||
TagInterceptor(listener, handler)
|
||||
)
|
||||
listener.onChange(NfcActivityState.READY)
|
||||
//listener.onChange(NfcActivityState.READY)
|
||||
}
|
||||
|
||||
override fun disable(activity: Activity) {
|
||||
@ -68,7 +69,7 @@ class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDisp
|
||||
private val logger = LoggerFactory.getLogger(TagInterceptor::class.java)
|
||||
|
||||
override fun onTag(tag: Tag) {
|
||||
listener.onChange(NfcActivityState.PROCESSING_STARTED)
|
||||
//listener.onChange(NfcActivityState.PROCESSING_STARTED)
|
||||
logger.debug("forwarding tag")
|
||||
tagHandler.onTag(tag)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
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.plugin.serialization" version "2.0.0" 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 '../../fido/models.dart';
|
||||
import '../../fido/state.dart';
|
||||
import '../tap_request_dialog.dart';
|
||||
|
||||
final _log = Logger('android.fido.state');
|
||||
|
||||
const _methods = MethodChannel('android.fido.methods');
|
||||
|
||||
final androidFidoStateProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<FidoStateNotifier, FidoState, DevicePath>(_FidoStateNotifier.new);
|
||||
|
||||
class _FidoStateNotifier extends FidoStateNotifier {
|
||||
final _events = const EventChannel('android.fido.sessionState');
|
||||
late StreamSubscription _sub;
|
||||
late final _FidoMethodChannelNotifier fido =
|
||||
ref.read(_fidoMethodsProvider.notifier);
|
||||
|
||||
@override
|
||||
FutureOr<FidoState> build(DevicePath devicePath) async {
|
||||
@ -79,7 +80,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
});
|
||||
|
||||
controller.onCancel = () async {
|
||||
await _methods.invokeMethod('cancelReset');
|
||||
await fido.cancelReset();
|
||||
if (!controller.isClosed) {
|
||||
await subscription.cancel();
|
||||
}
|
||||
@ -87,7 +88,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
|
||||
controller.onListen = () async {
|
||||
try {
|
||||
await _methods.invokeMethod('reset');
|
||||
await fido.reset();
|
||||
await controller.sink.close();
|
||||
ref.invalidateSelf();
|
||||
} catch (e) {
|
||||
@ -102,13 +103,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
@override
|
||||
Future<PinResult> setPin(String newPin, {String? oldPin}) async {
|
||||
try {
|
||||
final response = jsonDecode(await _methods.invokeMethod(
|
||||
'setPin',
|
||||
{
|
||||
'pin': oldPin,
|
||||
'newPin': newPin,
|
||||
},
|
||||
));
|
||||
final response = jsonDecode(await fido.setPin(newPin, oldPin: oldPin));
|
||||
if (response['success'] == true) {
|
||||
_log.debug('FIDO PIN set/change successful');
|
||||
return PinResult.success();
|
||||
@ -134,10 +129,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
@override
|
||||
Future<PinResult> unlock(String pin) async {
|
||||
try {
|
||||
final response = jsonDecode(await _methods.invokeMethod(
|
||||
'unlock',
|
||||
{'pin': pin},
|
||||
));
|
||||
final response = jsonDecode(await fido.unlock(pin));
|
||||
|
||||
if (response['success'] == true) {
|
||||
_log.debug('FIDO applet unlocked');
|
||||
@ -165,9 +157,7 @@ class _FidoStateNotifier extends FidoStateNotifier {
|
||||
@override
|
||||
Future<void> enableEnterpriseAttestation() async {
|
||||
try {
|
||||
final response = jsonDecode(await _methods.invokeMethod(
|
||||
'enableEnterpriseAttestation',
|
||||
));
|
||||
final response = jsonDecode(await fido.enableEnterpriseAttestation());
|
||||
|
||||
if (response['success'] == true) {
|
||||
_log.debug('Enterprise attestation enabled');
|
||||
@ -193,6 +183,8 @@ final androidFingerprintProvider = AsyncNotifierProvider.autoDispose
|
||||
class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
||||
final _events = const EventChannel('android.fido.fingerprints');
|
||||
late StreamSubscription _sub;
|
||||
late final _FidoMethodChannelNotifier fido =
|
||||
ref.read(_fidoMethodsProvider.notifier);
|
||||
|
||||
@override
|
||||
FutureOr<List<Fingerprint>> build(DevicePath devicePath) async {
|
||||
@ -243,15 +235,14 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
||||
controller.onCancel = () async {
|
||||
if (!controller.isClosed) {
|
||||
_log.debug('Cancelling fingerprint registration');
|
||||
await _methods.invokeMethod('cancelRegisterFingerprint');
|
||||
await fido.cancelFingerprintRegistration();
|
||||
await registerFpSub.cancel();
|
||||
}
|
||||
};
|
||||
|
||||
controller.onListen = () async {
|
||||
try {
|
||||
final registerFpResult =
|
||||
await _methods.invokeMethod('registerFingerprint', {'name': name});
|
||||
final registerFpResult = await fido.registerFingerprint(name);
|
||||
|
||||
_log.debug('Finished registerFingerprint with: $registerFpResult');
|
||||
|
||||
@ -286,13 +277,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
||||
Future<Fingerprint> renameFingerprint(
|
||||
Fingerprint fingerprint, String name) async {
|
||||
try {
|
||||
final renameFingerprintResponse = jsonDecode(await _methods.invokeMethod(
|
||||
'renameFingerprint',
|
||||
{
|
||||
'templateId': fingerprint.templateId,
|
||||
'name': name,
|
||||
},
|
||||
));
|
||||
final renameFingerprintResponse =
|
||||
jsonDecode(await fido.renameFingerprint(fingerprint, name));
|
||||
|
||||
if (renameFingerprintResponse['success'] == true) {
|
||||
_log.debug('FIDO rename fingerprint succeeded');
|
||||
@ -316,12 +302,8 @@ class _FidoFingerprintsNotifier extends FidoFingerprintsNotifier {
|
||||
@override
|
||||
Future<void> deleteFingerprint(Fingerprint fingerprint) async {
|
||||
try {
|
||||
final deleteFingerprintResponse = jsonDecode(await _methods.invokeMethod(
|
||||
'deleteFingerprint',
|
||||
{
|
||||
'templateId': fingerprint.templateId,
|
||||
},
|
||||
));
|
||||
final deleteFingerprintResponse =
|
||||
jsonDecode(await fido.deleteFingerprint(fingerprint));
|
||||
|
||||
if (deleteFingerprintResponse['success'] == true) {
|
||||
_log.debug('FIDO delete fingerprint succeeded');
|
||||
@ -348,6 +330,8 @@ final androidCredentialProvider = AsyncNotifierProvider.autoDispose
|
||||
class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
||||
final _events = const EventChannel('android.fido.credentials');
|
||||
late StreamSubscription _sub;
|
||||
late final _FidoMethodChannelNotifier fido =
|
||||
ref.read(_fidoMethodsProvider.notifier);
|
||||
|
||||
@override
|
||||
FutureOr<List<FidoCredential>> build(DevicePath devicePath) async {
|
||||
@ -371,13 +355,7 @@ class _FidoCredentialsNotifier extends FidoCredentialsNotifier {
|
||||
@override
|
||||
Future<void> deleteCredential(FidoCredential credential) async {
|
||||
try {
|
||||
await _methods.invokeMethod(
|
||||
'deleteCredential',
|
||||
{
|
||||
'rpId': credential.rpId,
|
||||
'credentialId': credential.credentialId,
|
||||
},
|
||||
);
|
||||
await fido.deleteCredential(credential);
|
||||
} on PlatformException catch (pe) {
|
||||
var decodedException = pe.decode();
|
||||
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 'state.dart';
|
||||
import 'tap_request_dialog.dart';
|
||||
import 'views/nfc/nfc_activity_command_listener.dart';
|
||||
import 'window_state_provider.dart';
|
||||
|
||||
Future<Widget> initialize() async {
|
||||
@ -106,6 +107,8 @@ Future<Widget> initialize() async {
|
||||
child: DismissKeyboard(
|
||||
child: YubicoAuthenticatorApp(page: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
ref.read(nfcActivityCommandListener).startListener(context);
|
||||
|
||||
Timer.run(() {
|
||||
ref.read(featureFlagProvider.notifier)
|
||||
// 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");
|
||||
* 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 '../../oath/models.dart';
|
||||
import '../../oath/state.dart';
|
||||
import '../tap_request_dialog.dart';
|
||||
|
||||
final _log = Logger('android.oath.state');
|
||||
|
||||
const _methods = MethodChannel('android.oath.methods');
|
||||
|
||||
final androidOathStateProvider = AsyncNotifierProvider.autoDispose
|
||||
.family<OathStateNotifier, OathState, DevicePath>(
|
||||
_AndroidOathStateNotifier.new);
|
||||
@ -47,6 +46,8 @@ final androidOathStateProvider = AsyncNotifierProvider.autoDispose
|
||||
class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
final _events = const EventChannel('android.oath.sessionState');
|
||||
late StreamSubscription _sub;
|
||||
late _OathMethodChannelNotifier oath =
|
||||
ref.watch(_oathMethodsProvider.notifier);
|
||||
|
||||
@override
|
||||
FutureOr<OathState> build(DevicePath arg) {
|
||||
@ -75,7 +76,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
// await ref
|
||||
// .read(androidAppContextHandler)
|
||||
// .switchAppContext(Application.accounts);
|
||||
await _methods.invokeMethod('reset');
|
||||
await oath.reset();
|
||||
} catch (e) {
|
||||
_log.debug('Calling reset failed with exception: $e');
|
||||
}
|
||||
@ -84,8 +85,8 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
@override
|
||||
Future<(bool, bool)> unlock(String password, {bool remember = false}) async {
|
||||
try {
|
||||
final unlockResponse = jsonDecode(await _methods.invokeMethod(
|
||||
'unlock', {'password': password, 'remember': remember}));
|
||||
final unlockResponse =
|
||||
jsonDecode(await oath.unlock(password, remember: remember));
|
||||
_log.debug('applet unlocked');
|
||||
|
||||
final unlocked = unlockResponse['unlocked'] == true;
|
||||
@ -106,8 +107,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
@override
|
||||
Future<bool> setPassword(String? current, String password) async {
|
||||
try {
|
||||
await _methods.invokeMethod(
|
||||
'setPassword', {'current': current, 'password': password});
|
||||
await oath.setPassword(current, password);
|
||||
return true;
|
||||
} on PlatformException catch (e) {
|
||||
_log.debug('Calling set password failed with exception: $e');
|
||||
@ -118,7 +118,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
@override
|
||||
Future<bool> unsetPassword(String current) async {
|
||||
try {
|
||||
await _methods.invokeMethod('unsetPassword', {'current': current});
|
||||
await oath.unsetPassword(current);
|
||||
return true;
|
||||
} on PlatformException catch (e) {
|
||||
_log.debug('Calling unset password failed with exception: $e');
|
||||
@ -129,7 +129,7 @@ class _AndroidOathStateNotifier extends OathStateNotifier {
|
||||
@override
|
||||
Future<void> forgetPassword() async {
|
||||
try {
|
||||
await _methods.invokeMethod('forgetPassword');
|
||||
await oath.forgetPassword();
|
||||
} on PlatformException catch (e) {
|
||||
_log.debug('Calling forgetPassword failed with exception: $e');
|
||||
}
|
||||
@ -161,12 +161,10 @@ Exception _decodeAddAccountException(PlatformException platformException) {
|
||||
|
||||
final addCredentialToAnyProvider =
|
||||
Provider((ref) => (Uri credentialUri, {bool requireTouch = false}) async {
|
||||
final oath = ref.watch(_oathMethodsProvider.notifier);
|
||||
try {
|
||||
String resultString = await _methods.invokeMethod(
|
||||
'addAccountToAny', {
|
||||
'uri': credentialUri.toString(),
|
||||
'requireTouch': requireTouch
|
||||
});
|
||||
String resultString = await oath.addAccountToAny(credentialUri,
|
||||
requireTouch: requireTouch);
|
||||
|
||||
var result = jsonDecode(resultString);
|
||||
return OathCredential.fromJson(result['credential']);
|
||||
@ -177,17 +175,13 @@ final addCredentialToAnyProvider =
|
||||
|
||||
final addCredentialsToAnyProvider = Provider(
|
||||
(ref) => (List<String> credentialUris, List<bool> touchRequired) async {
|
||||
final oath = ref.read(_oathMethodsProvider.notifier);
|
||||
try {
|
||||
_log.debug(
|
||||
'Calling android with ${credentialUris.length} credentials to be added');
|
||||
|
||||
String resultString = await _methods.invokeMethod(
|
||||
'addAccountsToAny',
|
||||
{
|
||||
'uris': credentialUris,
|
||||
'requireTouch': touchRequired,
|
||||
},
|
||||
);
|
||||
String resultString =
|
||||
await oath.addAccounts(credentialUris, touchRequired);
|
||||
|
||||
_log.debug('Call result: $resultString');
|
||||
var result = jsonDecode(resultString);
|
||||
@ -218,6 +212,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
final WithContext _withContext;
|
||||
final Ref _ref;
|
||||
late StreamSubscription _sub;
|
||||
late _OathMethodChannelNotifier oath =
|
||||
_ref.read(_oathMethodsProvider.notifier);
|
||||
|
||||
_AndroidCredentialListNotifier(this._withContext, this._ref) : super() {
|
||||
_sub = _events.receiveBroadcastStream().listen((event) {
|
||||
@ -264,8 +260,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
}
|
||||
|
||||
try {
|
||||
final resultJson = await _methods
|
||||
.invokeMethod('calculate', {'credentialId': credential.id});
|
||||
final resultJson = await oath.calculate(credential);
|
||||
_log.debug('Calculate', resultJson);
|
||||
return OathCode.fromJson(jsonDecode(resultJson));
|
||||
} on PlatformException catch (pe) {
|
||||
@ -280,9 +275,8 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
Future<OathCredential> addAccount(Uri credentialUri,
|
||||
{bool requireTouch = false}) async {
|
||||
try {
|
||||
String resultString = await _methods.invokeMethod('addAccount',
|
||||
{'uri': credentialUri.toString(), 'requireTouch': requireTouch});
|
||||
|
||||
String resultString =
|
||||
await oath.addAccount(credentialUri, requireTouch: requireTouch);
|
||||
var result = jsonDecode(resultString);
|
||||
return OathCredential.fromJson(result['credential']);
|
||||
} on PlatformException catch (pe) {
|
||||
@ -294,9 +288,7 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
Future<OathCredential> renameAccount(
|
||||
OathCredential credential, String? issuer, String name) async {
|
||||
try {
|
||||
final response = await _methods.invokeMethod('renameAccount',
|
||||
{'credentialId': credential.id, 'name': name, 'issuer': issuer});
|
||||
|
||||
final response = await oath.renameAccount(credential, issuer, name);
|
||||
_log.debug('Rename response: $response');
|
||||
|
||||
var responseJson = jsonDecode(response);
|
||||
@ -311,11 +303,149 @@ class _AndroidCredentialListNotifier extends OathCredentialListNotifier {
|
||||
@override
|
||||
Future<void> deleteAccount(OathCredential credential) async {
|
||||
try {
|
||||
await _methods
|
||||
.invokeMethod('deleteAccount', {'credentialId': credential.id});
|
||||
await oath.deleteAccount(credential);
|
||||
} on PlatformException catch (e) {
|
||||
_log.debug('Received exception: $e');
|
||||
throw e.decode();
|
||||
var decoded = 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:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.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/views/user_interaction.dart';
|
||||
import 'views/nfc/nfc_activity_widget.dart';
|
||||
import '../widgets/pulsing.dart';
|
||||
import 'state.dart';
|
||||
import 'views/nfc/nfc_activity_overlay.dart';
|
||||
|
||||
const _channel = MethodChannel('com.yubico.authenticator.channel.dialog');
|
||||
|
||||
// _DDesc contains id of title resource for the dialog
|
||||
enum _DTitle {
|
||||
tapKey,
|
||||
operationSuccessful,
|
||||
operationFailed,
|
||||
invalid;
|
||||
final androidDialogProvider =
|
||||
NotifierProvider<_DialogProvider, int>(_DialogProvider.new);
|
||||
|
||||
static _DTitle fromId(int? id) =>
|
||||
const {
|
||||
0: _DTitle.tapKey,
|
||||
1: _DTitle.operationSuccessful,
|
||||
2: _DTitle.operationFailed
|
||||
}[id] ??
|
||||
_DTitle.invalid;
|
||||
}
|
||||
class _DialogProvider extends Notifier<int> {
|
||||
Timer? processingTimer;
|
||||
bool explicitAction = false;
|
||||
|
||||
// _DDesc contains action description in the dialog
|
||||
enum _DDesc {
|
||||
// oath descriptions
|
||||
oathResetApplet,
|
||||
oathUnlockSession,
|
||||
oathSetPassword,
|
||||
oathUnsetPassword,
|
||||
oathAddAccount,
|
||||
oathRenameAccount,
|
||||
oathDeleteAccount,
|
||||
oathCalculateCode,
|
||||
oathActionFailure,
|
||||
oathAddMultipleAccounts,
|
||||
// FIDO descriptions
|
||||
fidoResetApplet,
|
||||
fidoUnlockSession,
|
||||
fidoSetPin,
|
||||
fidoDeleteCredential,
|
||||
fidoDeleteFingerprint,
|
||||
fidoRenameFingerprint,
|
||||
fidoRegisterFingerprint,
|
||||
fidoEnableEnterpriseAttestation,
|
||||
fidoActionFailure,
|
||||
// Others
|
||||
invalid;
|
||||
@override
|
||||
int build() {
|
||||
final l10n = ref.read(l10nProvider);
|
||||
ref.listen(androidNfcActivityProvider, (previous, current) {
|
||||
final notifier = ref.read(nfcActivityCommandNotifier.notifier);
|
||||
|
||||
static const int dialogDescriptionOathIndex = 100;
|
||||
static const int dialogDescriptionFidoIndex = 200;
|
||||
if (!explicitAction) {
|
||||
// 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) =>
|
||||
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 properties = ref.read(nfcActivityWidgetNotifier);
|
||||
|
||||
final androidDialogProvider = Provider<_DialogProvider>(
|
||||
(ref) {
|
||||
return _DialogProvider(ref.watch(withContextProvider));
|
||||
},
|
||||
);
|
||||
debugPrint('XXX now it is: $current');
|
||||
switch (current) {
|
||||
case NfcActivity.processingStarted:
|
||||
processingTimer?.cancel();
|
||||
|
||||
class _DialogProvider {
|
||||
final WithContext _withContext;
|
||||
final Widget _icon = const NfcActivityWidget(width: 64, height: 64);
|
||||
UserInteractionController? _controller;
|
||||
debugPrint('XXX explicit action: $explicitAction');
|
||||
final timeout = explicitAction ? 300 : 200;
|
||||
|
||||
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 {
|
||||
final args = jsonDecode(call.arguments);
|
||||
final notifier = ref.read(nfcActivityCommandNotifier.notifier);
|
||||
final properties = ref.read(nfcActivityWidgetNotifier);
|
||||
switch (call.method) {
|
||||
case 'close':
|
||||
_closeDialog();
|
||||
break;
|
||||
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;
|
||||
case 'state':
|
||||
await _updateDialogState(args['title'], args['description']);
|
||||
|
||||
case 'close':
|
||||
notifier.update(NfcActivityWidgetCommand(
|
||||
action: const NfcActivityWidgetActionHideWidget(timeoutMs: 0)));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw PlatformException(
|
||||
code: 'NotImplemented',
|
||||
@ -129,71 +148,112 @@ class _DialogProvider {
|
||||
);
|
||||
}
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
void _closeDialog() {
|
||||
_controller?.close();
|
||||
_controller = null;
|
||||
void cancelDialog() async {
|
||||
debugPrint('Cancelled dialog');
|
||||
explicitAction = false;
|
||||
await _channel.invokeMethod('cancel');
|
||||
}
|
||||
|
||||
String _getTitle(BuildContext context, int? titleId) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
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,
|
||||
_ => ''
|
||||
};
|
||||
}
|
||||
Future<void> waitForDialogClosed() async {
|
||||
final completer = Completer();
|
||||
|
||||
String _getDialogDescription(BuildContext context, int? descriptionId) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return switch (_DDesc.fromId(descriptionId)) {
|
||||
_DDesc.oathResetApplet => l10n.s_nfc_dialog_oath_reset,
|
||||
_DDesc.oathUnlockSession => l10n.s_nfc_dialog_oath_unlock,
|
||||
_DDesc.oathSetPassword => l10n.s_nfc_dialog_oath_set_password,
|
||||
_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,
|
||||
_ => ''
|
||||
};
|
||||
}
|
||||
Timer.periodic(
|
||||
const Duration(milliseconds: 200),
|
||||
(timer) {
|
||||
if (!ref.read(nfcActivityWidgetNotifier.select((s) => s.isShowing))) {
|
||||
timer.cancel();
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Future<void> _updateDialogState(int? title, int? description) async {
|
||||
await _withContext((context) async {
|
||||
_controller?.updateContent(
|
||||
title: _getTitle(context, title),
|
||||
description: _getDialogDescription(context, description),
|
||||
icon: (_DDesc.fromId(description) != _DDesc.oathActionFailure)
|
||||
? _icon
|
||||
: const Icon(Icons.warning_amber_rounded, size: 64),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showDialog(int title, int description) async {
|
||||
_controller = await _withContext((context) async {
|
||||
return promptUserInteraction(
|
||||
context,
|
||||
title: _getTitle(context, title),
|
||||
description: _getDialogDescription(context, description),
|
||||
icon: _icon,
|
||||
onCancel: () {
|
||||
_channel.invokeMethod('cancel');
|
||||
},
|
||||
);
|
||||
});
|
||||
await completer.future;
|
||||
}
|
||||
}
|
||||
|
||||
class _NfcActivityWidgetView extends StatelessWidget {
|
||||
final bool inProgress;
|
||||
final String? title;
|
||||
final String? subtitle;
|
||||
|
||||
const _NfcActivityWidgetView(
|
||||
{required this.title, this.subtitle, this.inProgress = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
Text(title ?? 'Missing title',
|
||||
textAlign: TextAlign.center,
|
||||
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
|
||||
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 =>
|
||||
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",
|
||||
"s_allow_screenshots": "Bildschirmfotos erlauben",
|
||||
|
||||
"l_nfc_dialog_tap_key": "Halten Sie Ihren Schlüssel dagegen",
|
||||
"s_nfc_dialog_operation_success": "Erfolgreich",
|
||||
"s_nfc_dialog_operation_failed": "Fehlgeschlagen",
|
||||
"@_ndef_oath_actions": {},
|
||||
"s_nfc_dialog_oath_reset": null,
|
||||
"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": "Aktion: OATH-Anwendung entsperren",
|
||||
"s_nfc_dialog_oath_set_password": "Aktion: OATH-Passwort setzen",
|
||||
"s_nfc_dialog_oath_unset_password": "Aktion: OATH-Passwort entfernen",
|
||||
"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_oath_unlock": null,
|
||||
"s_nfc_dialog_oath_unlock_processing": null,
|
||||
"s_nfc_dialog_oath_unlock_success": null,
|
||||
"s_nfc_dialog_oath_unlock_failure": null,
|
||||
|
||||
"s_nfc_dialog_fido_reset": "Aktion: FIDO-Anwendung zurücksetzen",
|
||||
"s_nfc_dialog_fido_unlock": "Aktion: FIDO-Anwendung entsperren",
|
||||
"l_nfc_dialog_fido_set_pin": "Aktion: FIDO-PIN setzen oder ändern",
|
||||
"s_nfc_dialog_fido_delete_credential": "Aktion: Passkey löschen",
|
||||
"s_nfc_dialog_fido_delete_fingerprint": "Aktion: Fingerabdruck löschen",
|
||||
"s_nfc_dialog_fido_rename_fingerprint": "Aktion: Fingerabdruck umbenennen",
|
||||
"s_nfc_dialog_fido_failure": "FIDO-Operation fehlgeschlagen",
|
||||
"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_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": {},
|
||||
"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",
|
||||
"s_allow_screenshots": "Allow screenshots",
|
||||
|
||||
"l_nfc_dialog_tap_key": "Tap and hold your key",
|
||||
"s_nfc_dialog_operation_success": "Success",
|
||||
"s_nfc_dialog_operation_failed": "Failed",
|
||||
"@_ndef_oath_actions": {},
|
||||
"s_nfc_dialog_oath_reset": "reset Accounts",
|
||||
"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": "Action: unlock OATH application",
|
||||
"s_nfc_dialog_oath_set_password": "Action: set OATH password",
|
||||
"s_nfc_dialog_oath_unset_password": "Action: remove OATH password",
|
||||
"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_oath_unlock": "unlock",
|
||||
"s_nfc_dialog_oath_unlock_processing": "Unlocking",
|
||||
"s_nfc_dialog_oath_unlock_success": "Accounts unlocked",
|
||||
"s_nfc_dialog_oath_unlock_failure": "Failed to unlock",
|
||||
|
||||
"s_nfc_dialog_fido_reset": "Action: reset FIDO application",
|
||||
"s_nfc_dialog_fido_unlock": "Action: unlock FIDO application",
|
||||
"l_nfc_dialog_fido_set_pin": "Action: set or change the FIDO PIN",
|
||||
"s_nfc_dialog_fido_delete_credential": "Action: delete Passkey",
|
||||
"s_nfc_dialog_fido_delete_fingerprint": "Action: delete fingerprint",
|
||||
"s_nfc_dialog_fido_rename_fingerprint": "Action: rename fingerprint",
|
||||
"s_nfc_dialog_fido_failure": "FIDO operation failed",
|
||||
"s_nfc_dialog_oath_set_password": "set password",
|
||||
"s_nfc_dialog_oath_change_password": "change password",
|
||||
"s_nfc_dialog_oath_set_password_processing": "Setting password",
|
||||
"s_nfc_dialog_oath_change_password_processing": "Changing password",
|
||||
"s_nfc_dialog_oath_set_password_success": "Password set",
|
||||
"s_nfc_dialog_oath_change_password_success": "Password changed",
|
||||
"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": {},
|
||||
"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",
|
||||
"s_allow_screenshots": "Autoriser captures d'écran",
|
||||
|
||||
"l_nfc_dialog_tap_key": "Appuyez et maintenez votre clé",
|
||||
"s_nfc_dialog_operation_success": "Succès",
|
||||
"s_nfc_dialog_operation_failed": "Échec",
|
||||
"@_ndef_oath_actions": {},
|
||||
"s_nfc_dialog_oath_reset": null,
|
||||
"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": "Action\u00a0: débloquer applet OATH",
|
||||
"s_nfc_dialog_oath_set_password": "Action\u00a0: définir mot de passe OATH",
|
||||
"s_nfc_dialog_oath_unset_password": "Action\u00a0: supprimer mot de passe OATH",
|
||||
"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_oath_unlock": null,
|
||||
"s_nfc_dialog_oath_unlock_processing": null,
|
||||
"s_nfc_dialog_oath_unlock_success": null,
|
||||
"s_nfc_dialog_oath_unlock_failure": null,
|
||||
|
||||
"s_nfc_dialog_fido_reset": "Action : réinitialiser l'application FIDO",
|
||||
"s_nfc_dialog_fido_unlock": "Action : déverrouiller l'application FIDO",
|
||||
"l_nfc_dialog_fido_set_pin": "Action : définir ou modifier le code PIN FIDO",
|
||||
"s_nfc_dialog_fido_delete_credential": "Action : supprimer le Passkey",
|
||||
"s_nfc_dialog_fido_delete_fingerprint": "Action : supprimer l'empreinte digitale",
|
||||
"s_nfc_dialog_fido_rename_fingerprint": "Action : renommer l'empreinte digitale",
|
||||
"s_nfc_dialog_fido_failure": "Échec de l'opération FIDO",
|
||||
"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_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": {},
|
||||
"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を使用できます",
|
||||
"s_allow_screenshots": "スクリーンショットを許可",
|
||||
|
||||
"l_nfc_dialog_tap_key": "キーをタップして長押しします",
|
||||
"s_nfc_dialog_operation_success": "成功",
|
||||
"s_nfc_dialog_operation_failed": "失敗",
|
||||
|
||||
"@_ndef_oath_actions": {},
|
||||
"s_nfc_dialog_oath_reset": "アクション:OATHアプレットをリセット",
|
||||
"s_nfc_dialog_oath_unlock": "アクション:OATHアプレットをロック解除",
|
||||
"s_nfc_dialog_oath_set_password": "アクション:OATHパスワードを設定",
|
||||
"s_nfc_dialog_oath_unset_password": "アクション:OATHパスワードを削除",
|
||||
"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_reset_processing": null,
|
||||
"s_nfc_dialog_oath_reset_success": null,
|
||||
"s_nfc_dialog_oath_reset_failure": null,
|
||||
|
||||
"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_processing": null,
|
||||
"s_nfc_dialog_fido_reset_success": null,
|
||||
"s_nfc_dialog_fido_reset_failure": null,
|
||||
|
||||
"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_fingerprint": "アクション: 指紋の削除",
|
||||
"s_nfc_dialog_fido_rename_fingerprint": "アクション: 指紋の名前を変更する",
|
||||
"s_nfc_dialog_fido_failure": "FIDO操作に失敗しました",
|
||||
"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": {},
|
||||
"p_ndef_set_otp": "OTPコードがYubiKeyからクリップボードに正常にコピーされました。",
|
||||
|
@ -885,28 +885,95 @@
|
||||
"l_launch_app_on_usb_off": "Inne aplikacje mogą korzystać z YubiKey przez USB",
|
||||
"s_allow_screenshots": "Zezwalaj na zrzuty ekranu",
|
||||
|
||||
"l_nfc_dialog_tap_key": null,
|
||||
"s_nfc_dialog_operation_success": "Powodzenie",
|
||||
"s_nfc_dialog_operation_failed": "Niepowodzenie",
|
||||
"@_ndef_oath_actions": {},
|
||||
"s_nfc_dialog_oath_reset": null,
|
||||
"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": "Działanie: odblokuj aplet OATH",
|
||||
"s_nfc_dialog_oath_set_password": "Działanie: ustaw hasło OATH",
|
||||
"s_nfc_dialog_oath_unset_password": "Działanie: usuń hasło OATH",
|
||||
"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_unlock": null,
|
||||
"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": 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_processing": null,
|
||||
"s_nfc_dialog_fido_reset_success": null,
|
||||
"s_nfc_dialog_fido_reset_failure": 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_fingerprint": null,
|
||||
"s_nfc_dialog_fido_rename_fingerprint": null,
|
||||
"s_nfc_dialog_fido_failure": 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": {},
|
||||
"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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
const defaultPrimaryColor = Colors.lightGreen;
|
||||
|
||||
@ -50,6 +51,9 @@ class AppTheme {
|
||||
fontFamily: 'Roboto',
|
||||
appBarTheme: const AppBarTheme(
|
||||
color: Colors.transparent,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
statusBarColor: Colors.transparent),
|
||||
),
|
||||
listTileTheme: const ListTileThemeData(
|
||||
// For alignment under menu button
|
||||
@ -81,6 +85,9 @@ class AppTheme {
|
||||
scaffoldBackgroundColor: colorScheme.surface,
|
||||
appBarTheme: const AppBarTheme(
|
||||
color: Colors.transparent,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light,
|
||||
statusBarColor: Colors.transparent),
|
||||
),
|
||||
listTileTheme: const ListTileThemeData(
|
||||
// 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