Merge PR #15.
@ -57,16 +57,25 @@ class MainPage extends ConsumerWidget {
|
||||
),
|
||||
actions: [
|
||||
InkWell(
|
||||
child: currentDevice == null
|
||||
? const Icon(Icons.info, size: 44)
|
||||
: DeviceAvatar(currentDevice, selected: true),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: currentDevice == null
|
||||
? SizedBox.square(
|
||||
dimension: 44,
|
||||
child: Icon(
|
||||
Icons.usb_off,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
)
|
||||
: DeviceAvatar(currentDevice, selected: true),
|
||||
),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => const MainActionsDialog(),
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: const MainPageDrawer(),
|
||||
|
@ -52,11 +52,10 @@ void main() async {
|
||||
page = ErrorPage(error: e.toString());
|
||||
}
|
||||
|
||||
// Only MacOS supports hiding the window at start currently.
|
||||
// For now, this size should match windows/runner/main.cpp and linux/flutter/my_application.cc
|
||||
windowManager.waitUntilReadyToShow().then((_) async {
|
||||
// Set to frameless window
|
||||
//await windowManager.setAsFrameless();
|
||||
await windowManager.setSize(const Size(400, 720));
|
||||
//await windowManager.setPosition(Offset.zero);
|
||||
windowManager.show();
|
||||
});
|
||||
|
||||
|
@ -61,13 +61,7 @@ class AccountView extends ConsumerWidget {
|
||||
),
|
||||
enabled: code != null,
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: code!.value));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Code copied'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
_copyToClipboard(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
@ -98,6 +92,38 @@ class AccountView extends ConsumerWidget {
|
||||
),
|
||||
];
|
||||
|
||||
_copyToClipboard(BuildContext context) {
|
||||
Clipboard.setData(ClipboardData(text: code!.value));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Code copied'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_calculate(BuildContext context, WidgetRef ref) async {
|
||||
VoidCallback? close;
|
||||
if (credential.touchRequired) {
|
||||
final sbc = ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Touch your YubiKey'),
|
||||
duration: Duration(seconds: 30),
|
||||
),
|
||||
)..closed.then((_) {
|
||||
close = null;
|
||||
});
|
||||
close = sbc.close;
|
||||
}
|
||||
try {
|
||||
await ref
|
||||
.read(credentialListProvider(device.path).notifier)
|
||||
.calculate(credential);
|
||||
} finally {
|
||||
close?.call();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final code = this.code;
|
||||
@ -105,9 +131,18 @@ class AccountView extends ConsumerWidget {
|
||||
final label = credential.issuer != null
|
||||
? '${credential.issuer} (${credential.name})'
|
||||
: credential.name;
|
||||
final trigger = code == null ||
|
||||
expired &&
|
||||
(credential.touchRequired || credential.oathType == OathType.hotp);
|
||||
|
||||
return ListTile(
|
||||
onTap: () {},
|
||||
onTap: () {
|
||||
if (trigger) {
|
||||
_calculate(context, ref);
|
||||
} else {
|
||||
_copyToClipboard(context);
|
||||
}
|
||||
},
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Colors.primaries
|
||||
.elementAt(label.hashCode % Colors.primaries.length),
|
||||
@ -129,52 +164,28 @@ class AccountView extends ConsumerWidget {
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (code == null ||
|
||||
expired &&
|
||||
(credential.touchRequired ||
|
||||
credential.oathType == OathType.hotp))
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () async {
|
||||
VoidCallback? close;
|
||||
if (credential.touchRequired) {
|
||||
final sbc = ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Touch your YubiKey'),
|
||||
duration: Duration(seconds: 30),
|
||||
),
|
||||
)..closed.then((_) {
|
||||
close = null;
|
||||
});
|
||||
close = sbc.close;
|
||||
}
|
||||
try {
|
||||
await ref
|
||||
.read(credentialListProvider(device.path).notifier)
|
||||
.calculate(credential);
|
||||
} finally {
|
||||
close?.call();
|
||||
}
|
||||
},
|
||||
),
|
||||
Stack(
|
||||
alignment: AlignmentDirectional.bottomCenter,
|
||||
Column(
|
||||
children: [
|
||||
if (code != null && code.validTo - code.validFrom < 600)
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: SizedBox.square(
|
||||
dimension: 16,
|
||||
child:
|
||||
CircleTimer(code.validFrom * 1000, code.validTo * 1000),
|
||||
),
|
||||
),
|
||||
Transform.scale(
|
||||
scale: 0.8,
|
||||
child: PopupMenuButton(
|
||||
itemBuilder: (context) => _buildPopupMenu(context, ref),
|
||||
),
|
||||
)
|
||||
Align(
|
||||
alignment: AlignmentDirectional.topCenter,
|
||||
child: trigger
|
||||
? const Icon(
|
||||
Icons.touch_app,
|
||||
size: 18,
|
||||
)
|
||||
: SizedBox.square(
|
||||
dimension: 16,
|
||||
child: CircleTimer(
|
||||
code.validFrom * 1000,
|
||||
code.validTo * 1000,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
PopupMenuButton(
|
||||
child: Icon(Icons.adaptive.more),
|
||||
itemBuilder: (context) => _buildPopupMenu(context, ref),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -14,11 +14,17 @@ class OathScreen extends ConsumerWidget {
|
||||
final state = ref.watch(oathStateProvider(device.path));
|
||||
|
||||
if (state == null) {
|
||||
return const CircularProgressIndicator();
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (state.locked) {
|
||||
return Center(
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@ -37,8 +43,9 @@ class OathScreen extends ConsumerWidget {
|
||||
final accounts = ref.watch(credentialListProvider(device.path));
|
||||
if (accounts == null) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: const [
|
||||
Text('Reading...'),
|
||||
Center(child: CircularProgressIndicator()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -40,14 +40,14 @@ static void my_application_activate(GApplication* application) {
|
||||
if (use_header_bar) {
|
||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||
gtk_header_bar_set_title(header_bar, "yubico_authenticator");
|
||||
gtk_header_bar_set_title(header_bar, "Yubico Authenticator");
|
||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||
} else {
|
||||
gtk_window_set_title(window, "yubico_authenticator");
|
||||
gtk_window_set_title(window, "Yubico Authenticator");
|
||||
}
|
||||
|
||||
gtk_window_set_default_size(window, 1280, 720);
|
||||
gtk_window_set_default_size(window, 400, 720);
|
||||
gtk_widget_show(GTK_WIDGET(window));
|
||||
|
||||
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||
|
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Normal file → Executable file
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 6.5 KiB |
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Normal file → Executable file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 5.7 KiB |
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Normal file → Executable file
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 19 KiB |
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Normal file → Executable file
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 7.6 KiB |
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Normal file → Executable file
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 28 KiB |
BIN
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Normal file → Executable file
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 11 KiB |
@ -11,6 +11,26 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
// new console when running with a debugger.
|
||||
if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
|
||||
CreateAndAttachConsole();
|
||||
} else {
|
||||
// Don't show console windows for launched processes.
|
||||
// See: https://github.com/flutter/flutter/issues/47891#issuecomment-708850435
|
||||
STARTUPINFO si = { 0 };
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
WCHAR lpszCmd[MAX_PATH] = L"cmd.exe";
|
||||
if (::CreateProcess(NULL, lpszCmd, NULL, NULL, FALSE, CREATE_NEW_CONSOLE | CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
|
||||
do {
|
||||
if (::AttachConsole(pi.dwProcessId)) {
|
||||
::TerminateProcess(pi.hProcess, 0);
|
||||
break;
|
||||
}
|
||||
} while (ERROR_INVALID_HANDLE == GetLastError());
|
||||
::CloseHandle(pi.hProcess);
|
||||
::CloseHandle(pi.hThread);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize COM, so that it is available for use in the library and/or
|
||||
@ -26,7 +46,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
|
||||
|
||||
FlutterWindow window(project);
|
||||
Win32Window::Point origin(10, 10);
|
||||
Win32Window::Size size(1280, 720);
|
||||
Win32Window::Size size(400, 720);
|
||||
if (!window.CreateAndShow(L"Yubico Authenticator", origin, size)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 16 KiB |