2022-10-04 13:12:54 +03:00
|
|
|
/*
|
2024-01-22 20:07:42 +03:00
|
|
|
* Copyright (C) 2022 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.
|
|
|
|
*/
|
|
|
|
|
2022-06-10 14:55:46 +03:00
|
|
|
import 'dart:io';
|
|
|
|
|
2022-06-10 14:49:02 +03:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter/services.dart';
|
2023-02-09 21:12:35 +03:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2023-02-09 15:43:27 +03:00
|
|
|
import 'package:window_manager/window_manager.dart';
|
|
|
|
|
2023-02-09 21:12:35 +03:00
|
|
|
import '../about_page.dart';
|
|
|
|
import '../core/state.dart';
|
2023-02-24 16:23:43 +03:00
|
|
|
import '../desktop/state.dart';
|
2023-02-09 15:43:27 +03:00
|
|
|
import '../oath/keys.dart';
|
2023-02-09 21:12:35 +03:00
|
|
|
import 'message.dart';
|
|
|
|
import 'models.dart';
|
|
|
|
import 'state.dart';
|
2023-06-28 18:14:15 +03:00
|
|
|
import 'views/keys.dart';
|
2023-03-03 18:09:40 +03:00
|
|
|
import 'views/settings_page.dart';
|
2022-06-10 14:49:02 +03:00
|
|
|
|
2023-02-09 15:43:27 +03:00
|
|
|
class CloseIntent extends Intent {
|
|
|
|
const CloseIntent();
|
|
|
|
}
|
|
|
|
|
2023-02-24 16:23:43 +03:00
|
|
|
class HideIntent extends Intent {
|
|
|
|
const HideIntent();
|
|
|
|
}
|
|
|
|
|
2022-06-10 14:49:02 +03:00
|
|
|
class SearchIntent extends Intent {
|
|
|
|
const SearchIntent();
|
|
|
|
}
|
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
class EscapeIntent extends Intent {
|
|
|
|
const EscapeIntent();
|
|
|
|
}
|
|
|
|
|
2023-02-09 21:12:35 +03:00
|
|
|
class NextDeviceIntent extends Intent {
|
|
|
|
const NextDeviceIntent();
|
|
|
|
}
|
|
|
|
|
|
|
|
class SettingsIntent extends Intent {
|
|
|
|
const SettingsIntent();
|
|
|
|
}
|
2022-06-10 14:55:46 +03:00
|
|
|
|
2023-02-09 21:12:35 +03:00
|
|
|
class AboutIntent extends Intent {
|
|
|
|
const AboutIntent();
|
|
|
|
}
|
2023-02-09 15:43:27 +03:00
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
class OpenIntent<T> extends Intent {
|
|
|
|
final T target;
|
|
|
|
const OpenIntent(this.target);
|
2023-02-10 19:37:42 +03:00
|
|
|
}
|
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
class CopyIntent<T> extends Intent {
|
|
|
|
final T target;
|
|
|
|
const CopyIntent(this.target);
|
2023-02-10 19:37:42 +03:00
|
|
|
}
|
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
class EditIntent<T> extends Intent {
|
|
|
|
final T target;
|
|
|
|
const EditIntent(this.target);
|
2023-09-29 15:40:17 +03:00
|
|
|
}
|
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
class DeleteIntent<T> extends Intent {
|
|
|
|
final T target;
|
|
|
|
const DeleteIntent(this.target);
|
|
|
|
}
|
|
|
|
|
|
|
|
class RefreshIntent<T> extends Intent {
|
|
|
|
final T target;
|
|
|
|
const RefreshIntent(this.target);
|
2024-01-09 14:50:26 +03:00
|
|
|
}
|
|
|
|
|
2023-03-07 13:03:31 +03:00
|
|
|
/// Use cmd on macOS, use ctrl on the other platforms
|
|
|
|
SingleActivator ctrlOrCmd(LogicalKeyboardKey key) =>
|
|
|
|
SingleActivator(key, meta: Platform.isMacOS, control: !Platform.isMacOS);
|
2023-02-09 15:43:27 +03:00
|
|
|
|
2024-01-17 18:29:28 +03:00
|
|
|
/// Common shortcuts for items
|
2024-01-18 16:46:15 +03:00
|
|
|
class ItemShortcuts<T> extends StatelessWidget {
|
|
|
|
final T item;
|
|
|
|
final Widget child;
|
|
|
|
const ItemShortcuts({super.key, required this.item, required this.child});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) => Shortcuts(
|
|
|
|
shortcuts: {
|
|
|
|
ctrlOrCmd(LogicalKeyboardKey.keyR): RefreshIntent<T>(item),
|
|
|
|
ctrlOrCmd(LogicalKeyboardKey.keyC): CopyIntent<T>(item),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.copy): CopyIntent<T>(item),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.delete):
|
|
|
|
DeleteIntent<T>(item),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.enter): OpenIntent<T>(item),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.space): OpenIntent<T>(item),
|
|
|
|
},
|
|
|
|
child: child,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Global keyboard shortcuts
|
|
|
|
class GlobalShortcuts extends ConsumerWidget {
|
|
|
|
final Widget child;
|
|
|
|
const GlobalShortcuts({super.key, required this.child});
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) => Actions(
|
|
|
|
actions: {
|
|
|
|
CloseIntent: CallbackAction<CloseIntent>(onInvoke: (_) {
|
|
|
|
windowManager.close();
|
|
|
|
return null;
|
|
|
|
}),
|
|
|
|
HideIntent: CallbackAction<HideIntent>(onInvoke: (_) {
|
|
|
|
if (isDesktop) {
|
|
|
|
ref
|
|
|
|
.read(desktopWindowStateProvider.notifier)
|
|
|
|
.setWindowHidden(true);
|
2023-02-09 21:12:35 +03:00
|
|
|
}
|
2024-01-18 16:46:15 +03:00
|
|
|
return null;
|
|
|
|
}),
|
|
|
|
SearchIntent: CallbackAction<SearchIntent>(onInvoke: (intent) {
|
|
|
|
// If the OATH view doesn't have focus, but is shown, find and select the search bar.
|
|
|
|
final searchContext = searchAccountsField.currentContext;
|
|
|
|
if (searchContext != null) {
|
|
|
|
if (!Navigator.of(searchContext).canPop()) {
|
|
|
|
return Actions.maybeInvoke(searchContext, intent);
|
2023-02-09 21:12:35 +03:00
|
|
|
}
|
|
|
|
}
|
2024-01-09 14:50:26 +03:00
|
|
|
return null;
|
2024-01-18 16:46:15 +03:00
|
|
|
}),
|
|
|
|
NextDeviceIntent: CallbackAction<NextDeviceIntent>(onInvoke: (_) {
|
|
|
|
ref.read(withContextProvider)((context) async {
|
|
|
|
// Only allow switching keys if no other views are open,
|
|
|
|
// with the exception of the drawer.
|
|
|
|
if (!Navigator.of(context).canPop() ||
|
|
|
|
scaffoldGlobalKey.currentState?.isDrawerOpen == true) {
|
|
|
|
final attached = ref
|
|
|
|
.read(attachedDevicesProvider)
|
|
|
|
.whereType<UsbYubiKeyNode>()
|
|
|
|
.toList();
|
|
|
|
if (attached.length > 1) {
|
|
|
|
final current = ref.read(currentDeviceProvider);
|
|
|
|
if (current != null && current is UsbYubiKeyNode) {
|
|
|
|
final index = attached.indexOf(current);
|
|
|
|
ref.read(currentDeviceProvider.notifier).setCurrentDevice(
|
|
|
|
attached[(index + 1) % attached.length]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
}),
|
|
|
|
SettingsIntent: CallbackAction<SettingsIntent>(onInvoke: (_) {
|
|
|
|
ref.read(withContextProvider)((context) async {
|
|
|
|
if (!Navigator.of(context).canPop()) {
|
|
|
|
await showBlurDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => const SettingsPage(),
|
|
|
|
routeSettings: const RouteSettings(name: 'settings'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
}),
|
|
|
|
AboutIntent: CallbackAction<AboutIntent>(onInvoke: (_) {
|
|
|
|
ref.read(withContextProvider)((context) async {
|
|
|
|
if (!Navigator.of(context).canPop()) {
|
|
|
|
await showBlurDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) => const AboutPage(),
|
|
|
|
routeSettings: const RouteSettings(name: 'about'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return null;
|
|
|
|
}),
|
|
|
|
EscapeIntent: CallbackAction<EscapeIntent>(
|
|
|
|
onInvoke: (_) {
|
|
|
|
FocusManager.instance.primaryFocus?.unfocus();
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
),
|
|
|
|
},
|
|
|
|
child: Shortcuts(
|
|
|
|
shortcuts: {
|
|
|
|
ctrlOrCmd(LogicalKeyboardKey.keyF): const SearchIntent(),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.escape):
|
|
|
|
const EscapeIntent(),
|
|
|
|
if (isDesktop) ...{
|
|
|
|
const SingleActivator(LogicalKeyboardKey.tab, control: true):
|
|
|
|
const NextDeviceIntent(),
|
|
|
|
},
|
|
|
|
if (Platform.isMacOS) ...{
|
|
|
|
const SingleActivator(LogicalKeyboardKey.keyW, meta: true):
|
|
|
|
const HideIntent(),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.keyQ, meta: true):
|
|
|
|
const CloseIntent(),
|
|
|
|
const SingleActivator(LogicalKeyboardKey.comma, meta: true):
|
|
|
|
const SettingsIntent(),
|
|
|
|
},
|
|
|
|
if (Platform.isWindows) ...{
|
|
|
|
const SingleActivator(LogicalKeyboardKey.keyW, control: true):
|
|
|
|
const HideIntent(),
|
|
|
|
},
|
|
|
|
if (Platform.isLinux) ...{
|
|
|
|
const SingleActivator(LogicalKeyboardKey.keyQ, control: true):
|
|
|
|
const CloseIntent(),
|
|
|
|
},
|
2024-01-09 14:50:26 +03:00
|
|
|
},
|
2024-01-18 16:46:15 +03:00
|
|
|
child: child,
|
2024-01-09 14:50:26 +03:00
|
|
|
),
|
2024-01-18 16:46:15 +03:00
|
|
|
);
|
|
|
|
}
|