Improved sign up screen

This commit is contained in:
Marc Rejohn Castillano 2026-02-22 14:46:59 +08:00
parent 62a9544533
commit dd29d2f90f

View File

@ -85,10 +85,11 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
body: ResponsiveBody( body: ResponsiveBody(
maxWidth: 480, maxWidth: 480,
padding: const EdgeInsets.symmetric(vertical: 24), padding: const EdgeInsets.symmetric(vertical: 24),
child: SingleChildScrollView(
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Center( Center(
@ -187,7 +188,7 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
return null; return null;
}, },
), ),
const SizedBox(height: 16), const SizedBox(height: 12),
Text('Offices', style: Theme.of(context).textTheme.titleSmall), Text('Offices', style: Theme.of(context).textTheme.titleSmall),
const SizedBox(height: 8), const SizedBox(height: 8),
officesAsync.when( officesAsync.when(
@ -195,28 +196,43 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
if (offices.isEmpty) { if (offices.isEmpty) {
return const Text('No offices available.'); return const Text('No offices available.');
} }
final officeNameById = <String, String>{
for (final o in offices) o.id: o.name,
};
return Column( return Column(
children: offices crossAxisAlignment: CrossAxisAlignment.start,
.map( children: [
(office) => CheckboxListTile( ElevatedButton.icon(
value: _selectedOfficeIds.contains(office.id), onPressed: _isLoading
onChanged: _isLoading
? null ? null
: (selected) { : () => _showOfficeSelectionDialog(offices),
setState(() { icon: const Icon(Icons.place),
if (selected == true) { label: const Text('Select Offices'),
_selectedOfficeIds.add(office.id);
} else {
_selectedOfficeIds.remove(office.id);
}
});
},
title: Text(office.name),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
), ),
) const SizedBox(height: 8),
.toList(), if (_selectedOfficeIds.isEmpty)
const Text('No office selected.')
else
Wrap(
spacing: 8,
runSpacing: 8,
children: _selectedOfficeIds.map((id) {
final name = officeNameById[id] ?? id;
return Chip(
label: Text(name),
onDeleted: _isLoading
? null
: () {
setState(
() => _selectedOfficeIds.remove(id),
);
},
);
}).toList(),
),
],
); );
}, },
loading: () => const LinearProgressIndicator(), loading: () => const LinearProgressIndicator(),
@ -242,6 +258,66 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
), ),
), ),
), ),
),
);
}
Future<void> _showOfficeSelectionDialog(List<dynamic> offices) async {
final tempSelected = Set<String>.from(_selectedOfficeIds);
await showDialog<void>(
context: context,
builder: (dialogCtx) => StatefulBuilder(
builder: (ctx2, setStateDialog) {
return AlertDialog(
title: const Text('Select Offices'),
content: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 480),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: offices.map<Widget>((office) {
final id = office.id;
final name = office.name;
return CheckboxListTile(
value: tempSelected.contains(id),
onChanged: (v) {
setStateDialog(() {
if (v == true) {
tempSelected.add(id);
} else {
tempSelected.remove(id);
}
});
},
title: Text(name),
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
);
}).toList(),
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.of(dialogCtx).pop(),
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () {
setState(() {
_selectedOfficeIds
..clear()
..addAll(tempSelected);
});
Navigator.of(dialogCtx).pop();
},
child: const Text('Save'),
),
],
);
},
),
); );
} }
@ -253,18 +329,29 @@ class _SignUpScreenState extends ConsumerState<SignUpScreen> {
if (RegExp(r'[A-Z]').hasMatch(text)) score++; if (RegExp(r'[A-Z]').hasMatch(text)) score++;
if (RegExp(r'[a-z]').hasMatch(text)) score++; if (RegExp(r'[a-z]').hasMatch(text)) score++;
if (RegExp(r'\d').hasMatch(text)) score++; if (RegExp(r'\d').hasMatch(text)) score++;
if (RegExp(r'[!@#$%^&*(),.?":{}|<>\[\]\\/+=;_-]').hasMatch(text)) { if (RegExp(r'[!@#\$%\^&\*(),.?":{}|<>\[\]\\/+=;_-]').hasMatch(text)) {
score++; score++;
} }
final normalized = (score / 6).clamp(0.0, 1.0); final normalized = (score / 6).clamp(0.0, 1.0);
final (label, color) = switch (normalized) { String label;
<= 0.2 => ('Very weak', Colors.red), Color color;
<= 0.4 => ('Weak', Colors.deepOrange), if (normalized <= 0.2) {
<= 0.6 => ('Fair', Colors.orange), label = 'Very weak';
<= 0.8 => ('Strong', Colors.green), color = Colors.red;
_ => ('Excellent', Colors.teal), } else if (normalized <= 0.4) {
}; label = 'Weak';
color = Colors.deepOrange;
} else if (normalized <= 0.6) {
label = 'Fair';
color = Colors.orange;
} else if (normalized <= 0.8) {
label = 'Strong';
color = Colors.green;
} else {
label = 'Excellent';
color = Colors.teal;
}
setState(() { setState(() {
_passwordStrength = normalized; _passwordStrength = normalized;