yubioath-flutter/lib/oath/views/actions.dart

150 lines
5.3 KiB
Dart
Raw Normal View History

2023-04-27 10:13:38 +03:00
/*
* 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.
*/
2023-02-10 19:37:42 +03:00
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
2023-02-10 19:37:42 +03:00
import '../../app/message.dart';
import '../../app/models.dart';
2023-02-10 19:37:42 +03:00
import '../../app/shortcuts.dart';
import '../../app/state.dart';
import '../../core/state.dart';
2023-02-10 19:37:42 +03:00
import '../../exception/cancellation_exception.dart';
import '../features.dart' as features;
import '../models.dart';
2023-02-10 19:37:42 +03:00
import '../state.dart';
import 'delete_account_dialog.dart';
import 'rename_account_dialog.dart';
2023-02-10 19:37:42 +03:00
class TogglePinIntent extends Intent {
final OathCredential target;
const TogglePinIntent(this.target);
2023-02-10 19:37:42 +03:00
}
Future<OathCode?> _calculateCode(
OathCredential credential, WidgetRef ref) async {
final node = ref.read(currentDeviceProvider)!;
try {
return await ref
.read(credentialListProvider(node.path).notifier)
.calculate(credential);
} on CancellationException catch (_) {
return null;
}
}
class OathActions extends ConsumerWidget {
final DevicePath devicePath;
final Map<Type, Action<Intent>> Function(BuildContext context)? actions;
final Widget Function(BuildContext context) builder;
const OathActions(
{super.key,
required this.devicePath,
this.actions,
required this.builder});
@override
Widget build(BuildContext context, WidgetRef ref) {
final withContext = ref.read(withContextProvider);
final hasFeature = ref.read(featureProvider);
return Actions(
actions: {
RefreshIntent<OathCredential>:
CallbackAction<RefreshIntent<OathCredential>>(onInvoke: (intent) {
final credential = intent.target;
final code = ref.read(codeProvider(credential));
if (!(credential.oathType == OathType.totp &&
code != null &&
!ref.read(expiredProvider(code.validTo)))) {
return _calculateCode(credential, ref);
2023-02-10 19:37:42 +03:00
}
return code;
}),
if (hasFeature(features.accountsClipboard))
CopyIntent<OathCredential>:
CallbackAction<CopyIntent<OathCredential>>(
onInvoke: (intent) async {
final credential = intent.target;
var code = ref.read(codeProvider(credential));
if (code == null ||
(credential.oathType == OathType.totp &&
ref.read(expiredProvider(code.validTo)))) {
code = await _calculateCode(credential, ref);
}
if (code != null) {
final clipboard = ref.watch(clipboardProvider);
await clipboard.setText(code.value, isSensitive: true);
if (!clipboard.platformGivesFeedback()) {
await withContext((context) async {
showMessage(context,
AppLocalizations.of(context)!.l_code_copied_clipboard);
});
}
}
return code;
}),
if (hasFeature(features.accountsPin))
TogglePinIntent: CallbackAction<TogglePinIntent>(onInvoke: (intent) {
ref
.read(favoritesProvider.notifier)
.toggleFavorite(intent.target.id);
return null;
}),
if (hasFeature(features.accountsRename))
EditIntent<OathCredential>:
CallbackAction<EditIntent<OathCredential>>(onInvoke: (intent) {
final credentials = ref.read(credentialsProvider);
return withContext((context) => showBlurDialog(
context: context,
builder: (context) => RenameAccountDialog.forOathCredential(
ref,
devicePath,
intent.target,
credentials?.map((e) => (e.issuer, e.name)).toList() ??
[],
)));
}),
if (hasFeature(features.accountsDelete))
DeleteIntent<OathCredential>:
CallbackAction<DeleteIntent<OathCredential>>(
onInvoke: (intent) {
return withContext((context) async =>
await showBlurDialog(
context: context,
builder: (context) => DeleteAccountDialog(
devicePath,
intent.target,
),
) ??
false);
},
),
},
child: Builder(
// Builder to ensure new scope for actions, they can invoke parent actions
builder: (context) {
final child = Builder(builder: builder);
return actions != null
? Actions(actions: actions!(context), child: child)
: child;
},
),
);
}
}