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 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), ], ], ); } }