Skip to content

Commit 0769a16

Browse files
Merge pull request #667 from ksrvnjord/feature/message-at-birthday
Feature/message at birthday
2 parents 8f12589 + 33c8eb5 commit 0769a16

8 files changed

+238
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:cloud_firestore/cloud_firestore.dart';
2+
import 'package:firebase_auth/firebase_auth.dart';
3+
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
5+
final hasSendBirthdayMessageProvider =
6+
StreamProvider.family<bool, String>((ref, receiverId) {
7+
final user = FirebaseAuth.instance.currentUser;
8+
9+
if (user == null) {
10+
return Stream.value(false);
11+
}
12+
13+
final userId = user.uid;
14+
final currentYear = DateTime.now().year;
15+
16+
final documentPath = '/people/$receiverId/birthday/$currentYear';
17+
18+
return FirebaseFirestore.instance
19+
.doc(documentPath)
20+
.snapshots()
21+
.map((snapshot) {
22+
if (!snapshot.exists) {
23+
return false;
24+
}
25+
26+
final data = snapshot.data();
27+
if (data == null || data['hasReceivedCongratulationsFrom'] == null) {
28+
return false;
29+
}
30+
31+
final ids = List<String>.from(data['hasReceivedCongratulationsFrom']);
32+
return ids.contains(userId);
33+
});
34+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import 'package:cloud_functions/cloud_functions.dart';
2+
import 'package:firebase_auth/firebase_auth.dart';
3+
import 'package:flutter/material.dart';
4+
import 'package:flutter_riverpod/flutter_riverpod.dart';
5+
import 'package:ksrvnjord_main_app/src/features/profiles/almanak_profile/api/has_send_birthday_message_provider.dart';
6+
import 'package:ksrvnjord_main_app/src/features/profiles/api/user_provider.dart';
7+
8+
class AlmanakBirthdayButton extends ConsumerWidget {
9+
const AlmanakBirthdayButton({
10+
super.key,
11+
required this.receiverId,
12+
required this.receiverFullName,
13+
});
14+
final String receiverId;
15+
final String receiverFullName;
16+
17+
@override
18+
Widget build(BuildContext context, WidgetRef ref) {
19+
final hasCongratulatedLidAsyncValue =
20+
ref.watch(hasSendBirthdayMessageProvider(receiverId));
21+
final userAsyncVal = ref.watch(currentUserProvider);
22+
return hasCongratulatedLidAsyncValue.when(
23+
data: (hasCongratulated) => Padding(
24+
padding: const EdgeInsets.only(left: 8.0),
25+
child: ElevatedButton(
26+
style: ElevatedButton.styleFrom(
27+
backgroundColor: hasCongratulated
28+
? Colors.grey // Set background color to grey if not pressable
29+
: Colors
30+
.pinkAccent, // Set background color to pink if pressable
31+
),
32+
onPressed: hasCongratulated
33+
? null
34+
: () async {
35+
userAsyncVal.whenData((sender) => _showBirthdayMessageDialog(
36+
context, sender.identifierString, sender.fullName));
37+
},
38+
child: Row(
39+
mainAxisSize: MainAxisSize.min,
40+
children: [
41+
const Icon(
42+
Icons.cake, // Add a birthday cake icon
43+
size: 20,
44+
color: Colors.white,
45+
),
46+
const SizedBox(width: 8), // Add spacing between icon and text
47+
const Text(
48+
"Verstuur Felicitatie",
49+
style: TextStyle(fontSize: 14, color: Colors.white),
50+
),
51+
],
52+
),
53+
),
54+
),
55+
loading: () => const CircularProgressIndicator(),
56+
error: (error, stack) => Text('Error: $error'),
57+
);
58+
}
59+
60+
void _showBirthdayMessageDialog(
61+
BuildContext context, String senderId, String senderFullName) async {
62+
final TextEditingController messageController = TextEditingController();
63+
showDialog(
64+
context: context,
65+
builder: (BuildContext context) {
66+
return AlertDialog(
67+
title: const Text('Felicitatie versturen'),
68+
content: Column(
69+
mainAxisSize: MainAxisSize.min,
70+
children: [
71+
const Text('Inhoud bericht:'),
72+
TextField(
73+
controller: messageController,
74+
decoration: const InputDecoration(
75+
hintText: 'Typ hier je bericht (max. 50 tekens)',
76+
),
77+
maxLines: null,
78+
maxLength: 100, // Limit the input to 140 characters
79+
),
80+
],
81+
),
82+
actions: [
83+
TextButton(
84+
onPressed: () {
85+
// Call the cloud function personalBirthdayMessage
86+
Navigator.of(context).pop();
87+
},
88+
child: const Text('Annuleren'),
89+
),
90+
TextButton(
91+
onPressed: () async {
92+
final message = messageController.text.trim();
93+
final user = FirebaseAuth.instance.currentUser;
94+
95+
if (user != null) {
96+
await _sendBirthdayMessage(
97+
context,
98+
message,
99+
senderId,
100+
senderFullName,
101+
user,
102+
);
103+
// ignore: use_build_context_synchronously
104+
Navigator.of(context).pop(); // only after message sent
105+
} else {
106+
ScaffoldMessenger.of(context).showSnackBar(
107+
const SnackBar(
108+
content: Text(
109+
'Je moet ingelogd zijn om een bericht te versturen.'),
110+
),
111+
);
112+
}
113+
},
114+
child: const Text('Versturen'),
115+
),
116+
],
117+
);
118+
},
119+
);
120+
}
121+
122+
Future<void> _sendBirthdayMessage(BuildContext context, String message,
123+
String senderId, String senderFullName, User senderUser) async {
124+
if (message.isEmpty) {
125+
ScaffoldMessenger.of(context).showSnackBar(
126+
const SnackBar(
127+
content:
128+
Text('Geen notificatie gestuurd, bericht mag niet leeg zijn.')),
129+
);
130+
return;
131+
}
132+
133+
try {
134+
await FirebaseAuth.instance.currentUser?.getIdToken(true);
135+
final result = await FirebaseFunctions.instanceFor(region: 'europe-west1')
136+
.httpsCallable('personalBirthdayMessage')
137+
.call({
138+
'receiverId': receiverId,
139+
'receiverFullName': receiverFullName,
140+
'senderId': senderId,
141+
'senderFullName': senderFullName,
142+
'message': message,
143+
});
144+
145+
if (result.data['success'] == true && context.mounted) {
146+
ScaffoldMessenger.of(context).showSnackBar(
147+
const SnackBar(content: Text('Bericht succesvol verstuurd!')),
148+
);
149+
} else if (context.mounted) {
150+
ScaffoldMessenger.of(context).showSnackBar(
151+
const SnackBar(
152+
content: Text(
153+
'Er is iets misgegaan bij het versturen. Probeer het later opnieuw.')),
154+
);
155+
}
156+
} catch (e) {
157+
if (context.mounted) {
158+
ScaffoldMessenger.of(context).showSnackBar(
159+
SnackBar(content: Text('Fout bij versturen: $e')),
160+
);
161+
}
162+
}
163+
}
164+
}

lib/src/features/profiles/almanak_profile/widgets/almanak_user_profile_view.dart

+17-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
55
import 'package:flutter_riverpod/flutter_riverpod.dart';
66
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
77
import 'package:intl/intl.dart';
8+
import 'package:ksrvnjord_main_app/src/features/profiles/almanak_profile/widgets/almanak_birthday_message_button.dart';
89
import 'package:ksrvnjord_main_app/src/features/profiles/api/groups_for_user_provider.dart';
910
import 'package:ksrvnjord_main_app/src/features/profiles/api/user_provider.dart';
1011
import 'package:ksrvnjord_main_app/src/features/profiles/almanak_profile/widgets/user_address_widget.dart';
@@ -137,9 +138,22 @@ class AlmanakUserProfileView extends ConsumerWidget {
137138
).padding(all: formFieldPadding),
138139
],
139140
),
140-
DataTextListTile(
141-
name: "Aankomstjaar",
142-
value: "20$yearOfArrival",
141+
Row(
142+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
143+
crossAxisAlignment: CrossAxisAlignment.center,
144+
children: [
145+
SizedBox(
146+
width: 200, // or whatever fixed width you want
147+
child: DataTextListTile(
148+
name: "Aankomstjaar",
149+
value: "20$yearOfArrival",
150+
),
151+
),
152+
if (u.isBirthday)
153+
AlmanakBirthdayButton(
154+
receiverId: u.identifierString,
155+
receiverFullName: u.fullName)
156+
],
143157
),
144158
if (userInfo.studie != null &&
145159
(userInfo.studie as String).isNotEmpty)

lib/src/features/profiles/api/almanak_user_provider.dart

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import 'dart:convert';
22

33
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
import 'package:ksrvnjord_main_app/src/features/profiles/models/django_user.dart';
45
import 'package:ksrvnjord_main_app/src/features/shared/model/dio_provider.dart';
56

67
// ignore: prefer-static-class
7-
Future<Map<String, dynamic>> almanakUsersProvider(
8+
Future<List<DjangoUser>> almanakUsersProvider(
89
int page,
910
String search,
1011
WidgetRef ref,
@@ -16,5 +17,7 @@ Future<Map<String, dynamic>> almanakUsersProvider(
1617
"search": search,
1718
});
1819

19-
return jsonDecode(res.toString()) as Map<String, dynamic>;
20+
final List<dynamic> data = jsonDecode(res.toString())['items'];
21+
22+
return data.map((json) => DjangoUser.fromJson(json)).toList();
2023
}

lib/src/features/profiles/api/birthday_users_provider.dart

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import 'dart:convert';
22

33
import 'package:flutter_riverpod/flutter_riverpod.dart';
4+
import 'package:ksrvnjord_main_app/src/features/profiles/models/django_user.dart';
45
import 'package:ksrvnjord_main_app/src/features/shared/model/dio_provider.dart';
56

67
// ignore: prefer-static-class
7-
Future<Map<String, dynamic>> almanakBirthdayUsersProvider(
8+
Future<List<DjangoUser>> almanakBirthdayUsersProvider(
89
int page,
910
String search,
1011
WidgetRef ref,
@@ -17,5 +18,7 @@ Future<Map<String, dynamic>> almanakBirthdayUsersProvider(
1718
"is_birthdate": true,
1819
});
1920

20-
return jsonDecode(res.toString()) as Map<String, dynamic>;
21+
final List<dynamic> data = jsonDecode(res.toString())['items'];
22+
23+
return data.map((json) => DjangoUser.fromJson(json)).toList();
2124
}

lib/src/features/profiles/leeden/widgets/almanak_scrolling_widget.dart

+5-11
Original file line numberDiff line numberDiff line change
@@ -38,20 +38,14 @@ class _AlmanakScrollingState extends ConsumerState<AlmanakScrollingWidget> {
3838
int pageKey,
3939
) async {
4040
const amountOfResults = 100;
41-
final birthdayResult =
41+
final birthdayUsers =
4242
await almanakBirthdayUsersProvider(pageKey, widget.search, ref);
43-
final result = await almanakUsersProvider(pageKey, widget.search, ref);
43+
final allUsers = await almanakUsersProvider(pageKey, widget.search, ref);
4444

45-
final birthdayUsers = birthdayResult['items'] as List;
46-
final nonBirthdayUsers = result['items'] as List;
47-
final users = birthdayUsers +
48-
nonBirthdayUsers; //list the users with the birthday people first
45+
final page = birthdayUsers +
46+
allUsers; //list the users with the birthday people first
4947

50-
List<DjangoUser>? page = users
51-
.map((user) => DjangoUser.fromJson(user as Map<String, dynamic>))
52-
.toList();
53-
54-
if (result['count'] / amountOfResults > pageKey) {
48+
if (page.length / amountOfResults > pageKey) {
5549
_pagingController.appendPage(page, pageKey + 1);
5650

5751
return;

lib/src/features/profiles/models/django_user.dart

+7
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ class DjangoUser {
8686

8787
Map<String, dynamic> toJson() => _$DjangoUserToJson(this);
8888

89+
bool get isBirthday {
90+
final currentDate = DateTime.now();
91+
final birthDateTime = DateTime.parse(birthDate);
92+
return currentDate.month == birthDateTime.month &&
93+
currentDate.day == birthDateTime.day;
94+
}
95+
8996
static Future<DjangoUser> getByIdentifier(
9097
Ref ref,
9198
String lidnummer,

lib/src/features/profiles/models/user.dart

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class User {
4040
String get infix => _django.infix;
4141
bool get isAdmin => _django.isStaff;
4242
String get birthDate => _django.birthDate;
43+
bool get isBirthday => _django.isBirthday;
4344
String get initials => _django.initials;
4445
String get iban => _django.iban;
4546

0 commit comments

Comments
 (0)