Skip to content

Commit 0d35b6d

Browse files
authored
Added notification provider (#645)
* Added notification provider * Fix getById * Use max in markAsRead * Move notificationProvider inside addPostFrameCallback * Move notificationProvider refresh inside a Future microtask * Move init notificationProvider * Fix getById using firstOrNull * Add getters for private properties * Removed unsued import * Remove refresh from didPopNext and do it only when message comes in * Use Future.microtask with unawaited * Fix notifylistener order * Make unwaited Future * Skip API requset on markAsRead if already read
1 parent d8dfc4a commit 0d35b6d

File tree

7 files changed

+214
-124
lines changed

7 files changed

+214
-124
lines changed

lib/main.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:mosquito_alert_app/app_config.dart';
1010
import 'package:mosquito_alert_app/pages/main/drawer_and_header.dart';
1111
import 'package:mosquito_alert_app/providers/auth_provider.dart';
1212
import 'package:mosquito_alert_app/providers/device_provider.dart';
13+
import 'package:mosquito_alert_app/providers/notification_provider.dart';
1314
import 'package:mosquito_alert_app/services/api_service.dart';
1415
import 'package:mosquito_alert_app/utils/Application.dart';
1516
import 'package:mosquito_alert_app/utils/BackgroundTracking.dart';
@@ -63,6 +64,9 @@ Future<void> main({String env = 'prod'}) async {
6364
ChangeNotifierProvider<AuthProvider>.value(value: authProvider),
6465
ChangeNotifierProvider<UserProvider>.value(value: userProvider),
6566
ChangeNotifierProvider<DeviceProvider>.value(value: deviceProvider),
67+
ChangeNotifierProvider<NotificationProvider>(
68+
create: (_) => NotificationProvider(apiClient: apiClient),
69+
),
6670
],
6771
child: MyApp(),
6872
),

lib/pages/main/drawer_and_header.dart

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'package:mosquito_alert_app/pages/settings_pages/info_page.dart';
1818
import 'package:mosquito_alert_app/pages/settings_pages/settings_page.dart';
1919
import 'package:mosquito_alert_app/providers/auth_provider.dart';
2020
import 'package:mosquito_alert_app/providers/device_provider.dart';
21+
import 'package:mosquito_alert_app/providers/notification_provider.dart';
2122
import 'package:mosquito_alert_app/providers/user_provider.dart';
2223
import 'package:mosquito_alert_app/utils/BackgroundTracking.dart';
2324
import 'package:mosquito_alert_app/utils/MyLocalizations.dart';
@@ -39,13 +40,15 @@ class MainVC extends StatefulWidget {
3940
class _MainVCState extends State<MainVC>
4041
with RouteAware, WidgetsBindingObserver {
4142
int _selectedIndex = 0;
42-
int unreadNotifications = 0;
4343
PackageInfo? packageInfo;
4444
bool isLoading = true;
4545

46+
late final NotificationProvider notificationProvider;
47+
4648
@override
4749
void initState() {
4850
super.initState();
51+
notificationProvider = context.read<NotificationProvider>();
4952
WidgetsBinding.instance.addObserver(this);
5053
getPackageInfo();
5154
_startAsyncTasks();
@@ -64,17 +67,11 @@ class _MainVCState extends State<MainVC>
6467
ObserverUtils.routeObserver.subscribe(this, ModalRoute.of(context)!);
6568
}
6669

67-
@override
68-
void didPopNext() {
69-
// Called when returning from another page
70-
_fetchNotificationCount();
71-
}
72-
7370
// Called when app lifecycle state changes
7471
@override
7572
void didChangeAppLifecycleState(AppLifecycleState state) {
7673
if (state == AppLifecycleState.resumed) {
77-
_fetchNotificationCount();
74+
notificationProvider.fetchUnreadNotificationsCount();
7875
}
7976
}
8077

@@ -94,33 +91,17 @@ class _MainVCState extends State<MainVC>
9491
isLoading = false;
9592
});
9693
if (initAuthSuccess) {
97-
await _fetchNotificationCount();
94+
unawaited(
95+
Future(() async {
96+
await notificationProvider.refresh();
97+
}),
98+
);
9899
}
99100
await initBackgroundTracking();
100101
final deviceProvider = context.read<DeviceProvider>();
101102
await PushNotificationsManager.init(provider: deviceProvider);
102103
}
103104

