Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 62 additions & 73 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,14 @@ class _PasswordExampleState extends State<PasswordExample>

void _onStateChanged() {
if (_state.errorMessage != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(_state.errorMessage!)),
);
_state.clearError();
final errorMessage = _state.errorMessage!;
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errorMessage)),
);
_state.clearError();
});
}

if (_state.password.value != _lastPassword) {
Expand Down Expand Up @@ -119,75 +123,60 @@ class _PasswordExampleState extends State<PasswordExample>
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(18.0),
child: ListenableBuilder(
listenable: _state,
builder: (context, _) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedSection(
index: 0,
child: HeaderCard(
onCustomize: _showCustomizeDialog,
currentThemeMode: widget.currentThemeMode,
onThemeToggle: widget.onThemeToggle,
),
),
const SizedBox(height: 20),
AnimatedSection(
index: 1,
child: PasswordDisplay(
password: _state.isPasswordVisible
? _state.password.value
: _state.password.masked,
strength: _state.strength,
feedback: _state.feedback,
fadeAnimation: _fadeAnimation,
estimatorLabel: _state.useZxcvbn
? 'zxcvbn (direct)'
: 'Entropy (default)',
isVisible: _state.isPasswordVisible,
onVisibilityToggle: _state.togglePasswordVisibility,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 2,
child: ActionButtons(
onGenerate: _state.generatePassword,
onGenerateStrong: _state.generateStrongPassword,
onCopy: _handleCopyPassword,
),
),
const SizedBox(height: 24),
AnimatedSection(
index: 3,
child: PasswordOptions(state: _state),
),
const SizedBox(height: 16),
AnimatedSection(
index: 4,
child: StrengthEstimatorCard(
useZxcvbn: _state.useZxcvbn,
onChanged: _state.toggleZxcvbn,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 5,
child: StrategyControlsPanel(
state: _state,
prefixController: _prefixController,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 6,
child: PolicyControlsCard(state: _state),
),
],
);
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
AnimatedSection(
index: 0,
child: HeaderCard(
onCustomize: _showCustomizeDialog,
currentThemeMode: widget.currentThemeMode,
onThemeToggle: widget.onThemeToggle,
),
),
const SizedBox(height: 20),
AnimatedSection(
index: 1,
child: PasswordDisplay(
state: _state,
fadeAnimation: _fadeAnimation,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 2,
child: ActionButtons(
onGenerate: _state.generatePassword,
onGenerateStrong: _state.generateStrongPassword,
onCopy: _handleCopyPassword,
),
),
const SizedBox(height: 24),
AnimatedSection(
index: 3,
child: PasswordOptions(state: _state),
),
const SizedBox(height: 16),
AnimatedSection(
index: 4,
child: StrengthEstimatorCard(
state: _state,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 5,
child: StrategyControlsPanel(
state: _state,
prefixController: _prefixController,
),
),
const SizedBox(height: 16),
AnimatedSection(
index: 6,
child: PolicyControlsCard(state: _state),
),
],
),
),
),
Expand Down
128 changes: 80 additions & 48 deletions example/lib/state/generator_state.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import 'package:flutter/material.dart';
import 'package:password_engine/password_engine.dart';

import '../constants/words.dart';
import '../policies/custom_corporate_policy.dart';
import '../strategies/custom_pin_strategy.dart';
import '../strategies/memorable_password_strategy.dart';
import '../strategies/pronounceable_password_strategy.dart';
import '../strategies/app_strategy_config.dart';
import '../strength_estimators/zxcvbn_strength_estimator.dart';

enum CharacterType { upper, lower, numbers, special, spaces }
Expand Down Expand Up @@ -34,6 +31,35 @@ class GeneratorState extends ChangeNotifier {
double _length = 16;
double get length => _length;

double get sliderMinLength {
final baseMin = _selectedStrategyConfig.minLength;
if (_selectedStrategyConfig.name != 'Random' &&
_selectedStrategyConfig.name != 'Pronounceable') {
return baseMin; // Word/PIN formats ignore strict char count policies visually.
}
if (_useCorporatePolicy) {
return CustomCorporatePolicy.strictPolicy.minLength.toDouble();
}
if (_usePolicy && _policyMinLength > baseMin) return _policyMinLength;
return baseMin;
}

double get sliderMaxLength {
final baseMax = _selectedStrategyConfig.maxLength;
if (_selectedStrategyConfig.name != 'Random' &&
_selectedStrategyConfig.name != 'Pronounceable') {
return baseMax;
}
if (_useCorporatePolicy) return baseMax;
if (_usePolicy && _usePolicyMaxLength && _policyMaxLength < baseMax) {
// Ensure max isn't physically lower than the current min bounds
return _policyMaxLength < sliderMinLength
? sliderMinLength
: _policyMaxLength;
}
return baseMax;
}

bool _useUpperCase = true;
bool get useUpperCase => _useUpperCase;

Expand Down Expand Up @@ -103,22 +129,16 @@ class GeneratorState extends ChangeNotifier {
static const _LowercasePasswordNormalizer _blocklistNormalizer =
_LowercasePasswordNormalizer();

late final List<IPasswordGenerationStrategy> strategies;
late IPasswordGenerationStrategy _selectedStrategy;
IPasswordGenerationStrategy get selectedStrategy => _selectedStrategy;
late final List<AppStrategyConfig> strategies;
late AppStrategyConfig _selectedStrategyConfig;
AppStrategyConfig get selectedStrategyConfig => _selectedStrategyConfig;

String _prefix = 'USER';
String get prefix => _prefix;

GeneratorState() {
strategies = [
RandomPasswordStrategy(),
PassphrasePasswordStrategy(wordlist: words, separator: ' '),
MemorablePasswordStrategy(),
PronounceablePasswordStrategy(),
CustomPinStrategy(),
];
_selectedStrategy = strategies[0];
strategies = availableAppStrategies;
_selectedStrategyConfig = strategies[0];
generatePassword();
}

Expand Down Expand Up @@ -192,7 +212,7 @@ class GeneratorState extends ChangeNotifier {

_generator = PasswordGenerator(
validator: baseValidator,
generationStrategy: _selectedStrategy,
generationStrategy: _selectedStrategyConfig.strategy,
strengthEstimator: _useZxcvbn
? ExampleZxcvbnStrengthEstimator(userInputs: _collectUserInputs())
: null,
Expand All @@ -212,20 +232,26 @@ class GeneratorState extends ChangeNotifier {
? _withSpaces(_characterSetProfile)
: _characterSetProfile;

_generator.updateConfig(
PasswordGeneratorConfigBuilder()
.length(_length.round())
.useUpperCase(_useUpperCase)
.useLowerCase(_useLowerCase)
.useNumbers(_useNumbers)
.useSpecialChars(_useSpecialChars)
.excludeAmbiguousChars(_excludeAmbiguousChars)
.characterSetProfile(profile)
.maxGenerationAttempts(_maxGenerationAttempts)
.policy(policy)
.extra('prefix', _prefix)
.build(),
);
final configBuilder = PasswordGeneratorConfigBuilder()
.length(_length.round())
.useUpperCase(_useUpperCase)
.useLowerCase(_useLowerCase)
.useNumbers(_useNumbers)
.useSpecialChars(_useSpecialChars)
.excludeAmbiguousChars(_excludeAmbiguousChars)
.characterSetProfile(profile)
.maxGenerationAttempts(_maxGenerationAttempts)
.policy(policy)
.extra('prefix', _prefix);

if (_selectedStrategyConfig.name == 'Passphrase' ||
_selectedStrategyConfig.name == 'Memorable') {
configBuilder.extra('wordCount', _length.round());
} else if (_selectedStrategyConfig.name == 'Custom PIN') {
configBuilder.extra('numericLength', _length.round());
}

_generator.updateConfig(configBuilder.build());
}

PasswordPolicy? _buildPolicy() {
Expand Down Expand Up @@ -276,29 +302,30 @@ class GeneratorState extends ChangeNotifier {
generatePassword();
}

void setStrategy(IPasswordGenerationStrategy strategy) {
_selectedStrategy = strategy;
if (_selectedStrategy is RandomPasswordStrategy) {
if (_length < 16) _length = 16;
if (_length > 128) _length = 128; // Ensure it respects typical bounds
} else if (_selectedStrategy is PassphrasePasswordStrategy) {
if (_length < 4) _length = 4;
if (_length > 8) _length = 8;
} else if (_selectedStrategy is MemorablePasswordStrategy) {
if (_length < 4) _length = 4;
if (_length > 8) _length = 8;
} else if (_selectedStrategy is PronounceablePasswordStrategy) {
if (_length < 8) _length = 8;
if (_length > 20) _length = 20;
} else if (_selectedStrategy is CustomPinStrategy) {
if (_length < 4) _length = 4;
if (_length > 12) _length = 12;
}
void _clampLengthToPolicy() {
double tempLength = _length;

// Always fall within the newly bounded UI layout constraints instead
final double safeMin = sliderMinLength;
final double safeMax = sliderMaxLength;

if (tempLength < safeMin) tempLength = safeMin;
if (tempLength > safeMax) tempLength = safeMax;

_length = tempLength;
}

void setStrategyConfig(AppStrategyConfig config) {
_selectedStrategyConfig = config;
if (_length < config.minLength) _length = config.minLength;
if (_length > config.maxLength) _length = config.maxLength;
_clampLengthToPolicy();
generatePassword();
}

void setLength(double value) {
_length = value;
_clampLengthToPolicy();
generatePassword();
}

Expand Down Expand Up @@ -374,11 +401,13 @@ class GeneratorState extends ChangeNotifier {

void togglePolicyEnabled(bool value) {
_usePolicy = value;
_clampLengthToPolicy();
generatePassword();
}

void toggleCorporatePolicy(bool value) {
_useCorporatePolicy = value;
_clampLengthToPolicy();
generatePassword();
}

Expand All @@ -387,6 +416,7 @@ class GeneratorState extends ChangeNotifier {
if (_usePolicyMaxLength && _policyMaxLength < _policyMinLength) {
_policyMaxLength = _policyMinLength;
}
_clampLengthToPolicy();
generatePassword();
}

Expand All @@ -395,11 +425,13 @@ class GeneratorState extends ChangeNotifier {
if (value && _policyMaxLength < _policyMinLength) {
_policyMaxLength = _policyMinLength;
}
_clampLengthToPolicy();
generatePassword();
}

void setPolicyMaxLength(double value) {
_policyMaxLength = value;
_clampLengthToPolicy();
generatePassword();
}

Expand Down
Loading