2022-10-04 13:12:54 +03:00
|
|
|
/*
|
2023-01-20 15:21:30 +03:00
|
|
|
* Copyright (C) 2022-2023 Yubico.
|
2022-10-04 13:12:54 +03:00
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2021-11-24 10:44:28 +03:00
|
|
|
import 'package:flutter/material.dart';
|
2023-02-28 13:34:29 +03:00
|
|
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
2023-10-18 16:34:31 +03:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
|
2023-02-28 13:34:29 +03:00
|
|
|
import '../../android/app_methods.dart';
|
2023-10-18 16:34:31 +03:00
|
|
|
import '../../android/qr_scanner/qr_scanner_provider.dart';
|
2023-02-28 13:34:29 +03:00
|
|
|
import '../../android/state.dart';
|
2022-12-20 16:14:14 +03:00
|
|
|
import '../../core/state.dart';
|
2023-10-18 16:34:31 +03:00
|
|
|
import '../../exception/cancellation_exception.dart';
|
2024-01-22 18:14:14 +03:00
|
|
|
import '../../fido/views/fingerprints_screen.dart';
|
|
|
|
import '../../fido/views/passkeys_screen.dart';
|
|
|
|
import '../../fido/views/webauthn_page.dart';
|
2024-01-26 12:36:10 +03:00
|
|
|
import '../../management/views/management_screen.dart';
|
2021-11-24 10:44:28 +03:00
|
|
|
import '../../oath/views/oath_screen.dart';
|
2023-12-13 21:35:17 +03:00
|
|
|
import '../../otp/views/otp_screen.dart';
|
2023-04-27 10:13:38 +03:00
|
|
|
import '../../piv/views/piv_screen.dart';
|
2023-02-28 13:34:29 +03:00
|
|
|
import '../../widgets/custom_icons.dart';
|
2024-01-26 12:36:10 +03:00
|
|
|
import '../message.dart';
|
2022-11-08 13:50:36 +03:00
|
|
|
import '../models.dart';
|
|
|
|
import '../state.dart';
|
|
|
|
import 'device_error_screen.dart';
|
|
|
|
import 'message_page.dart';
|
2021-11-24 10:44:28 +03:00
|
|
|
|
|
|
|
class MainPage extends ConsumerWidget {
|
2022-05-12 10:56:55 +03:00
|
|
|
const MainPage({super.key});
|
2021-11-24 10:44:28 +03:00
|
|
|
|
2022-03-25 18:24:15 +03:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2023-02-28 13:34:29 +03:00
|
|
|
final l10n = AppLocalizations.of(context)!;
|
2022-03-25 10:59:02 +03:00
|
|
|
ref.listen<Function(BuildContext)?>(
|
2022-04-28 16:23:04 +03:00
|
|
|
contextConsumer,
|
2022-03-25 10:59:02 +03:00
|
|
|
(previous, next) {
|
|
|
|
next?.call(context);
|
|
|
|
},
|
|
|
|
);
|
2022-11-25 13:46:30 +03:00
|
|
|
|
2023-02-08 19:12:49 +03:00
|
|
|
if (isAndroid) {
|
2023-02-28 13:34:29 +03:00
|
|
|
isNfcEnabled().then((value) =>
|
|
|
|
ref.read(androidNfcStateProvider.notifier).setNfcEnabled(value));
|
2023-02-08 19:12:49 +03:00
|
|
|
}
|
|
|
|
|
2022-06-10 17:02:07 +03:00
|
|
|
// If the current device changes, we need to pop any open dialogs.
|
2022-06-28 20:51:58 +03:00
|
|
|
ref.listen<AsyncValue<YubiKeyData>>(currentDeviceDataProvider, (_, __) {
|
2022-06-10 17:02:07 +03:00
|
|
|
Navigator.of(context).popUntil((route) {
|
|
|
|
return route.isFirst ||
|
|
|
|
[
|
|
|
|
'device_picker',
|
|
|
|
'settings',
|
|
|
|
'about',
|
|
|
|
'licenses',
|
2022-09-13 13:55:17 +03:00
|
|
|
'user_interaction_prompt',
|
2022-09-14 14:26:55 +03:00
|
|
|
'oath_add_account',
|
2023-02-24 12:59:12 +03:00
|
|
|
'oath_icon_pack_dialog',
|
2023-03-03 19:17:29 +03:00
|
|
|
'android_qr_scanner_view',
|
2022-06-10 17:02:07 +03:00
|
|
|
].contains(route.settings.name);
|
|
|
|
});
|
|
|
|
});
|
2022-03-14 12:57:00 +03:00
|
|
|
|
2022-06-28 20:51:58 +03:00
|
|
|
final deviceNode = ref.watch(currentDeviceProvider);
|
2022-11-07 17:44:35 +03:00
|
|
|
final noKeyImage = Image.asset(
|
2023-12-20 17:44:43 +03:00
|
|
|
'assets/graphics/no-key.png',
|
2022-11-07 17:44:35 +03:00
|
|
|
filterQuality: FilterQuality.medium,
|
|
|
|
scale: 2,
|
2023-12-20 17:09:31 +03:00
|
|
|
color: Theme.of(context).colorScheme.primary,
|
2022-11-07 17:44:35 +03:00
|
|
|
);
|
2022-06-28 20:51:58 +03:00
|
|
|
if (deviceNode == null) {
|
2022-09-14 14:26:55 +03:00
|
|
|
if (isAndroid) {
|
2023-02-08 19:12:49 +03:00
|
|
|
var hasNfcSupport = ref.watch(androidNfcSupportProvider);
|
|
|
|
var isNfcEnabled = ref.watch(androidNfcStateProvider);
|
2022-09-14 14:26:55 +03:00
|
|
|
return MessagePage(
|
2024-01-18 14:16:15 +03:00
|
|
|
centered: true,
|
2022-11-07 17:44:35 +03:00
|
|
|
graphic: noKeyImage,
|
2023-02-09 12:30:07 +03:00
|
|
|
message: hasNfcSupport && isNfcEnabled
|
2023-02-28 17:02:12 +03:00
|
|
|
? l10n.l_insert_or_tap_yk
|
|
|
|
: l10n.l_insert_yk,
|
2024-01-26 16:20:29 +03:00
|
|
|
actionsBuilder: (context, expanded) => [
|
2023-02-09 12:30:07 +03:00
|
|
|
if (hasNfcSupport && !isNfcEnabled)
|
|
|
|
ElevatedButton.icon(
|
2023-03-02 14:45:55 +03:00
|
|
|
label: Text(l10n.s_enable_nfc),
|
2023-02-09 12:30:07 +03:00
|
|
|
icon: nfcIcon,
|
|
|
|
onPressed: () async {
|
|
|
|
await openNfcSettings();
|
|
|
|
})
|
|
|
|
],
|
2022-12-08 18:40:00 +03:00
|
|
|
actionButtonBuilder: (context) => IconButton(
|
2022-09-14 16:30:19 +03:00
|
|
|
icon: const Icon(Icons.person_add_alt_1),
|
2023-03-02 14:45:55 +03:00
|
|
|
tooltip: l10n.s_add_account,
|
2022-09-15 12:47:39 +03:00
|
|
|
onPressed: () async {
|
2023-10-27 11:10:23 +03:00
|
|
|
final withContext = ref.read(withContextProvider);
|
|
|
|
final qrScanner = ref.read(qrScannerProvider);
|
|
|
|
if (qrScanner != null) {
|
2022-11-08 16:06:27 +03:00
|
|
|
try {
|
2023-10-27 11:10:23 +03:00
|
|
|
final qrData = await qrScanner.scanQr();
|
|
|
|
await AndroidQrScanner.handleScannedData(
|
|
|
|
qrData, withContext, qrScanner, l10n);
|
2022-11-08 16:06:27 +03:00
|
|
|
} on CancellationException catch (_) {
|
|
|
|
// ignored - user cancelled
|
|
|
|
return;
|
2022-09-15 12:47:39 +03:00
|
|
|
}
|
2023-12-05 18:50:06 +03:00
|
|
|
} else {
|
|
|
|
// no QR scanner - enter data manually
|
|
|
|
await AndroidQrScanner.showAccountManualEntryDialog(
|
|
|
|
withContext, l10n);
|
2022-09-15 12:47:39 +03:00
|
|
|
}
|
2022-09-14 14:26:55 +03:00
|
|
|
},
|
2022-09-13 13:55:17 +03:00
|
|
|
),
|
2022-09-14 14:26:55 +03:00
|
|
|
);
|
|
|
|
} else {
|
2022-11-07 17:44:35 +03:00
|
|
|
return MessagePage(
|
2024-01-18 14:16:15 +03:00
|
|
|
centered: true,
|
2023-12-20 17:09:31 +03:00
|
|
|
delayedContent: false,
|
2022-11-07 17:44:35 +03:00
|
|
|
graphic: noKeyImage,
|
2023-12-20 17:09:31 +03:00
|
|
|
header: l10n.l_insert_yk,
|
2022-11-07 17:44:35 +03:00
|
|
|
);
|
2022-09-14 14:26:55 +03:00
|
|
|
}
|
2022-06-28 20:51:58 +03:00
|
|
|
} else {
|
|
|
|
return ref.watch(currentDeviceDataProvider).when(
|
|
|
|
data: (data) {
|
|
|
|
final app = ref.watch(currentAppProvider);
|
2024-01-26 13:59:37 +03:00
|
|
|
final capabilities = app.getCapabilities();
|
2023-01-20 17:34:24 +03:00
|
|
|
if (data.info.supportedCapabilities.isEmpty &&
|
2023-01-20 15:21:30 +03:00
|
|
|
data.name == 'Unrecognized device') {
|
2023-02-28 13:34:29 +03:00
|
|
|
return MessagePage(
|
2024-01-26 12:36:10 +03:00
|
|
|
centered: true,
|
|
|
|
graphic: Icon(
|
|
|
|
Icons.help_outlined,
|
|
|
|
size: 96,
|
|
|
|
color: Theme.of(context).colorScheme.error,
|
|
|
|
),
|
2023-03-02 14:45:55 +03:00
|
|
|
header: l10n.s_yk_not_recognized,
|
2023-01-20 15:21:30 +03:00
|
|
|
);
|
2023-01-26 13:52:01 +03:00
|
|
|
} else if (app.getAvailability(data) ==
|
|
|
|
Availability.unsupported) {
|
2022-08-04 09:24:12 +03:00
|
|
|
return MessagePage(
|
2024-01-26 12:36:10 +03:00
|
|
|
title: app.getDisplayName(l10n),
|
2024-01-26 13:59:37 +03:00
|
|
|
capabilities: capabilities,
|
2023-03-02 14:45:55 +03:00
|
|
|
header: l10n.s_app_not_supported,
|
2024-01-26 13:59:37 +03:00
|
|
|
message: l10n.l_app_not_supported_on_yk(capabilities
|
|
|
|
.map((c) => c.getDisplayName(l10n))
|
|
|
|
.join(',')),
|
2023-02-09 12:14:31 +03:00
|
|
|
);
|
2022-08-04 09:24:12 +03:00
|
|
|
} else if (app.getAvailability(data) != Availability.enabled) {
|
|
|
|
return MessagePage(
|
2024-01-26 12:36:10 +03:00
|
|
|
title: app.getDisplayName(l10n),
|
2024-01-26 13:59:37 +03:00
|
|
|
capabilities: capabilities,
|
2023-03-02 14:45:55 +03:00
|
|
|
header: l10n.s_app_disabled,
|
2024-01-26 13:59:37 +03:00
|
|
|
message: l10n.l_app_disabled_desc(capabilities
|
|
|
|
.map((c) => c.getDisplayName(l10n))
|
|
|
|
.join(',')),
|
2024-01-26 16:20:29 +03:00
|
|
|
actionsBuilder: (context, expanded) => [
|
2024-01-26 12:36:10 +03:00
|
|
|
ActionChip(
|
|
|
|
label: Text(data.info.version.major > 4
|
|
|
|
? l10n.s_toggle_applications
|
|
|
|
: l10n.s_toggle_interfaces),
|
|
|
|
onPressed: () async {
|
|
|
|
await showBlurDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => ManagementScreen(data),
|
|
|
|
);
|
|
|
|
},
|
|
|
|
avatar: const Icon(Icons.construction),
|
|
|
|
)
|
|
|
|
],
|
2023-02-09 12:14:31 +03:00
|
|
|
);
|
2022-06-28 20:51:58 +03:00
|
|
|
}
|
|
|
|
|
2023-05-22 12:52:49 +03:00
|
|
|
return switch (app) {
|
2024-01-22 13:41:15 +03:00
|
|
|
Application.accounts => OathScreen(data.node.path),
|
2024-01-22 18:14:14 +03:00
|
|
|
Application.webauthn => const WebAuthnScreen(),
|
|
|
|
Application.passkeys => PasskeysScreen(data),
|
|
|
|
Application.fingerprints => FingerprintsScreen(data),
|
2024-01-22 13:41:15 +03:00
|
|
|
Application.certificates => PivScreen(data.node.path),
|
|
|
|
Application.slots => OtpScreen(data.node.path),
|
2023-05-22 12:52:49 +03:00
|
|
|
_ => MessagePage(
|
2023-03-02 14:45:55 +03:00
|
|
|
header: l10n.s_app_not_supported,
|
2023-02-28 17:02:12 +03:00
|
|
|
message: l10n.l_app_not_supported_desc,
|
2023-05-22 12:52:49 +03:00
|
|
|
),
|
|
|
|
};
|
2022-06-28 20:51:58 +03:00
|
|
|
},
|
|
|
|
loading: () => DeviceErrorScreen(deviceNode),
|
|
|
|
error: (error, _) => DeviceErrorScreen(deviceNode, error: error),
|
|
|
|
);
|
2021-11-24 10:44:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|