Skip to content

Commit 08f4142

Browse files
WIP pupil profile
1 parent b57c70d commit 08f4142

33 files changed

Lines changed: 1438 additions & 1641 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ Personal pupil data (`PupilIdentity`) is **never stored on the server**. It is s
5555

5656
### Flutter Client Architecture
5757

58-
**Dependency Injection** uses `get_it` (accessed via the `di` global from `watch_it`/`flutter_it`). Managers are registered in three ordered scopes:
58+
**Dependency Injection** uses `get_it` (accessed via the `di` global from `flutter_it`). Managers are registered in three ordered scopes:
5959

6060
1. **Core scope** (always registered, `InitManager.registerCoreManagers()`): `EnvManager`, `NotificationService`, `ServerpodConnectivityMonitor`, `ShorebirdUpdateManager`
6161
2. **Active-env scope** (`InitScope.onActiveEnvScope`): registered in `InitOnActiveEnv` after an environment (school key) is set

school_data_hub_flutter/lib/common/theme/app_colors.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,9 +521,9 @@ class AppColors {
521521
}
522522

523523
//- text colors
524-
static Color get ogsColor => palette.ogsColor;
524+
static Color get afterSchoolCardeColor => palette.ogsColor;
525525
static Color get groupColor => palette.groupColor;
526-
static Color get schoolyearColor => palette.schoolyearColor;
526+
static Color get schoolGradeColor => palette.schoolyearColor;
527527

528528
//- snackbars
529529

school_data_hub_flutter/lib/features/_attendance/presentation/attendance_page/widgets/attendance_filters.dart

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import 'package:flutter/material.dart';
2+
import 'package:flutter_it/flutter_it.dart';
23
import 'package:gap/gap.dart';
34
import 'package:school_data_hub_flutter/common/theme/styles.dart';
45
import 'package:school_data_hub_flutter/common/widgets/themed_filter_chip.dart';
56
import 'package:school_data_hub_flutter/features/_attendance/domain/filters/attendance_pupil_filter.dart';
67
import 'package:school_data_hub_flutter/features/_attendance/domain/models/enums.dart';
78
import 'package:school_data_hub_flutter/features/_pupil/domain/filters/pupils_filter.dart';
8-
import 'package:flutter_it/flutter_it.dart';
99

