Skip to content

Instantly share code, notes, and snippets.

@logickoder
Created November 4, 2024 06:58
Show Gist options
  • Save logickoder/b0f838c0b2872d8863f2ed8780762b83 to your computer and use it in GitHub Desktop.
Save logickoder/b0f838c0b2872d8863f2ed8780762b83 to your computer and use it in GitHub Desktop.
Multi Progress Indicator
import 'package:flutter/material.dart';
/// A progress indicator that is divided into sections, each section represents a part of the total progress
/// The current position of the progress indicator is represented by the [current] property
/// The [sections] property represents the sections of the progress indicator
/// The length of the [sections] list represents the total number of sections
class MultiProgressIndicator extends StatelessWidget {
/// The current position of the progress indicator in relation to the sections
final int current;
/// Represents the sections of the progress indicator, each section is a part of the total progress
final List<int> sections;
/// Whether the progress indicator should fill the available space
final bool shouldFill;
const MultiProgressIndicator({
super.key,
required this.current,
required this.sections,
this.shouldFill = false,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
for (var i = 0; i < sections.length; ++i) ...{
Expanded(
flex: shouldFill ? 1 : 0,
child: _buildSection(i, context),
),
if (i < sections.length - 1) ...{
const SizedBox(width: 8),
},
},
],
);
}
Widget _buildSection(int index, BuildContext context) {
final section = sections[index];
// the total count of all previous sections
final total = sections.sublist(0, index).fold(0, (p, e) => p + e);
return TweenAnimationBuilder(
tween: Tween<double>(
begin: 0,
end: current <= total
? 0
: current >= total + section
? 1
: (current - total) / section,
),
duration: Durations.medium1,
builder: (_, progress, __) {
return SizedBox(
height: 3,
width: 50,
child: LinearProgressIndicator(
value: progress,
backgroundColor: Colors.grey,
valueColor: const AlwaysStoppedAnimation(Colors.green),
borderRadius: BorderRadius.circular(3),
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'multi_progress_indicator.dart';
class PasswordStrength extends StatelessWidget {
final String password;
const PasswordStrength(this.password, {super.key});
@override
Widget build(BuildContext context) {
final current = [
hasLowercase,
hasUppercase,
hasDigits,
hasSpecialCharacters,
hasMinLength
].where((e) => e).length;
const sections = [2, 2, 1];
String strengthText;
Color strengthColor;
if (current >= 4) {
strengthText = 'strong';
strengthColor = Colors.green;
} else if (current >= 2) {
strengthText = 'fair';
strengthColor = Colors.yellow;
} else {
strengthText = 'weak';
strengthColor = Colors.red;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MultiProgressIndicator(
current: current,
sections: sections,
),
const SizedBox(height: 12),
AnimatedSwitcher(
duration: Durations.medium1,
child: RichText(
key: ValueKey(strengthText),
text: TextSpan(
text: 'Password strength: ',
style: const TextStyle(
fontSize: 12,
color: Colors.black12,
fontWeight: FontWeight.w600,
),
children: [
TextSpan(
text: strengthText,
style: TextStyle(
color: strengthColor,
),
),
],
),
),
),
const SizedBox(height: 12),
_PasswordCheck(
text: 'At least 1 upper and lower case letters.',
isChecked: hasUppercase && hasLowercase,
),
const SizedBox(height: 8),
_PasswordCheck(
text: 'At least 1 number and 1 special character.',
isChecked: hasDigits && hasSpecialCharacters,
),
const SizedBox(height: 8),
_PasswordCheck(
text: 'At least 8 characters',
isChecked: hasMinLength,
),
],
);
}
bool get hasLowercase => RegExp(r'[a-z]').hasMatch(password);
bool get hasUppercase => RegExp(r'[A-Z]').hasMatch(password);
bool get hasDigits => RegExp(r'[0-9]').hasMatch(password);
bool get hasSpecialCharacters =>
RegExp(r'[!@#$%^&*(),.?":{}|<>]').hasMatch(password);
bool get hasMinLength => password.length >= 8;
}
class _PasswordCheck extends StatelessWidget {
final String text;
final bool isChecked;
const _PasswordCheck({
required this.text,
required this.isChecked,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
AnimatedSwitcher(
duration: Durations.medium1,
child: Icon(
key: ValueKey(isChecked),
isChecked
? Icons.check_circle_outline_outlined
: Icons.circle_outlined,
color: isChecked ? Colors.green : Colors.black12,
),
),
const SizedBox(width: 8),
Text(
text,
style: const TextStyle(
fontSize: 12,
color: Colors.black12,
),
),
],
);
}
}
@logickoder
Copy link
Author

1000423918.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment