Skip to content

Commit

Permalink
Merge pull request #931 from SocialGouv/feat/pastille-centre-notif
Browse files Browse the repository at this point in the history
feat(notifications): integrate in-app notifications into accueil page…
  • Loading branch information
Alwein authored Feb 5, 2025
2 parents 6151d54 + dfc480a commit 2a3f6a8
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 13 deletions.
7 changes: 6 additions & 1 deletion lib/pages/accueil/accueil_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:pass_emploi_app/analytics/analytics_constants.dart';
import 'package:pass_emploi_app/analytics/tracker.dart';
import 'package:pass_emploi_app/features/accueil/accueil_actions.dart';
import 'package:pass_emploi_app/features/deep_link/deep_link_actions.dart';
import 'package:pass_emploi_app/features/in_app_notifications/in_app_notifications_actions.dart';
import 'package:pass_emploi_app/models/deep_link.dart';
import 'package:pass_emploi_app/pages/accueil/accueil_alertes.dart';
import 'package:pass_emploi_app/pages/accueil/accueil_campagne_recrutement.dart';
Expand Down Expand Up @@ -52,7 +53,10 @@ class _AccueilPageState extends State<AccueilPage> {
return Tracker(
tracking: AnalyticsScreenNames.accueil,
child: StoreConnector<AppState, AccueilViewModel>(
onInit: (store) => store.dispatch(AccueilRequestAction()),
onInit: (store) {
store.dispatch(AccueilRequestAction());
store.dispatch(InAppNotificationsRequestAction());
},
converter: (store) => AccueilViewModel.create(store),
builder: _builder,
onDidChange: (previousViewModel, viewModel) {
Expand Down Expand Up @@ -144,6 +148,7 @@ class _Body extends StatelessWidget {
slivers: [
PrimarySliverAppbar(
title: Strings.accueilAppBarTitle,
withNewNotifications: viewModel.withNewNotifications,
),
SliverToBoxAdapter(
child: AnimatedSwitcher(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import 'package:flutter_redux/flutter_redux.dart';
import 'package:pass_emploi_app/analytics/analytics_constants.dart';
import 'package:pass_emploi_app/analytics/tracker.dart';
import 'package:pass_emploi_app/features/date_consultation_notification/date_consultation_notification_actions.dart';
import 'package:pass_emploi_app/features/in_app_notifications/in_app_notifications_actions.dart';
import 'package:pass_emploi_app/presentation/display_state.dart';
import 'package:pass_emploi_app/presentation/notifications_center/notifications_center_view_model.dart';
import 'package:pass_emploi_app/redux/app_state.dart';
Expand Down Expand Up @@ -37,10 +36,7 @@ class NotificationCenter extends StatelessWidget {
backgroundColor: AppColors.grey100,
appBar: SecondaryAppBar(title: Strings.notificationsCenterTitle),
body: StoreConnector<AppState, NotificationsCenterViewModel>(
onInit: (store) {
store.dispatch(InAppNotificationsRequestAction());
store.dispatch(DateConsultationNotificationRequestAction());
},
onInit: (store) => store.dispatch(DateConsultationNotificationRequestAction()),
converter: (store) => NotificationsCenterViewModel.create(store),
builder: (context, viewModel) => _DisplayState(viewModel),
onDispose: (store) => store.dispatch(DateConsultationNotificationWriteAction(DateTime.now())),
Expand Down
22 changes: 22 additions & 0 deletions lib/presentation/accueil/accueil_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:pass_emploi_app/features/accueil/accueil_state.dart';
import 'package:pass_emploi_app/features/campagne_recrutement/campagne_recrutement_actions.dart';
import 'package:pass_emploi_app/features/deep_link/deep_link_actions.dart';
import 'package:pass_emploi_app/features/deep_link/deep_link_state.dart';
import 'package:pass_emploi_app/features/in_app_notifications/in_app_notifications_state.dart';
import 'package:pass_emploi_app/features/rating/rating_state.dart';
import 'package:pass_emploi_app/models/accompagnement.dart';
import 'package:pass_emploi_app/models/deep_link.dart';
Expand All @@ -24,6 +25,7 @@ class AccueilViewModel extends Equatable {
final bool shouldResetDeeplink;
final bool shouldShowOnboarding;
final bool shouldShowNavigationBottomSheet;
final bool withNewNotifications;
final Function() resetDeeplink;
final Function() retry;

Expand All @@ -34,6 +36,7 @@ class AccueilViewModel extends Equatable {
required this.shouldResetDeeplink,
required this.shouldShowOnboarding,
required this.shouldShowNavigationBottomSheet,
required this.withNewNotifications,
required this.resetDeeplink,
required this.retry,
});
Expand All @@ -46,6 +49,7 @@ class AccueilViewModel extends Equatable {
shouldResetDeeplink: _shouldResetDeeplink(store),
shouldShowOnboarding: _shouldShowOnboarding(store),
shouldShowNavigationBottomSheet: _shouldShowNavigationBottomSheet(store),
withNewNotifications: _withNewNotifications(store),
resetDeeplink: () => store.dispatch(ResetDeeplinkAction()),
retry: () => store.dispatch(AccueilRequestAction(forceRefresh: true)),
);
Expand Down Expand Up @@ -199,3 +203,21 @@ bool _shouldShowNavigationBottomSheet(Store<AppState> store) {
if (accueilState is! AccueilSuccessState || user == null) return false;
return user.accompagnement != Accompagnement.avenirPro;
}

bool _withNewNotifications(Store<AppState> store) {
final inAppNotificationsState = store.state.inAppNotificationsState;
final dateDerniereConsultation = store.state.dateConsultationNotificationState.date;

if (inAppNotificationsState is! InAppNotificationsSuccessState) {
return false;
}

final notification = inAppNotificationsState.notifications.firstOrNull;

if (notification == null) {
return false;
}

final isNew = dateDerniereConsultation != null ? notification.date.isAfter(dateDerniereConsultation) : true;
return isNew;
}
45 changes: 38 additions & 7 deletions lib/widgets/default_app_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import 'package:pass_emploi_app/widgets/profile_button.dart';

class PrimarySliverAppbar extends StatelessWidget {
final String title;
const PrimarySliverAppbar({required this.title});
final bool withNewNotifications;
const PrimarySliverAppbar({required this.title, required this.withNewNotifications});

static double expandedHeight = 90.0;

Expand Down Expand Up @@ -65,12 +66,7 @@ class PrimarySliverAppbar extends StatelessWidget {
),
),
),
TertiaryIconButton(
icon: AppIcons.notifications_outlined,
tooltip: Strings.notificationsCenterTooltip,
iconColor: Brand.isCej() ? AppColors.primary : Colors.white,
onTap: () => Navigator.of(context).push(NotificationCenter.route()),
),
_CentreNotif(withNewNotifications),
SizedBox(width: Margins.spacing_s),
ProfileButton(isDarkColor: Brand.isCej()),
],
Expand All @@ -92,6 +88,41 @@ class PrimarySliverAppbar extends StatelessWidget {
}
}

class _CentreNotif extends StatelessWidget {
const _CentreNotif(
this.withNewNotifications,
);

final bool withNewNotifications;

@override
Widget build(BuildContext context) {
return Stack(
children: [
TertiaryIconButton(
icon: AppIcons.notifications_outlined,
tooltip: Strings.notificationsCenterTooltip,
iconColor: Brand.isCej() ? AppColors.primary : Colors.white,
onTap: () => Navigator.of(context).push(NotificationCenter.route()),
),
if (withNewNotifications)
Positioned(
right: Margins.spacing_s,
top: Margins.spacing_s,
child: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
],
);
}
}

class _BottomBorder extends StatelessWidget implements PreferredSizeWidget {
@override
Widget build(BuildContext context) {
Expand Down
67 changes: 67 additions & 0 deletions test/presentation/accueil/accueil_view_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:pass_emploi_app/features/accueil/accueil_actions.dart';
import 'package:pass_emploi_app/features/accueil/accueil_state.dart';
import 'package:pass_emploi_app/features/date_consultation_notification/date_consultation_notification_state.dart';
import 'package:pass_emploi_app/features/onboarding/onboarding_state.dart';
import 'package:pass_emploi_app/models/accompagnement.dart';
import 'package:pass_emploi_app/models/accueil/accueil.dart';
Expand Down Expand Up @@ -498,5 +499,71 @@ void main() {
isTrue,
);
});

group('withNewNotifications', () {
test('should not show new notifications when in app notifications is not success state', () {
// Given
final store = givenState().loggedInMiloUser().withInAppNotificationsLoading().store();

// When
final viewModel = AccueilViewModel.create(store);

// Then
expect(
viewModel.withNewNotifications,
isFalse,
);
});

test('should not show new notifications when there is no notification', () {
// Given
final store = givenState() //
.loggedInMiloUser()
.withInAppNotificationsSuccess([]).store();

// When
final viewModel = AccueilViewModel.create(store);

// Then
expect(
viewModel.withNewNotifications,
isFalse,
);
});

test('should not show new notifications when notification is older than last consultation date', () {
// Given
final store = givenState() //
.loggedInMiloUser()
.copyWith(dateConsultationNotificationState: DateConsultationNotificationState(date: DateTime(2025)))
.withInAppNotificationsSuccess([mockInAppNotification(date: DateTime(2024))]).store();

// When
final viewModel = AccueilViewModel.create(store);

// Then
expect(
viewModel.withNewNotifications,
isFalse,
);
});

test('should show new notifications when notification is newer than last consultation date', () {
// Given
final store = givenState() //
.loggedInMiloUser()
.copyWith(dateConsultationNotificationState: DateConsultationNotificationState(date: DateTime(2024)))
.withInAppNotificationsSuccess([mockInAppNotification(date: DateTime(2025))]).store();

// When
final viewModel = AccueilViewModel.create(store);

// Then
expect(
viewModel.withNewNotifications,
isTrue,
);
});
});
});
}

0 comments on commit 2a3f6a8

Please sign in to comment.