From 0cb52a7eb4d049fefcbb5a153538254e96fcc121 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 11:57:51 +0200 Subject: [PATCH 01/12] Renamed ScreenLock callbacks to follow framework naming --- example/lib/main.dart | 18 +++--- example/pubspec.lock | 6 +- .../configurations/screen_lock_config.dart | 2 +- lib/src/functions.dart | 48 +++++++-------- lib/src/input_controller.dart | 11 ++-- lib/src/screen_lock.dart | 58 +++++++++---------- pubspec.lock | 31 ++++------ pubspec.yaml | 2 +- 8 files changed, 85 insertions(+), 91 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index f9f5fa9..2746679 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -62,8 +62,8 @@ class _MyHomePageState extends State { builder: (context) { return ScreenLock( correctString: '1234', - didCancelled: Navigator.of(context).pop, - didUnlocked: Navigator.of(context).pop, + onCancelled: Navigator.of(context).pop, + onUnlocked: Navigator.of(context).pop, ); }, ), @@ -86,7 +86,7 @@ class _MyHomePageState extends State { correctString: '', confirmation: true, inputController: inputController, - didConfirmed: (matchedText) { + onConfirmed: (matchedText) { // ignore: avoid_print print(matchedText); }, @@ -111,7 +111,7 @@ class _MyHomePageState extends State { customizedButtonTap: () async { await localAuth(context); }, - didOpened: () async { + onOpened: () async { await localAuth(context); }, ), @@ -218,7 +218,7 @@ class _MyHomePageState extends State { onPressed: () => screenLock( context: context, correctString: '1234', - didUnlocked: () { + onUnlocked: () { Navigator.pop(context); NextPage.show(context); }, @@ -229,7 +229,7 @@ class _MyHomePageState extends State { onPressed: () => screenLock( context: context, correctString: '1234', - withBlur: false, + useBlur: false, screenLockConfig: const ScreenLockConfig( /// If you don't want it to be transparent, override the config backgroundColor: Colors.black, @@ -258,7 +258,7 @@ class _MyHomePageState extends State { keyPadConfig: const KeyPadConfig( clearOnLongPressed: true, ), - didUnlocked: Navigator.of(context).pop, + onUnlocked: Navigator.of(context).pop, ); }, ), @@ -283,7 +283,7 @@ class _MyHomePageState extends State { input: input, length: length, ), - didUnlocked: Navigator.of(context).pop, + onUnlocked: Navigator.of(context).pop, ); }, ), @@ -303,7 +303,7 @@ class _MyHomePageState extends State { digits: 4, correctString: '', onValidate: (x) async { - sleep(Duration(milliseconds: 500)); + sleep(const Duration(milliseconds: 500)); return '1234' == x; }, ), diff --git a/example/pubspec.lock b/example/pubspec.lock index 79da53c..9d59b71 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,7 +7,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" clock: dependency: transitive description: @@ -75,14 +75,14 @@ packages: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: diff --git a/lib/src/configurations/screen_lock_config.dart b/lib/src/configurations/screen_lock_config.dart index fa21198..02847d9 100644 --- a/lib/src/configurations/screen_lock_config.dart +++ b/lib/src/configurations/screen_lock_config.dart @@ -15,7 +15,7 @@ class ScreenLockConfig { scaffoldBackgroundColor: const Color(0x88545454), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( - primary: const Color(0xFFFFFFFF), + foregroundColor: const Color(0xFFFFFFFF), backgroundColor: const Color(0xFF808080), shape: const CircleBorder(), padding: const EdgeInsets.all(0), diff --git a/lib/src/functions.dart b/lib/src/functions.dart index 53bf7fb..0f86670 100644 --- a/lib/src/functions.dart +++ b/lib/src/functions.dart @@ -14,12 +14,13 @@ import 'package:flutter_screen_lock/flutter_screen_lock.dart'; /// - `maxRetries`: `0` is unlimited. For example, if it is set to 1, didMaxRetries will be called on the first failure. Default `0` /// - `retryDelay`: Delay until we can retry. Duration.zero is no delay. /// - `delayChild`: Specify the widget during input invalidation by retry delay. -/// - `didUnlocked`: Called if the value matches the correctString. -/// - `didError`: Called if the value does not match the correctString. -/// - `didMaxRetries`: Events that have reached the maximum number of attempts -/// - `didOpened`: For example, when you want to perform biometric authentication -/// - `didConfirmed`: Called when the first and second inputs match during confirmation -/// - `didCancelled`: Called when the user cancels the screen +/// - `onUnlocked`: Called if the value matches the correctString. +/// - `onValidate`: Callback to validate input values filled in [digits]. +/// - `onError`: Called if the value does not match the correctString. +/// - `onMaxRetries`: Events that have reached the maximum number of attempts +/// - `onOpened`: For example, when you want to perform biometric authentication +/// - `onConfirmed`: Called when the first and second inputs match during confirmation +/// - `onCancelled`: Called when the user cancels the screen /// - `customizedButtonTap`: Tapped for left side lower button /// - `customizedButtonChild`: Child for bottom left side button /// - `footer`: Add a Widget to the footer @@ -31,16 +32,16 @@ import 'package:flutter_screen_lock/flutter_screen_lock.dart'; /// - `withBlur`: Blur the background /// - `secretsBuilder`: Custom secrets animation widget builder /// - `useLandscape`: Use a landscape orientation. Default `true` -/// - `onValidate`: Callback to validate input values filled in [digits]. Future screenLock({ required BuildContext context, required String correctString, - VoidCallback? didUnlocked, - VoidCallback? didOpened, - VoidCallback? didCancelled, - void Function(String matchedText)? didConfirmed, - void Function(int retries)? didError, - void Function(int retries)? didMaxRetries, + VoidCallback? onUnlocked, + ValidationCallback? onValidate, + VoidCallback? onOpened, + VoidCallback? onCancelled, + void Function(String matchedText)? onConfirmed, + void Function(int retries)? onError, + void Function(int retries)? onMaxRetries, VoidCallback? customizedButtonTap, bool confirmation = false, bool canCancel = true, @@ -58,10 +59,9 @@ Future screenLock({ Widget? cancelButton, Widget? deleteButton, InputController? inputController, - bool withBlur = true, SecretsBuilderCallback? secretsBuilder, + bool useBlur = true, bool useLandscape = true, - ValidationCallback? onValidate, }) async { return Navigator.push( context, @@ -69,24 +69,24 @@ Future screenLock({ opaque: false, barrierColor: Colors.black.withOpacity(0.8), pageBuilder: (context, animation, secondaryAnimation) => WillPopScope( - onWillPop: () async => canCancel && didCancelled == null, + onWillPop: () async => canCancel && onCancelled == null, child: ScreenLock( correctString: correctString, screenLockConfig: screenLockConfig, secretsConfig: secretsConfig, keyPadConfig: keyPadConfig, - didCancelled: - canCancel ? didCancelled ?? Navigator.of(context).pop : null, + onCancelled: + canCancel ? onCancelled ?? Navigator.of(context).pop : null, confirmation: confirmation, digits: digits, maxRetries: maxRetries, retryDelay: retryDelay, delayBuilder: delayBuilder, - didUnlocked: didUnlocked ?? Navigator.of(context).pop, - didError: didError, - didMaxRetries: didMaxRetries, - didConfirmed: didConfirmed, - didOpened: didOpened, + onUnlocked: onUnlocked ?? Navigator.of(context).pop, + onError: onError, + onMaxRetries: onMaxRetries, + onConfirmed: onConfirmed, + onOpened: onOpened, customizedButtonTap: customizedButtonTap, customizedButtonChild: customizedButtonChild, footer: footer, @@ -95,7 +95,7 @@ Future screenLock({ title: title, confirmTitle: confirmTitle, inputController: inputController, - withBlur: withBlur, + useBlur: useBlur, secretsBuilder: secretsBuilder, useLandscape: useLandscape, onValidate: onValidate, diff --git a/lib/src/input_controller.dart b/lib/src/input_controller.dart index c4383f4..ff8c7a4 100644 --- a/lib/src/input_controller.dart +++ b/lib/src/input_controller.dart @@ -110,11 +110,12 @@ class InputController { } /// Create each stream. - void initialize( - {required int digits, - required String correctString, - bool isConfirmed = false, - ValidationCallback? onValidate}) { + void initialize({ + required int digits, + required String correctString, + bool isConfirmed = false, + ValidationCallback? onValidate, + }) { _inputValueNotifier = ValueNotifier(''); _verifyController = StreamController.broadcast(); _confirmedController = StreamController.broadcast(); diff --git a/lib/src/screen_lock.dart b/lib/src/screen_lock.dart index aa7c40a..fffa6ec 100644 --- a/lib/src/screen_lock.dart +++ b/lib/src/screen_lock.dart @@ -25,12 +25,13 @@ class ScreenLock extends StatefulWidget { const ScreenLock({ Key? key, required this.correctString, - required this.didUnlocked, - this.didOpened, - this.didCancelled, - this.didConfirmed, - this.didError, - this.didMaxRetries, + required this.onUnlocked, + this.onOpened, + this.onValidate, + this.onCancelled, + this.onConfirmed, + this.onError, + this.onMaxRetries, this.customizedButtonTap, this.confirmation = false, this.digits = 4, @@ -47,10 +48,9 @@ class ScreenLock extends StatefulWidget { this.cancelButton, this.deleteButton, this.inputController, - this.withBlur = true, this.secretsBuilder, + this.useBlur = true, this.useLandscape = true, - this.onValidate, }) : title = title ?? const Text('Please enter passcode.'), confirmTitle = confirmTitle ?? const Text('Please enter confirm passcode.'), @@ -63,26 +63,31 @@ class ScreenLock extends StatefulWidget { final String correctString; /// Called if the value matches the correctString. - final VoidCallback didUnlocked; + final VoidCallback onUnlocked; + + /// Callback to validate input values filled in [digits]. + /// + /// If `true` is returned, the lock is unlocked. + final ValidationCallback? onValidate; /// Called when the screen is shown the first time. /// /// Useful if you want to show biometric authentication. - final VoidCallback? didOpened; + final VoidCallback? onOpened; /// Called when the user cancels. /// /// If null, the user cannot cancel. - final VoidCallback? didCancelled; + final VoidCallback? onCancelled; /// Called when the first and second inputs match during confirmation. - final void Function(String matchedText)? didConfirmed; + final void Function(String matchedText)? onConfirmed; /// Called if the value does not match the correctString. - final void Function(int retries)? didError; + final void Function(int retries)? onError; /// Events that have reached the maximum number of attempts. - final void Function(int retries)? didMaxRetries; + final void Function(int retries)? onMaxRetries; /// Tapped for left side lower button. final VoidCallback? customizedButtonTap; @@ -135,20 +140,15 @@ class ScreenLock extends StatefulWidget { /// Control inputs externally. final InputController? inputController; - /// Blur the background. - final bool withBlur; - /// Custom secrets animation widget builder. final SecretsBuilderCallback? secretsBuilder; + /// Blur the background. + final bool useBlur; + /// Use a landscape orientation. final bool useLandscape; - /// Callback to validate input values filled in [digits]. - /// - /// If `true` is returned, the lock is unlocked. - final ValidationCallback? onValidate; - @override State createState() => _ScreenLockState(); } @@ -201,10 +201,10 @@ class _ScreenLockState extends State { } void error() { - widget.didError?.call(retries); + widget.onError?.call(retries); if (widget.maxRetries >= 1 && widget.maxRetries <= retries) { - widget.didMaxRetries?.call(retries); + widget.onMaxRetries?.call(retries); // reset retries retries = 0; @@ -293,9 +293,9 @@ class _ScreenLockState extends State { if (success) { if (widget.confirmation) { - widget.didConfirmed?.call(inputController.confirmedInput); + widget.onConfirmed?.call(inputController.confirmedInput); } else { - widget.didUnlocked(); + widget.onUnlocked(); } } else { error(); @@ -303,7 +303,7 @@ class _ScreenLockState extends State { }); WidgetsBinding.instance - .addPostFrameCallback((_) => widget.didOpened?.call()); + .addPostFrameCallback((_) => widget.onOpened?.call()); } @override @@ -357,7 +357,7 @@ class _ScreenLockState extends State { enabled: !inputDelayed, keyPadConfig: widget.keyPadConfig, inputState: inputController, - didCancelled: widget.didCancelled, + didCancelled: widget.onCancelled, customizedButtonTap: widget.customizedButtonTap, customizedButtonChild: widget.customizedButtonChild, deleteButton: widget.deleteButton, @@ -403,7 +403,7 @@ class _ScreenLockState extends State { child: Scaffold( backgroundColor: widget.screenLockConfig.backgroundColor, body: SafeArea( - child: widget.withBlur ? buildContentWithBlur() : buildContent(), + child: widget.useBlur ? buildContentWithBlur() : buildContent(), ), ), ); diff --git a/pubspec.lock b/pubspec.lock index 0916f91..aaff18a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -21,7 +21,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -35,21 +35,14 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: @@ -77,7 +70,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.3.1" file: dependency: transitive description: @@ -122,21 +115,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" package_config: dependency: transitive description: @@ -150,7 +143,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.2" pub_semver: dependency: transitive description: @@ -169,7 +162,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.2" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -190,21 +183,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.9" + version: "0.4.12" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f742192..6db4a6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: flutter_screen_lock description: Provides the ability to lock the screen on ios and android. Biometric authentication can be used in addition to passcode. homepage: https://github.com/naoki0719/flutter_screen_lock -version: 7.0.4 +version: 8.0.0 environment: sdk: ">=2.17.0-0 <3.0.0" From 9e2260487020f5ecee63259dbc79953d7eadab02 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 14:40:00 +0200 Subject: [PATCH 02/12] Added ScreenLock.create to ensure type safety --- README.md | 196 +++++----- example/.metadata | 21 +- example/lib/main.dart | 496 ++++++++++++------------ example/pubspec.lock | 4 +- example/pubspec.yaml | 2 +- lib/src/functions.dart | 190 ++++++--- lib/src/input_controller.dart | 15 +- lib/src/layout/key_pad.dart | 5 +- lib/src/layout/secrets.dart | 20 +- lib/src/layout/styled_input_button.dart | 5 +- lib/src/screen_lock.dart | 291 +++++++------- test/input_state_test.dart | 6 +- 12 files changed, 686 insertions(+), 565 deletions(-) diff --git a/README.md b/README.md index cf6ef15..880411b 100644 --- a/README.md +++ b/README.md @@ -10,29 +10,6 @@ You can also use biometric authentication as an option. -## 6.x to 7 migration - -Change delayChilde to delayBuilder. -It used to push another screen, but has been changed to display a message in TextWidget. - -We would like to thank [clragon](https://github.com/clragon) for their significant contribution to these changes. - -## 5.x to 6 migration - -The major change is that Navigator.pop will be controlled by the developer. -This is because it is undesirable to pop inside the package in various situations. -However, we continue to pop in the initial value of the callback as before. - -We would like to thank [clragon](https://github.com/clragon) for their significant contribution to these changes. - -## 4.x to 5 migration - -Change to the next import only. - -```dart -import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -``` - ## Features - By the length of the character count @@ -63,9 +40,9 @@ screenLock( ); ``` -### Change digits +### Block user -Provides a screen lock that cannot be canceled. +Provides a screen lock that cannot be cancelled. ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; @@ -77,46 +54,38 @@ screenLock( ); ``` -### Confirmation screen +### Pin creation -You can display the confirmation screen and get the first input with didConfirmed if the first and second inputs match. +You can have users create a new pin with confirmation ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -screenLock( +screenLockCreate( context: context, - correctString: '', - confirmation: true, - didConfirmed: (matchedText) { - print(matchedText); - }, + onConfirmed: (value) => print(value), // store new pin somewhere here ); ``` -### Control the confirmation state +### Control the creation state ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; final inputController = InputController(); -screenLock( +screenLockCreate( context: context, - correctString: '', - confirmation: true, inputController: inputController, ); -// Release the confirmation state at any event. -inputController.unsetConfirmed(); +// Somewhere else... +inputController.unsetConfirmed(); // reset first and confirm input ``` ### Use local_auth -Add the local_auth package to pubspec.yml. - -https://pub.dev/packages/local_auth +Add the [local_auth](https://pub.dev/packages/local_auth) package to pubspec.yml. It includes an example that calls biometrics as soon as screenLock is displayed in `didOpened`. @@ -138,31 +107,27 @@ Future localAuth(BuildContext context) async { screenLock( context: context, correctString: '1234', - customizedButtonChild: Icon( - Icons.fingerprint, - ), - customizedButtonTap: () async { - await localAuth(context); - }, - didOpened: () async { - await localAuth(context); - }, + customizedButtonChild: Icon(Icons.fingerprint), + customizedButtonTap: () async => await localAuth(context), + didOpened: () async => await localAuth(context), ); ``` -### Full customize +### Fully customize + +You can customize every aspect of the screenlock. +Here is an example: ```dart import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -screenLock( +screenLockCreate( context: context, - title: Text('change title'), - confirmTitle: Text('change confirm title'), - correctString: '1234', - confirmation: true, - screenLockConfig: ScreenLockConfig( + title: const Text('change title'), + confirmTitle: const Text('change confirm title'), + onConfirmed: (value) => Navigator.of(context).pop(), + screenLockConfig: const ScreenLockConfig( backgroundColor: Colors.deepOrange, ), secretsConfig: SecretsConfig( @@ -175,39 +140,50 @@ screenLock( enabledColor: Colors.amber, height: 15, width: 15, - build: (context, {config, enabled}) { - return SizedBox( - child: Container( - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: enabled - ? config.enabledColor - : config.disabledColor, - border: Border.all( - width: config.borderSize, - color: config.borderColor, - ), - ), - padding: EdgeInsets.all(10), - width: config.width, - height: config.height, + build: (context, + {required config, required enabled}) => + Container( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: enabled + ? config.enabledColor + : config.disabledColor, + border: Border.all( + width: config.borderSize, + color: config.borderColor, ), - width: config.width, - height: config.height, - ); - }, + ), + padding: const EdgeInsets.all(10), + width: config.width, + height: config.height, + ), ), ), - inputButtonConfig: InputButtonConfig( - textStyle: - InputButtonConfig.getDefaultTextStyle(context).copyWith( - color: Colors.orange, - fontWeight: FontWeight.bold, - ), - buttonStyle: OutlinedButton.styleFrom( - shape: RoundedRectangleBorder(), - backgroundColor: Colors.deepOrange, + keyPadConfig: KeyPadConfig( + buttonConfig: StyledInputConfig( + textStyle: + StyledInputConfig.getDefaultTextStyle(context) + .copyWith( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), + buttonStyle: OutlinedButton.styleFrom( + shape: const RoundedRectangleBorder(), + backgroundColor: Colors.deepOrange, + ), ), + displayStrings: [ + '零', + '壱', + '弐', + '参', + '肆', + '伍', + '陸', + '質', + '捌', + '玖' + ], ), cancelButton: const Icon(Icons.close), deleteButton: const Icon(Icons.delete), @@ -216,14 +192,50 @@ screenLock( -## Apps I use +## Version migration + +### 7.x to 8 migration + +- Change all callback names from `didSomething` to `onSomething` +- change `screenLock` with `confirm: true` to `screenLockCreate` +- change `ScreenLock` with `confirm: true` to `ScreenLock.create` + +### 6.x to 7 migration + +- Requires dart >= 2.17 and Flutter 3.0 +- Change `delayChild` to `delayBuilder`. + + `delayBuilder` is no longer displayed in a new screen. Instead, it is now located above the `Secrets`. +- Accept `BuildContext` in `secretsBuilder` + +### 5.x to 6 migration + +- `ScreenLock` does not use `Navigator.pop` internally anymore. + The developer should now pop by themselves when desired. + `screenLock` call will pop automatically if `onUnlocked` is `null`. + +### 4.x to 5 migration + +Import name has changed from: + +```dart +import 'package:flutter_screen_lock/functions.dart'; +``` + +to + +```dart +import 'package:flutter_screen_lock/flutter_screen_lock.dart'; +``` + +## Apps that use this library -TimeKey +### TimeKey -[iOS](https://apps.apple.com/us/app/timekey-authenticator/id1506129753) +- [iOS](https://apps.apple.com/us/app/timekey-authenticator/id1506129753) -[Android](https://play.google.com/store/apps/details?id=net.incrementleaf.TimeKey) +- [Android](https://play.google.com/store/apps/details?id=net.incrementleaf.TimeKey) -## Back me up! +## Support me! Buy Me A Coffee diff --git a/example/.metadata b/example/.metadata index 0c50e4f..514cc27 100644 --- a/example/.metadata +++ b/example/.metadata @@ -1,10 +1,27 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: e6b34c2b5c96bb95325269a29a84e83ed8909b5f + revision: eb6d86ee27deecba4a83536aa20f366a6044895c channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: eb6d86ee27deecba4a83536aa20f366a6044895c + base_revision: eb6d86ee27deecba4a83536aa20f366a6044895c + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/lib/main.dart b/example/lib/main.dart index 2746679..f376633 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,16 +1,12 @@ -import 'dart:io'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/flutter_screen_lock.dart'; import 'package:local_auth/local_auth.dart'; -void main() { - runApp(const MyApp()); -} +void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); + const MyApp({super.key}); @override Widget build(BuildContext context) { @@ -26,7 +22,7 @@ class MyApp extends StatelessWidget { } class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key}) : super(key: key); + const MyHomePage({super.key}); @override _MyHomePageState createState() => _MyHomePageState(); @@ -47,277 +43,266 @@ class _MyHomePageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: const Text('Next Screen Lock'), - ), - body: SizedBox( - width: double.infinity, - child: Wrap( - spacing: 10, - alignment: WrapAlignment.center, - children: [ - ElevatedButton( - onPressed: () => showDialog( - context: context, - builder: (context) { - return ScreenLock( - correctString: '1234', - onCancelled: Navigator.of(context).pop, - onUnlocked: Navigator.of(context).pop, - ); - }, - ), - child: const Text('Manualy open'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - canCancel: false, + appBar: AppBar( + title: const Text('Next Screen Lock'), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 700, ), - child: const Text('Not cancelable'), - ), - ElevatedButton( - onPressed: () { - // Define it to control the confirmation state with its own events. - final inputController = InputController(); - screenLock( - context: context, - correctString: '', - confirmation: true, - inputController: inputController, - onConfirmed: (matchedText) { - // ignore: avoid_print - print(matchedText); - }, - footer: TextButton( + child: Wrap( + spacing: 16, + runSpacing: 16, + alignment: WrapAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () => showDialog( + context: context, + builder: (context) { + return ScreenLock( + correctString: '1234', + onCancelled: Navigator.of(context).pop, + onUnlocked: Navigator.of(context).pop, + ); + }, + ), + child: const Text('Manually open'), + ), + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + canCancel: false, + ), + child: const Text('Not cancelable'), + ), + ElevatedButton( onPressed: () { - // Release the confirmation state and return to the initial input state. - inputController.unsetConfirmed(); + // Define it to control the confirmation state with its own events. + final controller = InputController(); + screenLockCreate( + context: context, + inputController: controller, + onConfirmed: (matchedText) => + Navigator.of(context).pop(), + footer: TextButton( + onPressed: () { + // Release the confirmation state and return to the initial input state. + controller.unsetConfirmed(); + }, + child: const Text('Reset input'), + ), + ); }, - child: const Text('Return enter mode.'), + child: const Text('Confirm mode'), ), - ); - }, - child: const Text('Confirm mode'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - customizedButtonChild: const Icon( - Icons.fingerprint, - ), - customizedButtonTap: () async { - await localAuth(context); - }, - onOpened: () async { - await localAuth(context); - }, - ), - child: const Text( - 'use local_auth \n(Show local_auth when opened)', - textAlign: TextAlign.center, - ), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '123456', - digits: '123456'.length, - canCancel: false, - footer: Container( - padding: const EdgeInsets.only(top: 10), - child: OutlinedButton( - child: const Text('Cancel'), - onPressed: () => Navigator.pop(context), - style: OutlinedButton.styleFrom( - backgroundColor: Colors.transparent, + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + customizedButtonChild: const Icon( + Icons.fingerprint, + ), + customizedButtonTap: () async => await localAuth(context), + onOpened: () async => await localAuth(context), + ), + child: const Text( + 'use local_auth \n(Show local_auth when opened)', + textAlign: TextAlign.center, ), ), - ), - ), - child: const Text('Using footer'), - ), - ElevatedButton( - onPressed: () { - screenLock( - context: context, - title: const Text('change title'), - confirmTitle: const Text('change confirm title'), - correctString: '', - confirmation: true, - screenLockConfig: const ScreenLockConfig( - backgroundColor: Colors.deepOrange, + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '123456', + canCancel: false, + footer: Container( + padding: const EdgeInsets.only(top: 10), + child: OutlinedButton( + child: const Text('Cancel'), + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + backgroundColor: Colors.transparent, + ), + ), + ), + ), + child: const Text('Using footer'), ), - secretsConfig: SecretsConfig( - spacing: 15, // or spacingRatio - padding: const EdgeInsets.all(40), - secretConfig: SecretConfig( - borderColor: Colors.amber, - borderSize: 2.0, - disabledColor: Colors.black, - enabledColor: Colors.amber, - height: 15, - width: 15, - build: (context, {required config, required enabled}) { - return SizedBox( - child: Container( - decoration: BoxDecoration( - shape: BoxShape.rectangle, - color: enabled - ? config.enabledColor - : config.disabledColor, - border: Border.all( - width: config.borderSize, - color: config.borderColor, + ElevatedButton( + onPressed: () { + screenLockCreate( + context: context, + title: const Text('change title'), + confirmTitle: const Text('change confirm title'), + onConfirmed: (value) => Navigator.of(context).pop(), + screenLockConfig: const ScreenLockConfig( + backgroundColor: Colors.deepOrange, + ), + secretsConfig: SecretsConfig( + spacing: 15, // or spacingRatio + padding: const EdgeInsets.all(40), + secretConfig: SecretConfig( + borderColor: Colors.amber, + borderSize: 2.0, + disabledColor: Colors.black, + enabledColor: Colors.amber, + height: 15, + width: 15, + build: (context, + {required config, required enabled}) => + Container( + decoration: BoxDecoration( + shape: BoxShape.rectangle, + color: enabled + ? config.enabledColor + : config.disabledColor, + border: Border.all( + width: config.borderSize, + color: config.borderColor, + ), ), + padding: const EdgeInsets.all(10), + width: config.width, + height: config.height, ), - padding: const EdgeInsets.all(10), - width: config.width, - height: config.height, ), - width: config.width, - height: config.height, - ); + ), + keyPadConfig: KeyPadConfig( + buttonConfig: StyledInputConfig( + textStyle: + StyledInputConfig.getDefaultTextStyle(context) + .copyWith( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), + buttonStyle: OutlinedButton.styleFrom( + shape: const RoundedRectangleBorder(), + backgroundColor: Colors.deepOrange, + ), + ), + displayStrings: [ + '零', + '壱', + '弐', + '参', + '肆', + '伍', + '陸', + '質', + '捌', + '玖' + ], + ), + cancelButton: const Icon(Icons.close), + deleteButton: const Icon(Icons.delete), + ); + }, + child: const Text('Customize styles'), + ), + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + onUnlocked: () { + Navigator.pop(context); + NextPage.show(context); }, ), + child: const Text('Next page with unlock'), ), - keyPadConfig: KeyPadConfig( - buttonConfig: StyledInputConfig( - textStyle: StyledInputConfig.getDefaultTextStyle(context) - .copyWith( - color: Colors.orange, - fontWeight: FontWeight.bold, + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + useBlur: false, + screenLockConfig: const ScreenLockConfig( + /// If you don't want it to be transparent, override the config + backgroundColor: Colors.black, ), - buttonStyle: OutlinedButton.styleFrom( - shape: const RoundedRectangleBorder(), - backgroundColor: Colors.deepOrange, + ), + child: const Text('Not blur'), + ), + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + maxRetries: 2, + retryDelay: const Duration(seconds: 3), + delayBuilder: (context, delay) => Text( + 'Cannot be entered for ${(delay.inMilliseconds / 1000).ceil()} seconds.', ), ), - displayStrings: [ - '零', - '壱', - '弐', - '参', - '肆', - '伍', - '陸', - '質', - '捌', - '玖' - ], + child: const Text('Delay next retry'), ), - cancelButton: const Icon(Icons.close), - deleteButton: const Icon(Icons.delete), - ); - }, - child: const Text('Customize styles'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - onUnlocked: () { - Navigator.pop(context); - NextPage.show(context); - }, - ), - child: const Text('Next page with unlock'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - useBlur: false, - screenLockConfig: const ScreenLockConfig( - /// If you don't want it to be transparent, override the config - backgroundColor: Colors.black, - ), - ), - child: const Text('Not blur'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - maxRetries: 2, - retryDelay: const Duration(seconds: 3), - delayBuilder: (context, delay) => Text( - 'Cannot be entered for ${(delay.inMilliseconds / 1000).ceil()} seconds.', - ), - ), - child: const Text('Delay next retry'), - ), - ElevatedButton( - onPressed: () => showDialog( - context: context, - builder: (context) { - return ScreenLock( - correctString: '1234', - keyPadConfig: const KeyPadConfig( - clearOnLongPressed: true, + ElevatedButton( + onPressed: () => showDialog( + context: context, + builder: (context) => ScreenLock( + correctString: '1234', + keyPadConfig: const KeyPadConfig( + clearOnLongPressed: true, + ), + onUnlocked: Navigator.of(context).pop, + ), ), - onUnlocked: Navigator.of(context).pop, - ); - }, - ), - child: const Text('Delete long pressed to clear input'), - ), - ElevatedButton( - onPressed: () => showDialog( - context: context, - builder: (context) { - return ScreenLock( - correctString: '1234', - secretsBuilder: ( - context, - config, - length, - input, - verifyStream, - ) => - SecretsWithCustomAnimation( - verifyStream: verifyStream, - config: config, - input: input, - length: length, + child: const Text('Delete long pressed to clear input'), + ), + ElevatedButton( + onPressed: () => showDialog( + context: context, + builder: (context) => ScreenLock( + correctString: '1234', + secretsBuilder: ( + context, + config, + length, + input, + verifyStream, + ) => + SecretsWithCustomAnimation( + verifyStream: verifyStream, + config: config, + input: input, + length: length, + ), + onUnlocked: Navigator.of(context).pop, + ), ), - onUnlocked: Navigator.of(context).pop, - ); - }, - ), - child: const Text('Secrets custom animation widget'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - correctString: '1234', - useLandscape: false, - ), - child: const Text('Disable landscape mode'), - ), - ElevatedButton( - onPressed: () => screenLock( - context: context, - digits: 4, - correctString: '', - onValidate: (x) async { - sleep(const Duration(milliseconds: 500)); - return '1234' == x; - }, + child: const Text('Secrets custom animation widget'), + ), + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: '1234', + useLandscape: false, + ), + child: const Text('Disable landscape mode'), + ), + ElevatedButton( + onPressed: () => screenLock( + context: context, + correctString: 'x' * 4, + onValidate: (value) async => await Future.delayed( + const Duration(milliseconds: 500), + () => value == '1234', + ), + ), + child: const Text('Callback validation'), + ), + ], ), - child: const Text('Callback validation'), ), - ], - ), - ), - ); + ), + )); } } class NextPage extends StatelessWidget { - const NextPage({Key? key}) : super(key: key); + const NextPage({super.key}); static show(BuildContext context) { Navigator.of(context) @@ -336,12 +321,13 @@ class NextPage extends StatelessWidget { class SecretsWithCustomAnimation extends StatefulWidget { const SecretsWithCustomAnimation({ - Key? key, + super.key, required this.config, required this.length, required this.input, required this.verifyStream, - }) : super(key: key); + }); + final SecretsConfig config; final int length; final ValueListenable input; diff --git a/example/pubspec.lock b/example/pubspec.lock index 9d59b71..cc95f45 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -47,7 +47,7 @@ packages: path: ".." relative: true source: path - version: "7.0.4" + version: "8.0.0" intl: dependency: transitive description: @@ -110,5 +110,5 @@ packages: source: hosted version: "2.1.2" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=1.20.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index bed536b..7bce65e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -5,7 +5,7 @@ publish_to: "none" version: 4.0.0 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" dependencies: flutter: diff --git a/lib/src/functions.dart b/lib/src/functions.dart index 0f86670..f0e2c90 100644 --- a/lib/src/functions.dart +++ b/lib/src/functions.dart @@ -1,60 +1,51 @@ import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/flutter_screen_lock.dart'; +import 'package:flutter_screen_lock/src/layout/key_pad.dart'; /// Animated ScreenLock /// /// - `correctString`: Input correct string (Required). -/// If [confirmation] is `true`, it will be ignored, so set it to any string or empty. -/// - `screenLockConfig`: Configurations of [ScreenLock] -/// - `secretsConfig`: Configurations of [Secrets] -/// - `inputButtonConfig`: Configurations of [InputButton] -/// - `canCancel`: `true` is show cancel button -/// - `confirmation`: Make sure the first and second inputs are the same. -/// - `digits`: Set the maximum number of characters to enter when [confirmation] is `true`. -/// - `maxRetries`: `0` is unlimited. For example, if it is set to 1, didMaxRetries will be called on the first failure. Default `0` -/// - `retryDelay`: Delay until we can retry. Duration.zero is no delay. -/// - `delayChild`: Specify the widget during input invalidation by retry delay. /// - `onUnlocked`: Called if the value matches the correctString. +/// - `onOpened`: For example, when you want to perform biometric authentication. /// - `onValidate`: Callback to validate input values filled in [digits]. +/// - `onCancelled`: Called when the user cancels the screen. /// - `onError`: Called if the value does not match the correctString. -/// - `onMaxRetries`: Events that have reached the maximum number of attempts -/// - `onOpened`: For example, when you want to perform biometric authentication -/// - `onConfirmed`: Called when the first and second inputs match during confirmation -/// - `onCancelled`: Called when the user cancels the screen -/// - `customizedButtonTap`: Tapped for left side lower button -/// - `customizedButtonChild`: Child for bottom left side button -/// - `footer`: Add a Widget to the footer -/// - `cancelButton`: Change the child widget for the delete button -/// - `deleteButton`: Change the child widget for the delete button -/// - `title`: Change the title widget -/// - `confirmTitle`: Change the confirm title widget -/// - `inputController`: Control inputs externally -/// - `withBlur`: Blur the background -/// - `secretsBuilder`: Custom secrets animation widget builder -/// - `useLandscape`: Use a landscape orientation. Default `true` +/// - `onMaxRetries`: Events that have reached the maximum number of attempts. +/// - `maxRetries`: `0` is unlimited. For example, if it is set to 1, didMaxRetries will be called on the first failure. Default `0`. +/// - `retryDelay`: Delay until we can retry. Duration.zero is no delay. +/// - `title`: Change the title widget. +/// - `screenLockConfig`: Configurations of [ScreenLock]. +/// - `secretsConfig`: Configurations of [Secrets]. +/// - `keyPadConfig`: Configuration of [KeyPad]. +/// - `delayBuilder`: Specify the widget during input invalidation by retry delay. +/// - `customizedButtonChild`: Child for bottom left side button. +/// - `customizedButtonTap`: Tapped for left side lower button. +/// - `footer`: Add a Widget to the footer. +/// - `cancelButton`: Change the child widget for the delete button. +/// - `deleteButton`: Change the child widget for the delete button. +/// - `inputController`: Control inputs externally. +/// - `secretsBuilder`: Custom secrets animation widget builder. +/// - `useBlur`: Blur the background. +/// - `useLandscape`: Use a landscape orientation. Default `true`. +/// - `canCancel`: `true` is show cancel button. Future screenLock({ required BuildContext context, required String correctString, VoidCallback? onUnlocked, - ValidationCallback? onValidate, VoidCallback? onOpened, + ValidationCallback? onValidate, VoidCallback? onCancelled, - void Function(String matchedText)? onConfirmed, - void Function(int retries)? onError, - void Function(int retries)? onMaxRetries, - VoidCallback? customizedButtonTap, - bool confirmation = false, - bool canCancel = true, - int digits = 4, + ValueChanged? onError, + ValueChanged? onMaxRetries, int maxRetries = 0, Duration retryDelay = Duration.zero, Widget? title, - Widget? confirmTitle, ScreenLockConfig? screenLockConfig, SecretsConfig? secretsConfig, KeyPadConfig? keyPadConfig, DelayBuilderCallback? delayBuilder, Widget? customizedButtonChild, + VoidCallback? customizedButtonTap, Widget? footer, Widget? cancelButton, Widget? deleteButton, @@ -62,6 +53,7 @@ Future screenLock({ SecretsBuilderCallback? secretsBuilder, bool useBlur = true, bool useLandscape = true, + bool canCancel = true, }) async { return Navigator.push( context, @@ -72,33 +64,137 @@ Future screenLock({ onWillPop: () async => canCancel && onCancelled == null, child: ScreenLock( correctString: correctString, + onUnlocked: onUnlocked ?? Navigator.of(context).pop, + onOpened: onOpened, + onValidate: onValidate, + onCancelled: + canCancel ? onCancelled ?? Navigator.of(context).pop : null, + onError: onError, + onMaxRetries: onMaxRetries, + maxRetries: maxRetries, + retryDelay: retryDelay, + title: title, screenLockConfig: screenLockConfig, secretsConfig: secretsConfig, keyPadConfig: keyPadConfig, + delayBuilder: delayBuilder, + customizedButtonChild: customizedButtonChild, + customizedButtonTap: customizedButtonTap, + footer: footer, + cancelButton: cancelButton, + deleteButton: deleteButton, + inputController: inputController, + secretsBuilder: secretsBuilder, + useBlur: useBlur, + useLandscape: useLandscape, + ), + ), + transitionsBuilder: (context, animation, secondaryAnimation, child) => + SlideTransition( + position: Tween( + begin: const Offset(0.0, 2.4), + end: Offset.zero, + ).animate(animation), + child: SlideTransition( + position: Tween( + begin: Offset.zero, + end: const Offset(0.0, 2.4), + ).animate(secondaryAnimation), + child: child, + ), + ), + ), + ); +} + +/// Animated ScreenLock +/// +/// - `onConfirmed`: Called when the first and second inputs match during confirmation. +/// - `onOpened`: For example, when you want to perform biometric authentication. +/// - `onValidate`: Callback to validate input values filled in [digits]. +/// - `onCancelled`: Called when the user cancels the screen. +/// - `onError`: Called if the value does not match the correctString. +/// - `onMaxRetries`: Events that have reached the maximum number of attempts. +/// - `maxRetries`: `0` is unlimited. For example, if it is set to 1, didMaxRetries will be called on the first failure. Default `0`. +/// - `digits`: Set the maximum number of characters to enter. +/// - `retryDelay`: Delay until we can retry. Duration.zero is no delay. +/// - `title`: Change the title widget. +/// - `confirmTitle`: Change the confirm title widget. +/// - `screenLockConfig`: Configurations of [ScreenLock]. +/// - `secretsConfig`: Configurations of [Secrets]. +/// - `keyPadConfig`: Configuration of [KeyPad]. +/// - `delayBuilder`: Specify the widget during input invalidation by retry delay. +/// - `customizedButtonChild`: Child for bottom left side button. +/// - `customizedButtonTap`: Tapped for left side lower button. +/// - `footer`: Add a Widget to the footer. +/// - `cancelButton`: Change the child widget for the delete button. +/// - `deleteButton`: Change the child widget for the delete button. +/// - `inputController`: Control inputs externally. +/// - `secretsBuilder`: Custom secrets animation widget builder. +/// - `useBlur`: Blur the background. +/// - `useLandscape`: Use a landscape orientation. Default `true`. +/// - `canCancel`: `true` is show cancel button. +Future screenLockCreate({ + required BuildContext context, + required ValueChanged onConfirmed, + VoidCallback? onOpened, + ValidationCallback? onValidate, + VoidCallback? onCancelled, + ValueChanged? onError, + ValueChanged? onMaxRetries, + int maxRetries = 0, + int digits = 4, + Duration retryDelay = Duration.zero, + Widget? title, + Widget? confirmTitle, + ScreenLockConfig? screenLockConfig, + SecretsConfig? secretsConfig, + KeyPadConfig? keyPadConfig, + DelayBuilderCallback? delayBuilder, + Widget? customizedButtonChild, + VoidCallback? customizedButtonTap, + Widget? footer, + Widget? cancelButton, + Widget? deleteButton, + InputController? inputController, + SecretsBuilderCallback? secretsBuilder, + bool useBlur = true, + bool useLandscape = true, + bool canCancel = true, +}) async { + return Navigator.push( + context, + PageRouteBuilder( + opaque: false, + barrierColor: Colors.black.withOpacity(0.8), + pageBuilder: (context, animation, secondaryAnimation) => WillPopScope( + onWillPop: () async => canCancel && onCancelled == null, + child: ScreenLock.create( + onConfirmed: onConfirmed, + onOpened: onOpened, + onValidate: onValidate, onCancelled: canCancel ? onCancelled ?? Navigator.of(context).pop : null, - confirmation: confirmation, - digits: digits, + onError: onError, + onMaxRetries: onMaxRetries, maxRetries: maxRetries, + digits: digits, retryDelay: retryDelay, + title: title, + confirmTitle: confirmTitle, + screenLockConfig: screenLockConfig, + secretsConfig: secretsConfig, + keyPadConfig: keyPadConfig, delayBuilder: delayBuilder, - onUnlocked: onUnlocked ?? Navigator.of(context).pop, - onError: onError, - onMaxRetries: onMaxRetries, - onConfirmed: onConfirmed, - onOpened: onOpened, - customizedButtonTap: customizedButtonTap, customizedButtonChild: customizedButtonChild, + customizedButtonTap: customizedButtonTap, footer: footer, - deleteButton: deleteButton, cancelButton: cancelButton, - title: title, - confirmTitle: confirmTitle, + deleteButton: deleteButton, inputController: inputController, - useBlur: useBlur, secretsBuilder: secretsBuilder, + useBlur: useBlur, useLandscape: useLandscape, - onValidate: onValidate, ), ), transitionsBuilder: (context, animation, secondaryAnimation, child) => diff --git a/lib/src/input_controller.dart b/lib/src/input_controller.dart index ff8c7a4..d8aa287 100644 --- a/lib/src/input_controller.dart +++ b/lib/src/input_controller.dart @@ -7,8 +7,7 @@ class InputController { InputController(); late int _digits; - late String _correctString; - late bool _isConfirmed; + late String? _correctString; late ValidationCallback? _validationCallback; final List _currentInputs = []; @@ -43,7 +42,7 @@ class InputController { return; } - if (_isConfirmed && _firstInput.isEmpty) { + if (_correctString == null && _firstInput.isEmpty) { setConfirmed(); clear(); } else { @@ -87,10 +86,10 @@ class InputController { final inputText = _currentInputs.join(); late String correctString; - if (_isConfirmed) { - correctString = _firstInput; + if (_correctString != null) { + correctString = _correctString!; } else { - correctString = _correctString; + correctString = _firstInput; } if (_validationCallback == null) { @@ -112,8 +111,7 @@ class InputController { /// Create each stream. void initialize({ required int digits, - required String correctString, - bool isConfirmed = false, + required String? correctString, ValidationCallback? onValidate, }) { _inputValueNotifier = ValueNotifier(''); @@ -122,7 +120,6 @@ class InputController { _digits = digits; _correctString = correctString; - _isConfirmed = isConfirmed; _validationCallback = onValidate; } diff --git a/lib/src/layout/key_pad.dart b/lib/src/layout/key_pad.dart index bff04df..e2e42ad 100644 --- a/lib/src/layout/key_pad.dart +++ b/lib/src/layout/key_pad.dart @@ -9,7 +9,7 @@ import 'package:flutter_screen_lock/src/input_controller.dart'; /// which makes it difficult to specify the size intuitively. class KeyPad extends StatelessWidget { const KeyPad({ - Key? key, + super.key, required this.inputState, required this.didCancelled, this.enabled = true, @@ -18,8 +18,7 @@ class KeyPad extends StatelessWidget { this.customizedButtonTap, this.deleteButton, this.cancelButton, - }) : keyPadConfig = keyPadConfig ?? const KeyPadConfig(), - super(key: key); + }) : keyPadConfig = keyPadConfig ?? const KeyPadConfig(); final InputController inputState; final VoidCallback? didCancelled; diff --git a/lib/src/layout/secrets.dart b/lib/src/layout/secrets.dart index 3810dd2..e04ca6b 100644 --- a/lib/src/layout/secrets.dart +++ b/lib/src/layout/secrets.dart @@ -7,12 +7,13 @@ import 'package:flutter_screen_lock/src/configurations/secrets_config.dart'; class SecretsWithShakingAnimation extends StatefulWidget { const SecretsWithShakingAnimation({ - Key? key, + super.key, required this.config, required this.length, required this.input, required this.verifyStream, - }) : super(key: key); + }); + final SecretsConfig config; final int length; final ValueListenable input; @@ -81,11 +82,11 @@ class _SecretsWithShakingAnimationState class Secrets extends StatefulWidget { const Secrets({ - Key? key, - this.config = const SecretsConfig(), + super.key, + SecretsConfig? config, required this.input, required this.length, - }) : super(key: key); + }) : config = config ?? const SecretsConfig(); final SecretsConfig config; final ValueListenable input; @@ -139,14 +140,13 @@ class _SecretsState extends State with SingleTickerProviderStateMixin { class Secret extends StatelessWidget { const Secret({ - Key? key, + super.key, + SecretConfig? config, this.enabled = false, - this.config = const SecretConfig(), - }) : super(key: key); - - final bool enabled; + }) : config = config ?? const SecretConfig(); final SecretConfig config; + final bool enabled; @override Widget build(BuildContext context) { diff --git a/lib/src/layout/styled_input_button.dart b/lib/src/layout/styled_input_button.dart index 9f77198..a457451 100644 --- a/lib/src/layout/styled_input_button.dart +++ b/lib/src/layout/styled_input_button.dart @@ -4,13 +4,12 @@ import 'package:flutter_screen_lock/src/configurations/styled_input_button_confi /// [OutlinedButton] based button. class StyledInputButton extends StatelessWidget { const StyledInputButton({ - Key? key, + super.key, this.child, required this.onPressed, this.onLongPress, StyledInputConfig? config, - }) : config = config ?? const StyledInputConfig(), - super(key: key); + }) : config = config ?? const StyledInputConfig(); factory StyledInputButton.transparent({ Key? key, diff --git a/lib/src/screen_lock.dart b/lib/src/screen_lock.dart index fffa6ec..9007040 100644 --- a/lib/src/screen_lock.dart +++ b/lib/src/screen_lock.dart @@ -6,44 +6,26 @@ import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/flutter_screen_lock.dart'; import 'package:flutter_screen_lock/src/layout/key_pad.dart'; -typedef DelayBuilderCallback = Widget Function( - BuildContext context, Duration delay); - -typedef SecretsBuilderCallback = Widget Function( - BuildContext context, - SecretsConfig config, - int length, - ValueListenable input, - Stream verifyStream, -); - -typedef ValidationCallback = Future Function( - String input, -); - class ScreenLock extends StatefulWidget { + /// Animated ScreenLock const ScreenLock({ - Key? key, - required this.correctString, - required this.onUnlocked, + super.key, + required String this.correctString, + required VoidCallback this.onUnlocked, this.onOpened, this.onValidate, this.onCancelled, - this.onConfirmed, this.onError, this.onMaxRetries, - this.customizedButtonTap, - this.confirmation = false, - this.digits = 4, this.maxRetries = 0, this.retryDelay = Duration.zero, Widget? title, - Widget? confirmTitle, - ScreenLockConfig? screenLockConfig, + this.screenLockConfig, SecretsConfig? secretsConfig, this.keyPadConfig, this.delayBuilder, this.customizedButtonChild, + this.customizedButtonTap, this.footer, this.cancelButton, this.deleteButton, @@ -52,18 +34,53 @@ class ScreenLock extends StatefulWidget { this.useBlur = true, this.useLandscape = true, }) : title = title ?? const Text('Please enter passcode.'), + confirmTitle = null, + digits = correctString.length, + onConfirmed = null, + secretsConfig = secretsConfig ?? const SecretsConfig(), + assert(maxRetries > -1, 'max retries cannot be less than 0'), + assert(correctString.length > 0, 'correct string cannot be empty'); + + /// Animated ScreenLock + const ScreenLock.create({ + super.key, + required ValueChanged this.onConfirmed, + this.onOpened, + this.onValidate, + this.onCancelled, + this.onError, + this.onMaxRetries, + this.maxRetries = 0, + this.digits = 4, + this.retryDelay = Duration.zero, + Widget? title, + Widget? confirmTitle, + this.screenLockConfig, + SecretsConfig? secretsConfig, + this.keyPadConfig, + this.delayBuilder, + this.customizedButtonChild, + this.customizedButtonTap, + this.footer, + this.cancelButton, + this.deleteButton, + this.inputController, + this.secretsBuilder, + this.useBlur = false, + this.useLandscape = false, + }) : correctString = null, + title = title ?? const Text('Please enter new passcode.'), confirmTitle = - confirmTitle ?? const Text('Please enter confirm passcode.'), - screenLockConfig = screenLockConfig ?? const ScreenLockConfig(), + confirmTitle ?? const Text('Please confirm new passcode.'), + onUnlocked = null, secretsConfig = secretsConfig ?? const SecretsConfig(), - assert(maxRetries > -1), - super(key: key); + assert(maxRetries > -1); /// Input correct string. - final String correctString; + final String? correctString; /// Called if the value matches the correctString. - final VoidCallback onUnlocked; + final VoidCallback? onUnlocked; /// Callback to validate input values filled in [digits]. /// @@ -81,27 +98,24 @@ class ScreenLock extends StatefulWidget { final VoidCallback? onCancelled; /// Called when the first and second inputs match during confirmation. - final void Function(String matchedText)? onConfirmed; + final ValueChanged? onConfirmed; /// Called if the value does not match the correctString. - final void Function(int retries)? onError; + final ValueChanged? onError; /// Events that have reached the maximum number of attempts. - final void Function(int retries)? onMaxRetries; + final ValueChanged? onMaxRetries; /// Tapped for left side lower button. final VoidCallback? customizedButtonTap; - /// Make sure the first and second inputs are the same. - final bool confirmation; - - /// Set the maximum number of characters to enter when confirmation is true. - final int digits; - /// `0` is unlimited. /// For example, if it is set to 1, didMaxRetries will be called on the first failure. final int maxRetries; + /// Set the maximum number of characters to enter when confirmation is true. + final int digits; + /// Delay until we can retry. /// /// Duration.zero is no delay. @@ -111,10 +125,10 @@ class ScreenLock extends StatefulWidget { final Widget title; /// Heading confirm title for ScreenLock. - final Widget confirmTitle; + final Widget? confirmTitle; /// Configurations of [ScreenLock]. - final ScreenLockConfig screenLockConfig; + final ScreenLockConfig? screenLockConfig; /// Configurations of [Secrets]. final SecretsConfig secretsConfig; @@ -146,7 +160,7 @@ class ScreenLock extends StatefulWidget { /// Blur the background. final bool useBlur; - /// Use a landscape orientation. + /// Use a landscape orientation when sufficient width is available. final bool useLandscape; @override @@ -154,21 +168,62 @@ class ScreenLock extends StatefulWidget { } class _ScreenLockState extends State { - late InputController inputController; + late InputController inputController = + widget.inputController ?? InputController(); /// Logging retries. int retries = 1; - /// First input completed. - bool firstInputCompleted = false; - - String firstInput = ''; - final StreamController inputDelayController = StreamController.broadcast(); bool inputDelayed = false; + @override + void initState() { + super.initState(); + inputController.initialize( + correctString: widget.correctString, + digits: widget.digits, + onValidate: widget.onValidate, + ); + + inputController.verifyInput.listen((success) { + // Wait for the animation on failure. + Future.delayed(const Duration(milliseconds: 300), () { + inputController.clear(); + }); + + if (success) { + if (widget.correctString != null) { + widget.onUnlocked!(); + } else { + widget.onConfirmed!(inputController.confirmedInput); + } + } else { + error(); + } + }); + + WidgetsBinding.instance + .addPostFrameCallback((_) => widget.onOpened?.call()); + } + + @override + void didUpdateWidget(covariant ScreenLock oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.inputController != widget.inputController) { + inputController.dispose(); + inputController = widget.inputController ?? InputController(); + } + } + + @override + void dispose() { + inputController.dispose(); + super.dispose(); + } + void inputDelay() { if (widget.retryDelay == (Duration.zero)) { return; @@ -215,7 +270,7 @@ class _ScreenLockState extends State { retries++; } - Widget makeDelayBuilder(Duration duration) { + Widget buildDelayChild(Duration duration) { if (widget.delayBuilder != null) { return widget.delayBuilder!(context, duration); } else { @@ -227,15 +282,11 @@ class _ScreenLockState extends State { Widget buildHeadingText() { Widget buildConfirmed(Widget child) { - if (widget.confirmation) { + if (widget.correctString == null) { return StreamBuilder( stream: inputController.confirmed, - builder: (context, snapshot) { - if (snapshot.hasData && snapshot.data!) { - return widget.confirmTitle; - } - return child; - }, + builder: (context, snapshot) => + snapshot.data == true ? widget.confirmTitle! : child, ); } return child; @@ -247,7 +298,7 @@ class _ScreenLockState extends State { stream: inputDelayController.stream, builder: (context, snapshot) { if (inputDelayed && snapshot.hasData) { - return makeDelayBuilder(snapshot.data!); + return buildDelayChild(snapshot.data!); } return child; }, @@ -269,86 +320,31 @@ class _ScreenLockState extends State { ); } - ThemeData makeThemeData() { - return widget.screenLockConfig.themeData ?? - ScreenLockConfig.defaultThemeData; - } - - @override - void initState() { - super.initState(); - inputController = widget.inputController ?? InputController(); - inputController.initialize( - correctString: widget.correctString, - digits: widget.digits, - isConfirmed: widget.confirmation, - onValidate: widget.onValidate, - ); - - inputController.verifyInput.listen((success) { - // Wait for the animation on failure. - Future.delayed(const Duration(milliseconds: 300), () { - inputController.clear(); - }); - - if (success) { - if (widget.confirmation) { - widget.onConfirmed?.call(inputController.confirmedInput); - } else { - widget.onUnlocked(); - } - } else { - error(); - } - }); - - WidgetsBinding.instance - .addPostFrameCallback((_) => widget.onOpened?.call()); - } - - @override - void dispose() { - inputController.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { - late final int secretLength; - - if (widget.confirmation) { - secretLength = widget.digits; - } else { - secretLength = widget.correctString.isNotEmpty - ? widget.correctString.length - : widget.digits; - } - final orientations = { Orientation.portrait: Axis.vertical, + Orientation.landscape: + widget.useLandscape ? Axis.horizontal : Axis.vertical, }; - if (widget.useLandscape) { - orientations[Orientation.landscape] = Axis.horizontal; - } else { - orientations[Orientation.landscape] = Axis.vertical; - } - Widget buildSecrets() { - return widget.secretsBuilder == null - ? SecretsWithShakingAnimation( - config: widget.secretsConfig, - length: secretLength, - input: inputController.currentInput, - verifyStream: inputController.verifyInput, - ) - : widget.secretsBuilder!( - context, - widget.secretsConfig, - secretLength, - inputController.currentInput, - inputController.verifyInput, - ); + if (widget.secretsBuilder != null) { + return widget.secretsBuilder!( + context, + widget.secretsConfig, + widget.digits, + inputController.currentInput, + inputController.verifyInput, + ); + } else { + return SecretsWithShakingAnimation( + config: widget.secretsConfig, + length: widget.digits, + input: inputController.currentInput, + verifyStream: inputController.verifyInput, + ); + } } Widget buildKeyPad() { @@ -391,21 +387,40 @@ class _ScreenLockState extends State { ); } - Widget buildContentWithBlur() { - return BackdropFilter( - filter: ImageFilter.blur(sigmaX: 3.5, sigmaY: 3.5), - child: buildContent(), - ); + Widget buildContentWithBlur({required bool useBlur}) { + Widget child = buildContent(); + if (useBlur) { + return BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.5, sigmaY: 3.5), + child: child, + ); + } + return child; } return Theme( - data: makeThemeData(), + data: widget.screenLockConfig?.themeData ?? + ScreenLockConfig.defaultThemeData, child: Scaffold( - backgroundColor: widget.screenLockConfig.backgroundColor, body: SafeArea( - child: widget.useBlur ? buildContentWithBlur() : buildContent(), + child: buildContentWithBlur(useBlur: widget.useBlur), ), ), ); } } + +typedef DelayBuilderCallback = Widget Function( + BuildContext context, Duration delay); + +typedef SecretsBuilderCallback = Widget Function( + BuildContext context, + SecretsConfig config, + int length, + ValueListenable input, + Stream verifyStream, +); + +typedef ValidationCallback = Future Function( + String input, +); diff --git a/test/input_state_test.dart b/test/input_state_test.dart index 38003ad..fd5e192 100644 --- a/test/input_state_test.dart +++ b/test/input_state_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { test('input stream test', () { final state = InputController(); - state.initialize(digits: 4, correctString: '1234', isConfirmed: false); + state.initialize(digits: 4, correctString: '1234'); expectLater( state.currentInput, @@ -33,7 +33,7 @@ void main() { test('input verify', () { final state = InputController(); - state.initialize(digits: 4, correctString: '1234', isConfirmed: false); + state.initialize(digits: 4, correctString: '1234'); expectLater(state.verifyInput, emitsInOrder([true])); @@ -45,7 +45,7 @@ void main() { test('input verify as failed', () { final state = InputController(); - state.initialize(digits: 4, correctString: '1234', isConfirmed: false); + state.initialize(digits: 4, correctString: '1234'); expectLater(state.verifyInput, emitsInOrder([false])); From 90bd4a4081e39327da7776342c61af80614ff95c Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 14:51:26 +0200 Subject: [PATCH 03/12] Fixed ScreenLock.create defaults --- lib/src/screen_lock.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/screen_lock.dart b/lib/src/screen_lock.dart index 9007040..99cd011 100644 --- a/lib/src/screen_lock.dart +++ b/lib/src/screen_lock.dart @@ -66,8 +66,8 @@ class ScreenLock extends StatefulWidget { this.deleteButton, this.inputController, this.secretsBuilder, - this.useBlur = false, - this.useLandscape = false, + this.useBlur = true, + this.useLandscape = true, }) : correctString = null, title = title ?? const Text('Please enter new passcode.'), confirmTitle = From 715c66d2338f29d637168032fee4fb27ecb2f9f5 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 14:54:07 +0200 Subject: [PATCH 04/12] Improved readme --- README.md | 7 +-- example/README.md | 149 +++++++++++++++------------------------------- 2 files changed, 51 insertions(+), 105 deletions(-) diff --git a/README.md b/README.md index 880411b..bb0402c 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,16 @@ screenLock( ); ``` -### Pin creation +### Passcode creation -You can have users create a new pin with confirmation +You can have users create a new passcode with confirmation ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; screenLockCreate( context: context, - onConfirmed: (value) => print(value), // store new pin somewhere here + onConfirmed: (value) => print(value), // store new passcode somewhere here ); ``` @@ -94,7 +94,6 @@ import 'package:flutter_screen_lock/flutter_screen_lock.dart'; import 'package:local_auth/local_auth.dart'; import 'package:flutter/material.dart'; -/// Method extraction to call by initial display and custom buttons. Future localAuth(BuildContext context) async { final localAuth = LocalAuthentication(); final didAuthenticate = await localAuth.authenticateWithBiometrics( diff --git a/example/README.md b/example/README.md index 7719aa8..5ff7e18 100644 --- a/example/README.md +++ b/example/README.md @@ -4,17 +4,19 @@ This Flutter plugin provides an feature for screen lock. Enter your passcode to unlock the screen. You can also use biometric authentication as an option. - + ## Features -- Any number of digits can be specified -- You can change `Cancel` and `Delete` text -- The UI expands and contracts according to the size of the device +- By the length of the character count +- You can change `Cancel` and `Delete` widget +- Optimizes the UI for device size and orientation - You can disable cancellation -- You can use biometrics +- You can use biometrics (local_auth plugin) - Biometrics can be displayed on first launch - Unlocked callback +- You can specify a mismatch event. +- Limit the maximum number of retries ## Usage @@ -23,7 +25,7 @@ To unlock, enter correctString. ### Simple -If the passcode you entered matches, you can callback onUnlocked. +If the passcode the user entered matches, `onUnlocked` is called. ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; @@ -37,85 +39,65 @@ showLockScreen( ### Change digits -Default 4 digits can be changed. Change the correctString accordingly. +Digits will be adjusted to the length of `correctString`. ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -showLockScreen( +lockScreen( context: context, - digits: 6, correctString: '123456', ); ``` -### Use local_auth - -Specify `canBiometric` and `biometricAuthenticate`. - -Add local_auth processing to `biometricAuthenticate`. See the following page for details. - -https://pub.dev/packages/local_auth +When creating a PIN, you can specify the amount: ```dart import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -showLockScreen( +lockScreenCreate( context: context, - correctString: '1234', - canBiometric: true, - // biometricButton is default Icon(Icons.fingerprint) - // When you want to change the icon with `BiometricType.face`, etc. - biometricButton: Icon(Icons.face), - biometricAuthenticate: (context) async { - final localAuth = LocalAuthentication(); - final didAuthenticate = - await localAuth.authenticateWithBiometrics( - localizedReason: 'Please authenticate'); - - if (didAuthenticate) { - return true; - } - - return false; - }, + digits: 6, + onConfirmed: (value) => print(value), ); ``` -### Open biometric first & onUnlocked callback -add option showBiometricFirst. +### Use local_auth + +Add the [local_auth](https://pub.dev/packages/local_auth) package to pubspec.yml. + +It includes an example that calls biometrics as soon as screenLock is displayed in `didOpened`. ```dart -showLockScreen( +import 'package:flutter_screen_lock/flutter_screen_lock.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:flutter/material.dart'; + +Future localAuth(BuildContext context) async { + final localAuth = LocalAuthentication(); + final didAuthenticate = await localAuth.authenticateWithBiometrics( + localizedReason: 'Please authenticate'); + if (didAuthenticate) { + Navigator.pop(context); + } +} + +screenLock( context: context, correctString: '1234', - canBiometric: true, - showBiometricFirst: true, - biometricAuthenticate: (context) async { - final localAuth = LocalAuthentication(); - final didAuthenticate = - await localAuth.authenticateWithBiometrics( - localizedReason: 'Please authenticate'); - - if (didAuthenticate) { - return true; - } - - return false; - }, - onUnlocked: () { - print('Unlocked.'); - }, + customizedButtonChild: Icon(Icons.fingerprint), + customizedButtonTap: () async => await localAuth(context), + didOpened: () async => await localAuth(context), ); ``` -### Can't cancel +### Block user This is the case where you want to force authentication when the app is first launched. ```dart -showLockScreen( +lockScreen( context: context, correctString: '1234', canCancel: false, @@ -125,67 +107,32 @@ showLockScreen( ### Customize text You can change `Cancel` and `Delete` text. -We recommend no more than 6 characters at this time. ```dart showLockScreen( context: context, correctString: '1234', - cancelText: 'Close', - deleteText: 'Remove', + cancelButton: Text('Close'), + deleteButton: Text('Remove'), ); ``` -### Verifycation passcode (v1.1.1) +### User creating new passcode -use `showConfirmPasscode` function. +Will let user enter a new passcode and confirm it. - +You have to store the passcode somewhere manually. ```dart -showConfirmPasscode( - context: context, - confirmTitle: 'This is the second input.', - onCompleted: (context, verifyCode) { - // verifyCode is verified passcode - print(verifyCode); - // Please close yourself - Navigator.of(context).maybePop(); - }, -) -``` - -### Customize your style (v1.1.2) - -use `circleInputButtonConfig` option. - - +import 'package:flutter_screen_lock/flutter_screen_lock.dart'; -```dart -showLockScreen( +screenLockCreate( context: context, - correctString: '1234', - backgroundColor: Colors.grey.shade50, - backgroundColorOpacity: 1, - circleInputButtonConfig: CircleInputButtonConfig( - textStyle: TextStyle( - fontSize: MediaQuery.of(context).size.width * 0.1, - color: Colors.white, - ), - backgroundColor: Colors.blue, - backgroundOpacity: 0.5, - shape: RoundedRectangleBorder( - side: BorderSide( - width: 1, - color: Colors.blue, - style: BorderStyle.solid, - ), - ), - ), -) + onConfirmed: (value) => print(value), // store new passcode somewhere here +); ``` -## Help +## FAQ ### How to prevent the background from being transparent From 6c4a01511eb7b73b582fb8ba84b5865d43e848bb Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 17:17:54 +0200 Subject: [PATCH 05/12] Improved styling code --- README.md | 4 +- example/lib/main.dart | 27 ++--- lib/flutter_screen_lock.dart | 2 +- .../configurations/key_pad_button_config.dart | 76 +++++++++++++ lib/src/configurations/key_pad_config.dart | 2 +- .../configurations/screen_lock_config.dart | 90 ++++++++++----- lib/src/configurations/secret_config.dart | 27 ++--- lib/src/configurations/secrets_config.dart | 20 +--- .../styled_input_button_config.dart | 41 ------- lib/src/layout/key_pad.dart | 14 +-- lib/src/layout/key_pad_button.dart | 57 ++++++++++ lib/src/layout/secrets.dart | 61 +++++----- lib/src/layout/styled_input_button.dart | 104 ------------------ lib/src/screen_lock.dart | 4 +- 14 files changed, 262 insertions(+), 267 deletions(-) create mode 100644 lib/src/configurations/key_pad_button_config.dart delete mode 100644 lib/src/configurations/styled_input_button_config.dart create mode 100644 lib/src/layout/key_pad_button.dart delete mode 100644 lib/src/layout/styled_input_button.dart diff --git a/README.md b/README.md index bb0402c..389ebe1 100644 --- a/README.md +++ b/README.md @@ -195,9 +195,11 @@ screenLockCreate( ### 7.x to 8 migration -- Change all callback names from `didSomething` to `onSomething` +- change all callback names from `didSomething` to `onSomething` - change `screenLock` with `confirm: true` to `screenLockCreate` - change `ScreenLock` with `confirm: true` to `ScreenLock.create` +- replace `StyledInputConfig` with `KeyPadButtonConfig` +- replace `spacingRatio` with fixed value in `spacing` ### 6.x to 7 migration diff --git a/example/lib/main.dart b/example/lib/main.dart index f376633..d62f413 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -56,7 +56,7 @@ class _MyHomePageState extends State { child: Wrap( spacing: 16, runSpacing: 16, - alignment: WrapAlignment.spaceBetween, + alignment: WrapAlignment.start, children: [ ElevatedButton( onPressed: () => showDialog( @@ -150,11 +150,8 @@ class _MyHomePageState extends State { borderSize: 2.0, disabledColor: Colors.black, enabledColor: Colors.amber, - height: 15, - width: 15, - build: (context, - {required config, required enabled}) => - Container( + size: 15, + builder: (context, config, enabled) => Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: enabled @@ -166,20 +163,18 @@ class _MyHomePageState extends State { ), ), padding: const EdgeInsets.all(10), - width: config.width, - height: config.height, + width: config.size, + height: config.size, ), ), ), keyPadConfig: KeyPadConfig( - buttonConfig: StyledInputConfig( - textStyle: - StyledInputConfig.getDefaultTextStyle(context) - .copyWith( - color: Colors.orange, - fontWeight: FontWeight.bold, - ), + buttonConfig: KeyPadButtonConfig( buttonStyle: OutlinedButton.styleFrom( + textStyle: const TextStyle( + color: Colors.orange, + fontWeight: FontWeight.bold, + ), shape: const RoundedRectangleBorder(), backgroundColor: Colors.deepOrange, ), @@ -194,7 +189,7 @@ class _MyHomePageState extends State { '陸', '質', '捌', - '玖' + '玖', ], ), cancelButton: const Icon(Icons.close), diff --git a/lib/flutter_screen_lock.dart b/lib/flutter_screen_lock.dart index bae7202..4c03a71 100644 --- a/lib/flutter_screen_lock.dart +++ b/lib/flutter_screen_lock.dart @@ -1,6 +1,6 @@ library flutter_screen_lock; -export 'src/configurations/styled_input_button_config.dart'; +export 'src/configurations/key_pad_button_config.dart'; export 'src/configurations/key_pad_config.dart'; export 'src/configurations/screen_lock_config.dart'; export 'src/configurations/secret_config.dart'; diff --git a/lib/src/configurations/key_pad_button_config.dart b/lib/src/configurations/key_pad_button_config.dart new file mode 100644 index 0000000..5268a10 --- /dev/null +++ b/lib/src/configurations/key_pad_button_config.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; + +class KeyPadButtonConfig { + const KeyPadButtonConfig({ + double? size, + double? fontSize, + this.foregroundColor, + this.backgroundColor, + this.buttonStyle, + }) : size = size ?? 68, + fontSize = fontSize ?? 36; + + /// Button width and height. + final double size; + + /// Button font size. + final double fontSize; + + /// Button foreground (text) color. + final Color? foregroundColor; + + /// Button background color. + final Color? backgroundColor; + + /// Full button style. Overrides all other values except [size]. + final ButtonStyle? buttonStyle; + + /// Creates a configuration from an existing [ButtonStyle]. + KeyPadButtonConfig fromButtonStyle({ + double? size, + required ButtonStyle style, + }) { + Set states = MaterialState.values.toSet(); + return KeyPadButtonConfig( + size: size, + fontSize: style.textStyle?.resolve(states)?.fontSize, + foregroundColor: style.foregroundColor?.resolve(states), + backgroundColor: style.backgroundColor?.resolve(states), + ); + } + + /// Base [ButtonStyle] that is overriden by other specified values. + ButtonStyle toButtonStyle() { + ButtonStyle composed = OutlinedButton.styleFrom( + textStyle: TextStyle(fontSize: fontSize), + foregroundColor: foregroundColor, + backgroundColor: backgroundColor, + ); + if (buttonStyle != null) { + return buttonStyle!.copyWith( + textStyle: composed.textStyle, + foregroundColor: composed.foregroundColor, + backgroundColor: composed.backgroundColor, + ); + } else { + return composed; + } + } + + // Copies a [KeyPadButtonConfig] with new values. + KeyPadButtonConfig copyWith({ + double? size, + double? fontSize, + Color? foregroundColor, + Color? backgroundColor, + ButtonStyle? buttonStyle, + }) { + return KeyPadButtonConfig( + size: size ?? this.size, + fontSize: fontSize ?? this.fontSize, + foregroundColor: foregroundColor ?? this.foregroundColor, + backgroundColor: backgroundColor ?? this.backgroundColor, + buttonStyle: buttonStyle ?? this.buttonStyle, + ); + } +} diff --git a/lib/src/configurations/key_pad_config.dart b/lib/src/configurations/key_pad_config.dart index 363d4f0..198af37 100644 --- a/lib/src/configurations/key_pad_config.dart +++ b/lib/src/configurations/key_pad_config.dart @@ -14,7 +14,7 @@ class KeyPadConfig { '9', ]; - final StyledInputConfig? buttonConfig; + final KeyPadButtonConfig? buttonConfig; final List inputStrings; final List displayStrings; final bool clearOnLongPressed; diff --git a/lib/src/configurations/screen_lock_config.dart b/lib/src/configurations/screen_lock_config.dart index 02847d9..2c7b51b 100644 --- a/lib/src/configurations/screen_lock_config.dart +++ b/lib/src/configurations/screen_lock_config.dart @@ -3,39 +3,73 @@ import 'package:flutter/material.dart'; class ScreenLockConfig { const ScreenLockConfig({ this.backgroundColor, + this.titleTextStyle, + this.textStyle, + this.buttonStyle, this.themeData, }); - /// ScreenLock default theme data. - /// - /// - Heading title is textTheme.heading1 - /// - Bottom both side text color is outlinedButtonTheme > style > primary - /// - Number text button is outlinedButtonTheme - static final ThemeData defaultThemeData = ThemeData( - scaffoldBackgroundColor: const Color(0x88545454), - outlinedButtonTheme: OutlinedButtonThemeData( - style: OutlinedButton.styleFrom( - foregroundColor: const Color(0xFFFFFFFF), - backgroundColor: const Color(0xFF808080), - shape: const CircleBorder(), - padding: const EdgeInsets.all(0), - side: BorderSide.none, + /// Background color of the ScreenLock. + final Color? backgroundColor; + + /// Text style for title Texts. + final TextStyle? titleTextStyle; + + /// Text style for other Texts. + final TextStyle? textStyle; + + /// Button style for keypad buttons. + final ButtonStyle? buttonStyle; + + /// Base [ThemeData] that is overriden by other specified values. + final ThemeData? themeData; + + /// Adds the values of this config to another [ThemeData]. + ThemeData withThemeData(ThemeData other) { + return other.copyWith( + scaffoldBackgroundColor: backgroundColor, + outlinedButtonTheme: OutlinedButtonThemeData(style: buttonStyle), + textTheme: TextTheme( + headline1: titleTextStyle, + bodyText2: textStyle, ), + ); + } + + /// Returns this config as a [ThemeData]. + ThemeData toThemeData() => withThemeData(themeData ?? ThemeData()); + + // Copies a [ScreenLockConfig] with new values. + ScreenLockConfig copyWith({ + Color? backgroundColor, + TextStyle? titleTextStyle, + TextStyle? textStyle, + ButtonStyle? buttonStyle, + }) { + return ScreenLockConfig( + backgroundColor: backgroundColor ?? this.backgroundColor, + titleTextStyle: titleTextStyle ?? this.titleTextStyle, + textStyle: textStyle ?? this.textStyle, + buttonStyle: buttonStyle ?? this.buttonStyle, + ); + } + + static final ScreenLockConfig defaultConfig = ScreenLockConfig( + backgroundColor: const Color(0x88545454), + buttonStyle: OutlinedButton.styleFrom( + foregroundColor: const Color(0xFFFFFFFF), + backgroundColor: const Color(0xFF808080), + shape: const CircleBorder(), + padding: const EdgeInsets.all(0), + side: BorderSide.none, ), - textTheme: const TextTheme( - headline1: TextStyle( - color: Colors.white, - fontSize: 20, - ), - bodyText2: TextStyle( - color: Colors.white, - fontSize: 18, - ), + titleTextStyle: const TextStyle( + color: Colors.white, + fontSize: 20, + ), + textStyle: const TextStyle( + color: Colors.white, + fontSize: 18, ), ); - - /// background color of ScreenLock. - final Color? backgroundColor; - - final ThemeData? themeData; } diff --git a/lib/src/configurations/secret_config.dart b/lib/src/configurations/secret_config.dart index 639b99b..7bee706 100644 --- a/lib/src/configurations/secret_config.dart +++ b/lib/src/configurations/secret_config.dart @@ -1,42 +1,37 @@ import 'package:flutter/material.dart'; -/// Configuration of [Secret] +/// Configuration of a [Secret] widget. class SecretConfig { const SecretConfig({ - this.width = 16, - this.height = 16, - this.borderSize = 1.0, + this.size = 16, + this.borderSize = 1, this.borderColor = Colors.white, this.enabledColor = Colors.white, this.disabledColor = Colors.transparent, - this.build, + this.builder, }); - final double width; - final double height; + final double size; final double borderSize; final Color borderColor; final Color enabledColor; final Color disabledColor; - /// `build` override function final Widget Function( - BuildContext context, { - required bool enabled, - required SecretConfig config, - })? build; + BuildContext context, + SecretConfig config, + bool enabled, + )? builder; SecretConfig copyWith({ - double? width, - double? height, + double? size, double? borderSize, Color? borderColor, Color? enabledColor, Color? disabledColor, }) { return SecretConfig( - width: width ?? this.width, - height: height ?? this.height, + size: size ?? this.size, borderSize: borderSize ?? this.borderSize, borderColor: borderColor ?? this.borderColor, enabledColor: enabledColor ?? this.enabledColor, diff --git a/lib/src/configurations/secrets_config.dart b/lib/src/configurations/secrets_config.dart index abe6cd7..3559b6d 100644 --- a/lib/src/configurations/secrets_config.dart +++ b/lib/src/configurations/secrets_config.dart @@ -3,25 +3,17 @@ import 'package:flutter_screen_lock/src/configurations/secret_config.dart'; class SecretsConfig { const SecretsConfig({ - this.spacing, - this.spacingRatio = 0.05, + double? spacing, this.padding = const EdgeInsets.only(top: 20, bottom: 50), this.secretConfig = const SecretConfig(), - }); + }) : spacing = 12; - /// Absolute space between secret widgets. - /// If specified together with spacingRatio, this will take precedence. - final double? spacing; + /// Space between secret widgets. + final double spacing; - /// Space ratio between secret widgets. - /// - /// Default `0.05` - final double spacingRatio; - - /// padding of Secrets Widget. - /// - /// Default [EdgeInsets.only(top: 20, bottom: 50)] + /// Padding of secrets widget. final EdgeInsetsGeometry padding; + /// Config for secrets. final SecretConfig secretConfig; } diff --git a/lib/src/configurations/styled_input_button_config.dart b/lib/src/configurations/styled_input_button_config.dart deleted file mode 100644 index baec6de..0000000 --- a/lib/src/configurations/styled_input_button_config.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'package:flutter/material.dart'; - -class StyledInputConfig { - const StyledInputConfig({ - this.height, - this.width, - this.autoSize = true, - this.textStyle, - this.buttonStyle, - }); - - // TextStyle for this button - final TextStyle? textStyle; - - /// Button height - final double? height; - - /// Button width - final double? width; - - /// Automatically adjust the size of the square to fit the orientation of the device. - /// - /// Default: `true` - final bool autoSize; - - /// It is recommended that you use [OutlinedButton.styleFrom()] to change it. - final ButtonStyle? buttonStyle; - - /// Returns the default text style for buttons. - static TextStyle getDefaultTextStyle(BuildContext context) { - if (MediaQuery.of(context).orientation == Orientation.landscape) { - return TextStyle( - fontSize: MediaQuery.of(context).size.height * 0.07, - ); - } - - return TextStyle( - fontSize: MediaQuery.of(context).size.height * 0.045, - ); - } -} diff --git a/lib/src/layout/key_pad.dart b/lib/src/layout/key_pad.dart index e2e42ad..f810696 100644 --- a/lib/src/layout/key_pad.dart +++ b/lib/src/layout/key_pad.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/src/configurations/key_pad_config.dart'; -import 'package:flutter_screen_lock/src/layout/styled_input_button.dart'; +import 'package:flutter_screen_lock/src/layout/key_pad_button.dart'; import 'package:flutter_screen_lock/src/input_controller.dart'; /// In order to arrange the buttons neatly by their size, @@ -30,7 +30,7 @@ class KeyPad extends StatelessWidget { final Widget? cancelButton; Widget _buildDeleteButton() { - return StyledInputButton.transparent( + return KeyPadButton.transparent( onPressed: () => inputState.removeCharacter(), onLongPress: keyPadConfig.clearOnLongPressed ? () => inputState.clear() : null, @@ -44,7 +44,7 @@ class KeyPad extends StatelessWidget { return _buildHiddenButton(); } - return StyledInputButton.transparent( + return KeyPadButton.transparent( onPressed: didCancelled, config: keyPadConfig.buttonConfig, child: cancelButton ?? @@ -61,7 +61,7 @@ class KeyPad extends StatelessWidget { } Widget _buildHiddenButton() { - return StyledInputButton.transparent( + return KeyPadButton.transparent( onPressed: () {}, config: keyPadConfig.buttonConfig, ); @@ -85,7 +85,7 @@ class KeyPad extends StatelessWidget { return _buildHiddenButton(); } - return StyledInputButton.transparent( + return KeyPadButton.transparent( onPressed: customizedButtonTap!, config: keyPadConfig.buttonConfig, child: customizedButtonChild!, @@ -100,7 +100,7 @@ class KeyPad extends StatelessWidget { final input = keyPadConfig.inputStrings[number]; final display = keyPadConfig.displayStrings[number]; - return StyledInputButton( + return KeyPadButton( config: keyPadConfig.buttonConfig, onPressed: enabled ? () => inputState.addCharacter(input) : null, child: Text(display), @@ -118,7 +118,7 @@ class KeyPad extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ _buildLeftSideButton(), - StyledInputButton( + KeyPadButton( config: keyPadConfig.buttonConfig, onPressed: enabled ? () => inputState.addCharacter(input) : null, child: Text(display), diff --git a/lib/src/layout/key_pad_button.dart b/lib/src/layout/key_pad_button.dart new file mode 100644 index 0000000..2cc5b1b --- /dev/null +++ b/lib/src/layout/key_pad_button.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screen_lock/src/configurations/key_pad_button_config.dart'; + +/// [OutlinedButton] based button. +class KeyPadButton extends StatelessWidget { + const KeyPadButton({ + super.key, + this.child, + required this.onPressed, + this.onLongPress, + KeyPadButtonConfig? config, + }) : config = config ?? const KeyPadButtonConfig(); + + factory KeyPadButton.transparent({ + Key? key, + Widget? child, + required VoidCallback? onPressed, + VoidCallback? onLongPress, + KeyPadButtonConfig? config, + }) => + KeyPadButton( + key: key, + onPressed: onPressed, + onLongPress: onLongPress, + config: KeyPadButtonConfig( + size: config?.size, + fontSize: config?.fontSize, + foregroundColor: config?.foregroundColor, + backgroundColor: Colors.transparent, + buttonStyle: config?.buttonStyle?.copyWith( + backgroundColor: + MaterialStateProperty.all(Colors.transparent), + ), + ), + child: child, + ); + + final Widget? child; + final VoidCallback? onPressed; + final VoidCallback? onLongPress; + final KeyPadButtonConfig config; + + @override + Widget build(BuildContext context) { + return Container( + height: config.size, + width: config.size, + margin: const EdgeInsets.all(10), + child: OutlinedButton( + onPressed: onPressed, + onLongPress: onLongPress, + style: config.toButtonStyle(), + child: child ?? const SizedBox.shrink(), + ), + ); + } +} diff --git a/lib/src/layout/secrets.dart b/lib/src/layout/secrets.dart index e04ca6b..30658a6 100644 --- a/lib/src/layout/secrets.dart +++ b/lib/src/layout/secrets.dart @@ -97,43 +97,33 @@ class Secrets extends StatefulWidget { } class _SecretsState extends State with SingleTickerProviderStateMixin { - double _computeSpacing(BuildContext context) { - if (widget.config.spacing != null) { - return widget.config.spacing!; - } - - return MediaQuery.of(context).size.width * widget.config.spacingRatio; - } - @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: widget.input, - builder: (context, value, child) { - return Container( - padding: widget.config.padding, - child: Wrap( - spacing: _computeSpacing(context), - children: List.generate( - widget.length, - (index) { - if (value.isEmpty) { - return Secret( - config: widget.config.secretConfig, - enabled: false, - ); - } - + builder: (context, value, child) => Padding( + padding: widget.config.padding, + child: Wrap( + spacing: widget.config.spacing, + children: List.generate( + widget.length, + (index) { + if (value.isEmpty) { return Secret( config: widget.config.secretConfig, - enabled: index < value.length, + enabled: false, ); - }, - growable: false, - ), + } + + return Secret( + config: widget.config.secretConfig, + enabled: index < value.length, + ); + }, + growable: false, ), - ); - }, + ), + ), ); } } @@ -150,12 +140,11 @@ class Secret extends StatelessWidget { @override Widget build(BuildContext context) { - if (config.build != null) { - // Custom build. - return config.build!( + if (config.builder != null) { + return config.builder!( context, - config: config, - enabled: enabled, + config, + enabled, ); } @@ -168,8 +157,8 @@ class Secret extends StatelessWidget { color: config.borderColor, ), ), - width: config.width, - height: config.height, + width: config.size, + height: config.size, ); } } diff --git a/lib/src/layout/styled_input_button.dart b/lib/src/layout/styled_input_button.dart deleted file mode 100644 index a457451..0000000 --- a/lib/src/layout/styled_input_button.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screen_lock/src/configurations/styled_input_button_config.dart'; - -/// [OutlinedButton] based button. -class StyledInputButton extends StatelessWidget { - const StyledInputButton({ - super.key, - this.child, - required this.onPressed, - this.onLongPress, - StyledInputConfig? config, - }) : config = config ?? const StyledInputConfig(); - - factory StyledInputButton.transparent({ - Key? key, - Widget? child, - required VoidCallback? onPressed, - VoidCallback? onLongPress, - StyledInputConfig? config, - }) => - StyledInputButton( - key: key, - onPressed: onPressed, - onLongPress: onLongPress, - config: StyledInputConfig( - height: config?.height, - width: config?.width, - autoSize: config?.autoSize ?? true, - buttonStyle: - (config?.buttonStyle ?? OutlinedButton.styleFrom()).copyWith( - backgroundColor: - MaterialStateProperty.all(Colors.transparent), - ), - ), - child: child, - ); - - final Widget? child; - final VoidCallback? onPressed; - final VoidCallback? onLongPress; - final StyledInputConfig config; - - double computeHeight(Size boxSize) { - if (config.autoSize) { - return _computeAutoSize(boxSize); - } - - return boxSize.height; - } - - double computeWidth(Size boxSize) { - if (config.autoSize) { - return _computeAutoSize(boxSize); - } - - return boxSize.width; - } - - Size defaultSize(BuildContext context) { - if (MediaQuery.of(context).orientation == Orientation.landscape) { - return Size( - config.height ?? MediaQuery.of(context).size.height * 0.125, - - /// Subtract padding(horizontal: 50) from screen_lock.dart to calculate - config.width ?? (MediaQuery.of(context).size.width - 100) * 0.14, - ); - } - - return Size( - config.height ?? MediaQuery.of(context).size.height * 0.6 * 0.16, - - /// Subtract padding(horizontal: 50) from screen_lock.dart to calculate - config.width ?? (MediaQuery.of(context).size.width - 100) * 0.22, - ); - } - - double _computeAutoSize(Size size) { - return size.width < size.height ? size.width : size.height; - } - - ButtonStyle _makeButtonStyle(BuildContext context) { - return (config.buttonStyle ?? OutlinedButton.styleFrom()).copyWith( - textStyle: MaterialStateProperty.all( - config.textStyle ?? StyledInputConfig.getDefaultTextStyle(context), - ), - ); - } - - @override - Widget build(BuildContext context) { - final boxSize = defaultSize(context); - return Container( - height: computeHeight(boxSize), - width: computeWidth(boxSize), - margin: const EdgeInsets.all(10), - child: OutlinedButton( - onPressed: onPressed, - onLongPress: onLongPress, - style: _makeButtonStyle(context), - child: child ?? const Text(''), - ), - ); - } -} diff --git a/lib/src/screen_lock.dart b/lib/src/screen_lock.dart index 99cd011..172aad1 100644 --- a/lib/src/screen_lock.dart +++ b/lib/src/screen_lock.dart @@ -399,8 +399,8 @@ class _ScreenLockState extends State { } return Theme( - data: widget.screenLockConfig?.themeData ?? - ScreenLockConfig.defaultThemeData, + data: (widget.screenLockConfig ?? ScreenLockConfig.defaultConfig) + .toThemeData(), child: Scaffold( body: SafeArea( child: buildContentWithBlur(useBlur: widget.useBlur), From 8ec13e893d99220382fb253cc049ae6c17c07832 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 18:12:44 +0200 Subject: [PATCH 06/12] Improved styling code again --- .../configurations/key_pad_button_config.dart | 20 +++---------------- .../configurations/screen_lock_config.dart | 14 ++++++------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/lib/src/configurations/key_pad_button_config.dart b/lib/src/configurations/key_pad_button_config.dart index 5268a10..a0304c3 100644 --- a/lib/src/configurations/key_pad_button_config.dart +++ b/lib/src/configurations/key_pad_button_config.dart @@ -22,24 +22,10 @@ class KeyPadButtonConfig { /// Button background color. final Color? backgroundColor; - /// Full button style. Overrides all other values except [size]. + /// Base [ButtonStyle] that is overriden by other specified values. final ButtonStyle? buttonStyle; - /// Creates a configuration from an existing [ButtonStyle]. - KeyPadButtonConfig fromButtonStyle({ - double? size, - required ButtonStyle style, - }) { - Set states = MaterialState.values.toSet(); - return KeyPadButtonConfig( - size: size, - fontSize: style.textStyle?.resolve(states)?.fontSize, - foregroundColor: style.foregroundColor?.resolve(states), - backgroundColor: style.backgroundColor?.resolve(states), - ); - } - - /// Base [ButtonStyle] that is overriden by other specified values. + /// Returns this config as a [ButtonStyle]. ButtonStyle toButtonStyle() { ButtonStyle composed = OutlinedButton.styleFrom( textStyle: TextStyle(fontSize: fontSize), @@ -57,7 +43,7 @@ class KeyPadButtonConfig { } } - // Copies a [KeyPadButtonConfig] with new values. + /// Copies a [KeyPadButtonConfig] with new values. KeyPadButtonConfig copyWith({ double? size, double? fontSize, diff --git a/lib/src/configurations/screen_lock_config.dart b/lib/src/configurations/screen_lock_config.dart index 2c7b51b..bcd3f12 100644 --- a/lib/src/configurations/screen_lock_config.dart +++ b/lib/src/configurations/screen_lock_config.dart @@ -24,9 +24,9 @@ class ScreenLockConfig { /// Base [ThemeData] that is overriden by other specified values. final ThemeData? themeData; - /// Adds the values of this config to another [ThemeData]. - ThemeData withThemeData(ThemeData other) { - return other.copyWith( + /// Returns this config as a [ThemeData]. + ThemeData toThemeData() { + return (themeData ?? ThemeData()).copyWith( scaffoldBackgroundColor: backgroundColor, outlinedButtonTheme: OutlinedButtonThemeData(style: buttonStyle), textTheme: TextTheme( @@ -36,24 +36,24 @@ class ScreenLockConfig { ); } - /// Returns this config as a [ThemeData]. - ThemeData toThemeData() => withThemeData(themeData ?? ThemeData()); - - // Copies a [ScreenLockConfig] with new values. + /// Copies a [ScreenLockConfig] with new values. ScreenLockConfig copyWith({ Color? backgroundColor, TextStyle? titleTextStyle, TextStyle? textStyle, ButtonStyle? buttonStyle, + ThemeData? themeData, }) { return ScreenLockConfig( backgroundColor: backgroundColor ?? this.backgroundColor, titleTextStyle: titleTextStyle ?? this.titleTextStyle, textStyle: textStyle ?? this.textStyle, buttonStyle: buttonStyle ?? this.buttonStyle, + themeData: themeData ?? this.themeData, ); } + /// Default [ScreenLockConfig]. static final ScreenLockConfig defaultConfig = ScreenLockConfig( backgroundColor: const Color(0x88545454), buttonStyle: OutlinedButton.styleFrom( From 05e5ae58c506a07951e5d71e6fa26d5c43c5f22c Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 18:22:14 +0200 Subject: [PATCH 07/12] Improved comments --- lib/src/configurations/key_pad_config.dart | 44 +++++++++++++++++----- lib/src/configurations/secret_config.dart | 9 +++++ lib/src/configurations/secrets_config.dart | 13 +++++++ lib/src/layout/key_pad.dart | 6 +-- lib/src/layout/key_pad_button.dart | 2 +- 5 files changed, 59 insertions(+), 15 deletions(-) diff --git a/lib/src/configurations/key_pad_config.dart b/lib/src/configurations/key_pad_config.dart index 198af37..75a9d0c 100644 --- a/lib/src/configurations/key_pad_config.dart +++ b/lib/src/configurations/key_pad_config.dart @@ -1,6 +1,27 @@ import 'package:flutter_screen_lock/flutter_screen_lock.dart'; class KeyPadConfig { + const KeyPadConfig({ + this.buttonConfig, + this.inputStrings = _numbers, + List? displayStrings, + this.clearOnLongPressed = false, + }) : displayStrings = displayStrings ?? inputStrings; + + /// Config for all [KeyPadButton] children. + final KeyPadButtonConfig? buttonConfig; + + /// The strings the user can input. + final List inputStrings; + + /// The strings that are displayed to the user. + /// Mapped 1:1 to [inputStrings]. + /// Defaults to [inputStrings]. + final List displayStrings; + + /// Whether to clear the input when long pressing the clear key. + final bool clearOnLongPressed; + static const List _numbers = [ '0', '1', @@ -14,15 +35,18 @@ class KeyPadConfig { '9', ]; - final KeyPadButtonConfig? buttonConfig; - final List inputStrings; - final List displayStrings; - final bool clearOnLongPressed; - - const KeyPadConfig({ - this.buttonConfig, - this.inputStrings = _numbers, + /// Copies a [KeyPadConfig] with new values. + KeyPadConfig copyWith({ + KeyPadButtonConfig? buttonConfig, + List? inputStrings, List? displayStrings, - this.clearOnLongPressed = false, - }) : displayStrings = displayStrings ?? inputStrings; + bool? clearOnLongPressed, + }) { + return KeyPadConfig( + buttonConfig: buttonConfig ?? this.buttonConfig, + inputStrings: inputStrings ?? this.inputStrings, + displayStrings: displayStrings ?? this.displayStrings, + clearOnLongPressed: clearOnLongPressed ?? this.clearOnLongPressed, + ); + } } diff --git a/lib/src/configurations/secret_config.dart b/lib/src/configurations/secret_config.dart index 7bee706..4d42534 100644 --- a/lib/src/configurations/secret_config.dart +++ b/lib/src/configurations/secret_config.dart @@ -11,10 +11,19 @@ class SecretConfig { this.builder, }); + /// Size (width and height) the secret. final double size; + + /// Border size for the secret. final double borderSize; + + /// Border color for the secret. final Color borderColor; + + /// Color for the enabled secret. final Color enabledColor; + + /// Color for the disabled secret. final Color disabledColor; final Widget Function( diff --git a/lib/src/configurations/secrets_config.dart b/lib/src/configurations/secrets_config.dart index 3559b6d..2e6ac67 100644 --- a/lib/src/configurations/secrets_config.dart +++ b/lib/src/configurations/secrets_config.dart @@ -16,4 +16,17 @@ class SecretsConfig { /// Config for secrets. final SecretConfig secretConfig; + + /// Copies a [SecretsConfig] with new values. + SecretsConfig copyWith({ + double? spacing, + EdgeInsetsGeometry? padding, + SecretConfig? secretConfig, + }) { + return SecretsConfig( + spacing: spacing ?? this.spacing, + padding: padding ?? this.padding, + secretConfig: secretConfig ?? this.secretConfig, + ); + } } diff --git a/lib/src/layout/key_pad.dart b/lib/src/layout/key_pad.dart index f810696..8265a8f 100644 --- a/lib/src/layout/key_pad.dart +++ b/lib/src/layout/key_pad.dart @@ -3,10 +3,8 @@ import 'package:flutter_screen_lock/src/configurations/key_pad_config.dart'; import 'package:flutter_screen_lock/src/layout/key_pad_button.dart'; import 'package:flutter_screen_lock/src/input_controller.dart'; -/// In order to arrange the buttons neatly by their size, -/// I dared to adjust them without using GridView or Wrap. -/// If you use GridView, you have to specify the overall width to adjust the size of the button, -/// which makes it difficult to specify the size intuitively. +/// [GridView] or [Wrap] make it difficult to specify the item size intuitively. +/// We therefore arrange them manually with [Column]s and [Row]s class KeyPad extends StatelessWidget { const KeyPad({ super.key, diff --git a/lib/src/layout/key_pad_button.dart b/lib/src/layout/key_pad_button.dart index 2cc5b1b..e4fb9c6 100644 --- a/lib/src/layout/key_pad_button.dart +++ b/lib/src/layout/key_pad_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_screen_lock/src/configurations/key_pad_button_config.dart'; -/// [OutlinedButton] based button. +/// Button in a [KeyPad]. class KeyPadButton extends StatelessWidget { const KeyPadButton({ super.key, From ad76a7890354db3c7b7d9ad1d0c89f4de7dfe9c2 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 18:26:47 +0200 Subject: [PATCH 08/12] Improved example footer --- example/lib/main.dart | 7 ++++++- lib/src/layout/key_pad.dart | 12 +++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index d62f413..9a28580 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -120,9 +120,14 @@ class _MyHomePageState extends State { correctString: '123456', canCancel: false, footer: Container( + width: 68, + height: 68, padding: const EdgeInsets.only(top: 10), child: OutlinedButton( - child: const Text('Cancel'), + child: const Padding( + padding: EdgeInsets.all(10), + child: Text('Cancel'), + ), onPressed: () => Navigator.pop(context), style: OutlinedButton.styleFrom( backgroundColor: Colors.transparent, diff --git a/lib/src/layout/key_pad.dart b/lib/src/layout/key_pad.dart index 8265a8f..e9b6122 100644 --- a/lib/src/layout/key_pad.dart +++ b/lib/src/layout/key_pad.dart @@ -46,14 +46,12 @@ class KeyPad extends StatelessWidget { onPressed: didCancelled, config: keyPadConfig.buttonConfig, child: cancelButton ?? - const FittedBox( - child: Text( - 'Cancel', - style: TextStyle( - fontSize: 16, - ), - softWrap: false, + const Text( + 'Cancel', + style: TextStyle( + fontSize: 16, ), + softWrap: false, ), ); } From 023e6ffe2d5c65c4f5215462d61cbd46e4063a56 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 18:31:01 +0200 Subject: [PATCH 09/12] Improved readme migration --- README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 389ebe1..cba103f 100644 --- a/README.md +++ b/README.md @@ -195,19 +195,21 @@ screenLockCreate( ### 7.x to 8 migration -- change all callback names from `didSomething` to `onSomething` -- change `screenLock` with `confirm: true` to `screenLockCreate` -- change `ScreenLock` with `confirm: true` to `ScreenLock.create` -- replace `StyledInputConfig` with `KeyPadButtonConfig` -- replace `spacingRatio` with fixed value in `spacing` +- Change all callback names from `didSomething` to `onSomething` +- Change `screenLock` with `confirm: true` to `screenLockCreate` +- Change `ScreenLock` with `confirm: true` to `ScreenLock.create` +- Replace `StyledInputConfig` with `KeyPadButtonConfig` +- Replace `spacingRatio` with fixed value in `spacing` ### 6.x to 7 migration - Requires dart >= 2.17 and Flutter 3.0 +- Replace `InputButtonConfig` with `KeyPadConfig`. - Change `delayChild` to `delayBuilder`. `delayBuilder` is no longer displayed in a new screen. Instead, it is now located above the `Secrets`. -- Accept `BuildContext` in `secretsBuilder` +- Accept `BuildContext` in `secretsBuilder`. + ### 5.x to 6 migration From 21cf695af07b5e86baa60c696ad06225800cbc30 Mon Sep 17 00:00:00 2001 From: dragon Date: Tue, 18 Oct 2022 18:36:48 +0200 Subject: [PATCH 10/12] Improved migration guide --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cba103f..f98bc20 100644 --- a/README.md +++ b/README.md @@ -199,22 +199,20 @@ screenLockCreate( - Change `screenLock` with `confirm: true` to `screenLockCreate` - Change `ScreenLock` with `confirm: true` to `ScreenLock.create` - Replace `StyledInputConfig` with `KeyPadButtonConfig` -- Replace `spacingRatio` with fixed value in `spacing` +- Replace `spacingRatio` with fixed value `spacing` in `Secrets` ### 6.x to 7 migration - Requires dart >= 2.17 and Flutter 3.0 - Replace `InputButtonConfig` with `KeyPadConfig`. -- Change `delayChild` to `delayBuilder`. - +- Change `delayChild` to `delayBuilder`. `delayBuilder` is no longer displayed in a new screen. Instead, it is now located above the `Secrets`. - Accept `BuildContext` in `secretsBuilder`. - ### 5.x to 6 migration -- `ScreenLock` does not use `Navigator.pop` internally anymore. - The developer should now pop by themselves when desired. +- `ScreenLock` does not use `Navigator.pop` internally anymore. + The developer should now pop by themselves when desired. `screenLock` call will pop automatically if `onUnlocked` is `null`. ### 4.x to 5 migration From a56fc01acc2a443c9b43f5260dd3d6ac1d14aab9 Mon Sep 17 00:00:00 2001 From: naoki0719 Date: Thu, 27 Oct 2022 09:24:15 +0900 Subject: [PATCH 11/12] Fixed a problem that font size was reset when changing screenLockConfig for example only --- example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Podfile | 2 +- example/ios/Podfile.lock | 4 ++-- example/ios/Runner.xcodeproj/project.pbxproj | 12 ++++++------ example/lib/main.dart | 2 ++ 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index f2872cf..4f8d4d2 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/example/ios/Podfile b/example/ios/Podfile index 1e8c3c9..88359b2 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 41b70b0..49cd1fc 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -14,9 +14,9 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 local_auth: 25938960984c3a7f6e3253e3f8d962fdd16852bd -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 COCOAPODS: 1.11.3 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 3dc1b48..48338df 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -347,7 +347,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -369,7 +369,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -433,7 +433,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -482,7 +482,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -506,7 +506,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -537,7 +537,7 @@ "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/example/lib/main.dart b/example/lib/main.dart index 9a28580..e1acd7f 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -146,6 +146,7 @@ class _MyHomePageState extends State { onConfirmed: (value) => Navigator.of(context).pop(), screenLockConfig: const ScreenLockConfig( backgroundColor: Colors.deepOrange, + titleTextStyle: TextStyle(fontSize: 24), ), secretsConfig: SecretsConfig( spacing: 15, // or spacingRatio @@ -222,6 +223,7 @@ class _MyHomePageState extends State { screenLockConfig: const ScreenLockConfig( /// If you don't want it to be transparent, override the config backgroundColor: Colors.black, + titleTextStyle: TextStyle(fontSize: 24), ), ), child: const Text('Not blur'), From d5986257dd18fc4344d3c9d213500a6443d416b6 Mon Sep 17 00:00:00 2001 From: naoki0719 Date: Thu, 27 Oct 2022 21:57:14 +0900 Subject: [PATCH 12/12] Added change logs --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67824d3..f9abac8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [8.0.0] - 2022-10-27 + +- Change all callback names from `didSomething` to `onSomething`. +- Change `screenLock` with `confirm: true` to `screenLockCreate`. +- Change `ScreenLock` with `confirm: true` to `ScreenLock.create`. +- Replace `StyledInputConfig` with `KeyPadButtonConfig`. +- Replace `spacingRatio` with fixed value `spacing` in `Secrets`. + ## [7.0.3] - 2022-07-21 - Added option for onValidate callback. @@ -80,6 +88,7 @@ ## [5.0.6+1] - 2022-03-01 - I have formatted the code properly. + ## [5.0.6] - 2022-02-21 - Clear input chars on long pressed (#42).