Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] rework keyboard to have 3x4 grid and support different screen size for macos and web #50

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 21 additions & 17 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ class ExampleHomePage extends StatefulWidget {
}

class _ExampleHomePageState extends State<ExampleHomePage> {
final StreamController<bool> _verificationNotifier =
StreamController<bool>.broadcast();
final StreamController<bool> _verificationNotifier = StreamController<bool>.broadcast();

bool isAuthenticated = false;

Expand Down Expand Up @@ -79,12 +78,8 @@ class _ExampleHomePageState extends State<ExampleHomePage> {
onPressed: () {
_showLockScreen(context,
opaque: false,
circleUIConfig: CircleUIConfig(
borderColor: Colors.blue,
fillColor: Colors.blue,
circleSize: 30),
keyboardUIConfig: KeyboardUIConfig(
digitBorderWidth: 2, primaryColor: Colors.blue),
circleUIConfig: CircleUIConfig(borderColor: Colors.blue, fillColor: Colors.blue, circleSize: 30),
keyboardUIConfig: KeyboardUIConfig(digitBorderWidth: 2, primaryColor: Colors.blue),
cancelButton: Icon(
Icons.arrow_back,
color: Colors.blue,
Expand All @@ -102,17 +97,29 @@ class _ExampleHomePageState extends State<ExampleHomePage> {
required Widget cancelButton,
List<String>? digits,
}) {
final screenSize = MediaQuery.of(context).size;

Navigator.push(
context,
PageRouteBuilder(
opaque: opaque,
pageBuilder: (context, animation, secondaryAnimation) =>
PasscodeScreen(
title: Text(
'Enter App Passcode',
pageBuilder: (context, animation, secondaryAnimation) => PasscodeScreen(
title: Container(
margin: EdgeInsets.only(top: screenSize.height * 0.08),
child: Text(
'Enter App Passcode',
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 28),
),
),
subtitle: Container(
margin: EdgeInsets.only(top: 12, right: 16, left: 16),
child: Text(
"And subtitle here",
style: Theme.of(context).textTheme.subtitle2?.copyWith(color: Colors.white),
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white, fontSize: 28),
),
),
circleUIConfig: circleUIConfig,
keyboardUIConfig: keyboardUIConfig,
passwordEnteredCallback: _onPasscodeEntered,
Expand Down Expand Up @@ -160,10 +167,7 @@ class _ExampleHomePageState extends State<ExampleHomePage> {
child: Text(
"Reset passcode",
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w300),
style: const TextStyle(fontSize: 16, color: Colors.white, fontWeight: FontWeight.w300),
),
onPressed: _resetAppPassword,
// splashColor: Colors.white.withOpacity(0.4),
Expand Down
21 changes: 15 additions & 6 deletions lib/circle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,27 @@ class CircleUIConfig {
final double borderWidth;
final double circleSize;

//IndicatorWidget allows you to define your own style instead of using the [Cicle]
final IndicatorWidget Function(bool, double)? indicatorBuilder;

const CircleUIConfig({
this.borderColor = Colors.white,
this.borderWidth = 1,
this.fillColor = Colors.white,
this.circleSize = 20,
this.indicatorBuilder,
});
}

class Circle extends StatelessWidget {
final bool filled;
class Circle extends IndicatorWidget {
final CircleUIConfig circleUIConfig;
final double extraSize;

Circle({
Key? key,
this.filled = false,
bool filled = false,
required this.circleUIConfig,
this.extraSize = 0,
}) : super(key: key);
double extraSize = 0,
}) : super(key: key, filled: filled, extraSize: extraSize);

@override
Widget build(BuildContext context) {
Expand All @@ -44,3 +46,10 @@ class Circle extends StatelessWidget {
);
}
}

abstract class IndicatorWidget extends StatelessWidget {
final bool filled;
final double extraSize;

const IndicatorWidget({Key? key, required this.filled, this.extraSize = 0}) : super(key: key);
}
166 changes: 109 additions & 57 deletions lib/keyboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ class KeyboardUIConfig {
final Color digitFillColor;
final EdgeInsetsGeometry keyboardRowMargin;
final EdgeInsetsGeometry digitInnerMargin;
final EdgeInsetsGeometry keyboardRootMargin;
final BoxDecoration? digitItemDecoration;

//Size for the keyboard can be define and provided from the app.
//If it will not be provided the size will be adjusted to a screen size.
final Size? keyboardSize;

//KeyboardItemWidget allows you to define your own style instead of using the [KeyboardDigit]
final KeyboardItemWidget Function(String, KeyboardUIConfig, KeyboardTapCallback)? keyboardItemBuilder;

const KeyboardUIConfig({
this.digitBorderWidth = 1,
this.keyboardRowMargin = const EdgeInsets.only(top: 15, left: 4, right: 4),
this.keyboardRootMargin = const EdgeInsets.only(top: 16),
this.digitInnerMargin = const EdgeInsets.all(24),
this.primaryColor = Colors.white,
this.digitFillColor = Colors.transparent,
this.digitTextStyle = const TextStyle(fontSize: 30, color: Colors.white),
this.deleteButtonTextStyle =
const TextStyle(fontSize: 16, color: Colors.white),
this.deleteButtonTextStyle = const TextStyle(fontSize: 16, color: Colors.white),
this.digitItemDecoration,
this.keyboardSize,
this.keyboardItemBuilder,
});
}

Expand Down Expand Up @@ -58,43 +65,57 @@ class Keyboard extends StatelessWidget {
keyboardItems = digits!;
}
final screenSize = MediaQuery.of(context).size;
final keyboardHeight = screenSize.height > screenSize.width
? screenSize.height / 2
: screenSize.height - 80;
final keyboardHeight = screenSize.height > screenSize.width ? screenSize.height / 2 : screenSize.height - 80;
final keyboardWidth = keyboardHeight * 3 / 4;
final keyboardSize = this.keyboardUIConfig.keyboardSize != null
? this.keyboardUIConfig.keyboardSize!
: Size(keyboardWidth, keyboardHeight);
final keyboardSize = this.keyboardUIConfig.keyboardSize != null ? this.keyboardUIConfig.keyboardSize! : Size(keyboardWidth, keyboardHeight);
return Container(
width: keyboardSize.width,
height: keyboardSize.height,
margin: EdgeInsets.only(top: 16),
child: RawKeyboardListener(
focusNode: _focusNode,
autofocus: true,
onKey: (event) {
if (event is RawKeyUpEvent) {
if (keyboardItems.contains(event.data.keyLabel)) {
onKeyboardTap(event.logicalKey.keyLabel);
return;
}
if (event.logicalKey.keyLabel== 'Backspace' || event.logicalKey.keyLabel == 'Delete') {
onKeyboardTap(Keyboard.deleteButton);
return;
margin: this.keyboardUIConfig.keyboardRootMargin,
child: SizedBox(
width: keyboardSize.width + 16,
height: keyboardSize.height + 16,
child: RawKeyboardListener(
focusNode: _focusNode,
autofocus: true,
onKey: (event) {
if (event is RawKeyUpEvent) {
if (keyboardItems.contains(event.data.keyLabel)) {
onKeyboardTap(event.logicalKey.keyLabel);
return;
}
if (event.logicalKey.keyLabel == 'Backspace' || event.logicalKey.keyLabel == 'Delete') {
onKeyboardTap(Keyboard.deleteButton);
return;
}
}
}
},
child: AlignedGrid(
keyboardSize: keyboardSize,
children: List.generate(10, (index) {
return _buildKeyboardDigit(keyboardItems[index]);
}),
},
child: AlignedGrid(
keyboardSize: keyboardSize,
children: List.generate(keyboardItems.length, (index) {
if (keyboardUIConfig.keyboardItemBuilder != null) {
return keyboardUIConfig.keyboardItemBuilder!(keyboardItems[index], keyboardUIConfig, onKeyboardTap);
} else {
return KeyboardDigit(
text: keyboardItems[index],
keyboardUIConfig: keyboardUIConfig,
onKeyboardTap: onKeyboardTap,
);
}
}),
),
),
),
);
}
}

class KeyboardDigit extends KeyboardItemWidget {
final KeyboardUIConfig keyboardUIConfig;

Widget _buildKeyboardDigit(String text) {
const KeyboardDigit({Key? key, required String text, required this.keyboardUIConfig, required Function(String) onKeyboardTap})
: super(key: key, text: text, onKeyboardTap: onKeyboardTap);

@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(4),
child: ClipOval(
Expand All @@ -106,13 +127,15 @@ class Keyboard extends StatelessWidget {
onKeyboardTap(text);
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color: keyboardUIConfig.primaryColor,
width: keyboardUIConfig.digitBorderWidth),
),
decoration: keyboardUIConfig.digitItemDecoration ??
BoxDecoration(
shape: BoxShape.circle,
color: Colors.transparent,
border: Border.all(
color: keyboardUIConfig.primaryColor,
width: keyboardUIConfig.digitBorderWidth,
),
),
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
Expand All @@ -134,36 +157,65 @@ class Keyboard extends StatelessWidget {
}
}

abstract class KeyboardItemWidget extends StatelessWidget {
final String text;
final Function(String) onKeyboardTap;

const KeyboardItemWidget({Key? key, required this.text, required this.onKeyboardTap}) : super(key: key);
}

class AlignedGrid extends StatelessWidget {
final double runSpacing = 4;
final double spacing = 4;
final double runSpacing = 2;
final double spacing = 2.0;
final int listSize;
final columns = 3;
final rows = 4;
final List<Widget> children;
final Size keyboardSize;

const AlignedGrid(
{Key? key, required this.children, required this.keyboardSize})
const AlignedGrid({Key? key, required this.children, required this.keyboardSize})
: listSize = children.length,
super(key: key);

@override
Widget build(BuildContext context) {
final primarySize = keyboardSize.width > keyboardSize.height
? keyboardSize.height
: keyboardSize.width;
final itemSize = (primarySize - runSpacing * (columns - 1)) / columns;
return Wrap(
runSpacing: runSpacing,
spacing: spacing,
alignment: WrapAlignment.center,
children: children
.map((item) => Container(
width: itemSize,
height: itemSize,
child: item,
))
.toList(growable: false),
final primarySize;
final itemSize;
if (keyboardSize.width > keyboardSize.height) {
primarySize = keyboardSize.height;
itemSize = primarySize / rows;
} else {
primarySize = keyboardSize.width;
itemSize = primarySize / rows;
}

final numToItem = (item) => Container(
margin: EdgeInsets.all(2),
width: itemSize,
height: itemSize,
child: item,
);

return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: children.sublist(0, 3).map(numToItem).toList(),
),
Row(
mainAxisSize: MainAxisSize.min,
children: children.sublist(3, 6).map(numToItem).toList(),
),
Row(
mainAxisSize: MainAxisSize.min,
children: children.sublist(6, 9).map(numToItem).toList(),
),
Row(
mainAxisSize: MainAxisSize.min,
children: children.sublist(9).map(numToItem).toList(),
)
],
);
}
}