mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
Merge PR #955.
This commit is contained in:
commit
e71b619c67
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -23,9 +23,12 @@ import android.hardware.camera2.CameraManager
|
||||
import android.hardware.usb.UsbDevice
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.nfc.NfcAdapter
|
||||
import android.nfc.NfcAdapter.STATE_ON
|
||||
import android.nfc.NfcAdapter.STATE_TURNING_OFF
|
||||
import android.nfc.Tag
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings.ACTION_NFC_SETTINGS
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.WindowCompat
|
||||
@ -47,6 +50,7 @@ import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.json.JSONObject
|
||||
import java.io.Closeable
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@ -62,6 +66,7 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
// receives broadcasts when QR Scanner camera is closed
|
||||
private val qrScannerCameraClosedBR = QRScannerCameraClosedBR()
|
||||
private val nfcAdapterStateChangeBR = NfcAdapterStateChangedBR()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -155,11 +160,13 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
registerReceiver(qrScannerCameraClosedBR, QRScannerCameraClosedBR.intentFilter)
|
||||
registerReceiver(nfcAdapterStateChangeBR, NfcAdapterStateChangedBR.intentFilter)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
unregisterReceiver(qrScannerCameraClosedBR)
|
||||
unregisterReceiver(nfcAdapterStateChangeBR)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
@ -322,6 +329,23 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class NfcAdapterStateChangedBR : BroadcastReceiver() {
|
||||
companion object {
|
||||
val intentFilter = IntentFilter("android.nfc.action.ADAPTER_STATE_CHANGED")
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
intent?.let {
|
||||
val state = it.getIntExtra("android.nfc.extra.ADAPTER_STATE", 0)
|
||||
Log.d(TAG, "NfcAdapter state changed to $state")
|
||||
if (state == STATE_ON || state == STATE_TURNING_OFF) {
|
||||
(context as? MainActivity)?.appMethodChannel?.nfcAdapterStateChanged(state == STATE_ON)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
inner class AppMethodChannel(messenger: BinaryMessenger) {
|
||||
|
||||
private val methodChannel = MethodChannel(messenger, "app.methods")
|
||||
@ -356,10 +380,31 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
cameraService.cameraIdList.isNotEmpty()
|
||||
)
|
||||
}
|
||||
"hasNfc" -> result.success(
|
||||
packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)
|
||||
)
|
||||
"isNfcEnabled" -> {
|
||||
val nfcAdapter = NfcAdapter.getDefaultAdapter(this@MainActivity)
|
||||
|
||||
result.success(
|
||||
nfcAdapter != null && nfcAdapter.isEnabled
|
||||
)
|
||||
}
|
||||
"openNfcSettings" -> {
|
||||
startActivity(Intent(ACTION_NFC_SETTINGS))
|
||||
result.success(true)
|
||||
}
|
||||
else -> Log.w(TAG, "Unknown app method: ${methodCall.method}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun nfcAdapterStateChanged(value: Boolean) {
|
||||
methodChannel.invokeMethod(
|
||||
"nfcAdapterStateChanged",
|
||||
JSONObject(mapOf("nfcEnabled" to value)).toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun allowScreenshots(value: Boolean): Boolean {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/android/state.dart';
|
||||
|
||||
const appMethodsChannel = MethodChannel('app.methods');
|
||||
|
||||
@ -22,6 +26,18 @@ Future<bool> getHasCamera() async {
|
||||
return await appMethodsChannel.invokeMethod('hasCamera');
|
||||
}
|
||||
|
||||
Future<bool> getHasNfc() async {
|
||||
return await appMethodsChannel.invokeMethod('hasNfc');
|
||||
}
|
||||
|
||||
Future<bool> isNfcEnabled() async {
|
||||
return await appMethodsChannel.invokeMethod('isNfcEnabled');
|
||||
}
|
||||
|
||||
Future<void> openNfcSettings() async {
|
||||
await appMethodsChannel.invokeMethod('openNfcSettings');
|
||||
}
|
||||
|
||||
Future<int> getAndroidSdkVersion() async {
|
||||
return await appMethodsChannel.invokeMethod('getAndroidSdkVersion');
|
||||
}
|
||||
@ -30,3 +46,22 @@ Future<void> setPrimaryClip(String toClipboard, bool isSensitive) async {
|
||||
await appMethodsChannel.invokeMethod('setPrimaryClip',
|
||||
{'toClipboard': toClipboard, 'isSensitive': isSensitive});
|
||||
}
|
||||
|
||||
void setupAppMethodsChannel(WidgetRef ref) {
|
||||
appMethodsChannel.setMethodCallHandler((call) async {
|
||||
final args = jsonDecode(call.arguments);
|
||||
switch (call.method) {
|
||||
case 'nfcAdapterStateChanged':
|
||||
{
|
||||
var nfcEnabled = args['nfcEnabled'];
|
||||
ref.read(androidNfcStateProvider.notifier).setNfcEnabled(nfcEnabled);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw PlatformException(
|
||||
code: 'NotImplemented',
|
||||
message: 'Method ${call.method} is not implemented',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -82,6 +82,7 @@ Future<Widget> initialize() async {
|
||||
(ref) => ref.watch(androidClipboardProvider),
|
||||
),
|
||||
androidSdkVersionProvider.overrideWithValue(await getAndroidSdkVersion()),
|
||||
androidNfcSupportProvider.overrideWithValue(await getHasNfc()),
|
||||
supportedThemesProvider
|
||||
.overrideWith(
|
||||
(ref) => ref.watch(androidSupportedThemesProvider),
|
||||
@ -99,6 +100,8 @@ Future<Widget> initialize() async {
|
||||
// set context which will handle otpauth links
|
||||
setupOtpAuthLinkHandler(context);
|
||||
|
||||
setupAppMethodsChannel(ref);
|
||||
|
||||
return const MainPage();
|
||||
},
|
||||
)),
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -62,8 +62,22 @@ class _AndroidClipboard extends AppClipboard {
|
||||
}
|
||||
}
|
||||
|
||||
class NfcStateNotifier extends StateNotifier<bool> {
|
||||
NfcStateNotifier(): super(false);
|
||||
|
||||
void setNfcEnabled(bool value) {
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
final androidSdkVersionProvider = Provider<int>((ref) => -1);
|
||||
|
||||
final androidNfcSupportProvider = Provider<bool>((ref) => false);
|
||||
|
||||
final androidNfcStateProvider = StateNotifierProvider<NfcStateNotifier, bool>((ref) =>
|
||||
NfcStateNotifier()
|
||||
);
|
||||
|
||||
final androidSupportedThemesProvider = StateProvider<List<ThemeMode>>((ref) {
|
||||
if (ref.read(androidSdkVersionProvider) < 29) {
|
||||
// the user can select from light or dark theme of the app
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Yubico.
|
||||
* Copyright (C) 2022-2023 Yubico.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,15 +17,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:yubico_authenticator/android/state.dart';
|
||||
import 'package:yubico_authenticator/app/logging.dart';
|
||||
|
||||
import '../app/models.dart';
|
||||
import 'app_methods.dart';
|
||||
|
||||
final _log = Logger('android.window_state_provider');
|
||||
|
||||
final _windowStateProvider =
|
||||
StateNotifierProvider<_WindowStateNotifier, WindowState>(
|
||||
(ref) => _WindowStateNotifier());
|
||||
(ref) => _WindowStateNotifier(ref));
|
||||
|
||||
final androidWindowStateProvider = Provider<WindowState>(
|
||||
(ref) => ref.watch(_windowStateProvider),
|
||||
@ -33,7 +35,8 @@ final androidWindowStateProvider = Provider<WindowState>(
|
||||
|
||||
class _WindowStateNotifier extends StateNotifier<WindowState>
|
||||
with WidgetsBindingObserver {
|
||||
_WindowStateNotifier()
|
||||
final StateNotifierProviderRef<_WindowStateNotifier, WindowState> _ref;
|
||||
_WindowStateNotifier(this._ref)
|
||||
: super(WindowState(focused: true, visible: true, active: true)) {
|
||||
_init();
|
||||
}
|
||||
@ -52,6 +55,11 @@ class _WindowStateNotifier extends StateNotifier<WindowState>
|
||||
visible: requestedState,
|
||||
active: requestedState);
|
||||
_log.debug('Updated windowState to $state');
|
||||
if (lifeCycleState == AppLifecycleState.resumed) {
|
||||
_log.debug('Reading nfc enabled value');
|
||||
isNfcEnabled().then((value) =>
|
||||
_ref.read(androidNfcStateProvider.notifier).setNfcEnabled(value));
|
||||
}
|
||||
} else {
|
||||
_log.debug('Ignoring appLifecycleStateChange');
|
||||
}
|
||||
|
@ -16,6 +16,9 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/android/app_methods.dart';
|
||||
import 'package:yubico_authenticator/android/state.dart';
|
||||
import 'package:yubico_authenticator/widgets/custom_icons.dart';
|
||||
|
||||
import '../../exception/cancellation_exception.dart';
|
||||
import '../../core/state.dart';
|
||||
@ -41,6 +44,10 @@ class MainPage extends ConsumerWidget {
|
||||
},
|
||||
);
|
||||
|
||||
if (isAndroid) {
|
||||
isNfcEnabled().then((value) => ref.read(androidNfcStateProvider.notifier).setNfcEnabled(value));
|
||||
}
|
||||
|
||||
// If the current device changes, we need to pop any open dialogs.
|
||||
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, __) {
|
||||
Navigator.of(context).popUntil((route) {
|
||||
@ -67,9 +74,23 @@ class MainPage extends ConsumerWidget {
|
||||
);
|
||||
if (deviceNode == null) {
|
||||
if (isAndroid) {
|
||||
|
||||
var hasNfcSupport = ref.watch(androidNfcSupportProvider);
|
||||
var isNfcEnabled = ref.watch(androidNfcStateProvider);
|
||||
return MessagePage(
|
||||
graphic: noKeyImage,
|
||||
message: 'Insert or tap your YubiKey',
|
||||
message: hasNfcSupport && isNfcEnabled
|
||||
? 'Tap or insert your YubiKey'
|
||||
: 'Insert your YubiKey',
|
||||
actions: [
|
||||
if (hasNfcSupport && !isNfcEnabled)
|
||||
ElevatedButton.icon(
|
||||
label: const Text('Enable NFC'),
|
||||
icon: nfcIcon,
|
||||
onPressed: () async {
|
||||
await openNfcSettings();
|
||||
})
|
||||
],
|
||||
actionButtonBuilder: (context) => IconButton(
|
||||
icon: const Icon(Icons.person_add_alt_1),
|
||||
tooltip: 'Add account',
|
||||
|
Loading…
Reference in New Issue
Block a user