| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../core/widgets/pressable.dart';
- /// Four small status dots for the PIN entry.
- class PinDots extends StatelessWidget {
- const PinDots({
- super.key,
- required this.length,
- required this.filled,
- this.error = false,
- });
- final int length;
- final int filled;
- final bool error;
- @override
- Widget build(BuildContext context) {
- final colors = context.colors;
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- for (var index = 0; index < length; index++)
- AnimatedContainer(
- duration: const Duration(milliseconds: 140),
- curve: Curves.easeOutCubic,
- width: 16,
- height: 16,
- margin: const EdgeInsets.symmetric(horizontal: 9),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: index < filled
- ? (error ? colors.danger : colors.accent)
- : Colors.transparent,
- border: Border.all(
- color: index < filled
- ? (error ? colors.danger : colors.accent)
- : colors.inkFaint.withValues(alpha: 0.5),
- width: 1.4,
- ),
- ),
- ),
- ],
- );
- }
- }
- /// The numeric keypad shared by the lock and PIN setup screens.
- class PinKeypad extends StatelessWidget {
- const PinKeypad({
- super.key,
- required this.onDigit,
- required this.onBackspace,
- this.enabled = true,
- });
- final ValueChanged<String> onDigit;
- final VoidCallback onBackspace;
- final bool enabled;
- @override
- Widget build(BuildContext context) {
- const rows = [
- ['1', '2', '3'],
- ['4', '5', '6'],
- ['7', '8', '9'],
- ];
- return AnimatedOpacity(
- duration: const Duration(milliseconds: 140),
- opacity: enabled ? 1 : 0.42,
- child: IgnorePointer(
- ignoring: !enabled,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- for (final row in rows) ...[
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- for (final digit in row) ...[
- _DigitButton(
- label: digit,
- onTap: () {
- HapticFeedback.selectionClick();
- onDigit(digit);
- },
- ),
- if (digit != row.last) const SizedBox(width: 16),
- ],
- ],
- ),
- const SizedBox(height: 14),
- ],
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const SizedBox(width: 76, height: 68),
- const SizedBox(width: 16),
- _DigitButton(
- label: '0',
- onTap: () {
- HapticFeedback.selectionClick();
- onDigit('0');
- },
- ),
- const SizedBox(width: 16),
- _BackspaceButton(
- onTap: () {
- HapticFeedback.selectionClick();
- onBackspace();
- },
- ),
- ],
- ),
- ],
- ),
- ),
- );
- }
- }
- class _DigitButton extends StatelessWidget {
- const _DigitButton({required this.label, required this.onTap});
- final String label;
- final VoidCallback onTap;
- @override
- Widget build(BuildContext context) {
- final colors = context.colors;
- return Pressable(
- onTap: onTap,
- semanticLabel: 'Digit $label',
- pressedScale: 0.93,
- child: Container(
- width: 76,
- height: 68,
- alignment: Alignment.center,
- decoration: BoxDecoration(
- color: colors.surface,
- borderRadius: BorderRadius.circular(22),
- border: Border.all(color: colors.border),
- ),
- child: Text(
- label,
- style: Theme.of(context).textTheme.headlineMedium
- ?.copyWith(fontWeight: FontWeight.w500, fontSize: 26),
- ),
- ),
- );
- }
- }
- class _BackspaceButton extends StatelessWidget {
- const _BackspaceButton({required this.onTap});
- final VoidCallback onTap;
- @override
- Widget build(BuildContext context) {
- final colors = context.colors;
- return Pressable(
- onTap: onTap,
- semanticLabel: 'Delete digit',
- pressedScale: 0.93,
- child: Container(
- width: 76,
- height: 68,
- alignment: Alignment.center,
- decoration: BoxDecoration(
- color: Colors.transparent,
- borderRadius: BorderRadius.circular(22),
- border: Border.all(color: colors.border.withValues(alpha: 0.7)),
- ),
- child: Icon(Icons.backspace_outlined, size: 23, color: colors.inkMuted),
- ),
- );
- }
- }
- /// The three-by-three dot mark used on the lock screen.
- class BrandMark extends StatelessWidget {
- const BrandMark({super.key, this.size = 8, this.gap = 7});
- final double size;
- final double gap;
- @override
- Widget build(BuildContext context) {
- final colors = context.colors;
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- for (var row = 0; row < 3; row++) ...[
- Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- for (var column = 0; column < 3; column++) ...[
- Container(
- width: size,
- height: size,
- decoration: BoxDecoration(
- color: colors.accent,
- shape: BoxShape.circle,
- ),
- ),
- if (column < 2) SizedBox(width: gap),
- ],
- ],
- ),
- if (row < 2) SizedBox(height: gap),
- ],
- ],
- );
- }
- }
|