Team Color, image compression for attendance verification, improved wherebouts

This commit is contained in:
2026-03-08 12:23:28 +08:00
parent a8751ca728
commit d87b5e73d7
11 changed files with 757 additions and 191 deletions
+105
View File
@@ -277,6 +277,9 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
final itStaff = profiles.where((p) => p.role == 'it_staff').toList();
final nameController = TextEditingController(text: team?.name ?? '');
String? leaderId = team?.leaderId;
Color? teamColor = team?.color != null
? Color(int.parse(team!.color!, radix: 16) | 0xFF000000)
: null;
List<String> selectedOffices = List<String>.from(team?.officeIds ?? []);
List<String> selectedMembers = [];
final isEdit = team != null;
@@ -305,6 +308,11 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
decoration: const InputDecoration(labelText: 'Team Name'),
),
const SizedBox(height: 12),
_TeamColorPicker(
selectedColor: teamColor,
onColorChanged: (c) => setState(() => teamColor = c),
),
const SizedBox(height: 12),
DropdownButtonFormField<String>(
initialValue: leaderId,
items: [
@@ -408,6 +416,11 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
'name': name,
'leader_id': leaderId,
'office_ids': selectedOffices,
'color': teamColor != null
? (teamColor!.toARGB32() & 0xFFFFFF)
.toRadixString(16)
.padLeft(6, '0')
: null,
})
.eq('id', team.id);
final upErr = extractSupabaseError(upRes);
@@ -454,6 +467,11 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
'name': name,
'leader_id': leaderId,
'office_ids': selectedOffices,
'color': teamColor != null
? (teamColor!.toARGB32() & 0xFFFFFF)
.toRadixString(16)
.padLeft(6, '0')
: null,
})
.select()
.single();
@@ -657,3 +675,90 @@ class _TeamsScreenState extends ConsumerState<TeamsScreen> {
}
}
}
/// Inline color picker for team color selection.
class _TeamColorPicker extends StatelessWidget {
const _TeamColorPicker({
required this.selectedColor,
required this.onColorChanged,
});
final Color? selectedColor;
final ValueChanged<Color?> onColorChanged;
static const List<Color> _presetColors = [
Color(0xFFE53935), // Red
Color(0xFFD81B60), // Pink
Color(0xFF8E24AA), // Purple
Color(0xFF5E35B1), // Deep Purple
Color(0xFF3949AB), // Indigo
Color(0xFF1E88E5), // Blue
Color(0xFF039BE5), // Light Blue
Color(0xFF00ACC1), // Cyan
Color(0xFF00897B), // Teal
Color(0xFF43A047), // Green
Color(0xFF7CB342), // Light Green
Color(0xFFFDD835), // Yellow
Color(0xFFFFB300), // Amber
Color(0xFFFB8C00), // Orange
Color(0xFFF4511E), // Deep Orange
Color(0xFF6D4C41), // Brown
];
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text('Team Color', style: Theme.of(context).textTheme.bodyMedium),
if (selectedColor != null) ...[
const SizedBox(width: 8),
GestureDetector(
onTap: () => onColorChanged(null),
child: Text(
'Clear',
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.primary,
),
),
),
],
],
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: _presetColors.map((color) {
final isSelected =
selectedColor != null &&
(selectedColor!.toARGB32() & 0xFFFFFF) ==
(color.toARGB32() & 0xFFFFFF);
return GestureDetector(
onTap: () => onColorChanged(color),
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: isSelected
? Border.all(
color: Theme.of(context).colorScheme.onSurface,
width: 3,
)
: null,
),
child: isSelected
? Icon(Icons.check, color: Colors.white, size: 18)
: null,
),
);
}).toList(),
),
],
);
}
}