mirror of
https://github.com/Yubico/yubioath-flutter.git
synced 2024-12-23 10:11:52 +03:00
replace settings with dialog
This commit is contained in:
parent
3dc620855e
commit
3fde9681d3
@ -17,7 +17,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_settings.dart';
|
||||
|
||||
import '../../app/state.dart';
|
||||
import '../../core/state.dart';
|
||||
@ -169,8 +168,7 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
|
||||
SwitchListTile(
|
||||
title: const Text('Silence NFC sounds'),
|
||||
subtitle: nfcSilenceSounds
|
||||
? const Text(
|
||||
'No sounds will be played on NFC tap')
|
||||
? const Text('No sounds will be played on NFC tap')
|
||||
: const Text('Sound will play on NFC tap'),
|
||||
value: nfcSilenceSounds,
|
||||
key: keys.nfcSilenceSoundsSettings,
|
||||
@ -204,7 +202,6 @@ class _AndroidSettingsPageState extends ConsumerState<AndroidSettingsPage> {
|
||||
ref.read(themeModeProvider.notifier).setThemeMode(newMode);
|
||||
},
|
||||
),
|
||||
const IconPackSettings()
|
||||
],
|
||||
),
|
||||
),
|
||||
|
139
lib/oath/icon_provider/icon_pack_dialog.dart
Normal file
139
lib/oath/icon_provider/icon_pack_dialog.dart
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:yubico_authenticator/app/message.dart';
|
||||
import 'package:yubico_authenticator/app/state.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_manager.dart';
|
||||
import 'package:yubico_authenticator/widgets/responsive_dialog.dart';
|
||||
|
||||
class IconPackDialog extends ConsumerWidget {
|
||||
const IconPackDialog({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final packManager = ref.watch(iconPackManager);
|
||||
final hasIconPack = packManager.hasIconPack;
|
||||
|
||||
return ResponsiveDialog(
|
||||
title: const Text('Manage icons'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 18.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('By loading an external icon pack, the avatar icons '
|
||||
'of the accounts will be easier to distinguish throught the issuers '
|
||||
'familiar logos and colors.\n\n'
|
||||
'We recommend the Aegis icon packs which can be downloaded '
|
||||
'from below.'),
|
||||
TextButton(
|
||||
child: const Text(
|
||||
'https://aegis-icons.github.io/',
|
||||
style: TextStyle(decoration: TextDecoration.underline),
|
||||
),
|
||||
onPressed: () async {
|
||||
await launchUrl(
|
||||
Uri.parse('https://aegis-icons.github.io/'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
await _importIconPack(context, ref);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
const Icon(Icons.download, size: 16),
|
||||
const SizedBox(width: 4),
|
||||
hasIconPack
|
||||
? const Text('Replace icon pack')
|
||||
: const Text('Load icon pack')
|
||||
]),
|
||||
),
|
||||
if (hasIconPack)
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
final removePackStatus =
|
||||
await ref.read(iconPackManager).removePack();
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
if (removePackStatus) {
|
||||
showMessage(context, 'Icon pack removed');
|
||||
} else {
|
||||
showMessage(context, 'Error removing icon pack');
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: const [
|
||||
Icon(Icons.delete_rounded, size: 16),
|
||||
SizedBox(width: 4),
|
||||
Text('Remove icon pack')
|
||||
]),
|
||||
)
|
||||
]),
|
||||
const SizedBox(height: 16),
|
||||
if (hasIconPack)
|
||||
Text(
|
||||
'Loaded: ${packManager.iconPackName} (version: ${packManager.iconPackVersion})',
|
||||
style:
|
||||
TextStyle(fontSize: 11, color: theme.colorScheme.primary),
|
||||
)
|
||||
else
|
||||
const Text('')
|
||||
]
|
||||
.map((e) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: e,
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<bool> _importIconPack(BuildContext context, WidgetRef ref) async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowedExtensions: ['zip'],
|
||||
type: FileType.custom,
|
||||
allowMultiple: false,
|
||||
lockParentWindow: true,
|
||||
dialogTitle: 'Choose icon pack');
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
final importStatus =
|
||||
await ref.read(iconPackManager).importPack(result.paths.first!);
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
if (importStatus) {
|
||||
showMessage(context, 'Icon pack imported');
|
||||
} else {
|
||||
showMessage(context,
|
||||
'Error importing icon pack: ${ref.read(iconPackManager).lastError}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:yubico_authenticator/app/message.dart';
|
||||
import 'package:yubico_authenticator/app/state.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_manager.dart';
|
||||
import 'package:yubico_authenticator/oath/state.dart';
|
||||
import 'package:yubico_authenticator/widgets/list_title.dart';
|
||||
|
||||
class IconPackSettings extends ConsumerWidget {
|
||||
const IconPackSettings({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final packManager = ref.watch(iconPackManager);
|
||||
final hasIconPack = packManager.hasIconPack;
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Column(children: [
|
||||
const ListTitle('Account icon pack'),
|
||||
ListTile(
|
||||
title: hasIconPack
|
||||
? const Text('Icon pack imported')
|
||||
: const Text('Not using icon pack'),
|
||||
subtitle: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
hasIconPack
|
||||
? Row(
|
||||
children: [
|
||||
const Text('Name: ', style: TextStyle(fontSize: 11)),
|
||||
Text(
|
||||
'${packManager.iconPackName} (version: ${packManager.iconPackVersion})',
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: theme.colorScheme.primary)),
|
||||
],
|
||||
)
|
||||
: const Text('Tap to import', style: TextStyle(fontSize: 10)),
|
||||
],
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.info_outline_rounded),
|
||||
onPressed: () async {
|
||||
await _showIconPackInfo(context, ref);
|
||||
},
|
||||
),
|
||||
onTap: () async {
|
||||
if (hasIconPack) {
|
||||
await _removeOrChangeIconPack(context, ref);
|
||||
} else {
|
||||
await _importIconPack(context, ref);
|
||||
}
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
ref.invalidate(credentialsProvider);
|
||||
});
|
||||
},
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<bool> _importIconPack(BuildContext context, WidgetRef ref) async {
|
||||
final result = await FilePicker.platform.pickFiles(
|
||||
allowedExtensions: ['zip'],
|
||||
type: FileType.custom,
|
||||
allowMultiple: false,
|
||||
lockParentWindow: true,
|
||||
dialogTitle: 'Choose icon pack');
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
final importStatus =
|
||||
await ref.read(iconPackManager).importPack(result.paths.first!);
|
||||
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
if (importStatus) {
|
||||
showMessage(context, 'Icon pack imported');
|
||||
} else {
|
||||
showMessage(context,
|
||||
'Error importing icon pack: ${ref.read(iconPackManager).lastError}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> _removeOrChangeIconPack(
|
||||
BuildContext context, WidgetRef ref) async =>
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return SimpleDialog(
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('Replace icon pack'),
|
||||
onTap: () async {
|
||||
await _importIconPack(context, ref);
|
||||
await ref.read(withContextProvider)((context) async {
|
||||
Navigator.pop(context);
|
||||
});
|
||||
}),
|
||||
ListTile(
|
||||
title: const Text('Remove icon pack'),
|
||||
onTap: () async {
|
||||
final removePackStatus =
|
||||
await ref.read(iconPackManager).removePack();
|
||||
await ref.read(withContextProvider)(
|
||||
(context) async {
|
||||
if (removePackStatus) {
|
||||
showMessage(context, 'Icon pack removed');
|
||||
} else {
|
||||
showMessage(context, 'Error removing icon pack');
|
||||
}
|
||||
Navigator.pop(context);
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
Future<void> _showIconPackInfo(BuildContext context, WidgetRef ref) async =>
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return const SimpleDialog(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text('About icon packs'),
|
||||
subtitle: Text('Icon packs contain icons for accounts. '
|
||||
'To use an icon-pack, download and import one\n\n'
|
||||
'The supported format is aegis-icons.'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
@ -17,7 +17,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:vector_graphics/vector_graphics.dart';
|
||||
import 'package:yubico_authenticator/app/message.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_file_loader.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_dialog.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_manager.dart';
|
||||
import 'package:yubico_authenticator/widgets/delayed_visibility.dart';
|
||||
|
||||
@ -34,7 +36,7 @@ class AccountIcon extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final issuerImageFile = ref.watch(iconPackManager).getFileForIssuer(issuer);
|
||||
return issuerImageFile != null
|
||||
final issuerWidget = issuerImageFile != null
|
||||
? VectorGraphic(
|
||||
width: 40,
|
||||
height: 40,
|
||||
@ -53,5 +55,16 @@ class AccountIcon extends ConsumerWidget {
|
||||
);
|
||||
})
|
||||
: defaultWidget;
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
await showBlurDialog(
|
||||
context: context,
|
||||
builder: (context) => const IconPackDialog(),
|
||||
);
|
||||
},
|
||||
icon: issuerWidget,
|
||||
tooltip: 'Select icon',
|
||||
padding: const EdgeInsets.all(1.0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:yubico_authenticator/oath/icon_provider/icon_pack_settings.dart';
|
||||
|
||||
import 'app/logging.dart';
|
||||
import 'app/state.dart';
|
||||
@ -78,7 +77,6 @@ class SettingsPage extends ConsumerWidget {
|
||||
_log.debug('Set theme mode to $mode');
|
||||
},
|
||||
),
|
||||
const IconPackSettings()
|
||||
],
|
||||
),
|
||||
),
|
||||
|
Loading…
Reference in New Issue
Block a user