#543 vaults: custom pattern lock

This commit is contained in:
Thibault Deckers 2023-03-03 20:27:12 +01:00
parent e82d042fc7
commit 2183ff7cd7
12 changed files with 286 additions and 53 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Added
- Vaults: custom pattern lock
### Changed
- upgraded Flutter to stable v3.7.6

View file

@ -226,7 +226,8 @@
"unitSystemMetric": "Metric",
"unitSystemImperial": "Imperial",
"vaultLockTypePin": "Pin",
"vaultLockTypePattern": "Pattern",
"vaultLockTypePin": "PIN",
"vaultLockTypePassword": "Password",
"videoControlsPlay": "Play",
@ -384,8 +385,11 @@
"vaultDialogLockModeWhenScreenOff": "Lock when screen turns off",
"vaultDialogLockTypeLabel": "Lock type",
"pinDialogEnter": "Enter pin",
"pinDialogConfirm": "Confirm pin",
"patternDialogEnter": "Enter pattern",
"patternDialogConfirm": "Confirm pattern",
"pinDialogEnter": "Enter PIN",
"pinDialogConfirm": "Confirm PIN",
"passwordDialogEnter": "Enter password",
"passwordDialogConfirm": "Confirm password",

View file

@ -1,13 +1,15 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:flutter/widgets.dart';
enum VaultLockType { system, pin, password }
enum VaultLockType { system, pattern, pin, password }
extension ExtraVaultLockType on VaultLockType {
String getText(BuildContext context) {
switch (this) {
case VaultLockType.system:
return context.l10n.settingsSystemDefault;
case VaultLockType.pattern:
return context.l10n.vaultLockTypePattern;
case VaultLockType.pin:
return context.l10n.vaultLockTypePin;
case VaultLockType.password:

View file

@ -7,6 +7,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:aves/widgets/dialogs/filter_editors/password_dialog.dart';
import 'package:aves/widgets/dialogs/filter_editors/pattern_dialog.dart';
import 'package:aves/widgets/dialogs/filter_editors/pin_dialog.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
@ -160,6 +161,16 @@ class Vaults extends ChangeNotifier {
}
}
break;
case VaultLockType.pattern:
final pattern = await showDialog<String>(
context: context,
builder: (context) => const PatternDialog(needConfirmation: false),
routeSettings: const RouteSettings(name: PatternDialog.routeName),
);
if (pattern != null) {
confirmed = pattern == await securityService.readValue(details.passKey);
}
break;
case VaultLockType.pin:
final pin = await showDialog<String>(
context: context,
@ -211,6 +222,16 @@ class Vaults extends ChangeNotifier {
}
}
break;
case VaultLockType.pattern:
final pattern = await showDialog<String>(
context: context,
builder: (context) => const PatternDialog(needConfirmation: true),
routeSettings: const RouteSettings(name: PatternDialog.routeName),
);
if (pattern != null) {
return await securityService.writeValue(details.passKey, pattern);
}
break;
case VaultLockType.pin:
final pin = await showDialog<String>(
context: context,

View file

@ -260,6 +260,11 @@ class Dependencies {
license: apache2,
sourceUrl: 'https://github.com/zesage/panorama',
),
Dependency(
name: 'Pattern Lock',
license: apache2,
sourceUrl: 'https://github.com/qwert2603/pattern_lock',
),
Dependency(
name: 'Percent Indicator',
license: bsd2,

View file

@ -39,6 +39,7 @@ class _EditVaultDialogState extends State<EditVaultDialog> {
final List<VaultLockType> _lockTypeOptions = [
if (device.canAuthenticateUser) VaultLockType.system,
if (device.canUseCrypto) ...[
VaultLockType.pattern,
VaultLockType.pin,
VaultLockType.password,
],

View file

@ -41,31 +41,7 @@ class _PasswordDialogState extends State<PasswordDialog> {
controller: _controller,
focusNode: _focusNode,
obscureText: true,
onSubmitted: (password) {
if (widget.needConfirmation) {
if (_confirming) {
final match = _firstPassword == password;
Navigator.maybeOf(context)?.pop<String>(match ? password : null);
if (!match) {
showDialog(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericFailureFeedback),
actions: const [OkButton()],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
);
}
} else {
_firstPassword = password;
_controller.clear();
setState(() => _confirming = true);
WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus());
}
} else {
Navigator.maybeOf(context)?.pop<String>(password);
}
},
onSubmitted: _submit,
autofillHints: const [AutofillHints.password],
),
),
@ -73,4 +49,30 @@ class _PasswordDialogState extends State<PasswordDialog> {
),
);
}
void _submit(String password) {
if (widget.needConfirmation) {
if (_confirming) {
final match = _firstPassword == password;
Navigator.maybeOf(context)?.pop<String>(match ? password : null);
if (!match) {
showDialog(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericFailureFeedback),
actions: const [OkButton()],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
);
}
} else {
_firstPassword = password;
_controller.clear();
setState(() => _confirming = true);
WidgetsBinding.instance.addPostFrameCallback((_) => _focusNode.requestFocus());
}
} else {
Navigator.maybeOf(context)?.pop<String>(password);
}
}
}

