mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-11-22 00:12:09 +03:00
simplify states, add main page widget
This commit is contained in:
parent
567f41aa57
commit
ae2e04b125
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
};
|
||||
|
||||
|
41
lib/android/views/nfc/main_page_nfc_activity_widget.dart
Normal file
41
lib/android/views/nfc/main_page_nfc_activity_widget.dart
Normal 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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user