Skip to content

Commit 66b8355

Browse files
committed
UI FIX and TEST
1 parent 00af6d0 commit 66b8355

14 files changed

Lines changed: 529 additions & 275 deletions

firestore.rules

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ service cloud.firestore {
5858
return isAuthenticated();
5959
}
6060

61+
// Check if target user has a pending join request to a group where current user is admin
62+
// NOTE: This cannot be done efficiently in rules (would require querying join_requests).
63+
// Instead, we embed the requester's name IN the join_request document at creation time,
64+
// so admins don't need to read the user document directly.
65+
// This function is a placeholder for documentation purposes.
66+
function hasPendingJoinRequestToMyGroup(targetUserId) {
67+
// Cannot be implemented efficiently in rules - handled at app level
68+
return false;
69+
}
70+
6171
// ============================================
6272
// 1. USERS COLLECTION
6373
// ============================================
@@ -169,9 +179,9 @@ service cloud.firestore {
169179
// Read: Strictly limited to group members
170180
allow read: if isAuthenticated() && isGroupMember(resource.data.groupId);
171181

172-
// Only Owner can create
182+
// Owner or Admin can create
173183
allow create: if isAuthenticated() &&
174-
isGroupOwner(request.resource.data.groupId);
184+
isGroupAdmin(request.resource.data.groupId);
175185

176186
// Owner or Admin can update
177187
allow update: if isAuthenticated() &&
@@ -282,6 +292,7 @@ service cloud.firestore {
282292
request.resource.data.status == 'pending';
283293

284294
// Only Owner/Admin can process (approve/reject)
295+
// Restrict changes to specific fields only
285296
allow update: if isAuthenticated() &&
286297
isGroupAdmin(resource.data.groupId) &&
287298
request.resource.data.diff(resource.data).affectedKeys()
@@ -306,5 +317,20 @@ service cloud.firestore {
306317
// No updates or deletes
307318
allow update, delete: if false;
308319
}
320+
321+
// ============================================
322+
// 10. NOTIFICATIONS COLLECTION (Top-level)
323+
// ============================================
324+
match /notifications/{notificationId} {
325+
// User can read their own notifications (userId field matches)
326+
allow read: if isAuthenticated() && resource.data.userId == userId();
327+
328+
// Any authenticated user can create or update notifications
329+
// (needed for batch.set with merge:true for deduplication)
330+
allow create, update: if isAuthenticated();
331+
332+
// User can delete their own notifications
333+
allow delete: if isAuthenticated() && resource.data.userId == userId();
334+
}
309335
}
310336
}

lib/add_event_modal.dart

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -309,15 +309,14 @@ class _AddEventModalState extends State<AddEventModal> {
309309
final changes = <String>[];
310310

311311
if (oldEvent.title != newEvent.title) {
312-
changes.add("Title: ${oldEvent.title} -> ${newEvent.title}");
312+
changes.add("Title Updated");
313313
}
314314

315315
if (oldEvent.venue != newEvent.venue) {
316-
final oldV = oldEvent.venue != null && oldEvent.venue!.isNotEmpty ? oldEvent.venue! : 'None';
317-
final newV = newEvent.venue != null && newEvent.venue!.isNotEmpty ? newEvent.venue! : 'None';
318-
// Only show if actually changed
316+
final oldV = oldEvent.venue != null && oldEvent.venue!.isNotEmpty ? oldEvent.venue! : '';
317+
final newV = newEvent.venue != null && newEvent.venue!.isNotEmpty ? newEvent.venue! : '';
319318
if (oldV != newV) {
320-
changes.add("Venue: $oldV -> $newV");
319+
changes.add("Venue Updated");
321320
}
322321
}
323322

@@ -326,28 +325,28 @@ class _AddEventModalState extends State<AddEventModal> {
326325
final newDateStr = DateFormat('yyyy-MM-dd').format(newEvent.date);
327326

328327
if (oldDateStr != newDateStr) {
329-
changes.add("Date: $oldDateStr -> $newDateStr");
328+
changes.add("Date Updated");
330329
}
331330

332-
// Compare times if hasTime changed or time value changed
331+
// Compare times
333332
if (oldEvent.hasTime != newEvent.hasTime ||
334333
(oldEvent.hasTime && newEvent.hasTime && oldEvent.date != newEvent.date)) {
335334
if (newEvent.hasTime) {
336-
final oldTime = oldEvent.hasTime ? DateFormat('HH:mm').format(oldEvent.date) : 'No time';
335+
final oldTime = oldEvent.hasTime ? DateFormat('HH:mm').format(oldEvent.date) : '';
337336
final newTime = DateFormat('HH:mm').format(newEvent.date);
338337
if (oldTime != newTime) {
339-
changes.add("Time: $oldTime -> $newTime");
338+
changes.add("Time Updated");
340339
}
341340
} else {
342-
changes.add("Time removed");
341+
changes.add("Time Removed");
343342
}
344343
}
345344

346345
if (oldEvent.description != newEvent.description) {
347-
changes.add("Description updated");
346+
changes.add("Description Updated");
348347
}
349348

350349
if (changes.isEmpty) return null;
351-
return changes.join("\n");
350+
return changes.join(", ");
352351
}
353352
}