104-
Future<void> _fetchNotificationCount() async {
105-
int count = 0;
106-
try {
107-
MosquitoAlert apiClient =
108-
Provider.of<MosquitoAlert>(context, listen: false);
109-
NotificationsApi notificationsApi = apiClient.getNotificationsApi();
110-
111-
final Response<PaginatedNotificationList> response =
112-
await notificationsApi.listMine(isRead: false, pageSize: 1);
113-
count = response.data?.count ?? 0;
114-
} catch (e, stackTrace) {
115-
print('Failed to fetch notification count: $e');
116-
debugPrintStack(stackTrace: stackTrace);
117-
}
118-
if (!mounted) return;
119-
setState(() {
120-
unreadNotifications = count;
121-
});
122-
}
123-
124105
Future<void> getPackageInfo() async {
125106
PackageInfo _packageInfo = await PackageInfo.fromPlatform();
126107
setState(() {
@@ -245,6 +226,8 @@ class _MainVCState extends State<MainVC>
245226
@override
246227
Widget build(BuildContext context) {
247228
final User? user = context.watch<UserProvider>().user;
229+
final unreadNotifications =
230+
context.watch<NotificationProvider>().unreadNotificationsCount;
248231
return Scaffold(
249232
appBar: AppBar(
250233
backgroundColor: Colors.white,

lib/pages/notification_pages/notification_detail_page.dart

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,40 @@
1+
import 'dart:async';
2+
13
import 'package:intl/intl.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_html/flutter_html.dart' as html;
46
import 'package:mosquito_alert/mosquito_alert.dart' as sdk;
7+
import 'package:mosquito_alert_app/providers/notification_provider.dart';
58
import 'package:provider/provider.dart';
69
import 'package:mosquito_alert_app/utils/style.dart';
710
import 'package:mosquito_alert_app/utils/html_parser.dart';
811

912
class NotificationDetailPage extends StatefulWidget {
1013
final sdk.Notification notification;
11-
final void Function(sdk.Notification)? onNotificationUpdated;
1214

1315
const NotificationDetailPage({
1416
super.key,
1517
required this.notification,
16-
this.onNotificationUpdated,
1718
});
1819

19-
static Future<NotificationDetailPage> fromId({
20-
required BuildContext context,
21-
required int notificationId,
22-
void Function(sdk.Notification)? onNotificationUpdated,
23-
}) async {
24-
final sdk.MosquitoAlert apiClient =
25-
Provider.of<sdk.MosquitoAlert>(context, listen: false);
26-
final sdk.NotificationsApi notificationsApi =
27-
apiClient.getNotificationsApi();
28-
final response = await notificationsApi.retrieve(id: notificationId);
20+
static Future<NotificationDetailPage> fromId(
21+
{required BuildContext context,
22+
required int notificationId,
23+
bool refresh = false}) async {
24+
final notificationProvider = context.read<NotificationProvider>();
25+
if (refresh) {
26+
// Run refresh in the background without blocking
27+
unawaited(
28+
Future(() async {
29+
await notificationProvider.refresh();
30+
}),
31+
);
32+
}
33+
34+
final notification = await notificationProvider.getById(id: notificationId);
2935

3036
return NotificationDetailPage(
31-
notification: response.data!,
32-
onNotificationUpdated: onNotificationUpdated,
37+
notification: notification!,
3338
);
3439
}
3540

@@ -39,38 +44,15 @@ class NotificationDetailPage extends StatefulWidget {
3944

4045
class _NotificationDetailPageState extends State<NotificationDetailPage> {
4146
late sdk.Notification _notification; // local mutable copy
42-
late sdk.NotificationsApi notificationsApi;
4347

4448
@override
4549
void initState() {
4650
super.initState();
4751
_notification = widget.notification;
4852

49-
sdk.MosquitoAlert apiClient =
50-
Provider.of<sdk.MosquitoAlert>(context, listen: false);
51-
notificationsApi = apiClient.getNotificationsApi();
52-
53-
WidgetsBinding.instance.addPostFrameCallback((_) => _markAsRead());
54-
}
55-
56-
Future<void> _markAsRead() async {
57-
if (_notification.isRead) return;
58-
59-
try {
60-
final request = sdk.PatchedNotificationRequest((b) => b..isRead = true);
61-
final response = await notificationsApi.partialUpdate(
62-
id: _notification.id, patchedNotificationRequest: request);
63-
64-
final updatedNotification = response.data!;
65-
if (!mounted) return;
66-
67-
setState(() => _notification = updatedNotification);
68-
69-
// notify parent
70-
widget.onNotificationUpdated?.call(updatedNotification);
71-
} catch (e) {
72-
debugPrint("Failed to mark notification as read: $e");
73-
}
53+
WidgetsBinding.instance.addPostFrameCallback((_) => context
54+
.read<NotificationProvider>()
55+
.markAsRead(notification: _notification));
7456
}
7557

7658
String get formattedDate {

lib/pages/notification_pages/notifications_page.dart

Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import 'dart:async';
22

3-
import 'package:built_collection/built_collection.dart';
43
import 'package:flutter/material.dart';
54
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
65
import 'package:mosquito_alert/mosquito_alert.dart' as sdk;
76
import 'package:mosquito_alert_app/pages/notification_pages/notification_detail_page.dart';
7+
import 'package:mosquito_alert_app/providers/notification_provider.dart';
88
import 'package:mosquito_alert_app/services/analytics_service.dart';
99
import 'package:mosquito_alert_app/utils/MyLocalizations.dart';
1010
import 'package:mosquito_alert_app/utils/UserManager.dart';
@@ -27,51 +27,26 @@ class NotificationsPage extends StatefulWidget {
2727

2828
class _NotificationsPageState extends State<NotificationsPage> {
2929
String languageCode = 'en';
30-
31-
bool _isLastPage = false;
32-
static const _pageSize = 20;
33-
late final _pagingController =
34-
PagingController<int, sdk.Notification>(getNextPageKey: (state) {
35-
return _isLastPage ? null : state.nextIntPageKey;
36-
}, fetchPage: (pageKey) async {
37-
final response = await notificationsApi.listMine(
38-
page: pageKey,
39-
pageSize: _pageSize,
40-
orderBy: BuiltList<String>(["-created_at"]),
41-
);
42-
final data = response.data;
43-
if (data == null) return [];
44-
_isLastPage = data.next == null;
45-
46-
return data.results?.toList() ?? [];
47-
});
48-
49-
StreamController<bool> loadingStream = StreamController<bool>.broadcast();
50-
late sdk.NotificationsApi notificationsApi;
5130
late AnalyticsService _analyticsService;
5231

5332
@override
5433
void initState() {
5534
super.initState();
56-
sdk.MosquitoAlert apiClient =
57-
Provider.of<sdk.MosquitoAlert>(context, listen: false);
58-
notificationsApi = apiClient.getNotificationsApi();
5935
_analyticsService = widget.analyticsService ?? FirebaseAnalyticsService();
6036
initLanguage();
6137
_logScreenView();
62-
loadingStream.add(true);
38+
WidgetsBinding.instance.addPostFrameCallback((_) {
39+
final provider = context.read<NotificationProvider>();
40+
if (provider.notifications.isEmpty) {
41+
provider.fetchNextPage();
42+
}
43+
});
6344
}
6445

6546
Future<void> initLanguage() async {
6647
languageCode = await UserManager.getLanguage() ?? 'en';
6748
}
6849

69-
@override
70-
void dispose() {
71-
_pagingController.dispose();
72-
super.dispose();
73-
}
74-
7550
Future<void> _logScreenView() async {
7651
await _analyticsService.logScreenView(screenName: '/notifications');
7752
}
@@ -91,14 +66,21 @@ class _NotificationsPageState extends State<NotificationsPage> {
9166
body: SafeArea(
9267
child: RefreshIndicator(
9368
onRefresh: () async {
94-
_pagingController.refresh();
69+
// Call the provider's refresh method
70+
final provider = context.read<NotificationProvider>();
71+
await provider.refresh();
9572
},
96-
child: PagingListener<int, sdk.Notification>(
97-
controller: _pagingController,
98-
builder: (context, state, fetchNextPage) {
73+
child: Consumer<NotificationProvider>(
74+
builder: (context, provider, _) {
9975
return PagedListView<int, sdk.Notification>(
100-
state: state,
101-
fetchNextPage: fetchNextPage,
76+
state: PagingState(
77+
pages: [provider.notifications],
78+
keys: [1],
79+
hasNextPage: provider.hasMoreNotifications,
80+
isLoading: provider.isLoading,
81+
error: provider.errorMessage,
82+
),
83+
fetchNextPage: provider.fetchNextPage,
10284
builderDelegate: PagedChildBuilderDelegate<sdk.Notification>(
10385
itemBuilder: (context, notification, index) {
10486
return Column(children: [
@@ -107,16 +89,8 @@ class _NotificationsPageState extends State<NotificationsPage> {
10789
Navigator.of(context).push(
10890
MaterialPageRoute(
10991
builder: (context) => NotificationDetailPage(
110-
notification: notification,
111-
onNotificationUpdated: (notification) {
112-
// Update the notification in the paging controller
113-
// See: https://github.com/EdsonBueno/infinite_scroll_pagination/issues/389
114-
_pagingController.mapItems(
115-
(sdk.Notification item) =>
116-
item.id == notification.id
117-
? notification
118-
: item);
119-
}),
92+
notification: notification,
93+
),
12094
),
12195
);
12296
},

0 commit comments

Comments
 (0)