1010
class AttendanceFilters extends WatchingWidget {
1111
const AttendanceFilters({super.key});
1212

1313
@override
1414
Widget build(BuildContext context) {
1515
final pupilsFilter = di<PupilsFilter>();
16-
final ogsFilters = pupilsFilter.afterSchoolCareFilters;
16+
final afterSchoolCareFilters = pupilsFilter.afterSchoolCareFilters;
1717

18-
final attendanceFilterLocator = di<AttendancePupilFilterManager>();
18+
final attendanceFilterManager = di<AttendancePupilFilterManager>();
1919
final Map<AttendancePupilFilter, bool> activeAttendanceFilters = watchValue(
2020
(AttendancePupilFilterManager x) => x.attendancePupilFilterState,
2121
);
@@ -28,11 +28,11 @@ class AttendanceFilters extends WatchingWidget {
2828
bool valueUnexcused =
2929
activeAttendanceFilters[AttendancePupilFilter.unexcused]!;
3030

31-
// OGS filter: index 0 = has after school care, index 1 = no after school care
32-
final ogsFilter = ogsFilters[0];
33-
final notOgsFilter = ogsFilters[1];
34-
bool valueOgs = watch(ogsFilter).isActive;
35-
bool valueNotOgs = watch(notOgsFilter).isActive;
31+
// after school care filter: index 0 = has after school care, index 1 = no after school care
32+
final afterSchoolCareFilter = afterSchoolCareFilters[0];
33+
final noAfterSchoolCareFilter = afterSchoolCareFilters[1];
34+
bool valueOgs = watch(afterSchoolCareFilter).isActive;
35+
bool valueNotOgs = watch(noAfterSchoolCareFilter).isActive;
3636

3737
return Column(
3838
children: [
@@ -50,7 +50,7 @@ class AttendanceFilters extends WatchingWidget {
5050
// in case present is selected, not present and unexcused should be deselected
5151

5252
if (val) {
53-
attendanceFilterLocator.setAttendancePupilFilter(
53+
attendanceFilterManager.setAttendancePupilFilter(
5454
attendancePupilFilterRecords: [
5555
(
5656
attendancePupilFilter: AttendancePupilFilter.notPresent,
@@ -68,7 +68,7 @@ class AttendanceFilters extends WatchingWidget {
6868
);
6969
return;
7070
}
71-
attendanceFilterLocator.setAttendancePupilFilter(
71+
attendanceFilterManager.setAttendancePupilFilter(
7272
attendancePupilFilterRecords: [
7373
(
7474
attendancePupilFilter: AttendancePupilFilter.present,
@@ -85,7 +85,7 @@ class AttendanceFilters extends WatchingWidget {
8585
// in case not present is selected, present should be deselected
8686
if (val) {
8787
//_valuePresent = false;
88-
attendanceFilterLocator.setAttendancePupilFilter(
88+
attendanceFilterManager.setAttendancePupilFilter(
8989
attendancePupilFilterRecords: [
9090
(
9191
attendancePupilFilter: AttendancePupilFilter.notPresent,
@@ -104,7 +104,7 @@ class AttendanceFilters extends WatchingWidget {
104104
return;
105105
}
106106

107-
attendanceFilterLocator.setAttendancePupilFilter(
107+
attendanceFilterManager.setAttendancePupilFilter(
108108
attendancePupilFilterRecords: [
109109
(
110110
attendancePupilFilter: AttendancePupilFilter.notPresent,
@@ -120,7 +120,7 @@ class AttendanceFilters extends WatchingWidget {
120120
onSelected: (val) {
121121
// in case unexcused is selected, present should be deselected
122122
if (val) {
123-
attendanceFilterLocator.setAttendancePupilFilter(
123+
attendanceFilterManager.setAttendancePupilFilter(
124124
attendancePupilFilterRecords: [
125125
(
126126
attendancePupilFilter: AttendancePupilFilter.unexcused,
@@ -138,7 +138,7 @@ class AttendanceFilters extends WatchingWidget {
138138
);
139139
return;
140140
}
141-
attendanceFilterLocator.setAttendancePupilFilter(
141+
attendanceFilterManager.setAttendancePupilFilter(
142142
attendancePupilFilterRecords: [
143143
(
144144
attendancePupilFilter: AttendancePupilFilter.unexcused,
@@ -153,19 +153,19 @@ class AttendanceFilters extends WatchingWidget {
153153
selected: valueOgs,
154154
onSelected: (val) {
155155
if (val) {
156-
notOgsFilter.reset();
156+
noAfterSchoolCareFilter.reset();
157157
}
158-
ogsFilter.toggle(val);
158+
afterSchoolCareFilter.toggle(val);
159159
},
160160
),
161161
ThemedFilterChip(
162162
label: 'nicht OGS',
163163
selected: valueNotOgs,
164164
onSelected: (val) {
165165
if (val) {
166-
ogsFilter.reset();
166+
afterSchoolCareFilter.reset();
167167
}
168-
notOgsFilter.toggle(val);
168+
noAfterSchoolCareFilter.toggle(val);
169169
},
170170
),
171171
],

school_data_hub_flutter/lib/features/_authorizations/presentation/new_authorization_page/new_authorization_page.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ class NewAuthorizationPageState extends State<NewAuthorizationPage> {
219219
fontWeight:
220220
FontWeight.bold,
221221
color: AppColors
222-
.schoolyearColor,
222+
.schoolGradeColor,
223223
fontSize: 18,
224224
),
225225
),

school_data_hub_flutter/lib/features/_pupil/domain/pupil_label_pdf_service.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class PupilLabelPdfService {
8888
}
8989

9090
return pw.Padding(
91-
padding: pw.EdgeInsets.only(top: _topMargin, left: _leftMargin),
91+
padding: const pw.EdgeInsets.only(top: _topMargin, left: _leftMargin),
9292
child: pw.Table(children: rows),
9393
);
9494
}

school_data_hub_flutter/lib/features/_pupil/presentation/after_school_care/widgets/after_school_care_details.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ class _EmergencyCareCard extends StatelessWidget {
8585
final isNotSet = emergencyCare == null;
8686

8787
return Card(
88-
elevation: 2,
89-
shadowColor: Colors.black26,
88+
color: AppColors.cardInCardColor,
9089
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
9190
child: InkWell(
9291
onTap: () => afterSchoolCareEditEmergencyCareDialog(context, pupil),
@@ -167,8 +166,8 @@ class _PickUpTimesCard extends StatelessWidget {
167166
final pickUpTimes = afterSchoolCare.pickUpTimes;
168167

169168
return Card(
170-
elevation: 2,
171-
shadowColor: Colors.black26,
169+
color: AppColors.cardInCardColor,
170+
172171
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
173172
child: Padding(
174173
padding: const EdgeInsets.all(16.0),
@@ -533,8 +532,7 @@ class _OgsInfoCard extends StatelessWidget {
533532
afterSchoolCare.afterSchoolCareInfo!.isNotEmpty;
534533

535534
return Card(
536-
elevation: 2,
537-
shadowColor: Colors.black26,
535+
color: AppColors.cardInCardColor,
538536
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
539537
child: InkWell(
540538
onTap: () => _editInfo(context),

school_data_hub_flutter/lib/features/_pupil/presentation/pupil_profile_page/pupil_profile_page.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,28 +30,28 @@ class PupilProfilePage extends StatelessWidget {
3030
children: [
3131
Expanded(
3232
child: NestedScrollView(
33+
physics: const BouncingScrollPhysics(
34+
decelerationRate: ScrollDecelerationRate.fast,
35+
),
3336
headerSliverBuilder: (context, innerBoxIsScrolled) => [
3437
SliverAppBar(
3538
systemOverlayStyle: SystemUiOverlayStyle(
3639
statusBarColor:
3740
AppColors.pupilProfileBackgroundColor,
3841
),
39-
pinned: false,
42+
pinned: true,
4043
floating: true,
44+
snap: true,
4145
scrolledUnderElevation: null,
4246
automaticallyImplyLeading: false,
4347
leading: null,
4448
backgroundColor:
4549
AppColors.pupilProfileBackgroundColor,
46-
collapsedHeight: 120,
50+
toolbarHeight: 60,
4751
expandedHeight: 120,
4852
stretch: false,
4953
elevation: 0,
50-
flexibleSpace: FlexibleSpaceBar(
51-
expandedTitleScale: 1,
52-
collapseMode: CollapseMode.none,
53-
background: PupilProfileHeadingCard(pupil: pupil),
54-
),
54+
flexibleSpace: PupilProfileHeadingCard(pupil: pupil),
5555
),
5656
],
5757
body: PupilProfilePageContent(pupil: pupil),

school_data_hub_flutter/lib/features/_pupil/presentation/pupil_profile_page/widgets/pupil_profile_heading_card.dart

Lines changed: 66 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,67 @@ class PupilProfileHeadingCard extends WatchingWidget {
1212

1313
@override
1414
Widget build(BuildContext context) {
15-
return Container(
16-
padding: const EdgeInsets.all(10),
17-
margin: const EdgeInsets.only(top: 5, bottom: 5, left: 2, right: 2),
18-
decoration: BoxDecoration(
19-
color: AppColors.backgroundColor,
20-
borderRadius: BorderRadius.circular(10),
21-
),
22-
child: Row(
23-
crossAxisAlignment: CrossAxisAlignment.start,
24-
children: [
25-
AvatarImage(pupil: pupil, size: 80),
26-
const Gap(12),
27-
Column(
15+
return LayoutBuilder(
16+
builder: (context, constraints) {
17+
final settings = context
18+
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>();
19+
final double expandedHeight = settings?.maxExtent ?? 130;
20+
final double collapsedHeight = settings?.minExtent ?? 70;
21+
final double currentHeight = settings?.currentExtent ?? expandedHeight;
22+
final double t =
23+
((currentHeight - collapsedHeight) /
24+
(expandedHeight - collapsedHeight))
25+
.clamp(0.0, 1.0);
26+
27+
final double avatarSize = 40 + (40 * t); // 40 collapsed, 80 expanded
28+
final double fontSize = 16 + (4 * t); // 16 collapsed, 20 expanded
29+
30+
return Container(
31+
padding: const EdgeInsets.all(10),
32+
margin: const EdgeInsets.only(top: 5, left: 5, right: 5, bottom: 5),
33+
decoration: BoxDecoration(
34+
color: Colors.white,
35+
borderRadius: BorderRadius.circular(10),
36+
),
37+
child: Row(
2838
crossAxisAlignment: CrossAxisAlignment.start,
29-
mainAxisAlignment: MainAxisAlignment.start,
3039
children: [
31-
_PupilNameRow(pupil: pupil),
32-
const Gap(8),
33-
_BadgesRow(pupil: pupil),
40+
AvatarImage(pupil: pupil, size: avatarSize),
41+
const Gap(12),
42+
Expanded(
43+
child: Column(
44+
crossAxisAlignment: CrossAxisAlignment.start,
45+
mainAxisAlignment: MainAxisAlignment.start,
46+
mainAxisSize: MainAxisSize.min,
47+
children: [
48+
_PupilNameRow(pupil: pupil, fontSize: fontSize, t: t),
49+
if (t > 0.3) ...[
50+
const Gap(8),
51+
Opacity(
52+
opacity: ((t - 0.3) / 0.7).clamp(0.0, 1.0),
53+
child: _BadgesRow(pupil: pupil),
54+
),
55+
],
56+
],
57+
),
58+
),
3459
],
3560
),
36-
],
37-
),
61+
);
62+
},
3863
);
3964
}
4065
}
4166

4267
class _PupilNameRow extends WatchingWidget {
4368
final PupilProxy pupil;
44-
const _PupilNameRow({required this.pupil});
69+
final double fontSize;
70+
final double t;
71+
const _PupilNameRow({
72+
required this.pupil,
73+
required this.fontSize,
74+
required this.t,
75+
});
4576

4677
@override
4778
Widget build(BuildContext context) {
@@ -51,17 +82,23 @@ class _PupilNameRow extends WatchingWidget {
5182
children: [
5283
Text(
5384
firstName,
54-
style: const TextStyle(
55-
fontSize: 20.0,
85+
style: TextStyle(
86+
fontSize: fontSize,
5687
fontWeight: FontWeight.bold,
57-
color: Colors.white,
88+
color: Colors.black,
5889
),
5990
),
6091
const Gap(6),
6192
Text(
6293
lastName,
63-
style: const TextStyle(fontSize: 20.0, color: Colors.white),
94+
style: TextStyle(fontSize: fontSize, color: Colors.black),
6495
),
96+
if (t < 0.3) ...[
97+
const Gap(8),
98+
GroupBadgeContainer(pupil: pupil, badgeSize: 24),
99+
const Gap(4),
100+
SchoolGradeBadgeContainer(pupil: pupil, badgeSize: 24),
101+
],
65102
],
66103
);
67104
}
@@ -90,7 +127,7 @@ class _BadgesRow extends WatchingWidget {
90127
width: _badgeSize,
91128
height: _badgeSize,
92129
decoration: BoxDecoration(
93-
color: AppColors.ogsColor,
130+
color: AppColors.afterSchoolCardeColor,
94131
shape: BoxShape.circle,
95132
),
96133
child: const Center(
@@ -191,18 +228,10 @@ class _BadgesRow extends WatchingWidget {
191228
'Besondere Information',
192229
pupil.specialInformation!,
193230
),
194-
child: Container(
195-
width: _badgeSize,
196-
height: _badgeSize,
197-
decoration: const BoxDecoration(
198-
color: Colors.white,
199-
shape: BoxShape.circle,
200-
),
201-
child: const Icon(
202-
Icons.info_rounded,
203-
size: _badgeSize,
204-
color: Color.fromARGB(255, 6, 92, 163),
205-
),
231+
child: const Icon(
232+
Icons.info_rounded,
233+
size: _badgeSize + 4,
234+
color: Color.fromARGB(255, 6, 92, 163),
206235
),
207236
),
208237
],

0 commit comments

Comments
 (0)