View file

@ -0,0 +1,75 @@
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/dialogs/aves_dialog.dart';
import 'package:flutter/material.dart';
import 'package:pattern_lock/pattern_lock.dart';
import 'package:provider/provider.dart';
class PatternDialog extends StatefulWidget {
static const routeName = '/dialog/pattern';
final bool needConfirmation;
const PatternDialog({
super.key,
required this.needConfirmation,
});
@override
State<PatternDialog> createState() => _PatternDialogState();
}
class _PatternDialogState extends State<PatternDialog> {
bool _confirming = false;
String? _firstPattern;
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return AvesDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(_confirming ? context.l10n.patternDialogConfirm : context.l10n.patternDialogEnter),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: SizedBox.square(
dimension: context.select<MediaQueryData, double>((mq) => mq.size.shortestSide / 2),
child: PatternLock(
relativePadding: .4,
selectedColor: colorScheme.secondary,
notSelectedColor: colorScheme.onBackground,
pointRadius: 8,
fillPoints: true,
onInputComplete: (input) => _submit(input.join()),
),
),
),
],
),
);
}
void _submit(String pattern) {
if (widget.needConfirmation) {
if (_confirming) {
final match = _firstPattern == pattern;
Navigator.maybeOf(context)?.pop<String>(match ? pattern : null);
if (!match) {
showDialog(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericFailureFeedback),
actions: const [OkButton()],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
);
}
} else {
_firstPattern = pattern;
setState(() => _confirming = true);
}
} else {
Navigator.maybeOf(context)?.pop<String>(pattern);
}
}
}

View file

@ -38,30 +38,7 @@ class _PinDialogState extends State<PinDialog> {
controller: _controller,
obscureText: true,
onChanged: (v) {},
onCompleted: (pin) {
if (widget.needConfirmation) {
if (_confirming) {
final match = _firstPin == pin;
Navigator.maybeOf(context)?.pop<String>(match ? pin : null);
if (!match) {
showDialog(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericFailureFeedback),
actions: const [OkButton()],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
);
}
} else {
_firstPin = pin;
_controller.clear();
setState(() => _confirming = true);
}
} else {
Navigator.maybeOf(context)?.pop<String>(pin);
}
},
onCompleted: _submit,
animationType: AnimationType.scale,
keyboardType: TextInputType.number,
autoFocus: true,
@ -80,4 +57,29 @@ class _PinDialogState extends State<PinDialog> {
),
);
}
void _submit(String pin) {
if (widget.needConfirmation) {
if (_confirming) {
final match = _firstPin == pin;
Navigator.maybeOf(context)?.pop<String>(match ? pin : null);
if (!match) {
showDialog(
context: context,
builder: (context) => AvesDialog(
content: Text(context.l10n.genericFailureFeedback),
actions: const [OkButton()],
),
routeSettings: const RouteSettings(name: AvesDialog.warningRouteName),
);
}
} else {
_firstPin = pin;
_controller.clear();
setState(() => _confirming = true);
}
} else {
Navigator.maybeOf(context)?.pop<String>(pin);
}
}
}

View file

@ -853,6 +853,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pattern_lock:
dependency: "direct main"
description:
name: pattern_lock
sha256: "7929f877bcc82744ba2c0d55aaad1ff7cf09744f516b4853363fd29a1d7f539f"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
pdf:
dependency: "direct main"
description:

View file

@ -80,6 +80,7 @@ dependencies:
url: https://github.com/deckerst/aves_panorama.git
ref: aves
path:
pattern_lock:
pdf:
percent_indicator:
permission_handler:

View file

