simplify states, add main page widget

This commit is contained in:
Adam Velebil 2023-06-08 15:57:36 +02:00
parent 567f41aa57
commit ae2e04b125
No known key found for this signature in database
GPG Key ID: C9B1E4A3CBBD2E10
8 changed files with 88 additions and 97 deletions

View File

@ -50,6 +50,7 @@ import com.yubico.authenticator.oath.AppLinkMethodChannel
import com.yubico.authenticator.oath.OathManager
import com.yubico.authenticator.oath.OathViewModel
import com.yubico.authenticator.yubikit.NfcActivityDispatcher
import com.yubico.authenticator.yubikit.NfcActivityListener
import com.yubico.authenticator.yubikit.NfcActivityState
import com.yubico.yubikit.android.YubiKitManager
import com.yubico.yubikit.android.transport.nfc.NfcConfiguration
@ -85,6 +86,20 @@ class MainActivity : FlutterFragmentActivity() {
private val logger = LoggerFactory.getLogger(MainActivity::class.java)
private val nfcActivityListener = object : NfcActivityListener {
var appMethodChannel : AppMethodChannel? = null
override fun onChange(newState: NfcActivityState) {
appMethodChannel?.let {
logger.debug("setting nfc activity state to ${newState.name}")
it.nfcActivityStateChanged(newState)
} ?: {
logger.warn("cannot set nfc activity state to ${newState.name} - no method channel")
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -98,7 +113,7 @@ class MainActivity : FlutterFragmentActivity() {
yubikit = YubiKitManager(
UsbYubiKeyManager(this),
NfcYubiKeyManager(this, NfcActivityDispatcher(lifecycleScope))
NfcYubiKeyManager(this, NfcActivityDispatcher(nfcActivityListener))
)
}
@ -136,7 +151,6 @@ class MainActivity : FlutterFragmentActivity() {
this,
::processYubiKey
)
appMethodChannel.nfcActivityStateChanged(NfcActivityState.READY)
hasNfc = true
} catch (e: NfcNotAvailable) {
hasNfc = false
@ -144,7 +158,6 @@ class MainActivity : FlutterFragmentActivity() {
private fun stopNfcDiscovery() {
if (hasNfc) {
appMethodChannel.nfcActivityStateChanged(NfcActivityState.NOT_ACTIVE)
yubikit.stopNfcDiscovery(this)
logger.debug("Stopped nfc discovery")
}
@ -313,6 +326,8 @@ class MainActivity : FlutterFragmentActivity() {
appMethodChannel = AppMethodChannel(messenger)
appLinkMethodChannel = AppLinkMethodChannel(messenger)
nfcActivityListener.appMethodChannel = appMethodChannel
flutterStreams = listOf(
viewModel.deviceInfo.streamTo(this, messenger, "android.devices.deviceInfo"),
oathViewModel.sessionState.streamTo(this, messenger, "android.oath.sessionState"),
@ -328,7 +343,8 @@ class MainActivity : FlutterFragmentActivity() {
viewModel,
oathViewModel,
dialogManager,
appPreferences
appPreferences,
nfcActivityListener
)
else -> null
}
@ -337,6 +353,7 @@ class MainActivity : FlutterFragmentActivity() {
}
override fun cleanUpFlutterEngine(flutterEngine: FlutterEngine) {
nfcActivityListener.appMethodChannel = null
flutterStreams.forEach { it.close() }
super.cleanUpFlutterEngine(flutterEngine)
}

View File

@ -42,6 +42,7 @@ import com.yubico.authenticator.oath.keystore.ClearingMemProvider
import com.yubico.authenticator.oath.keystore.KeyProvider
import com.yubico.authenticator.oath.keystore.KeyStoreProvider
import com.yubico.authenticator.oath.keystore.SharedPrefProvider
import com.yubico.authenticator.yubikit.NfcActivityListener
import com.yubico.authenticator.yubikit.NfcActivityState
import com.yubico.authenticator.yubikit.getDeviceInfo
import com.yubico.authenticator.yubikit.withConnection
@ -76,6 +77,7 @@ class OathManager(
private val oathViewModel: OathViewModel,
private val dialogManager: DialogManager,
private val appPreferences: AppPreferences,
private val nfcActivityListener: NfcActivityListener
) : AppContextManager {
companion object {
const val NFC_DATA_CLEANUP_DELAY = 30L * 1000 // 30s
@ -331,20 +333,13 @@ class OathManager(
"Successfully read Oath session info (and credentials if unlocked) from connected key"
)
(lifecycleOwner as MainActivity).appMethodChannel.nfcActivityStateChanged(
NfcActivityState.PROCESSING_FINISHED
)
nfcActivityListener.onChange(NfcActivityState.PROCESSING_FINISHED)
} catch (e: Exception) {
// OATH not enabled/supported, try to get DeviceInfo over other USB interfaces
logger.error("Failed to connect to CCID", e)
logger.debug("Set nfc activity state to PROCESSING_INTERRUPTED")
(lifecycleOwner as MainActivity).appMethodChannel.nfcActivityStateChanged(
NfcActivityState.PROCESSING_INTERRUPTED
)
lifecycleOwner.appMethodChannel.nfcActivityStateChanged(
NfcActivityState.TAG_PRESENT
)
nfcActivityListener.onChange(NfcActivityState.PROCESSING_INTERRUPTED)
if (device.transport == Transport.USB || e is ApplicationNotAvailableException) {
val deviceInfo = try {

View File

@ -12,7 +12,11 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.slf4j.LoggerFactory
class NfcActivityDispatcher(private val coroutineScope: CoroutineScope) : NfcDispatcher {
interface NfcActivityListener {
fun onChange(newState: NfcActivityState)
}
class NfcActivityDispatcher(private val listener: NfcActivityListener) : NfcDispatcher {
private lateinit var adapter: NfcAdapter
private lateinit var yubikitNfcDispatcher: NfcReaderDispatcher
@ -32,31 +36,29 @@ class NfcActivityDispatcher(private val coroutineScope: CoroutineScope) : NfcDis
yubikitNfcDispatcher.enable(
activity,
nfcConfiguration,
TagInterceptor(activity as MainActivity, coroutineScope, handler)
TagInterceptor(listener, handler)
)
listener.onChange(NfcActivityState.READY)
}
override fun disable(activity: Activity) {
listener.onChange(NfcActivityState.NOT_ACTIVE)
yubikitNfcDispatcher.disable(activity)
logger.info("disabling yubikit NFC activity dispatcher")
}
class TagInterceptor(
private val activity: MainActivity,
private val coroutineScope: CoroutineScope,
private val listener: NfcActivityListener,
private val tagHandler: NfcDispatcher.OnTagHandler
) : NfcDispatcher.OnTagHandler {
private val logger = LoggerFactory.getLogger(TagInterceptor::class.java)
override fun onTag(tag: Tag) {
coroutineScope.launch {
activity.appMethodChannel.nfcActivityStateChanged(NfcActivityState.TAG_PRESENT)
activity.appMethodChannel.nfcActivityStateChanged(NfcActivityState.PROCESSING_STARTED)
logger.info("Calling original onTag")
tagHandler.onTag(tag)
}
listener.onChange(NfcActivityState.PROCESSING_STARTED)
logger.debug("forwarding tag")
tagHandler.onTag(tag)
}
}

View File

@ -3,8 +3,7 @@ package com.yubico.authenticator.yubikit
enum class NfcActivityState(val value: Int) {
NOT_ACTIVE(0),
READY(1),
TAG_PRESENT(2),
PROCESSING_STARTED(3),
PROCESSING_FINISHED(4),
PROCESSING_INTERRUPTED(5)
PROCESSING_STARTED(2),
PROCESSING_FINISHED(3),
PROCESSING_INTERRUPTED(4)
}

View File

@ -78,7 +78,6 @@ class NfcStateNotifier extends StateNotifier<bool> {
enum NfcActivity {
notActive,
ready,
tagPresent,
processingStarted,
processingFinished,
processingInterrupted,
@ -94,10 +93,9 @@ class NfcActivityNotifier extends StateNotifier<NfcActivity> {
var newState = switch (stateValue) {
0 => NfcActivity.notActive,
1 => NfcActivity.ready,
2 => NfcActivity.tagPresent,
3 => NfcActivity.processingStarted,
4 => NfcActivity.processingFinished,
5 => NfcActivity.processingInterrupted,
2 => NfcActivity.processingStarted,
3 => NfcActivity.processingFinished,
4 => NfcActivity.processingInterrupted,
_ => NfcActivity.notActive
};

View File

@ -0,0 +1,41 @@
/*
* 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.
*/
import 'package:flutter/material.dart';
import 'package:yubico_authenticator/android/state.dart';
import 'package:yubico_authenticator/android/views/nfc/nfc_activity_widget.dart';
class MainPageNfcActivityWidget extends StatelessWidget {
final Widget widget;
const MainPageNfcActivityWidget(this.widget, {super.key});
@override
Widget build(BuildContext context) {
return NfcActivityWidget(
width: 128.0,
height: 128.0,
iconView: (nfcActivityState) {
return Opacity(
opacity: switch (nfcActivityState) {
NfcActivity.processingStarted => 1.0,
_ => 0.8
},
child: widget,
);
},
);
}
}

View File

@ -27,73 +27,11 @@ class NfcActivityIcon extends StatelessWidget {
@override
Widget build(BuildContext context) => switch (nfcActivity) {
NfcActivity.notActive ||
NfcActivity.ready =>
const _NfcIconWithOpacity(0.5),
NfcActivity.tagPresent => const _NfcIconWithOpacity(0.8),
NfcActivity.processingStarted => const _NfcIconProcessing(),
NfcActivity.processingFinished => const _NfcIconWithOpacity(0.5),
NfcActivity.processingInterrupted => const _NfcIconWithOpacity(0.5),
NfcActivity.processingStarted => const _NfcIconWithOpacity(1.0),
_ => const _NfcIconWithOpacity(0.8)
};
}
class _NfcIconProcessing extends StatefulWidget {
const _NfcIconProcessing();
@override
State<StatefulWidget> createState() => _NfcIconProcessingState();
}
class _NfcIconProcessingState extends State<_NfcIconProcessing>
with SingleTickerProviderStateMixin {
late Animation<double> animation;
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 100),
vsync: this,
);
animation = CurvedAnimation(parent: _controller, curve: Curves.linear)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => _AnimatedNfcIcon(animation: animation);
}
class _AnimatedNfcIcon extends AnimatedWidget {
const _AnimatedNfcIcon({required Animation<double> animation})
: super(listenable: animation);
static final _opacityTween = Tween<double>(begin: 0.0, end: 1.0);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Opacity(
opacity: _opacityTween.evaluate(animation),
child: const _NfcIcon(),
);
}
}
class _NfcIconWithOpacity extends StatelessWidget {
final double opacity;

View File

@ -20,6 +20,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../android/app_methods.dart';
import '../../android/state.dart';
import '../../android/views/nfc/main_page_nfc_activity_widget.dart';
import '../../exception/cancellation_exception.dart';
import '../../core/state.dart';
import '../../fido/views/fido_screen.dart';
@ -82,7 +83,7 @@ class MainPage extends ConsumerWidget {
var hasNfcSupport = ref.watch(androidNfcSupportProvider);
var isNfcEnabled = ref.watch(androidNfcStateProvider);
return MessagePage(
graphic: noKeyImage,
graphic: MainPageNfcActivityWidget(noKeyImage),
message: hasNfcSupport && isNfcEnabled
? l10n.l_insert_or_tap_yk
: l10n.l_insert_yk,