lib/birthday_baby_dialog.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class BirthdayBabyDialog extends StatelessWidget {
112112
color: Colors.indigo,
113113
shape: BoxShape.circle,
114114
),
115-
child: const Text('🌙', style: TextStyle(fontSize: 10)),
115+
child: const Text('🏮', style: TextStyle(fontSize: 10)),
116116
),
117117
),
118118
],

lib/detail_modal.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,7 +1367,13 @@ class _DetailModalState extends State<DetailModal> {
13671367
: 'Unknown';
13681368
return Builder(
13691369
builder: (context) {
1370+
final isDark = Theme.of(context).brightness == Brightness.dark;
13701371
return Card(
1372+
color: isDark ? Colors.grey.shade900 : null,
1373+
shape: RoundedRectangleBorder(
1374+
borderRadius: BorderRadius.circular(8),
1375+
side: isDark ? BorderSide(color: Colors.grey.shade700) : BorderSide.none,
1376+
),
13711377
child: Padding(
13721378
padding: const EdgeInsets.all(12),
13731379
child: Row(

lib/firestore_service.dart

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,32 @@ class FirestoreService {
507507
await _db.collection('notifications').doc(notificationId).update({'read': true});
508508
}
509509

510+
Future<void> markNotificationUnread(String notificationId) async {
511+
await _db.collection('notifications').doc(notificationId).update({'read': false});
512+
}
513+
514+
Future<void> deleteNotification(String notificationId) async {
515+
await _db.collection('notifications').doc(notificationId).delete();
516+
}
517+
518+
Future<void> deleteAllNotifications(String userId) async {
519+
final snapshot = await _db.collection('notifications').where('userId', isEqualTo: userId).get();
520+
final batch = _db.batch();
521+
for (final doc in snapshot.docs) {
522+
batch.delete(doc.reference);
523+
}
524+
await batch.commit();
525+
}
526+
527+
Future<void> markAllAsUnread(String userId) async {
528+
final snapshot = await _db.collection('notifications').where('userId', isEqualTo: userId).get();
529+
final batch = _db.batch();
530+
for (final doc in snapshot.docs) {
531+
batch.update(doc.reference, {'read': false});
532+
}
533+
await batch.commit();
534+
}
535+
510536
// --- Events ---
511537

512538
Future<void> createEvent(GroupEvent event) async {
@@ -555,19 +581,8 @@ class FirestoreService {
555581

556582
await _db.collection('events').doc(event.id).update(updateData);
557583

558-
// Notify members
559-
final groupDoc = await _db.collection('groups').doc(event.groupId).get();
560-
if (groupDoc.exists) {
561-
final group = Group.fromFirestore(groupDoc);
562-
debugPrint('[FirestoreService] Notifying ${group.members.length} members for event update: ${event.title}');
563-
await NotificationService().notifyEventUpdated(
564-
memberIds: group.members,
565-
editorId: editedByUserId,
566-
eventId: event.id,
567-
eventTitle: event.title,
568-
groupId: event.groupId,
569-
);
570-
}
584+
// NOTE: Notification is handled by add_event_modal.dart with changeSummary
585+
// Do NOT send notification here to avoid duplicates
571586
}
572587

573588
Future<void> deleteEvent(String eventId, String deleterId) async {
@@ -990,7 +1005,7 @@ class FirestoreService {
9901005
final group = Group.fromFirestore(groupDoc);
9911006

9921007
await NotificationService().notifyInheritanceRequest(
993-
adminIds: group.admins, // admins includes owner usually
1008+
adminIds: [...group.admins, group.ownerId], // Include owner explicitly
9941009
requesterId: requesterId,
9951010
requesterName: userName,
9961011
placeholderName: phName,
@@ -1224,8 +1239,12 @@ class FirestoreService {
12241239
await _db.collection('groups').doc(request.groupId).update({
12251240
'members': FieldValue.arrayUnion([request.requesterId]),
12261241
});
1227-
// Note: We used to update user's joinedGroupIds here, but Admins can't write to other Users.
1228-
// The user must sync their own group list on next app launch.
1242+
1243+
// Force sync the new member's joinedGroupIds immediately
1244+
// This allows them to read other group members' profiles right away
1245+
await _db.collection('users').doc(request.requesterId).update({
1246+
'joinedGroupIds': FieldValue.arrayUnion([request.groupId]),
1247+
});
12291248

12301249
// Get group name for notification
12311250
final groupDoc = await _db.collection('groups').doc(request.groupId).get();

0 commit comments

Comments
 (0)