@ -136,6 +136,7 @@
"themeBrightnessBlack",
"unitSystemMetric",
"unitSystemImperial",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"videoControlsPlay",
@ -195,6 +196,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -688,6 +691,7 @@
"themeBrightnessBlack",
"unitSystemMetric",
"unitSystemImperial",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"videoControlsPlay",
@ -747,6 +751,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -1170,6 +1176,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -1177,6 +1184,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -1200,6 +1209,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -1207,6 +1217,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -1224,12 +1236,27 @@
"el": [
"chipActionGoToPlacePage",
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm",
"exportEntryDialogWriteMetadata",
"drawerPlacePage",
"placePageTitle",
"placeEmpty"
],
"es": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"eu": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"fa": [
"clearTooltip",
"chipActionGoToPlacePage",
@ -1269,6 +1296,7 @@
"lengthUnitPixel",
"lengthUnitPercent",
"nameConflictStrategySkip",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"videoControlsNone",
@ -1315,6 +1343,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -1698,6 +1728,12 @@
"filePickerUseThisFolder"
],
"fr": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"gl": [
"columnCount",
"chipActionGoToPlacePage",
@ -1731,6 +1767,7 @@
"themeBrightnessLight",
"themeBrightnessDark",
"themeBrightnessBlack",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"videoPlaybackSkip",
@ -1783,6 +1820,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -2353,6 +2392,7 @@
"themeBrightnessBlack",
"unitSystemMetric",
"unitSystemImperial",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"videoControlsPlay",
@ -2412,6 +2452,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -2827,10 +2869,19 @@
"filePickerUseThisFolder"
],
"id": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"it": [
"chipActionGoToPlacePage",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm",
"exportEntryDialogWriteMetadata",
"drawerPlacePage",
"placePageTitle",
@ -2855,6 +2906,7 @@
"lengthUnitPercent",
"subtitlePositionTop",
"subtitlePositionBottom",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -2862,6 +2914,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -2884,6 +2938,12 @@
"settingsWidgetDisplayedItem"
],
"ko": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"lt": [
"columnCount",
"chipActionGoToPlacePage",
@ -2896,6 +2956,7 @@
"keepScreenOnVideoPlayback",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -2903,6 +2964,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -2932,6 +2995,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -2939,6 +3003,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -2975,6 +3041,7 @@
"lengthUnitPercent",
"subtitlePositionTop",
"subtitlePositionBottom",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"widgetDisplayedItemRandom",
@ -2984,6 +3051,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -3026,6 +3095,7 @@
"displayRefreshRatePreferLowest",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"wallpaperTargetHome",
@ -3036,6 +3106,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -3323,6 +3395,12 @@
"wallpaperUseScrollEffect"
],
"pl": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"pt": [
"columnCount",
"chipActionGoToPlacePage",
@ -3331,8 +3409,11 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"newVaultWarningDialogMessage",
"patternDialogEnter",
"patternDialogConfirm",
"passwordDialogConfirm",
"authenticateToConfigureVault",
"authenticateToUnlockVault",
@ -3351,6 +3432,9 @@
"chipActionGoToPlacePage",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm",
"exportEntryDialogWriteMetadata",
"drawerPlacePage",
"placePageTitle",
@ -3363,6 +3447,9 @@
"filterLocatedLabel",
"filterTaggedLabel",
"lengthUnitPixel",
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm",
"authenticateToConfigureVault",
"authenticateToUnlockVault",
"exportEntryDialogWriteMetadata",
@ -3390,6 +3477,7 @@
"coordinateDms",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"otherDirectoryDescription",
@ -3404,6 +3492,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -3808,6 +3898,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -3815,6 +3906,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -4153,6 +4246,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -4160,6 +4254,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -4175,6 +4271,12 @@
"settingsDisablingBinWarningDialogMessage"
],
"uk": [
"vaultLockTypePattern",
"patternDialogEnter",
"patternDialogConfirm"
],
"zh": [
"chipActionGoToPlacePage",
"chipActionLock",
@ -4185,6 +4287,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -4192,6 +4295,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",
@ -4224,6 +4329,7 @@
"albumTierVaults",
"lengthUnitPixel",
"lengthUnitPercent",
"vaultLockTypePattern",
"vaultLockTypePin",
"vaultLockTypePassword",
"newVaultWarningDialogMessage",
@ -4231,6 +4337,8 @@
"configureVaultDialogTitle",
"vaultDialogLockModeWhenScreenOff",
"vaultDialogLockTypeLabel",
"patternDialogEnter",
"patternDialogConfirm",
"pinDialogEnter",
"pinDialogConfirm",
"passwordDialogEnter",