add support for badges and notifications, adjust nav pages
All checks were successful
Release / meta (push) Successful in 7s
Release / linux-build (push) Successful in 6m49s
Release / android-build (push) Successful in 15m55s
Release / release-master (push) Successful in 24s
Release / release-dev (push) Successful in 26s

This commit is contained in:
2025-12-26 18:36:37 +00:00
parent 44d79e7c28
commit 4bd6f0bbed
16 changed files with 1161 additions and 144 deletions

View File

@@ -9,4 +9,5 @@ import 'package:mileograph_flutter/services/api_service.dart';
part 'data_service_core.dart';
part 'data_service_traction.dart';
part 'data_service_trips.dart';
part 'data_service_notifications.dart';
part 'data_service_badges.dart';

View File

@@ -0,0 +1,42 @@
part of 'data_service.dart';
extension DataServiceBadges on DataService {
Future<void> fetchBadgeAwards() async {
_isBadgeAwardsLoading = true;
try {
final json = await api.get('/badge/awards/me');
List<dynamic>? list;
if (json is List) {
list = json;
} else if (json is Map) {
for (final key in ['awards', 'badge_awards', 'data']) {
final value = json[key];
if (value is List) {
list = value;
break;
}
}
}
final parsed = list
?.whereType<Map<String, dynamic>>()
.map(BadgeAward.fromJson)
.toList();
if (parsed != null) {
parsed.sort((a, b) {
final aTs = a.awardedAt?.millisecondsSinceEpoch ?? 0;
final bTs = b.awardedAt?.millisecondsSinceEpoch ?? 0;
return bTs.compareTo(aTs);
});
_badgeAwards = parsed;
} else {
_badgeAwards = [];
}
} catch (e) {
debugPrint('Failed to fetch badge awards: $e');
_badgeAwards = [];
} finally {
_isBadgeAwardsLoading = false;
_notifyAsync();
}
}
}

View File

@@ -91,6 +91,18 @@ class DataService extends ChangeNotifier {
bool _isOnThisDayLoading = false;
bool get isOnThisDayLoading => _isOnThisDayLoading;
// Notifications
List<UserNotification> _notifications = [];
List<UserNotification> get notifications => _notifications;
bool _isNotificationsLoading = false;
bool get isNotificationsLoading => _isNotificationsLoading;
// Badges
List<BadgeAward> _badgeAwards = [];
List<BadgeAward> get badgeAwards => _badgeAwards;
bool _isBadgeAwardsLoading = false;
bool get isBadgeAwardsLoading => _isBadgeAwardsLoading;
static const List<EventField> _fallbackEventFields = [
EventField(name: 'operator', display: 'Operator'),
EventField(name: 'status', display: 'Status'),

View File

@@ -0,0 +1,62 @@
part of 'data_service.dart';
extension DataServiceNotifications on DataService {
Future<void> fetchNotifications() async {
_isNotificationsLoading = true;
try {
final json = await api.get('/notifications');
List<dynamic>? list;
if (json is List) {
list = json;
} else if (json is Map) {
for (final key in ['notifications', 'data', 'items']) {
final value = json[key];
if (value is List) {
list = value;
break;
}
}
}
final parsed = list
?.whereType<Map<String, dynamic>>()
.map(UserNotification.fromJson)
.where((n) => !n.dismissed)
.toList();
if (parsed != null) {
parsed.sort((a, b) {
final aTs = a.createdAt?.millisecondsSinceEpoch ?? 0;
final bTs = b.createdAt?.millisecondsSinceEpoch ?? 0;
return bTs.compareTo(aTs);
});
_notifications = parsed;
} else {
_notifications = [];
}
} catch (e) {
debugPrint('Failed to fetch notifications: $e');
_notifications = [];
} finally {
_isNotificationsLoading = false;
_notifyAsync();
}
}
Future<void> dismissNotifications(List<int> notificationIds) async {
if (notificationIds.isEmpty) return;
try {
await api.put('/notifications/dismiss', {
"notification_ids": notificationIds,
"payload": {"dismissed": true},
});
_notifications = _notifications
.where((n) => !notificationIds.contains(n.id))
.toList();
} catch (e) {
debugPrint('Failed to dismiss notifications: $e');
rethrow;
} finally {
_notifyAsync();
}
}
}