From a8389beaf15099430b2c7ca1b9d288f09ab08ecb Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:08:00 +0100 Subject: [PATCH 01/78] feat(lib): add DevicePlatform enum - Create a new enum to identify the mobile operating system of a user's device - Add two values: ios and android - Include template comments for documentation purposes --- lib/src/enums/device_platform.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lib/src/enums/device_platform.dart diff --git a/lib/src/enums/device_platform.dart b/lib/src/enums/device_platform.dart new file mode 100644 index 00000000..38206ef8 --- /dev/null +++ b/lib/src/enums/device_platform.dart @@ -0,0 +1,10 @@ +/// {@template device_platform} +/// Identifies the mobile operating system of a user's device. +/// {@endtemplate} +enum DevicePlatform { + /// Apple iOS. + ios, + + /// Google Android. + android, +} From 58beac323d8fb526c92dec60d9af730d9c53291f Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:08:27 +0100 Subject: [PATCH 02/78] feat(enum): add push notification provider definition - Introduce new enum 'PushNotificationProvider' to define available push notification service providers - Include two providers: Firebase Cloud Messaging and OneSignal - Enhance backend functionality by allowing selection of appropriate client for sending notifications - Enable device registration with configured push notification provider --- lib/src/enums/push_notification_provider.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lib/src/enums/push_notification_provider.dart diff --git a/lib/src/enums/push_notification_provider.dart b/lib/src/enums/push_notification_provider.dart new file mode 100644 index 00000000..0407e320 --- /dev/null +++ b/lib/src/enums/push_notification_provider.dart @@ -0,0 +1,14 @@ +/// {@template push_notification_provider} +/// Defines the available push notification service providers. +/// +/// This allows the backend to select the appropriate client for sending a +/// notification and allows devices to register themselves with the provider +/// they are configured to use. +/// {@endtemplate} +enum PushNotificationProvider { + /// Firebase Cloud Messaging. + firebase, + + /// OneSignal. + oneSignal, +} From 5b5be158da50a183b5e3ed6cb081448e6bceef7a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:09:46 +0100 Subject: [PATCH 03/78] feat(enums): add SubscriptionDeliveryType enum - Defines the types of notifications a user can receive for a subscription - Includes options for breaking news, daily digest, and weekly roundup - Allows users to opt into multiple delivery types for flexible alert configurations --- lib/src/enums/subscription_delivery_type.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/src/enums/subscription_delivery_type.dart diff --git a/lib/src/enums/subscription_delivery_type.dart b/lib/src/enums/subscription_delivery_type.dart new file mode 100644 index 00000000..9ec355ac --- /dev/null +++ b/lib/src/enums/subscription_delivery_type.dart @@ -0,0 +1,19 @@ +/// {@template subscription_delivery_type} +/// Defines the types of notifications a user can receive for a subscription. +/// +/// A user can opt into multiple delivery types for a single notification +/// subscription, allowing for flexible alert configurations. +/// {@endtemplate} +enum SubscriptionDeliveryType { + /// Delivers a notification immediately only when a matching headline is + /// editorially marked as "breaking news". + breakingOnly, + + /// Delivers a single, AI-powered summary of all matching headlines from the + /// past day, sent at a scheduled time. + dailyDigest, + + /// Delivers a single, AI-powered summary of all matching headlines from the + /// past week, sent on a recurring day. + weeklyRoundup, +} From 5e6a4e9bb8b2e5d4f581eabb788563549d7f2955 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:13:50 +0100 Subject: [PATCH 04/78] feat(notifications): add Device model for push notifications - Create Device model to represent user's device registered for push notifications - Include properties for device token, push notification provider, platform, and user association - Implement JSON serialization and deserialization - Add Equatable mixin for value comparison --- lib/src/models/notifications/device.dart | 91 ++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 lib/src/models/notifications/device.dart diff --git a/lib/src/models/notifications/device.dart b/lib/src/models/notifications/device.dart new file mode 100644 index 00000000..ac3b6d11 --- /dev/null +++ b/lib/src/models/notifications/device.dart @@ -0,0 +1,91 @@ +import 'package:core/src/enums/device_platform.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/utils/json_helpers.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'device.g.dart'; + +/// {@template device} +/// Represents a user's device that is registered for push notifications. +/// +/// This model stores the device token, the push notification provider, the +/// platform (iOS/Android), and links it to a specific user. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class Device extends Equatable { + /// {@macro device} + const Device({ + required this.id, + required this.userId, + required this.token, + required this.provider, + required this.platform, + required this.createdAt, + required this.updatedAt, + }); + + /// Creates a [Device] from JSON data. + factory Device.fromJson(Map json) => _$DeviceFromJson(json); + + /// The unique identifier for this device registration. + final String id; + + /// The ID of the user who owns this device. + final String userId; + + /// The unique token issued by the push notification provider for this device. + final String token; + + /// The push notification provider (e.g., Firebase, OneSignal). + final PushNotificationProvider provider; + + /// The mobile operating system of the device. + final DevicePlatform platform; + + /// The timestamp when this device was first registered. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// The timestamp when this device registration was last updated. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime updatedAt; + + /// Converts this [Device] instance to JSON data. + Map toJson() => _$DeviceToJson(this); + + @override + List get props => [ + id, + userId, + token, + provider, + platform, + createdAt, + updatedAt, + ]; + + /// Creates a copy of this [Device] but with the given fields replaced with + /// the new values. + Device copyWith({ + String? id, + String? userId, + String? token, + PushNotificationProvider? provider, + DevicePlatform? platform, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return Device( + id: id ?? this.id, + userId: userId ?? this.userId, + token: token ?? this.token, + provider: provider ?? this.provider, + platform: platform ?? this.platform, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} From 4e9ef7afbbd5ab4a85e91f28dbbe43157a55a89a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:17:34 +0100 Subject: [PATCH 05/78] feat(notifications): add NotificationPayload model - Define the generic structure of a push notification message - Include properties for title, body, optional image URL, and custom data map - Implement JSON serialization and deserialization - Add copyWith method for easy instance modification --- .../notifications/notification_payload.dart | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 lib/src/models/notifications/notification_payload.dart diff --git a/lib/src/models/notifications/notification_payload.dart b/lib/src/models/notifications/notification_payload.dart new file mode 100644 index 00000000..254bee0a --- /dev/null +++ b/lib/src/models/notifications/notification_payload.dart @@ -0,0 +1,66 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'notification_payload.g.dart'; + +/// {@template notification_payload} +/// Represents the generic structure of a push notification message. +/// +/// This model defines the content of a notification, such as its title and +/// body, and includes a flexible `data` map for custom payloads, typically +/// used for deep-linking or passing additional information to the client app. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NotificationPayload extends Equatable { + /// {@macro notification_payload} + const NotificationPayload({ + required this.title, + required this.body, + required this.data, + this.imageUrl, + }); + + /// Creates a [NotificationPayload] from JSON data. + factory NotificationPayload.fromJson(Map json) => + _$NotificationPayloadFromJson(json); + + /// The title of the notification. + final String title; + + /// The main body text of the notification. + final String body; + + /// An optional URL for an image to be displayed in the notification. + final String? imageUrl; + + /// A map of custom key-value data to be sent with the notification. + /// + /// This is commonly used for deep-linking, allowing the app to navigate to + /// a specific screen or content when the notification is tapped. + /// For example: `{'contentType': 'headline', 'id': 'headline-123'}`. + final Map data; + + /// Converts this [NotificationPayload] instance to JSON data. + Map toJson() => _$NotificationPayloadToJson(this); + + @override + List get props => [title, body, imageUrl, data]; + + /// Creates a copy of this [NotificationPayload] but with the given fields + /// replaced with the new values. + NotificationPayload copyWith({ + String? title, + String? body, + String? imageUrl, + Map? data, + }) { + return NotificationPayload( + title: title ?? this.title, + body: body ?? this.body, + imageUrl: imageUrl ?? this.imageUrl, + data: data ?? this.data, + ); + } +} From 121a962c3432c83e3fc256314fab2beaef0b4fb0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:18:30 +0100 Subject: [PATCH 06/78] feat(models): add NotificationSubscription model - Represents a user's saved notification filter - Stores criteria for topics, sources, and countries - Defines notification preferences for the filter - Includes factory constructor for JSON deserialization - Provides method for JSON serialization - Implements Equatable for value comparison - Includes copyWith method for instance duplication --- .../notification_subscription.dart | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 lib/src/models/notifications/notification_subscription.dart diff --git a/lib/src/models/notifications/notification_subscription.dart b/lib/src/models/notifications/notification_subscription.dart new file mode 100644 index 00000000..423906e4 --- /dev/null +++ b/lib/src/models/notifications/notification_subscription.dart @@ -0,0 +1,96 @@ +import 'package:core/src/enums/subscription_delivery_type.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'notification_subscription.g.dart'; + +/// {@template notification_subscription} +/// Represents a user's saved notification filter. +/// +/// This model stores a named set of criteria, including topics, sources, and +/// countries, allowing users to subscribe to notifications for specific news +/// segments. It also defines which types of notifications the user wants to +/// receive for this filter (e.g., breaking news, daily digests). +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NotificationSubscription extends Equatable { + /// {@macro notification_subscription} + const NotificationSubscription({ + required this.id, + required this.userId, + required this.name, + required this.topics, + required this.sources, + required this.countries, + required this.deliveryTypes, + }); + + /// Creates a [NotificationSubscription] from JSON data. + factory NotificationSubscription.fromJson(Map json) => + _$NotificationSubscriptionFromJson(json); + + /// The unique identifier for the notification subscription. + final String id; + + /// The ID of the user who owns this subscription. + final String userId; + + /// The user-provided name for this saved subscription. + final String name; + + /// A list of topic IDs to include in the filter. + /// An empty list means no topic filter is applied. + final List topics; + + /// A list of source IDs to include in the filter. + /// An empty list means no source filter is applied. + final List sources; + + /// A list of country IDs to include in the filter. + /// An empty list means no country filter is applied. + final List countries; + + /// The set of delivery types the user has opted into for this subscription. + /// + /// For example, a user could choose to receive both `breakingOnly` and + /// `dailyDigest` notifications for the same subscription. + final Set deliveryTypes; + + /// Converts this [NotificationSubscription] instance to JSON data. + Map toJson() => _$NotificationSubscriptionToJson(this); + + @override + List get props => [ + id, + userId, + name, + topics, + sources, + countries, + deliveryTypes, + ]; + + /// Creates a copy of this [NotificationSubscription] but with the given + /// fields replaced with the new values. + NotificationSubscription copyWith({ + String? id, + String? userId, + String? name, + List? topics, + List? sources, + List? countries, + Set? deliveryTypes, + }) { + return NotificationSubscription( + id: id ?? this.id, + userId: userId ?? this.userId, + name: name ?? this.name, + topics: topics ?? this.topics, + sources: sources ?? this.sources, + countries: countries ?? this.countries, + deliveryTypes: deliveryTypes ?? this.deliveryTypes, + ); + } +} From 8dc89ba62e0d9f7c550c62f046af7d40f9d99536 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:21:12 +0100 Subject: [PATCH 07/78] feat(headline): add isBreaking flag to Headline model - Add isBreaking property to Headline class - Update props list to include isBreaking - Modify copyWith method to support isBreaking - Add documentation for the new isBreaking flag --- lib/src/models/entities/headline.dart | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index b8a9f367..db84510b 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -30,6 +30,7 @@ class Headline extends FeedItem { required this.createdAt, required this.updatedAt, required this.status, + required this.isBreaking, }) : super(type: 'headline'); /// Creates a [Headline] instance from a JSON map. @@ -73,6 +74,12 @@ class Headline extends FeedItem { /// ensuring backward compatibility. This is suitable for ingested content. final ContentStatus status; + /// A flag indicating whether this headline is considered "breaking news". + /// + /// This is editorially controlled and used to trigger immediate push + /// notifications for users subscribed to `breakingOnly` alerts. + final bool isBreaking; + /// Topic of the current headline. final Topic topic; @@ -85,19 +92,20 @@ class Headline extends FeedItem { @override List get props => [ - id, - title, - excerpt, - url, - imageUrl, - createdAt, - updatedAt, - status, - source, - eventCountry, - topic, - type, - ]; + id, + title, + excerpt, + url, + imageUrl, + createdAt, + updatedAt, + status, + source, + eventCountry, + topic, + isBreaking, + type, + ]; @override bool get stringify => true; @@ -116,6 +124,7 @@ class Headline extends FeedItem { Source? source, Country? eventCountry, Topic? topic, + bool? isBreaking, }) { return Headline( id: id ?? this.id, @@ -129,6 +138,7 @@ class Headline extends FeedItem { source: source ?? this.source, eventCountry: eventCountry ?? this.eventCountry, topic: topic ?? this.topic, + isBreaking: isBreaking ?? this.isBreaking, ); } } From 0cb6b7e6e10532cf5b561972663e5d385c3e3c72 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:21:53 +0100 Subject: [PATCH 08/78] feat(config): add push notification configuration model - Define PushNotificationConfig model for remote configuration of push notifications - Include properties for enabling/disabling notifications and setting primary provider - Add methods for JSON serialization and copying with updated values --- .../config/push_notification_config.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/src/models/config/push_notification_config.dart diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart new file mode 100644 index 00000000..0e15e37b --- /dev/null +++ b/lib/src/models/config/push_notification_config.dart @@ -0,0 +1,58 @@ +import 'package:core/src/enums/device_platform.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'push_notification_config.g.dart'; + +/// {@template push_notification_config} +/// Defines the global configuration for the push notification system. +/// +/// This model is part of the overall `RemoteConfig` and allows for remotely +/// enabling or disabling the entire notification feature and setting the +/// primary service provider. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class PushNotificationConfig extends Equatable { + /// {@macro push_notification_config} + const PushNotificationConfig({ + required this.enabled, + required this.primaryProvider, + }); + + /// Creates a [PushNotificationConfig] from JSON data. + factory PushNotificationConfig.fromJson(Map json) => + _$PushNotificationConfigFromJson(json); + + /// A global switch to enable or disable the entire push notification system. + /// + /// If `false`, no notification-related logic should be executed by clients + /// or the backend. + final bool enabled; + + /// The primary push notification service provider to be used by the system. + /// + /// This allows for dynamically switching between providers like Firebase + /// and OneSignal without requiring a client update. + final PushNotificationProvider primaryProvider; + + /// Converts this [PushNotificationConfig] instance to JSON data. + Map toJson() => _$PushNotificationConfigToJson(this); + + @override + List get props => [enabled, primaryProvider]; + + /// Creates a copy of this [PushNotificationConfig] but with the given fields + /// replaced with the new values. + PushNotificationConfig copyWith({ + bool? enabled, + PushNotificationProvider? primaryProvider, + }) { + return PushNotificationConfig( + enabled: enabled ?? this.enabled, + primaryProvider: primaryProvider ?? this.primaryProvider, + ); + } +} From c4aa87dc78f0c64ffcf24b9a8e2d9671541fb5ff Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:24:58 +0100 Subject: [PATCH 09/78] refactor(push_notification_config): remove unused import - Remove unused import statement for 'DevicePlatform' enum --- lib/src/models/config/push_notification_config.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index 0e15e37b..37e0eb64 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -1,4 +1,3 @@ -import 'package:core/src/enums/device_platform.dart'; import 'package:core/src/enums/push_notification_provider.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; From 556a1a4d138499fcab3377938eab128380cdb301 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:26:05 +0100 Subject: [PATCH 10/78] feat(remote_config): add push notification config and optimize imports - Add PushNotificationConfig to RemoteConfig model - Include core package instead of individual imports - Update RemoteConfig constructor and copyWith method - Modify props list to include new pushNotificationConfig field --- lib/src/models/config/remote_config.dart | 37 ++++++++++++++---------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 61e03777..ac06a345 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,9 +1,6 @@ -import 'package:core/src/enums/feed_decorator_type.dart'; -import 'package:core/src/models/config/ad_config.dart'; -import 'package:core/src/models/config/app_status.dart'; -import 'package:core/src/models/config/feed_decorator_config.dart'; -import 'package:core/src/models/config/user_preference_config.dart'; -import 'package:core/src/utils/json_helpers.dart'; + +import 'package:core/core.dart'; +import 'package:core/src/models/config/push_notification_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -19,12 +16,14 @@ part 'remote_config.g.dart'; /// identified by a fixed ID (e.g., 'app_config'). @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class RemoteConfig extends Equatable { + /// Creates a new [RemoteConfig] instance. const RemoteConfig({ required this.id, required this.userPreferenceConfig, required this.adConfig, required this.feedDecoratorConfig, required this.appStatus, + required this.pushNotificationConfig, required this.createdAt, required this.updatedAt, }); @@ -45,9 +44,13 @@ class RemoteConfig extends Equatable { /// Defines configuration settings for all feed decorators. final Map feedDecoratorConfig; - /// Defines configuration settings related to the overall application status (maintenance, updates). + /// Defines configuration settings related to the overall application status + /// (maintenance, updates). final AppStatus appStatus; + /// Defines the global configuration for the push notification system. + final PushNotificationConfig pushNotificationConfig; + /// The creation timestamp of the remote config. @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) final DateTime createdAt; @@ -66,6 +69,7 @@ class RemoteConfig extends Equatable { AdConfig? adConfig, Map? feedDecoratorConfig, AppStatus? appStatus, + PushNotificationConfig? pushNotificationConfig, DateTime? createdAt, DateTime? updatedAt, }) { @@ -75,6 +79,8 @@ class RemoteConfig extends Equatable { adConfig: adConfig ?? this.adConfig, feedDecoratorConfig: feedDecoratorConfig ?? this.feedDecoratorConfig, appStatus: appStatus ?? this.appStatus, + pushNotificationConfig: + pushNotificationConfig ?? this.pushNotificationConfig, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); @@ -82,14 +88,15 @@ class RemoteConfig extends Equatable { @override List get props => [ - id, - userPreferenceConfig, - adConfig, - feedDecoratorConfig, - appStatus, - createdAt, - updatedAt, - ]; + id, + userPreferenceConfig, + adConfig, + feedDecoratorConfig, + appStatus, + pushNotificationConfig, + createdAt, + updatedAt, + ]; @override bool get stringify => true; From df6a9e8be6a4200e9e34a3f34fdb89adf8df400d Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:26:24 +0100 Subject: [PATCH 11/78] docs(CHANGELOG): add entry for push notification data models - Add information about new data models for push notification system - Update changelog with upcoming features and breaking changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 108596ed..367d1259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Upcoming +- **feat**: add data models for push notification system. - **BREAKING** feat!: Deprecate and remove `LocalAd` model and related fixtures. - **feat**: Add 10 new user fixtures, including publishers and standard users. - **chore**: Expand user-related fixtures with detailed settings and preferences. From 85d725ab0c532cd6b4006e7bca8ec3f7d72ee113 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 08:26:38 +0100 Subject: [PATCH 12/78] docs(README): add push notification models and configurations - Add models for push notifications and subscriptions - Include PushNotificationConfig in Application Configuration - Update documentation to reflect new features --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index fc18d5d1..bc8f17bb 100644 --- a/README.md +++ b/README.md @@ -27,12 +27,18 @@ This package provides the critical building blocks for a professional news appli - **`User`, `AppUserRole`, `DashboardUserRole`, `Permission`:** Robust models for user profiles, roles, and permissions, enabling secure and personalized experiences. - **`UserContentPreferences`, `UserAppSettings`:** Detailed models for storing user-specific content preferences (e.g., followed topics, saved headlines) and application settings (e.g., theme, language). +### 📲 Push Notifications & Subscriptions +- **`NotificationSubscription`:** A model for storing user-defined notification subscriptions. +- **`Device`:** Represents a user's device registered for push notifications. +- **`NotificationPayload`:** Defines the structure for a push notification message. + ### 💾 User Presets - **`SavedFilter`:** A model for storing user-defined filter combinations. ### ⚙️ Application Configuration - **`RemoteConfig`:** A central container for all dynamic application settings, fetched from a remote source. This includes: - **`AdConfig`:** Master configuration for all advertising, now featuring **highly flexible, role-based control** over ad visibility and frequency for feed, article, and interstitial ads. This allows for granular control over monetization strategies per user segment. + - **`PushNotificationConfig`:** Manages global settings for the push notification system. - **`UserPreferenceConfig`:** Defines user preference limits (e.g., max followed items, saved headlines) tiered by user role. - **`AppStatus`:** Manages application-wide status, including maintenance mode and force update directives. - **`FeedDecoratorConfig`:** Configures dynamic in-feed elements like calls-to-action and content collections, with role-based visibility. From 75ff00b1c032c925eb7626cd4d5fab7dcb69eb9c Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:12:55 +0100 Subject: [PATCH 13/78] feat(config): add PushNotificationProviderConfig base class - Introduce an abstract class for push notification provider configurations - Implement polymorphic deserialization using a factory constructor - Support Firebase and OneSignal provider configurations - Include providerName as a discriminator field for JSON serialization --- .../push_notification_provider_config.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/src/models/config/push_notification_provider_config.dart diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart new file mode 100644 index 00000000..58e6ed4d --- /dev/null +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -0,0 +1,58 @@ +import 'package:core/src/enums/enums.dart'; +import 'package:core/src/models/remote_config/firebase_provider_config.dart'; +import 'package:core/src/models/remote_config/one_signal_provider_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +/// {@template push_notification_provider_config} +/// An abstract base class for push notification provider configurations. +/// +/// This class uses a factory constructor to enable polymorphic deserialization, +/// allowing for different concrete configuration models (e.g., +/// [FirebaseProviderConfig], [OneSignalProviderConfig]) based on a +/// `providerName` discriminator field in the JSON data. +/// {@endtemplate} +@JsonSerializable(createToJson: false, checked: true) +abstract class PushNotificationProviderConfig extends Equatable { + /// {@macro push_notification_provider_config} + const PushNotificationProviderConfig({required this.providerName}); + + /// Creates a [PushNotificationProviderConfig] instance from a JSON map. + /// + /// This factory uses the `providerName` field in the JSON map to dispatch + /// to the correct concrete `fromJson` constructor. + /// + /// Throws [FormatException] if the `providerName` field is missing or unknown. + factory PushNotificationProviderConfig.fromJson(Map json) { + final providerName = $enumDecodeNullable( + PushNotificationProvider.values, + json['providerName'], + ); + + if (providerName == null) { + throw const FormatException( + 'Missing or unknown "providerName" in PushNotificationProviderConfig JSON.', + ); + } + + switch (providerName) { + case PushNotificationProvider.firebase: + return FirebaseProviderConfig.fromJson(json); + case PushNotificationProvider.oneSignal: + return OneSignalProviderConfig.fromJson(json); + } + } + + /// The name of the provider, used as a discriminator for deserialization. + @JsonKey(includeToJson: true) + final PushNotificationProvider providerName; + + /// Converts this instance to JSON data. + /// + /// Concrete implementations are responsible for providing the full JSON + /// representation, including the `providerName` field. + Map toJson(); + + @override + List get props => [providerName]; +} From 01e670ccab75b2c9581d05fd142f614cf9aff4c4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:13:06 +0100 Subject: [PATCH 14/78] feat(core): add firebase provider config model - Create FirebaseProviderConfig class for Firebase Cloud Messaging credentials - Extend PushNotificationProviderConfig - Implement JSON serialization and deserialization - Include projectId, clientEmail, and privateKey fields - Add copyWith method for easy instance duplication --- .../config/firebase_provider_config.dart | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 lib/src/models/config/firebase_provider_config.dart diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart new file mode 100644 index 00000000..d5fff193 --- /dev/null +++ b/lib/src/models/config/firebase_provider_config.dart @@ -0,0 +1,55 @@ +import 'package:core/src/enums/enums.dart'; +import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part '../../../../firebase_provider_config.g.dart'; + +/// {@template firebase_provider_config} +/// A concrete implementation of [PushNotificationProviderConfig] for Firebase. +/// +/// This model holds the specific credentials required for the backend to +/// authenticate with Firebase Cloud Messaging (FCM) using a service account. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FirebaseProviderConfig extends PushNotificationProviderConfig { + /// {@macro firebase_provider_config} + const FirebaseProviderConfig({ + required this.projectId, + required this.clientEmail, + required this.privateKey, + }) : super(providerName: PushNotificationProvider.firebase); + + /// Creates a [FirebaseProviderConfig] from JSON data. + factory FirebaseProviderConfig.fromJson(Map json) => + _$FirebaseProviderConfigFromJson(json); + + /// The project ID from the Firebase service account credentials. + final String projectId; + + /// The client email from the Firebase service account credentials. + final String clientEmail; + + /// The private key from the Firebase service account credentials. + final String privateKey; + + @override + Map toJson() => _$FirebaseProviderConfigToJson(this); + + @override + List get props => [providerName, projectId, clientEmail, privateKey]; + + /// Creates a copy of this instance with the given fields replaced. + FirebaseProviderConfig copyWith({ + String? projectId, + String? clientEmail, + String? privateKey, + }) { + return FirebaseProviderConfig( + projectId: projectId ?? this.projectId, + clientEmail: clientEmail ?? this.clientEmail, + privateKey: privateKey ?? this.privateKey, + ); + } +} From 72c0908b7a44421c0b0be5679e6e8c0a612ac00b Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:13:20 +0100 Subject: [PATCH 15/78] feat(config): add OneSignalProviderConfig model - Create a new model for OneSignal push notification provider configuration - Implement JSON serialization and deserialization - Include appId and restApiKey fields for OneSignal credentials - Extend PushNotificationProviderConfig with specific OneSignal implementation --- .../config/one_signal_provider_config.dart | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 lib/src/models/config/one_signal_provider_config.dart diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart new file mode 100644 index 00000000..28d30e04 --- /dev/null +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -0,0 +1,44 @@ +import 'package:core/src/enums/enums.dart'; +import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part '../../../../one_signal_provider_config.g.dart'; + +/// {@template one_signal_provider_config} +/// A concrete implementation of [PushNotificationProviderConfig] for OneSignal. +/// +/// This model holds the specific credentials required for the backend to +/// authenticate with the OneSignal REST API. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class OneSignalProviderConfig extends PushNotificationProviderConfig { + /// {@macro one_signal_provider_config} + const OneSignalProviderConfig({required this.appId, required this.restApiKey}) + : super(providerName: PushNotificationProvider.oneSignal); + + /// Creates a [OneSignalProviderConfig] from JSON data. + factory OneSignalProviderConfig.fromJson(Map json) => + _$OneSignalProviderConfigFromJson(json); + + /// The OneSignal App ID for your application. + final String appId; + + /// The OneSignal REST API Key for server-side operations. + final String restApiKey; + + @override + Map toJson() => _$OneSignalProviderConfigToJson(this); + + @override + List get props => [providerName, appId, restApiKey]; + + /// Creates a copy of this instance with the given fields replaced. + OneSignalProviderConfig copyWith({String? appId, String? restApiKey}) { + return OneSignalProviderConfig( + appId: appId ?? this.appId, + restApiKey: restApiKey ?? this.restApiKey, + ); + } +} From 0fb86d8d4a859bcecd833871949a624ec441e2bf Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:13:38 +0100 Subject: [PATCH 16/78] feat(models): add push notification delivery role config model - Create new model for role-specific push notification delivery configuration - Include subscription limit as a configurable parameter - Implement Equatable for value comparison and JSON serialization --- ...ush_notification_delivery_role_config.dart | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/src/models/config/push_notification_delivery_role_config.dart diff --git a/lib/src/models/config/push_notification_delivery_role_config.dart b/lib/src/models/config/push_notification_delivery_role_config.dart new file mode 100644 index 00000000..a7700847 --- /dev/null +++ b/lib/src/models/config/push_notification_delivery_role_config.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part '../../../../push_notification_delivery_role_config.g.dart'; + +/// {@template push_notification_delivery_role_config} +/// Defines the role-specific settings for a single push notification delivery type. +/// +/// This model holds the configuration for a specific user role (e.g., +/// 'standardUser') in relation to a specific delivery type (e.g., +/// 'dailyDigest'). It primarily defines the maximum number of subscriptions +/// of that type the user is allowed to create. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class PushNotificationDeliveryRoleConfig extends Equatable { + /// {@macro push_notification_delivery_role_config} + const PushNotificationDeliveryRoleConfig({required this.subscriptionLimit}); + + /// Creates a [PushNotificationDeliveryRoleConfig] from JSON data. + factory PushNotificationDeliveryRoleConfig.fromJson(Map json) => + _$PushNotificationDeliveryRoleConfigFromJson(json); + + /// The maximum number of subscriptions of this specific delivery type that + /// a user with this role is allowed to create. + final int subscriptionLimit; + + /// Converts this instance to JSON data. + Map toJson() => _$PushNotificationDeliveryRoleConfigToJson(this); + + @override + List get props => [subscriptionLimit]; + + /// Creates a copy of this instance with the given fields replaced. + PushNotificationDeliveryRoleConfig copyWith({int? subscriptionLimit}) { + return PushNotificationDeliveryRoleConfig( + subscriptionLimit: subscriptionLimit ?? this.subscriptionLimit, + ); + } +} From 3a8999a91e7b80d5f56f3a1985411982cfe62880 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:13:50 +0100 Subject: [PATCH 17/78] feat(core): add push notification delivery config model - Define a new model for push notification delivery configuration - Include visibility and limits based on user roles - Support JSON serialization and deserialization - Add Equatable mixin for value comparison --- .../push_notification_delivery_config.dart | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/src/models/config/push_notification_delivery_config.dart diff --git a/lib/src/models/config/push_notification_delivery_config.dart b/lib/src/models/config/push_notification_delivery_config.dart new file mode 100644 index 00000000..354bd219 --- /dev/null +++ b/lib/src/models/config/push_notification_delivery_config.dart @@ -0,0 +1,46 @@ +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/models/remote_config/notification_delivery_role_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part '../../../../push_notification_delivery_config.g.dart'; + +/// {@template push_notification_delivery_config} +/// Defines the configuration for a single push notification delivery type, such as +/// 'dailyDigest' or 'breakingOnly'. +/// +/// This model uses a `visibleTo` map to specify which user roles can see and +/// use this delivery type, and what their specific limits are. If a role is +/// not present in the map, that delivery type will be unavailable to them. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class PushNotificationDeliveryConfig extends Equatable { + /// {@macro push_notification_delivery_config} + const PushNotificationDeliveryConfig({required this.visibleTo}); + + /// Creates a [PushNotificationDeliveryConfig] from JSON data. + factory PushNotificationDeliveryConfig.fromJson(Map json) => + _$PushNotificationDeliveryConfigFromJson(json); + + /// A map that defines the visibility and limits for this delivery type + /// based on user roles. + /// + /// The key is the [AppUserRole], and the value is the role-specific + /// configuration, including subscription limits. + final Map visibleTo; + + /// Converts this instance to JSON data. + Map toJson() => _$PushNotificationDeliveryConfigToJson(this); + + @override + List get props => [visibleTo]; + + /// Creates a copy of this instance with the given fields replaced. + PushNotificationDeliveryConfig copyWith({ + Map? visibleTo, + }) { + return PushNotificationDeliveryConfig(visibleTo: visibleTo ?? this.visibleTo); + } +} From 53faeb9c00358d956f4fef493c929f377c5fee3c Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:14:13 +0100 Subject: [PATCH 18/78] feat(config): enhance push notification config model - Add providerConfigs and deliveryConfigs to PushNotificationConfig - Update imports to include new model classes - Expand class documentation to reflect new capabilities - Modify props to include new fields for equality comparison - Update copyWith method to support new configuration options --- .../config/push_notification_config.dart | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index 37e0eb64..f01cc605 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -1,4 +1,6 @@ -import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/enums/enums.dart'; +import 'package:core/src/models/remote_config/notification_delivery_config.dart'; +import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -9,8 +11,8 @@ part 'push_notification_config.g.dart'; /// Defines the global configuration for the push notification system. /// /// This model is part of the overall `RemoteConfig` and allows for remotely -/// enabling or disabling the entire notification feature and setting the -/// primary service provider. +/// managing all aspects of push notifications, including provider credentials, +/// feature availability, and user-specific limits. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) @@ -19,6 +21,8 @@ class PushNotificationConfig extends Equatable { const PushNotificationConfig({ required this.enabled, required this.primaryProvider, + required this.providerConfigs, + required this.deliveryConfigs, }); /// Creates a [PushNotificationConfig] from JSON data. @@ -28,30 +32,50 @@ class PushNotificationConfig extends Equatable { /// A global switch to enable or disable the entire push notification system. /// /// If `false`, no notification-related logic should be executed by clients - /// or the backend. final bool enabled; /// The primary push notification service provider to be used by the system. /// /// This allows for dynamically switching between providers like Firebase - /// and OneSignal without requiring a client update. final PushNotificationProvider primaryProvider; + /// A map holding the credentials for each potential push provider. + /// + /// The key is the provider type, and the value is the corresponding + /// polymorphic configuration object (e.g., [FirebaseProviderConfig]). + final Map + providerConfigs; + + /// A map to globally enable or disable each specific notification type + /// and define its role-based limits using the `visibleTo` pattern. + final Map + deliveryConfigs; + /// Converts this [PushNotificationConfig] instance to JSON data. Map toJson() => _$PushNotificationConfigToJson(this); @override - List get props => [enabled, primaryProvider]; + List get props => [ + enabled, + primaryProvider, + providerConfigs, + deliveryConfigs, + ]; /// Creates a copy of this [PushNotificationConfig] but with the given fields /// replaced with the new values. PushNotificationConfig copyWith({ bool? enabled, PushNotificationProvider? primaryProvider, + Map? + providerConfigs, + Map? deliveryConfigs, }) { return PushNotificationConfig( enabled: enabled ?? this.enabled, primaryProvider: primaryProvider ?? this.primaryProvider, + providerConfigs: providerConfigs ?? this.providerConfigs, + deliveryConfigs: deliveryConfigs ?? this.deliveryConfigs, ); } } From 600f91e6aedd2a1d2eab5f2a22e893bc206cdf90 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:14:42 +0100 Subject: [PATCH 19/78] refactor(RemoteConfig): reorder class properties - Adjusted the order of properties in the RemoteConfig class - Moved 'appStatus' below 'userPreferenceConfig' for better logical flow - Updated the 'props' list to match the new property order --- lib/src/models/config/remote_config.dart | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index ac06a345..611c4986 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,4 +1,3 @@ - import 'package:core/core.dart'; import 'package:core/src/models/config/push_notification_config.dart'; import 'package:equatable/equatable.dart'; @@ -19,10 +18,10 @@ class RemoteConfig extends Equatable { /// Creates a new [RemoteConfig] instance. const RemoteConfig({ required this.id, - required this.userPreferenceConfig, + required this.appStatus, required this.adConfig, required this.feedDecoratorConfig, - required this.appStatus, + required this.userPreferenceConfig, required this.pushNotificationConfig, required this.createdAt, required this.updatedAt, @@ -88,15 +87,15 @@ class RemoteConfig extends Equatable { @override List get props => [ - id, - userPreferenceConfig, - adConfig, - feedDecoratorConfig, - appStatus, - pushNotificationConfig, - createdAt, - updatedAt, - ]; + id, + userPreferenceConfig, + adConfig, + feedDecoratorConfig, + appStatus, + pushNotificationConfig, + createdAt, + updatedAt, + ]; @override bool get stringify => true; From 471b588d2e9d198adf89d2ad06d59ca9d90a8179 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:15:21 +0100 Subject: [PATCH 20/78] feat(auth): add push notification subscriptions to User model - Import necessary enums and models for push notifications - Add pushNotificationSubscriptions field to User class - Implement JSON serialization and deserialization for pushNotificationSubscriptions - Update User copyWith method to include pushNotificationSubscriptions - Add helper functions for handling push notification subscriptions map --- lib/src/models/auth/user.dart | 54 +++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index 5acf2c9c..4f8138d5 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -1,6 +1,8 @@ import 'package:core/src/enums/app_user_role.dart'; import 'package:core/src/enums/dashboard_user_role.dart'; import 'package:core/src/enums/feed_decorator_type.dart'; +import 'package:core/src/enums/notifications.dart'; +import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:core/src/models/auth/user_feed_decorator_status.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -18,6 +20,9 @@ class User extends Equatable { /// [email], and [createdAt]. The [feedDecoratorStatus] tracks user /// interactions with in-feed actions and is guaranteed to be complete for /// all [FeedDecoratorType] values. + /// + /// The [pushNotificationSubscriptions] map holds the user's current + /// notification subscription status. const User({ required this.id, required this.appRole, @@ -25,6 +30,7 @@ class User extends Equatable { required this.email, required this.createdAt, required this.feedDecoratorStatus, + required this.pushNotificationSubscriptions, }); /// Creates a User from JSON data. @@ -60,6 +66,14 @@ class User extends Equatable { ) final Map feedDecoratorStatus; + /// A map defining the user's subscription status for each notification type. + @JsonKey( + fromJson: _pushNotificationSubscriptionsFromJson, + toJson: _pushNotificationSubscriptionsToJson, + ) + final Map + pushNotificationSubscriptions; + /// Converts this User instance to JSON data. Map toJson() => _$UserToJson(this); @@ -71,6 +85,7 @@ class User extends Equatable { dashboardRole, createdAt, feedDecoratorStatus, + pushNotificationSubscriptions, ]; @override @@ -89,6 +104,8 @@ class User extends Equatable { DashboardUserRole? dashboardRole, DateTime? createdAt, Map? feedDecoratorStatus, + Map? + pushNotificationSubscriptions, }) { return User( id: id ?? this.id, @@ -97,6 +114,8 @@ class User extends Equatable { dashboardRole: dashboardRole ?? this.dashboardRole, createdAt: createdAt ?? this.createdAt, feedDecoratorStatus: feedDecoratorStatus ?? this.feedDecoratorStatus, + pushNotificationSubscriptions: + pushNotificationSubscriptions ?? this.pushNotificationSubscriptions, ); } } @@ -142,3 +161,38 @@ Map _feedDecoratorStatusToJson( (key, value) => MapEntry(key.name, value.toJson()), ); } + +/// Deserializes the push notification subscriptions map from JSON and ensures +/// it's complete. +Map + _pushNotificationSubscriptionsFromJson( + Map json, +) { + final existingSubscriptions = json.map((key, value) { + final deliveryType = SubscriptionDeliveryType.values.byName(key); + return MapEntry( + deliveryType, + PushNotificationSubscription.fromJson(value as Map), + ); + }); + + return Map.fromEntries( + SubscriptionDeliveryType.values.map( + (type) => MapEntry( + type, + existingSubscriptions[type] ?? + PushNotificationSubscription( + isEnabled: false, + subscribesTo: type, + ), + ), + ), + ); +} + +/// Serializes the push notification subscriptions map to JSON. +Map _pushNotificationSubscriptionsToJson( + Map subscriptions, +) { + return subscriptions.map((key, value) => MapEntry(key.name, value.toJson())); +} From 323ee6237ace74e9722f3843b360562807cf9f82 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:16:01 +0100 Subject: [PATCH 21/78] feat(user_preferences): add notification subscriptions to UserContentPreferences - Import necessary packages for notifications and push subscriptions - Add notificationSubscriptions field to UserContentPreferences class - Update constructor, toJson, and copyWith methods to include new field - Add documentation for the new notificationSubscriptions field --- .../user_preferences/user_content_preferences.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/src/models/user_preferences/user_content_preferences.dart b/lib/src/models/user_preferences/user_content_preferences.dart index d93d3b0b..eed8abbe 100644 --- a/lib/src/models/user_preferences/user_content_preferences.dart +++ b/lib/src/models/user_preferences/user_content_preferences.dart @@ -1,7 +1,9 @@ +import 'package:core/src/enums/notifications.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/headline.dart'; import 'package:core/src/models/entities/source.dart'; import 'package:core/src/models/entities/topic.dart'; +import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:core/src/models/user_presets/saved_filter.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -34,6 +36,7 @@ class UserContentPreferences extends Equatable { required this.followedTopics, required this.savedHeadlines, required this.savedFilters, + required this.notificationSubscriptions, }); /// Factory method to create a [UserContentPreferences] instance from a JSON map. @@ -58,6 +61,10 @@ class UserContentPreferences extends Equatable { /// List of filter combinations the user has saved. final List savedFilters; + /// A map defining the user's subscription status for each notification type. + final Map + notificationSubscriptions; + /// Converts this [UserContentPreferences] instance to a JSON map. Map toJson() => _$UserContentPreferencesToJson(this); @@ -69,6 +76,7 @@ class UserContentPreferences extends Equatable { followedTopics, savedHeadlines, savedFilters, + notificationSubscriptions, ]; @override @@ -83,6 +91,8 @@ class UserContentPreferences extends Equatable { List? followedTopics, List? savedHeadlines, List? savedFilters, + Map? + notificationSubscriptions, }) { return UserContentPreferences( id: id ?? this.id, @@ -91,6 +101,8 @@ class UserContentPreferences extends Equatable { followedTopics: followedTopics ?? this.followedTopics, savedHeadlines: savedHeadlines ?? this.savedHeadlines, savedFilters: savedFilters ?? this.savedFilters, + notificationSubscriptions: + notificationSubscriptions ?? this.notificationSubscriptions, ); } } From dc885487a948d1cc9c2cd748093fdee17e1fb879 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:19:55 +0100 Subject: [PATCH 22/78] chore: file relocation --- .../models/config/push_notification_delivery_config.dart | 6 +++--- .../config/push_notification_delivery_role_config.dart | 2 +- .../models/config/push_notification_provider_config.dart | 6 +++--- .../{notifications => push_notifications}/device.dart | 2 +- .../notification_payload.dart | 2 +- .../notification_subscription.dart | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename lib/src/models/{notifications => push_notifications}/device.dart (98%) rename lib/src/models/{notifications => push_notifications}/notification_payload.dart (97%) rename lib/src/models/{notifications => push_notifications}/notification_subscription.dart (98%) diff --git a/lib/src/models/config/push_notification_delivery_config.dart b/lib/src/models/config/push_notification_delivery_config.dart index 354bd219..745fa968 100644 --- a/lib/src/models/config/push_notification_delivery_config.dart +++ b/lib/src/models/config/push_notification_delivery_config.dart @@ -1,10 +1,10 @@ import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/models/remote_config/notification_delivery_role_config.dart'; +import 'package:core/src/models/config/push_notification_delivery_role_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../../../../push_notification_delivery_config.g.dart'; +part 'push_notification_delivery_config.g.dart'; /// {@template push_notification_delivery_config} /// Defines the configuration for a single push notification delivery type, such as @@ -29,7 +29,7 @@ class PushNotificationDeliveryConfig extends Equatable { /// /// The key is the [AppUserRole], and the value is the role-specific /// configuration, including subscription limits. - final Map visibleTo; + final Map visibleTo; /// Converts this instance to JSON data. Map toJson() => _$PushNotificationDeliveryConfigToJson(this); diff --git a/lib/src/models/config/push_notification_delivery_role_config.dart b/lib/src/models/config/push_notification_delivery_role_config.dart index a7700847..18ce4e46 100644 --- a/lib/src/models/config/push_notification_delivery_role_config.dart +++ b/lib/src/models/config/push_notification_delivery_role_config.dart @@ -2,7 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../../../../push_notification_delivery_role_config.g.dart'; +part 'push_notification_delivery_role_config.g.dart'; /// {@template push_notification_delivery_role_config} /// Defines the role-specific settings for a single push notification delivery type. diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index 58e6ed4d..bbd7b0e4 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -1,6 +1,6 @@ -import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/remote_config/firebase_provider_config.dart'; -import 'package:core/src/models/remote_config/one_signal_provider_config.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/models/config/firebase_provider_config.dart'; +import 'package:core/src/models/config/one_signal_provider_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/src/models/notifications/device.dart b/lib/src/models/push_notifications/device.dart similarity index 98% rename from lib/src/models/notifications/device.dart rename to lib/src/models/push_notifications/device.dart index ac3b6d11..62031ad7 100644 --- a/lib/src/models/notifications/device.dart +++ b/lib/src/models/push_notifications/device.dart @@ -5,7 +5,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'device.g.dart'; +part '../notifications/device.g.dart'; /// {@template device} /// Represents a user's device that is registered for push notifications. diff --git a/lib/src/models/notifications/notification_payload.dart b/lib/src/models/push_notifications/notification_payload.dart similarity index 97% rename from lib/src/models/notifications/notification_payload.dart rename to lib/src/models/push_notifications/notification_payload.dart index 254bee0a..7df53e6f 100644 --- a/lib/src/models/notifications/notification_payload.dart +++ b/lib/src/models/push_notifications/notification_payload.dart @@ -2,7 +2,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'notification_payload.g.dart'; +part '../notifications/notification_payload.g.dart'; /// {@template notification_payload} /// Represents the generic structure of a push notification message. diff --git a/lib/src/models/notifications/notification_subscription.dart b/lib/src/models/push_notifications/notification_subscription.dart similarity index 98% rename from lib/src/models/notifications/notification_subscription.dart rename to lib/src/models/push_notifications/notification_subscription.dart index 423906e4..596e6caf 100644 --- a/lib/src/models/notifications/notification_subscription.dart +++ b/lib/src/models/push_notifications/notification_subscription.dart @@ -3,7 +3,7 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'notification_subscription.g.dart'; +part '../notifications/notification_subscription.g.dart'; /// {@template notification_subscription} /// Represents a user's saved notification filter. From 7917750dca8190a1f63631ea1e915f232ceb761a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:20:50 +0100 Subject: [PATCH 23/78] chore: file rename --- ...ad.dart => push_notification_payload.dart} | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) rename lib/src/models/push_notifications/{notification_payload.dart => push_notification_payload.dart} (69%) diff --git a/lib/src/models/push_notifications/notification_payload.dart b/lib/src/models/push_notifications/push_notification_payload.dart similarity index 69% rename from lib/src/models/push_notifications/notification_payload.dart rename to lib/src/models/push_notifications/push_notification_payload.dart index 7df53e6f..f5b201a5 100644 --- a/lib/src/models/push_notifications/notification_payload.dart +++ b/lib/src/models/push_notifications/push_notification_payload.dart @@ -2,9 +2,9 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../notifications/notification_payload.g.dart'; +part 'push_notification_payload.g.dart'; -/// {@template notification_payload} +/// {@template push_notification_payload} /// Represents the generic structure of a push notification message. /// /// This model defines the content of a notification, such as its title and @@ -13,18 +13,18 @@ part '../notifications/notification_payload.g.dart'; /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class NotificationPayload extends Equatable { - /// {@macro notification_payload} - const NotificationPayload({ +class PushNotificationPayload extends Equatable { + /// {@macro push_notification_payload} + const PushNotificationPayload({ required this.title, required this.body, required this.data, this.imageUrl, }); - /// Creates a [NotificationPayload] from JSON data. - factory NotificationPayload.fromJson(Map json) => - _$NotificationPayloadFromJson(json); + /// Creates a [PushNotificationPayload] from JSON data. + factory PushNotificationPayload.fromJson(Map json) => + _$PushNotificationPayloadFromJson(json); /// The title of the notification. final String title; @@ -42,21 +42,21 @@ class NotificationPayload extends Equatable { /// For example: `{'contentType': 'headline', 'id': 'headline-123'}`. final Map data; - /// Converts this [NotificationPayload] instance to JSON data. - Map toJson() => _$NotificationPayloadToJson(this); + /// Converts this [PushNotificationPayload] instance to JSON data. + Map toJson() => _$PushNotificationPayloadToJson(this); @override List get props => [title, body, imageUrl, data]; - /// Creates a copy of this [NotificationPayload] but with the given fields + /// Creates a copy of this [PushNotificationPayload] but with the given fields /// replaced with the new values. - NotificationPayload copyWith({ + PushNotificationPayload copyWith({ String? title, String? body, String? imageUrl, Map? data, }) { - return NotificationPayload( + return PushNotificationPayload( title: title ?? this.title, body: body ?? this.body, imageUrl: imageUrl ?? this.imageUrl, From aed900041c0aab11c2f4941235cb2b8cef73af54 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:22:07 +0100 Subject: [PATCH 24/78] chore: file rename --- ...rt => push_notification_subscription.dart} | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) rename lib/src/models/push_notifications/{notification_subscription.dart => push_notification_subscription.dart} (76%) diff --git a/lib/src/models/push_notifications/notification_subscription.dart b/lib/src/models/push_notifications/push_notification_subscription.dart similarity index 76% rename from lib/src/models/push_notifications/notification_subscription.dart rename to lib/src/models/push_notifications/push_notification_subscription.dart index 596e6caf..a35ed757 100644 --- a/lib/src/models/push_notifications/notification_subscription.dart +++ b/lib/src/models/push_notifications/push_notification_subscription.dart @@ -3,9 +3,9 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../notifications/notification_subscription.g.dart'; +part 'push_notification_subscription.g.dart'; -/// {@template notification_subscription} +/// {@template push_notification_subscription} /// Represents a user's saved notification filter. /// /// This model stores a named set of criteria, including topics, sources, and @@ -15,9 +15,9 @@ part '../notifications/notification_subscription.g.dart'; /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class NotificationSubscription extends Equatable { - /// {@macro notification_subscription} - const NotificationSubscription({ +class PushNotificationSubscription extends Equatable { + /// {@macro push_notification_subscription} + const PushNotificationSubscription({ required this.id, required this.userId, required this.name, @@ -27,9 +27,9 @@ class NotificationSubscription extends Equatable { required this.deliveryTypes, }); - /// Creates a [NotificationSubscription] from JSON data. - factory NotificationSubscription.fromJson(Map json) => - _$NotificationSubscriptionFromJson(json); + /// Creates a [PushNotificationSubscription] from JSON data. + factory PushNotificationSubscription.fromJson(Map json) => + _$PushNotificationSubscriptionFromJson(json); /// The unique identifier for the notification subscription. final String id; @@ -58,8 +58,8 @@ class NotificationSubscription extends Equatable { /// `dailyDigest` notifications for the same subscription. final Set deliveryTypes; - /// Converts this [NotificationSubscription] instance to JSON data. - Map toJson() => _$NotificationSubscriptionToJson(this); + /// Converts this [PushNotificationSubscription] instance to JSON data. + Map toJson() => _$PushNotificationSubscriptionToJson(this); @override List get props => [ @@ -72,9 +72,9 @@ class NotificationSubscription extends Equatable { deliveryTypes, ]; - /// Creates a copy of this [NotificationSubscription] but with the given + /// Creates a copy of this [PushNotificationSubscription] but with the given /// fields replaced with the new values. - NotificationSubscription copyWith({ + PushNotificationSubscription copyWith({ String? id, String? userId, String? name, @@ -83,7 +83,7 @@ class NotificationSubscription extends Equatable { List? countries, Set? deliveryTypes, }) { - return NotificationSubscription( + return PushNotificationSubscription( id: id ?? this.id, userId: userId ?? this.userId, name: name ?? this.name, From eb219efb7ce3b5a813a7873567093228d779b0c6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:24:12 +0100 Subject: [PATCH 25/78] chore: file rename --- ...ice.dart => push_notification_device.dart} | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) rename lib/src/models/push_notifications/{device.dart => push_notification_device.dart} (76%) diff --git a/lib/src/models/push_notifications/device.dart b/lib/src/models/push_notifications/push_notification_device.dart similarity index 76% rename from lib/src/models/push_notifications/device.dart rename to lib/src/models/push_notifications/push_notification_device.dart index 62031ad7..0cae03e1 100644 --- a/lib/src/models/push_notifications/device.dart +++ b/lib/src/models/push_notifications/push_notification_device.dart @@ -5,9 +5,9 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../notifications/device.g.dart'; +part 'push_notification_device.g.dart'; -/// {@template device} +/// {@template push_notification_device} /// Represents a user's device that is registered for push notifications. /// /// This model stores the device token, the push notification provider, the @@ -15,9 +15,9 @@ part '../notifications/device.g.dart'; /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class Device extends Equatable { - /// {@macro device} - const Device({ +class PushNotificationDevice extends Equatable { + /// {@macro push_notification_device} + const PushNotificationDevice({ required this.id, required this.userId, required this.token, @@ -27,8 +27,8 @@ class Device extends Equatable { required this.updatedAt, }); - /// Creates a [Device] from JSON data. - factory Device.fromJson(Map json) => _$DeviceFromJson(json); + /// Creates a [PushNotificationDevice] from JSON data. + factory PushNotificationDevice.fromJson(Map json) => _$PushNotificationDeviceFromJson(json); /// The unique identifier for this device registration. final String id; @@ -53,8 +53,8 @@ class Device extends Equatable { @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) final DateTime updatedAt; - /// Converts this [Device] instance to JSON data. - Map toJson() => _$DeviceToJson(this); + /// Converts this [PushNotificationDevice] instance to JSON data. + Map toJson() => _$PushNotificationDeviceToJson(this); @override List get props => [ @@ -67,9 +67,9 @@ class Device extends Equatable { updatedAt, ]; - /// Creates a copy of this [Device] but with the given fields replaced with + /// Creates a copy of this [PushNotificationDevice] but with the given fields replaced with /// the new values. - Device copyWith({ + PushNotificationDevice copyWith({ String? id, String? userId, String? token, @@ -78,7 +78,7 @@ class Device extends Equatable { DateTime? createdAt, DateTime? updatedAt, }) { - return Device( + return PushNotificationDevice( id: id ?? this.id, userId: userId ?? this.userId, token: token ?? this.token, From 1fc02b8addbab824fd10836304b8d3293b935b19 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:27:39 +0100 Subject: [PATCH 26/78] chore: file rename --- ...ification_subscription_delivery_type.dart} | 4 +- .../notification_subscription.dart | 96 +++++++++++++++++++ .../push_notification_subscription.dart | 6 +- 3 files changed, 101 insertions(+), 5 deletions(-) rename lib/src/enums/{subscription_delivery_type.dart => push_notification_subscription_delivery_type.dart} (85%) create mode 100644 lib/src/models/push_notifications/notification_subscription.dart diff --git a/lib/src/enums/subscription_delivery_type.dart b/lib/src/enums/push_notification_subscription_delivery_type.dart similarity index 85% rename from lib/src/enums/subscription_delivery_type.dart rename to lib/src/enums/push_notification_subscription_delivery_type.dart index 9ec355ac..b7ecee88 100644 --- a/lib/src/enums/subscription_delivery_type.dart +++ b/lib/src/enums/push_notification_subscription_delivery_type.dart @@ -1,10 +1,10 @@ -/// {@template subscription_delivery_type} +/// {@template push_notification_subscription_delivery_type} /// Defines the types of notifications a user can receive for a subscription. /// /// A user can opt into multiple delivery types for a single notification /// subscription, allowing for flexible alert configurations. /// {@endtemplate} -enum SubscriptionDeliveryType { +enum PushNotificationSubscriptionDeliveryType { /// Delivers a notification immediately only when a matching headline is /// editorially marked as "breaking news". breakingOnly, diff --git a/lib/src/models/push_notifications/notification_subscription.dart b/lib/src/models/push_notifications/notification_subscription.dart new file mode 100644 index 00000000..8f03040c --- /dev/null +++ b/lib/src/models/push_notifications/notification_subscription.dart @@ -0,0 +1,96 @@ +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part '../notifications/notification_subscription.g.dart'; + +/// {@template notification_subscription} +/// Represents a user's saved notification filter. +/// +/// This model stores a named set of criteria, including topics, sources, and +/// countries, allowing users to subscribe to notifications for specific news +/// segments. It also defines which types of notifications the user wants to +/// receive for this filter (e.g., breaking news, daily digests). +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NotificationSubscription extends Equatable { + /// {@macro notification_subscription} + const NotificationSubscription({ + required this.id, + required this.userId, + required this.name, + required this.topics, + required this.sources, + required this.countries, + required this.deliveryTypes, + }); + + /// Creates a [NotificationSubscription] from JSON data. + factory NotificationSubscription.fromJson(Map json) => + _$NotificationSubscriptionFromJson(json); + + /// The unique identifier for the notification subscription. + final String id; + + /// The ID of the user who owns this subscription. + final String userId; + + /// The user-provided name for this saved subscription. + final String name; + + /// A list of topic IDs to include in the filter. + /// An empty list means no topic filter is applied. + final List topics; + + /// A list of source IDs to include in the filter. + /// An empty list means no source filter is applied. + final List sources; + + /// A list of country IDs to include in the filter. + /// An empty list means no country filter is applied. + final List countries; + + /// The set of delivery types the user has opted into for this subscription. + /// + /// For example, a user could choose to receive both `breakingOnly` and + /// `dailyDigest` notifications for the same subscription. + final Set deliveryTypes; + + /// Converts this [NotificationSubscription] instance to JSON data. + Map toJson() => _$NotificationSubscriptionToJson(this); + + @override + List get props => [ + id, + userId, + name, + topics, + sources, + countries, + deliveryTypes, + ]; + + /// Creates a copy of this [NotificationSubscription] but with the given + /// fields replaced with the new values. + NotificationSubscription copyWith({ + String? id, + String? userId, + String? name, + List? topics, + List? sources, + List? countries, + Set? deliveryTypes, + }) { + return NotificationSubscription( + id: id ?? this.id, + userId: userId ?? this.userId, + name: name ?? this.name, + topics: topics ?? this.topics, + sources: sources ?? this.sources, + countries: countries ?? this.countries, + deliveryTypes: deliveryTypes ?? this.deliveryTypes, + ); + } +} diff --git a/lib/src/models/push_notifications/push_notification_subscription.dart b/lib/src/models/push_notifications/push_notification_subscription.dart index a35ed757..a321dcb4 100644 --- a/lib/src/models/push_notifications/push_notification_subscription.dart +++ b/lib/src/models/push_notifications/push_notification_subscription.dart @@ -1,4 +1,4 @@ -import 'package:core/src/enums/subscription_delivery_type.dart'; +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -56,7 +56,7 @@ class PushNotificationSubscription extends Equatable { /// /// For example, a user could choose to receive both `breakingOnly` and /// `dailyDigest` notifications for the same subscription. - final Set deliveryTypes; + final Set deliveryTypes; /// Converts this [PushNotificationSubscription] instance to JSON data. Map toJson() => _$PushNotificationSubscriptionToJson(this); @@ -81,7 +81,7 @@ class PushNotificationSubscription extends Equatable { List? topics, List? sources, List? countries, - Set? deliveryTypes, + Set? deliveryTypes, }) { return PushNotificationSubscription( id: id ?? this.id, From b007012a912e47bc3fa765b0badd26395aac3f9f Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:28:07 +0100 Subject: [PATCH 27/78] chore: updated enums barrel file --- lib/src/enums/enums.dart | 3 ++ lib/src/fixtures/remote_configs.dart | 51 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/lib/src/enums/enums.dart b/lib/src/enums/enums.dart index 148d147f..e0404450 100644 --- a/lib/src/enums/enums.dart +++ b/lib/src/enums/enums.dart @@ -9,10 +9,13 @@ export 'banner_ad_shape.dart'; export 'content_status.dart'; export 'content_type.dart'; export 'dashboard_user_role.dart'; +export 'device_platform.dart'; export 'feed_decorator_category.dart'; export 'feed_decorator_type.dart'; export 'headline_density.dart'; export 'headline_image_style.dart'; export 'in_article_ad_slot_type.dart'; +export 'push_notification_provider.dart'; +export 'push_notification_subscription_delivery_type.dart'; export 'sort_order.dart'; export 'source_type.dart'; diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 99e5e04b..e74ef587 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -1,4 +1,8 @@ import 'package:core/core.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/models/config/firebase_provider_config.dart'; +import 'package:core/src/models/config/one_signal_provider_config.dart'; +import 'package:core/src/models/config/push_notification_config.dart'; /// A list of initial remote config data to be loaded into the in-memory /// remote config repository. @@ -110,5 +114,52 @@ final List remoteConfigsFixturesData = [ }, ), }, + pushNotificationConfig: const PushNotificationConfig( + enabled: true, + primaryProvider: PushNotificationProvider.firebase, + providerConfigs: { + PushNotificationProvider.firebase: FirebaseProviderConfig( + projectId: 'your-firebase-project-id', + clientEmail: 'your-firebase-client-email', + privateKey: 'your-firebase-private-key', + ), + PushNotificationProvider.oneSignal: OneSignalProviderConfig( + appId: 'your-onesignal-app-id', + restApiKey: 'your-onesignal-rest-api-key', + ), + }, + deliveryConfigs: { + SubscriptionDeliveryType.dailyDigest : NotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: NotificationSubscriptionLimit( + isAllowed: true, + maxSubscriptions: 1, + ), + AppUserRole.standardUser: NotificationSubscriptionLimit( + isAllowed: true, + maxSubscriptions: 3, + ), + AppUserRole.premiumUser: NotificationSubscriptionLimit( + isAllowed: true, + maxSubscriptions: 10, + ), + }, + ), + SubscriptionDeliveryType.breakingOnly: NotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: + NotificationSubscriptionLimit(isAllowed: false), + AppUserRole.standardUser: NotificationSubscriptionLimit( + isAllowed: true, + maxSubscriptions: 5, + ), + AppUserRole.premiumUser: + NotificationSubscriptionLimit(isAllowed: true), + }, + ), + }, + ), ), ]; From 42fc4babc50d888fd8442f2109911f232cbbcd42 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:28:37 +0100 Subject: [PATCH 28/78] chore: updated models barrel file --- lib/src/models/config/config.dart | 6 ++++++ lib/src/models/models.dart | 11 ----------- .../models/push_notifications/push_notifications.dart | 4 ++++ 3 files changed, 10 insertions(+), 11 deletions(-) delete mode 100644 lib/src/models/models.dart create mode 100644 lib/src/models/push_notifications/push_notifications.dart diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index 5654d4b8..91dee86b 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -6,7 +6,13 @@ export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; export 'feed_decorator_config.dart'; export 'feed_decorator_role_config.dart'; +export 'firebase_provider_config.dart'; export 'interstitial_ad_configuration.dart'; export 'interstitial_ad_frequency_config.dart'; +export 'one_signal_provider_config.dart'; +export 'push_notification_config.dart'; +export 'push_notification_delivery_config.dart'; +export 'push_notification_delivery_role_config.dart'; +export 'push_notification_provider_config.dart'; export 'remote_config.dart'; export 'user_preference_config.dart'; diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart deleted file mode 100644 index 3876f142..00000000 --- a/lib/src/models/models.dart +++ /dev/null @@ -1,11 +0,0 @@ -export 'auth/auth.dart'; -export 'config/config.dart'; -export 'core/core.dart'; -export 'dashboard/dashboard.dart'; -export 'entities/entities.dart'; -export 'feed_decorators/feed_decorators.dart'; -export 'query/query.dart'; -export 'responses/responses.dart'; -export 'user_preferences/user_preferences.dart'; -export 'user_presets/user_presets.dart'; -export 'user_settings/user_settings.dart'; diff --git a/lib/src/models/push_notifications/push_notifications.dart b/lib/src/models/push_notifications/push_notifications.dart new file mode 100644 index 00000000..c3627c3f --- /dev/null +++ b/lib/src/models/push_notifications/push_notifications.dart @@ -0,0 +1,4 @@ +export 'notification_subscription.dart'; +export 'push_notification_device.dart'; +export 'push_notification_payload.dart'; +export 'push_notification_subscription.dart'; From 922494bf1d01b349a9c60d59ecc5c557bf962ca3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:39:38 +0100 Subject: [PATCH 29/78] refactor(models): organize imports and update namespaces - Update import paths for consistency across multiple files - Rename `SubscriptionDeliveryType` to `PushNotificationSubscriptionDeliveryType` - Rename `NotificationDeliveryRoleConfig` to `PushNotificationDeliveryRoleConfig` - Adjust part file references for better organization - Update `RemoteConfig` to import from `config` instead of `push_notification_config` --- lib/src/models/auth/user.dart | 7 ++++--- lib/src/models/config/feed_ad_configuration.dart | 1 + lib/src/models/config/firebase_provider_config.dart | 4 ++-- lib/src/models/config/one_signal_provider_config.dart | 4 ++-- lib/src/models/config/push_notification_config.dart | 7 +++---- .../models/config/push_notification_delivery_config.dart | 2 +- lib/src/models/config/remote_config.dart | 3 ++- lib/src/models/core/feed_item.dart | 3 ++- 8 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index 4f8138d5..d7d23703 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -1,5 +1,6 @@ import 'package:core/src/enums/app_user_role.dart'; import 'package:core/src/enums/dashboard_user_role.dart'; +import 'package:core/src/enums/enums.dart'; import 'package:core/src/enums/feed_decorator_type.dart'; import 'package:core/src/enums/notifications.dart'; import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; @@ -164,12 +165,12 @@ Map _feedDecoratorStatusToJson( /// Deserializes the push notification subscriptions map from JSON and ensures /// it's complete. -Map +Map _pushNotificationSubscriptionsFromJson( Map json, ) { final existingSubscriptions = json.map((key, value) { - final deliveryType = SubscriptionDeliveryType.values.byName(key); + final deliveryType = PushNotificationSubscriptionDeliveryType.values.byName(key); return MapEntry( deliveryType, PushNotificationSubscription.fromJson(value as Map), @@ -177,7 +178,7 @@ Map }); return Map.fromEntries( - SubscriptionDeliveryType.values.map( + PushNotificationSubscriptionDeliveryType.values.map( (type) => MapEntry( type, existingSubscriptions[type] ?? diff --git a/lib/src/models/config/feed_ad_configuration.dart b/lib/src/models/config/feed_ad_configuration.dart index 140b2f18..53b8ca96 100644 --- a/lib/src/models/config/feed_ad_configuration.dart +++ b/lib/src/models/config/feed_ad_configuration.dart @@ -1,4 +1,5 @@ import 'package:core/core.dart'; +import 'package:core/src/models/config/config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart index d5fff193..33d30159 100644 --- a/lib/src/models/config/firebase_provider_config.dart +++ b/lib/src/models/config/firebase_provider_config.dart @@ -1,9 +1,9 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; +import 'package:core/src/models/config/config.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../../../../firebase_provider_config.g.dart'; +part 'firebase_provider_config.g.dart'; /// {@template firebase_provider_config} /// A concrete implementation of [PushNotificationProviderConfig] for Firebase. diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart index 28d30e04..9554d6ad 100644 --- a/lib/src/models/config/one_signal_provider_config.dart +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -1,9 +1,9 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; +import 'package:core/src/models/config/config.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part '../../../../one_signal_provider_config.g.dart'; +part 'one_signal_provider_config.g.dart'; /// {@template one_signal_provider_config} /// A concrete implementation of [PushNotificationProviderConfig] for OneSignal. diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index f01cc605..2f152f31 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -1,6 +1,5 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/remote_config/notification_delivery_config.dart'; -import 'package:core/src/models/remote_config/push_notification_provider_config.dart'; +import 'package:core/src/models/config/config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -48,7 +47,7 @@ class PushNotificationConfig extends Equatable { /// A map to globally enable or disable each specific notification type /// and define its role-based limits using the `visibleTo` pattern. - final Map + final Map deliveryConfigs; /// Converts this [PushNotificationConfig] instance to JSON data. @@ -69,7 +68,7 @@ class PushNotificationConfig extends Equatable { PushNotificationProvider? primaryProvider, Map? providerConfigs, - Map? deliveryConfigs, + Map? deliveryConfigs, }) { return PushNotificationConfig( enabled: enabled ?? this.enabled, diff --git a/lib/src/models/config/push_notification_delivery_config.dart b/lib/src/models/config/push_notification_delivery_config.dart index 745fa968..4257875b 100644 --- a/lib/src/models/config/push_notification_delivery_config.dart +++ b/lib/src/models/config/push_notification_delivery_config.dart @@ -39,7 +39,7 @@ class PushNotificationDeliveryConfig extends Equatable { /// Creates a copy of this instance with the given fields replaced. PushNotificationDeliveryConfig copyWith({ - Map? visibleTo, + Map? visibleTo, }) { return PushNotificationDeliveryConfig(visibleTo: visibleTo ?? this.visibleTo); } diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 611c4986..9c8f037b 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; -import 'package:core/src/models/config/push_notification_config.dart'; +import 'package:core/src/models/config/config.dart'; +import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/src/models/core/feed_item.dart b/lib/src/models/core/feed_item.dart index ac61359a..2e3d08ed 100644 --- a/lib/src/models/core/feed_item.dart +++ b/lib/src/models/core/feed_item.dart @@ -1,4 +1,5 @@ -import 'package:core/core.dart'; +import 'package:core/src/models/entities/entities.dart'; +import 'package:core/src/models/feed_decorators/feed_decorators.dart'; import 'package:equatable/equatable.dart'; /// {@template feed_item} From d81f1031e1ffa57e749511d69bbcf58807edbd80 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:42:27 +0100 Subject: [PATCH 30/78] chore: misc --- lib/src/models/models.dart | 12 +++ .../notification_subscription.dart | 96 ------------------- .../push_notifications.dart | 1 - 3 files changed, 12 insertions(+), 97 deletions(-) create mode 100644 lib/src/models/models.dart delete mode 100644 lib/src/models/push_notifications/notification_subscription.dart diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart new file mode 100644 index 00000000..a975c62c --- /dev/null +++ b/lib/src/models/models.dart @@ -0,0 +1,12 @@ +export 'auth/auth.dart'; +export 'config/config.dart'; +export 'core/core.dart'; +export 'dashboard/dashboard.dart'; +export 'entities/entities.dart'; +export 'feed_decorators/feed_decorators.dart'; +export 'push_notifications/push_notifications.dart'; +export 'query/query.dart'; +export 'responses/responses.dart'; +export 'user_preferences/user_preferences.dart'; +export 'user_presets/user_presets.dart'; +export 'user_settings/user_settings.dart'; diff --git a/lib/src/models/push_notifications/notification_subscription.dart b/lib/src/models/push_notifications/notification_subscription.dart deleted file mode 100644 index 8f03040c..00000000 --- a/lib/src/models/push_notifications/notification_subscription.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part '../notifications/notification_subscription.g.dart'; - -/// {@template notification_subscription} -/// Represents a user's saved notification filter. -/// -/// This model stores a named set of criteria, including topics, sources, and -/// countries, allowing users to subscribe to notifications for specific news -/// segments. It also defines which types of notifications the user wants to -/// receive for this filter (e.g., breaking news, daily digests). -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class NotificationSubscription extends Equatable { - /// {@macro notification_subscription} - const NotificationSubscription({ - required this.id, - required this.userId, - required this.name, - required this.topics, - required this.sources, - required this.countries, - required this.deliveryTypes, - }); - - /// Creates a [NotificationSubscription] from JSON data. - factory NotificationSubscription.fromJson(Map json) => - _$NotificationSubscriptionFromJson(json); - - /// The unique identifier for the notification subscription. - final String id; - - /// The ID of the user who owns this subscription. - final String userId; - - /// The user-provided name for this saved subscription. - final String name; - - /// A list of topic IDs to include in the filter. - /// An empty list means no topic filter is applied. - final List topics; - - /// A list of source IDs to include in the filter. - /// An empty list means no source filter is applied. - final List sources; - - /// A list of country IDs to include in the filter. - /// An empty list means no country filter is applied. - final List countries; - - /// The set of delivery types the user has opted into for this subscription. - /// - /// For example, a user could choose to receive both `breakingOnly` and - /// `dailyDigest` notifications for the same subscription. - final Set deliveryTypes; - - /// Converts this [NotificationSubscription] instance to JSON data. - Map toJson() => _$NotificationSubscriptionToJson(this); - - @override - List get props => [ - id, - userId, - name, - topics, - sources, - countries, - deliveryTypes, - ]; - - /// Creates a copy of this [NotificationSubscription] but with the given - /// fields replaced with the new values. - NotificationSubscription copyWith({ - String? id, - String? userId, - String? name, - List? topics, - List? sources, - List? countries, - Set? deliveryTypes, - }) { - return NotificationSubscription( - id: id ?? this.id, - userId: userId ?? this.userId, - name: name ?? this.name, - topics: topics ?? this.topics, - sources: sources ?? this.sources, - countries: countries ?? this.countries, - deliveryTypes: deliveryTypes ?? this.deliveryTypes, - ); - } -} diff --git a/lib/src/models/push_notifications/push_notifications.dart b/lib/src/models/push_notifications/push_notifications.dart index c3627c3f..66963837 100644 --- a/lib/src/models/push_notifications/push_notifications.dart +++ b/lib/src/models/push_notifications/push_notifications.dart @@ -1,4 +1,3 @@ -export 'notification_subscription.dart'; export 'push_notification_device.dart'; export 'push_notification_payload.dart'; export 'push_notification_subscription.dart'; From 0772c7f6969d131cd89846d68113dd33e1c29304 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:47:54 +0100 Subject: [PATCH 31/78] build(serialization): misc --- lib/src/models/auth/user.g.dart | 8 +++ .../config/firebase_provider_config.g.dart | 32 +++++++++ .../config/one_signal_provider_config.g.dart | 30 +++++++++ .../config/push_notification_config.g.dart | 66 +++++++++++++++++++ .../push_notification_delivery_config.g.dart | 40 +++++++++++ ...h_notification_delivery_role_config.g.dart | 25 +++++++ lib/src/models/config/remote_config.g.dart | 17 +++-- lib/src/models/entities/headline.g.dart | 2 + .../push_notification_device.g.dart | 56 ++++++++++++++++ .../push_notification_payload.g.dart | 28 ++++++++ .../push_notification_subscription.g.dart | 61 +++++++++++++++++ .../user_content_preferences.g.dart | 59 ----------------- 12 files changed, 359 insertions(+), 65 deletions(-) create mode 100644 lib/src/models/config/firebase_provider_config.g.dart create mode 100644 lib/src/models/config/one_signal_provider_config.g.dart create mode 100644 lib/src/models/config/push_notification_config.g.dart create mode 100644 lib/src/models/config/push_notification_delivery_config.g.dart create mode 100644 lib/src/models/config/push_notification_delivery_role_config.g.dart create mode 100644 lib/src/models/push_notifications/push_notification_device.g.dart create mode 100644 lib/src/models/push_notifications/push_notification_payload.g.dart create mode 100644 lib/src/models/push_notifications/push_notification_subscription.g.dart delete mode 100644 lib/src/models/user_preferences/user_content_preferences.g.dart diff --git a/lib/src/models/auth/user.g.dart b/lib/src/models/auth/user.g.dart index eb2857ce..1297e010 100644 --- a/lib/src/models/auth/user.g.dart +++ b/lib/src/models/auth/user.g.dart @@ -27,6 +27,11 @@ User _$UserFromJson(Map json) => 'feedDecoratorStatus', (v) => _feedDecoratorStatusFromJson(v as Map), ), + pushNotificationSubscriptions: $checkedConvert( + 'pushNotificationSubscriptions', + (v) => + _pushNotificationSubscriptionsFromJson(v as Map), + ), ); return val; }); @@ -40,6 +45,9 @@ Map _$UserToJson(User instance) => { 'feedDecoratorStatus': _feedDecoratorStatusToJson( instance.feedDecoratorStatus, ), + 'pushNotificationSubscriptions': _pushNotificationSubscriptionsToJson( + instance.pushNotificationSubscriptions, + ), }; const _$AppUserRoleEnumMap = { diff --git a/lib/src/models/config/firebase_provider_config.g.dart b/lib/src/models/config/firebase_provider_config.g.dart new file mode 100644 index 00000000..9e447a1d --- /dev/null +++ b/lib/src/models/config/firebase_provider_config.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'firebase_provider_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FirebaseProviderConfig _$FirebaseProviderConfigFromJson( + Map json, +) => $checkedCreate('FirebaseProviderConfig', json, ($checkedConvert) { + final val = FirebaseProviderConfig( + projectId: $checkedConvert('projectId', (v) => v as String), + clientEmail: $checkedConvert('clientEmail', (v) => v as String), + privateKey: $checkedConvert('privateKey', (v) => v as String), + ); + return val; +}); + +Map _$FirebaseProviderConfigToJson( + FirebaseProviderConfig instance, +) => { + 'providerName': _$PushNotificationProviderEnumMap[instance.providerName]!, + 'projectId': instance.projectId, + 'clientEmail': instance.clientEmail, + 'privateKey': instance.privateKey, +}; + +const _$PushNotificationProviderEnumMap = { + PushNotificationProvider.firebase: 'firebase', + PushNotificationProvider.oneSignal: 'oneSignal', +}; diff --git a/lib/src/models/config/one_signal_provider_config.g.dart b/lib/src/models/config/one_signal_provider_config.g.dart new file mode 100644 index 00000000..a331a791 --- /dev/null +++ b/lib/src/models/config/one_signal_provider_config.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'one_signal_provider_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +OneSignalProviderConfig _$OneSignalProviderConfigFromJson( + Map json, +) => $checkedCreate('OneSignalProviderConfig', json, ($checkedConvert) { + final val = OneSignalProviderConfig( + appId: $checkedConvert('appId', (v) => v as String), + restApiKey: $checkedConvert('restApiKey', (v) => v as String), + ); + return val; +}); + +Map _$OneSignalProviderConfigToJson( + OneSignalProviderConfig instance, +) => { + 'providerName': _$PushNotificationProviderEnumMap[instance.providerName]!, + 'appId': instance.appId, + 'restApiKey': instance.restApiKey, +}; + +const _$PushNotificationProviderEnumMap = { + PushNotificationProvider.firebase: 'firebase', + PushNotificationProvider.oneSignal: 'oneSignal', +}; diff --git a/lib/src/models/config/push_notification_config.g.dart b/lib/src/models/config/push_notification_config.g.dart new file mode 100644 index 00000000..241809c7 --- /dev/null +++ b/lib/src/models/config/push_notification_config.g.dart @@ -0,0 +1,66 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationConfig _$PushNotificationConfigFromJson( + Map json, +) => $checkedCreate('PushNotificationConfig', json, ($checkedConvert) { + final val = PushNotificationConfig( + enabled: $checkedConvert('enabled', (v) => v as bool), + primaryProvider: $checkedConvert( + 'primaryProvider', + (v) => $enumDecode(_$PushNotificationProviderEnumMap, v), + ), + providerConfigs: $checkedConvert( + 'providerConfigs', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$PushNotificationProviderEnumMap, k), + PushNotificationProviderConfig.fromJson(e as Map), + ), + ), + ), + deliveryConfigs: $checkedConvert( + 'deliveryConfigs', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, k), + PushNotificationDeliveryConfig.fromJson(e as Map), + ), + ), + ), + ); + return val; +}); + +Map _$PushNotificationConfigToJson( + PushNotificationConfig instance, +) => { + 'enabled': instance.enabled, + 'primaryProvider': + _$PushNotificationProviderEnumMap[instance.primaryProvider]!, + 'providerConfigs': instance.providerConfigs.map( + (k, e) => MapEntry(_$PushNotificationProviderEnumMap[k]!, e.toJson()), + ), + 'deliveryConfigs': instance.deliveryConfigs.map( + (k, e) => MapEntry( + _$PushNotificationSubscriptionDeliveryTypeEnumMap[k]!, + e.toJson(), + ), + ), +}; + +const _$PushNotificationProviderEnumMap = { + PushNotificationProvider.firebase: 'firebase', + PushNotificationProvider.oneSignal: 'oneSignal', +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; diff --git a/lib/src/models/config/push_notification_delivery_config.g.dart b/lib/src/models/config/push_notification_delivery_config.g.dart new file mode 100644 index 00000000..3e21896b --- /dev/null +++ b/lib/src/models/config/push_notification_delivery_config.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_delivery_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationDeliveryConfig _$PushNotificationDeliveryConfigFromJson( + Map json, +) => $checkedCreate('PushNotificationDeliveryConfig', json, ($checkedConvert) { + final val = PushNotificationDeliveryConfig( + visibleTo: $checkedConvert( + 'visibleTo', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$AppUserRoleEnumMap, k), + PushNotificationDeliveryRoleConfig.fromJson( + e as Map, + ), + ), + ), + ), + ); + return val; +}); + +Map _$PushNotificationDeliveryConfigToJson( + PushNotificationDeliveryConfig instance, +) => { + 'visibleTo': instance.visibleTo.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), +}; + +const _$AppUserRoleEnumMap = { + AppUserRole.premiumUser: 'premiumUser', + AppUserRole.standardUser: 'standardUser', + AppUserRole.guestUser: 'guestUser', +}; diff --git a/lib/src/models/config/push_notification_delivery_role_config.g.dart b/lib/src/models/config/push_notification_delivery_role_config.g.dart new file mode 100644 index 00000000..e923ffb4 --- /dev/null +++ b/lib/src/models/config/push_notification_delivery_role_config.g.dart @@ -0,0 +1,25 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_delivery_role_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationDeliveryRoleConfig _$PushNotificationDeliveryRoleConfigFromJson( + Map json, +) => $checkedCreate('PushNotificationDeliveryRoleConfig', json, ( + $checkedConvert, +) { + final val = PushNotificationDeliveryRoleConfig( + subscriptionLimit: $checkedConvert( + 'subscriptionLimit', + (v) => (v as num).toInt(), + ), + ); + return val; +}); + +Map _$PushNotificationDeliveryRoleConfigToJson( + PushNotificationDeliveryRoleConfig instance, +) => {'subscriptionLimit': instance.subscriptionLimit}; diff --git a/lib/src/models/config/remote_config.g.dart b/lib/src/models/config/remote_config.g.dart index 8e241737..35643788 100644 --- a/lib/src/models/config/remote_config.g.dart +++ b/lib/src/models/config/remote_config.g.dart @@ -10,9 +10,9 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => $checkedCreate('RemoteConfig', json, ($checkedConvert) { final val = RemoteConfig( id: $checkedConvert('id', (v) => v as String), - userPreferenceConfig: $checkedConvert( - 'userPreferenceConfig', - (v) => UserPreferenceConfig.fromJson(v as Map), + appStatus: $checkedConvert( + 'appStatus', + (v) => AppStatus.fromJson(v as Map), ), adConfig: $checkedConvert( 'adConfig', @@ -27,9 +27,13 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => ), ), ), - appStatus: $checkedConvert( - 'appStatus', - (v) => AppStatus.fromJson(v as Map), + userPreferenceConfig: $checkedConvert( + 'userPreferenceConfig', + (v) => UserPreferenceConfig.fromJson(v as Map), + ), + pushNotificationConfig: $checkedConvert( + 'pushNotificationConfig', + (v) => PushNotificationConfig.fromJson(v as Map), ), createdAt: $checkedConvert( 'createdAt', @@ -52,6 +56,7 @@ Map _$RemoteConfigToJson(RemoteConfig instance) => (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), ), 'appStatus': instance.appStatus.toJson(), + 'pushNotificationConfig': instance.pushNotificationConfig.toJson(), 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), }; diff --git a/lib/src/models/entities/headline.g.dart b/lib/src/models/entities/headline.g.dart index ba4484f0..f45c9ff6 100644 --- a/lib/src/models/entities/headline.g.dart +++ b/lib/src/models/entities/headline.g.dart @@ -38,6 +38,7 @@ Headline _$HeadlineFromJson(Map json) => 'status', (v) => $enumDecode(_$ContentStatusEnumMap, v), ), + isBreaking: $checkedConvert('isBreaking', (v) => v as bool), ); return val; }); @@ -53,6 +54,7 @@ Map _$HeadlineToJson(Headline instance) => { 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), 'status': _$ContentStatusEnumMap[instance.status]!, + 'isBreaking': instance.isBreaking, 'topic': instance.topic.toJson(), }; diff --git a/lib/src/models/push_notifications/push_notification_device.g.dart b/lib/src/models/push_notifications/push_notification_device.g.dart new file mode 100644 index 00000000..aeb73030 --- /dev/null +++ b/lib/src/models/push_notifications/push_notification_device.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_device.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationDevice _$PushNotificationDeviceFromJson( + Map json, +) => $checkedCreate('PushNotificationDevice', json, ($checkedConvert) { + final val = PushNotificationDevice( + id: $checkedConvert('id', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), + token: $checkedConvert('token', (v) => v as String), + provider: $checkedConvert( + 'provider', + (v) => $enumDecode(_$PushNotificationProviderEnumMap, v), + ), + platform: $checkedConvert( + 'platform', + (v) => $enumDecode(_$DevicePlatformEnumMap, v), + ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + updatedAt: $checkedConvert( + 'updatedAt', + (v) => dateTimeFromJson(v as String?), + ), + ); + return val; +}); + +Map _$PushNotificationDeviceToJson( + PushNotificationDevice instance, +) => { + 'id': instance.id, + 'userId': instance.userId, + 'token': instance.token, + 'provider': _$PushNotificationProviderEnumMap[instance.provider]!, + 'platform': _$DevicePlatformEnumMap[instance.platform]!, + 'createdAt': dateTimeToJson(instance.createdAt), + 'updatedAt': dateTimeToJson(instance.updatedAt), +}; + +const _$PushNotificationProviderEnumMap = { + PushNotificationProvider.firebase: 'firebase', + PushNotificationProvider.oneSignal: 'oneSignal', +}; + +const _$DevicePlatformEnumMap = { + DevicePlatform.ios: 'ios', + DevicePlatform.android: 'android', +}; diff --git a/lib/src/models/push_notifications/push_notification_payload.g.dart b/lib/src/models/push_notifications/push_notification_payload.g.dart new file mode 100644 index 00000000..2356b9b1 --- /dev/null +++ b/lib/src/models/push_notifications/push_notification_payload.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_payload.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationPayload _$PushNotificationPayloadFromJson( + Map json, +) => $checkedCreate('PushNotificationPayload', json, ($checkedConvert) { + final val = PushNotificationPayload( + title: $checkedConvert('title', (v) => v as String), + body: $checkedConvert('body', (v) => v as String), + data: $checkedConvert('data', (v) => v as Map), + imageUrl: $checkedConvert('imageUrl', (v) => v as String?), + ); + return val; +}); + +Map _$PushNotificationPayloadToJson( + PushNotificationPayload instance, +) => { + 'title': instance.title, + 'body': instance.body, + 'imageUrl': instance.imageUrl, + 'data': instance.data, +}; diff --git a/lib/src/models/push_notifications/push_notification_subscription.g.dart b/lib/src/models/push_notifications/push_notification_subscription.g.dart new file mode 100644 index 00000000..f776e172 --- /dev/null +++ b/lib/src/models/push_notifications/push_notification_subscription.g.dart @@ -0,0 +1,61 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_subscription.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationSubscription _$PushNotificationSubscriptionFromJson( + Map json, +) => $checkedCreate('PushNotificationSubscription', json, ($checkedConvert) { + final val = PushNotificationSubscription( + id: $checkedConvert('id', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), + name: $checkedConvert('name', (v) => v as String), + topics: $checkedConvert( + 'topics', + (v) => (v as List).map((e) => e as String).toList(), + ), + sources: $checkedConvert( + 'sources', + (v) => (v as List).map((e) => e as String).toList(), + ), + countries: $checkedConvert( + 'countries', + (v) => (v as List).map((e) => e as String).toList(), + ), + deliveryTypes: $checkedConvert( + 'deliveryTypes', + (v) => (v as List) + .map( + (e) => $enumDecode( + _$PushNotificationSubscriptionDeliveryTypeEnumMap, + e, + ), + ) + .toSet(), + ), + ); + return val; +}); + +Map _$PushNotificationSubscriptionToJson( + PushNotificationSubscription instance, +) => { + 'id': instance.id, + 'userId': instance.userId, + 'name': instance.name, + 'topics': instance.topics, + 'sources': instance.sources, + 'countries': instance.countries, + 'deliveryTypes': instance.deliveryTypes + .map((e) => _$PushNotificationSubscriptionDeliveryTypeEnumMap[e]!) + .toList(), +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; diff --git a/lib/src/models/user_preferences/user_content_preferences.g.dart b/lib/src/models/user_preferences/user_content_preferences.g.dart deleted file mode 100644 index 6e546f89..00000000 --- a/lib/src/models/user_preferences/user_content_preferences.g.dart +++ /dev/null @@ -1,59 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_content_preferences.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserContentPreferences _$UserContentPreferencesFromJson( - Map json, -) => $checkedCreate('UserContentPreferences', json, ($checkedConvert) { - final val = UserContentPreferences( - id: $checkedConvert('id', (v) => v as String), - followedCountries: $checkedConvert( - 'followedCountries', - (v) => (v as List) - .map((e) => Country.fromJson(e as Map)) - .toList(), - ), - followedSources: $checkedConvert( - 'followedSources', - (v) => (v as List) - .map((e) => Source.fromJson(e as Map)) - .toList(), - ), - followedTopics: $checkedConvert( - 'followedTopics', - (v) => (v as List) - .map((e) => Topic.fromJson(e as Map)) - .toList(), - ), - savedHeadlines: $checkedConvert( - 'savedHeadlines', - (v) => (v as List) - .map((e) => Headline.fromJson(e as Map)) - .toList(), - ), - savedFilters: $checkedConvert( - 'savedFilters', - (v) => (v as List) - .map((e) => SavedFilter.fromJson(e as Map)) - .toList(), - ), - ); - return val; -}); - -Map _$UserContentPreferencesToJson( - UserContentPreferences instance, -) => { - 'id': instance.id, - 'followedCountries': instance.followedCountries - .map((e) => e.toJson()) - .toList(), - 'followedSources': instance.followedSources.map((e) => e.toJson()).toList(), - 'followedTopics': instance.followedTopics.map((e) => e.toJson()).toList(), - 'savedHeadlines': instance.savedHeadlines.map((e) => e.toJson()).toList(), - 'savedFilters': instance.savedFilters.map((e) => e.toJson()).toList(), -}; From 47d1a15e0f319a88f52e114844de0ec4868ac24a Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:53:49 +0100 Subject: [PATCH 32/78] fix: misc --- lib/src/fixtures/headlines.dart | 109 ++++++++++++++++++ .../fixtures/user_content_preferences.dart | 4 + lib/src/fixtures/users.dart | 11 ++ lib/src/models/auth/user.dart | 12 +- .../models/config/feed_ad_configuration.dart | 1 - lib/src/models/config/remote_config.dart | 1 - .../user_content_preferences.dart | 6 +- 7 files changed, 131 insertions(+), 13 deletions(-) diff --git a/lib/src/fixtures/headlines.dart b/lib/src/fixtures/headlines.dart index ee2111e6..6589e2ce 100644 --- a/lib/src/fixtures/headlines.dart +++ b/lib/src/fixtures/headlines.dart @@ -9,6 +9,7 @@ import 'package:core/src/models/entities/headline.dart'; final headlinesFixturesData = [ Headline( id: kHeadlineId1, + isBreaking: false, title: 'AI Breakthrough: New Model Achieves Human-Level Performance', excerpt: 'Researchers announce a significant leap in artificial intelligence, with a new model demonstrating unprecedented capabilities.', @@ -23,6 +24,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId2, + isBreaking: false, title: 'Local Team Wins Championship in Thrilling Final', excerpt: 'The city celebrates as the underdog team clinches the national championship in a nail-biting finish.', @@ -37,6 +39,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId3, + isBreaking: false, title: 'Global Leaders Meet to Discuss Climate Change Policies', excerpt: 'A summit of world leaders convenes to address urgent climate change issues and propose new international agreements.', @@ -51,6 +54,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId4, + isBreaking: false, title: 'New Planet Discovered in Distant Galaxy', excerpt: 'Astronomers confirm the existence of a new exoplanet, sparking excitement in the scientific community.', @@ -65,6 +69,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId5, + isBreaking: false, title: 'Breakthrough in Cancer Research Offers New Hope', excerpt: 'A new study reveals a promising treatment approach for a common type of cancer, moving closer to a cure.', @@ -79,6 +84,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId6, + isBreaking: false, title: 'Blockbuster Movie Breaks Box Office Records', excerpt: 'The highly anticipated film shatters previous box office records in its opening weekend, delighting fans worldwide.', @@ -93,6 +99,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId7, + isBreaking: false, title: 'Stock Market Reaches All-Time High Amid Economic Boom', excerpt: 'Major indices surge as strong economic data and corporate earnings drive investor confidence.', @@ -107,6 +114,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId8, + isBreaking: false, title: 'New Travel Restrictions Lifted for Popular Destinations', excerpt: 'Governments ease travel advisories, opening up new opportunities for international tourism.', @@ -121,6 +129,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId9, + isBreaking: false, title: 'Michelin Star Chef Opens New Restaurant in City Center', excerpt: 'A world-renowned chef brings their culinary expertise to the city with a highly anticipated new dining establishment.', @@ -135,6 +144,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId10, + isBreaking: false, title: 'Innovative Teaching Methods Boost Student Engagement', excerpt: 'Schools adopting new pedagogical approaches report significant improvements in student participation and learning outcomes.', @@ -149,6 +159,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId11, + isBreaking: false, title: 'Cybersecurity Firms Warn of New Global Threat', excerpt: 'Experts advise immediate updates as a sophisticated new malware strain targets critical infrastructure worldwide.', @@ -163,6 +174,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId12, + isBreaking: false, title: 'Olympics Committee Announces Host City for 2032 Games', excerpt: 'The highly anticipated decision for the next Summer Olympics host city has been revealed, promising a spectacular event.', @@ -177,6 +189,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId13, + isBreaking: false, title: 'New Bill Aims to Reform Healthcare System', excerpt: 'Legislators introduce a comprehensive bill designed to address rising healthcare costs and expand access to services.', @@ -191,6 +204,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId14, + isBreaking: false, title: 'Archaeologists Uncover Ancient City Ruins', excerpt: 'A team of archaeologists makes a groundbreaking discovery, revealing a previously unknown ancient civilization.', @@ -205,6 +219,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId15, + isBreaking: false, title: 'Dietary Guidelines Updated for Public Health', excerpt: 'New recommendations from health organizations aim to improve public nutrition and combat chronic diseases.', @@ -219,6 +234,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId16, + isBreaking: false, title: 'Music Festival Announces Star-Studded Lineup', excerpt: 'Fans eagerly await the annual music festival as organizers unveil a lineup featuring top artists from various genres.', @@ -233,6 +249,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId17, + isBreaking: false, title: 'Tech Giant Acquires Startup in Multi-Billion Dollar Deal', excerpt: 'A major technology company expands its portfolio with the acquisition of a promising startup, signaling market consolidation.', @@ -247,6 +264,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId18, + isBreaking: false, title: 'Space Tourism Takes Off: First Commercial Flights Announced', excerpt: 'The era of space tourism begins as companies unveil plans for regular commercial flights to orbit.', @@ -261,6 +279,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId19, + isBreaking: false, title: 'Future of Food: Lab-Grown Meat Gains Popularity', excerpt: 'As sustainability concerns grow, lab-grown meat alternatives are becoming a staple in modern diets.', @@ -275,6 +294,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId20, + isBreaking: false, title: 'Online Learning Platforms See Surge in Enrollment', excerpt: 'The shift to digital education continues as more students opt for flexible online courses and certifications.', @@ -289,6 +309,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId21, + isBreaking: false, title: 'Quantum Computing Achieves New Milestone', excerpt: 'Scientists report a significant advancement in quantum computing, bringing the technology closer to practical applications.', @@ -303,6 +324,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId22, + isBreaking: false, title: 'World Cup Qualifiers: Unexpected Upsets Shake Rankings', excerpt: 'Several top-ranked teams suffer surprising defeats in the latest World Cup qualifiers, reshuffling the global football landscape.', @@ -317,6 +339,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId23, + isBreaking: false, title: 'Election Results: New Government Takes Power', excerpt: 'Following a closely contested election, a new political party forms the government, promising significant policy changes.', @@ -331,6 +354,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId24, + isBreaking: false, title: 'Breakthrough in Fusion Energy Research Announced', excerpt: 'Scientists achieve a major milestone in fusion energy, bringing clean, limitless power closer to reality.', @@ -345,6 +369,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId25, + isBreaking: false, title: 'Mental Health Awareness Campaign Launched Globally', excerpt: 'A new international initiative aims to destigmatize mental health issues and provide greater support resources.', @@ -359,6 +384,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId26, + isBreaking: false, title: 'Gaming Industry Sees Record Growth in Virtual Reality', excerpt: 'The virtual reality sector of the gaming industry experiences unprecedented expansion, driven by new hardware and immersive titles.', @@ -373,6 +399,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId27, + isBreaking: false, title: 'Global Supply Chain Disruptions Impacting Consumer Goods', excerpt: 'Ongoing challenges in global logistics are leading to shortages and price increases for a wide range of consumer products.', @@ -387,6 +414,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId28, + isBreaking: false, title: 'Arctic Expedition Discovers New Marine Species', excerpt: 'Scientists on an Arctic research mission identify several previously unknown species of marine life, highlighting biodiversity.', @@ -401,6 +429,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId29, + isBreaking: false, title: 'Rise of Plant-Based Cuisine: New Restaurants Open', excerpt: 'The culinary scene is embracing plant-based diets with an increasing number of restaurants specializing in vegan and vegetarian dishes.', @@ -415,6 +444,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId30, + isBreaking: false, title: 'Education Technology Transforms Classrooms', excerpt: 'New digital tools and platforms are revolutionizing traditional classroom settings, enhancing interactive learning experiences.', @@ -429,6 +459,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId31, + isBreaking: false, title: 'SpaceX Launches New Satellite Constellation', excerpt: "Elon Musk's SpaceX successfully deploys a new batch of Starlink satellites, expanding global internet coverage.", @@ -443,6 +474,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId32, + isBreaking: false, title: 'Football Legend Announces Retirement', excerpt: 'A celebrated football player declares their retirement, marking the end of an illustrious career.', @@ -457,6 +489,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId33, + isBreaking: false, title: 'G7 Summit Concludes with Joint Statement on Global Economy', excerpt: 'Leaders from the G7 nations issue a unified statement addressing economic challenges and future cooperation.', @@ -471,6 +504,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId34, + isBreaking: false, title: "Breakthrough in Alzheimer's Research Offers New Treatment Path", excerpt: "Scientists identify a novel therapeutic target for Alzheimer's disease, paving the way for more effective treatments.", @@ -485,6 +519,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId35, + isBreaking: false, title: 'Global Vaccination Campaign Reaches Billions', excerpt: 'International efforts to vaccinate the world population against a new virus achieve unprecedented reach.', @@ -499,6 +534,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId36, + isBreaking: false, title: 'Streaming Wars Intensify with New Platform Launches', excerpt: 'The competition in the streaming market heats up as several new services enter the fray, offering diverse content.', @@ -513,6 +549,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId37, + isBreaking: false, title: 'Cryptocurrency Market Experiences Major Volatility', excerpt: 'Digital currency values fluctuate wildly, prompting investors to reassess their strategies.', @@ -527,6 +564,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId38, + isBreaking: false, title: 'Sustainable Tourism Initiatives Gain Momentum', excerpt: 'Travel industry shifts towards eco-friendly practices, offering responsible options for environmentally conscious travelers.', @@ -541,6 +579,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId39, + isBreaking: false, title: 'Food Security Summit Addresses Global Hunger', excerpt: 'International conference focuses on strategies to combat food insecurity and ensure equitable access to nutrition worldwide.', @@ -555,6 +594,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId40, + isBreaking: false, title: 'Robotics in Education: New Tools for Learning', excerpt: 'Schools integrate advanced robotics into their curriculum, providing hands-on learning experiences for students.', @@ -569,6 +609,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId41, + isBreaking: false, title: 'AI Ethics Debate Intensifies Among Tech Leaders', excerpt: 'Discussions around the ethical implications of artificial intelligence gain traction, with calls for stricter regulations.', @@ -583,6 +624,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId42, + isBreaking: false, title: 'Esports Industry Sees Massive Investment Boom', excerpt: 'The competitive gaming sector attracts record investments, solidifying its position as a major entertainment industry.', @@ -597,6 +639,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId43, + isBreaking: false, title: 'International Sanctions Imposed on Rogue State', excerpt: "Global powers unite to impose new economic sanctions in response to a nation's controversial actions.", @@ -611,6 +654,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId44, + isBreaking: false, title: 'New Species of Deep-Sea Creature Discovered', excerpt: 'Oceanographers exploring the deepest parts of the ocean encounter a never-before-seen marine organism.', @@ -625,6 +669,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId45, + isBreaking: false, title: 'Global Health Crisis: New Pandemic Preparedness Plan', excerpt: 'International health organizations unveil a comprehensive strategy to prevent and respond to future pandemics.', @@ -639,6 +684,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId46, + isBreaking: false, title: 'Hollywood Strikes Continue: Impact on Film Production', excerpt: 'Ongoing labor disputes in Hollywood lead to widespread production halts, affecting upcoming movie and TV releases.', @@ -653,6 +699,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId47, + isBreaking: false, title: 'Emerging Markets Show Strong Economic Resilience', excerpt: 'Despite global uncertainties, several emerging economies demonstrate robust growth and attract foreign investment.', @@ -667,6 +714,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId48, + isBreaking: false, title: 'Adventure Tourism Booms in Remote Regions', excerpt: 'Travelers seek unique experiences in off-the-beaten-path destinations, boosting local economies in remote areas.', @@ -681,6 +729,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId49, + isBreaking: false, title: 'The Rise of Sustainable Food Packaging', excerpt: 'Innovations in eco-friendly packaging solutions are transforming the food industry, reducing environmental impact.', @@ -695,6 +744,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId50, + isBreaking: false, title: 'Personalized Learning: Tailoring Education to Individual Needs', excerpt: "New educational models focus on customized learning paths, adapting to each student's pace and preferences.", @@ -713,6 +763,7 @@ final headlinesFixturesData = [ // --- Local News Outlets (kSourceId11 - kSourceId20) --- Headline( id: kHeadlineId51, + isBreaking: false, title: 'City Council Approves New Downtown Development Plan', excerpt: 'The San Francisco City Council has given the green light to a major redevelopment project aimed at revitalizing the downtown core.', @@ -728,6 +779,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId52, + isBreaking: false, title: 'Tech Startups Flourish in the Bay Area', excerpt: 'A new report shows a significant increase in venture capital funding for tech startups in San Francisco.', @@ -743,6 +795,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId53, + isBreaking: false, title: 'Golden Gate Bridge Retrofit Project Begins', excerpt: 'A multi-year seismic retrofit project for the Golden Gate Bridge has officially commenced.', @@ -758,6 +811,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId54, + isBreaking: false, title: 'Local Chef Wins Prestigious Culinary Award', excerpt: 'A San Francisco-based chef has been awarded the coveted "Golden Spoon" for culinary innovation.', @@ -773,6 +827,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId55, + isBreaking: false, title: 'Warriors Secure Victory in Season Opener', excerpt: 'The Golden State Warriors started their season with a decisive win at the Chase Center.', @@ -788,6 +843,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId56, + isBreaking: false, title: 'Manchester United Announces New Stadium Expansion Plans', excerpt: 'The club has revealed ambitious plans to increase the capacity of Old Trafford.', @@ -803,6 +859,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId57, + isBreaking: false, title: 'New Tram Line Opens in Greater Manchester', excerpt: 'The new Metrolink line is set to improve public transport links across the region.', @@ -818,6 +875,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId58, + isBreaking: false, title: 'Manchester Tech Hub Attracts Global Talent', excerpt: 'A report highlights Manchester as a growing hub for technology and innovation in Europe.', @@ -833,6 +891,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId59, + isBreaking: false, title: 'Coronation Street Filming Causes Local Buzz', excerpt: 'Fans gather as the popular soap opera films on location in central Manchester.', @@ -848,6 +907,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId60, + isBreaking: false, title: 'Council Debates Clean Air Zone Implementation', excerpt: 'Greater Manchester leaders are in talks over the future of the controversial Clean Air Zone.', @@ -863,6 +923,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId61, + isBreaking: false, title: 'Sydney Opera House Announces New Season Lineup', excerpt: 'A star-studded lineup of performances has been announced for the upcoming season at the iconic venue.', @@ -878,6 +939,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId62, + isBreaking: false, title: 'Housing Prices in Sydney Continue to Climb', excerpt: 'The latest real estate data shows a persistent upward trend in property values across the Sydney metropolitan area.', @@ -893,6 +955,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId63, + isBreaking: false, title: 'NSW Government Unveils New Infrastructure Projects', excerpt: 'The New South Wales government has committed billions to new transport and public works projects.', @@ -908,6 +971,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId64, + isBreaking: false, title: 'Swans Triumph in AFL Derby Match', excerpt: 'The Sydney Swans secured a memorable victory over their local rivals in a heated AFL match.', @@ -923,6 +987,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId65, + isBreaking: false, title: 'Bondi Beach Erosion Concerns Prompt Action', excerpt: 'Local authorities are exploring new measures to combat coastal erosion at the world-famous Bondi Beach.', @@ -938,6 +1003,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId66, + isBreaking: false, title: 'Paris Metro Expansion: New Stations Opened', excerpt: 'The Grand Paris Express project reaches a new milestone with the opening of several new metro stations.', @@ -952,6 +1018,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId67, + isBreaking: false, title: 'Louvre Museum Unveils New Egyptian Antiquities Wing', excerpt: 'A new wing dedicated to ancient Egyptian artifacts has been opened to the public at the Louvre.', @@ -966,6 +1033,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId68, + isBreaking: false, title: 'Paris Saint-Germain Secures Ligue 1 Title', excerpt: 'PSG has been crowned champions of France after a dominant season.', @@ -980,6 +1048,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId69, + isBreaking: false, title: 'Mayor of Paris Announces New Green Initiatives', excerpt: 'The mayor has outlined a plan to increase green spaces and reduce pollution in the city.', @@ -994,6 +1063,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId70, + isBreaking: false, title: 'Paris Fashion Week Highlights New Trends', excerpt: "The world's top designers showcased their latest collections during the celebrated Paris Fashion Week.", @@ -1008,6 +1078,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId71, + isBreaking: false, title: 'Toronto Raptors Make Key Trade Ahead of Deadline', excerpt: 'The Raptors have made a significant move to bolster their roster for the playoff push.', @@ -1023,6 +1094,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId72, + isBreaking: false, title: 'TTC Announces Service Changes for Summer', excerpt: 'The Toronto Transit Commission has released its updated schedule and service adjustments for the summer season.', @@ -1038,6 +1110,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId73, + isBreaking: false, title: 'Toronto International Film Festival (TIFF) Lineup Revealed', excerpt: "Organizers of TIFF have announced a highly anticipated lineup of films for this year's festival.", @@ -1053,6 +1126,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId74, + isBreaking: false, title: 'City of Toronto Grapples with Housing Affordability', excerpt: 'City council is debating new policies to address the ongoing housing affordability crisis in Toronto.', @@ -1068,6 +1142,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId75, + isBreaking: false, title: 'New Waterfront Development Project Approved', excerpt: "A major new development on Toronto's waterfront has received final approval from the city.", @@ -1083,6 +1158,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId76, + isBreaking: false, title: 'Berlin Philharmonic Announces New Conductor', excerpt: 'The world-renowned orchestra has named a new chief conductor, marking a new era.', @@ -1098,6 +1174,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId77, + isBreaking: false, title: 'Remnants of Berlin Wall Unearthed During Construction', excerpt: 'A previously unknown section of the Berlin Wall has been discovered at a construction site in the city center.', @@ -1113,6 +1190,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId78, + isBreaking: false, title: 'Hertha BSC Faces Relegation Battle', excerpt: 'The Berlin-based football club is in a tough fight to avoid relegation from the Bundesliga.', @@ -1128,6 +1206,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId79, + isBreaking: false, title: 'Berlin Senate Approves Rent Control Measures', excerpt: 'New measures aimed at controlling rent prices in the German capital have been approved by the Berlin Senate.', @@ -1143,6 +1222,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId80, + isBreaking: false, title: 'Brandenburg Airport Reports Record Passenger Numbers', excerpt: "Berlin's new airport has reported its busiest month on record, signaling a recovery in air travel.", @@ -1158,6 +1238,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId81, + isBreaking: false, title: 'Tokyo Government Tackles Aging Population Issues', excerpt: 'The Tokyo Metropolitan Government has announced new policies to support its rapidly aging population.', @@ -1173,6 +1254,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId82, + isBreaking: false, title: 'New Shinkansen Line to Connect Tokyo and Tsuruga', excerpt: 'The Hokuriku Shinkansen line has been extended, reducing travel time between Tokyo and the Hokuriku region.', @@ -1188,6 +1270,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId83, + isBreaking: false, title: 'Yomiuri Giants Clinch Central League Pennant', excerpt: 'The Tokyo-based Yomiuri Giants have won the Central League pennant in Japanese professional baseball.', @@ -1203,6 +1286,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId84, + isBreaking: false, title: 'Studio Ghibli Announces New Film Project', excerpt: 'The celebrated animation studio has announced its first new feature film in several years, exciting fans worldwide.', @@ -1218,6 +1302,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId85, + isBreaking: false, title: "Tokyo's Tsukiji Outer Market Thrives After Relocation", excerpt: 'Years after the inner market moved, the Tsukiji Outer Market continues to be a vibrant destination for food lovers.', @@ -1233,6 +1318,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId86, + isBreaking: false, title: 'Mumbai Metro Expands with New Aqua Line', excerpt: 'The new Aqua Line of the Mumbai Metro is now operational, aiming to ease traffic congestion in the city.', @@ -1248,6 +1334,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId87, + isBreaking: false, title: 'Bollywood Film Shoots Bring Stars to Mumbai Streets', excerpt: 'Major Bollywood productions are currently filming across Mumbai, drawing crowds of onlookers.', @@ -1263,6 +1350,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId88, + isBreaking: false, title: 'Mumbai Indians Gear Up for IPL Season', excerpt: 'The local cricket franchise, Mumbai Indians, has begun its training camp ahead of the new IPL season.', @@ -1278,6 +1366,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId89, + isBreaking: false, title: 'BMC Tackles Monsoon Preparedness in Mumbai', excerpt: 'The Brihanmumbai Municipal Corporation (BMC) has outlined its plan for monsoon preparedness to prevent flooding.', @@ -1293,6 +1382,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId90, + isBreaking: false, title: "Mumbai's Financial District Sees New Investments", excerpt: 'The Bandra Kurla Complex (BKC) continues to attract major national and international business investments.', @@ -1308,6 +1398,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId91, + isBreaking: false, title: 'Rio Carnival Preparations in Full Swing', excerpt: 'Samba schools across Rio de Janeiro are finalizing their preparations for the world-famous Carnival parade.', @@ -1323,6 +1414,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId92, + isBreaking: false, title: 'Flamengo Wins Key Match at Maracanã Stadium', excerpt: "Rio's beloved football club, Flamengo, celebrated a crucial victory in front of a packed Maracanã stadium.", @@ -1338,6 +1430,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId93, + isBreaking: false, title: 'Security Boosted in Rio Ahead of Major Summit', excerpt: 'Security measures are being increased across Rio de Janeiro as the city prepares to host an international summit.', @@ -1353,6 +1446,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId94, + isBreaking: false, title: 'Sugarloaf Mountain Cable Car Undergoes Modernization', excerpt: 'The iconic cable car system for Sugarloaf Mountain is being updated with new technology and cabins.', @@ -1368,6 +1462,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId95, + isBreaking: false, title: "Bossa Nova Festival Celebrates Rio's Musical Heritage", excerpt: 'A music festival in Ipanema is celebrating the rich history of Bossa Nova, born in the neighborhoods of Rio.', @@ -1383,6 +1478,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId96, + isBreaking: false, title: 'Sagrada Família Nears Completion After 140 Years', excerpt: "Barcelona's iconic basilica, designed by Gaudí, is entering its final phase of construction.", @@ -1398,6 +1494,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId97, + isBreaking: false, title: 'FC Barcelona Presents New Kit at Camp Nou', excerpt: 'The football club has unveiled its new home kit for the upcoming La Liga season.', @@ -1413,6 +1510,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId98, + isBreaking: false, title: 'Catalan Government Discusses Tourism Strategy', excerpt: 'Leaders in Catalonia are debating a new long-term strategy to manage tourism in Barcelona and the wider region.', @@ -1428,6 +1526,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId99, + isBreaking: false, title: "Barcelona's Tech Scene Booms with New Hub", excerpt: 'The 22@ innovation district in Barcelona continues to expand, attracting tech companies from around the globe.', @@ -1443,6 +1542,7 @@ final headlinesFixturesData = [ ), Headline( id: kHeadlineId100, + isBreaking: false, title: 'La Boqueria Market: A Taste of Barcelona', excerpt: 'A feature on the historic La Boqueria market, exploring its culinary delights and cultural significance.', @@ -1464,6 +1564,7 @@ final headlinesFixturesData = [ // Example for USA Today: Headline( id: kHeadlineId101, + isBreaking: false, title: 'National Parks See Record Visitor Numbers', excerpt: 'A new report from the National Park Service shows a surge in visitors to parks across the USA.', @@ -1481,6 +1582,7 @@ final headlinesFixturesData = [ // Example for The Globe and Mail: Headline( id: kHeadlineId106, + isBreaking: false, title: 'Canadian Government Announces New Federal Budget', excerpt: 'The federal budget includes new spending on healthcare and climate initiatives across Canada.', @@ -1500,6 +1602,7 @@ final headlinesFixturesData = [ // Example for CNN International: Headline( id: kHeadlineId151, + isBreaking: false, title: 'Global Supply Chain Issues Persist', excerpt: 'Experts warn that global supply chain disruptions are likely to continue affecting international trade.', @@ -1518,6 +1621,7 @@ final headlinesFixturesData = [ // Example for ESPN: Headline( id: kHeadlineId201, + isBreaking: false, title: 'World Cup Finals: An Unforgettable Match', excerpt: 'The World Cup final delivered a thrilling conclusion to the tournament with a dramatic penalty shootout.', @@ -1536,6 +1640,7 @@ final headlinesFixturesData = [ // Example for Stratechery: Headline( id: kHeadlineId251, + isBreaking: false, title: 'The Future of Content and Aggregation', excerpt: 'A deep dive into how AI is changing the landscape of content creation and aggregation platforms.', @@ -1554,6 +1659,7 @@ final headlinesFixturesData = [ // Example for WhiteHouse.gov: Headline( id: kHeadlineId301, + isBreaking: false, title: 'President Signs Executive Order on Cybersecurity', excerpt: "A new executive order has been signed to strengthen the nation's cybersecurity infrastructure.", @@ -1572,6 +1678,7 @@ final headlinesFixturesData = [ // Example for Google News: Headline( id: kHeadlineId351, + isBreaking: false, title: 'This Week in Tech: A Google News Roundup', excerpt: 'Google News aggregates the top technology stories of the week, from AI breakthroughs to new gadget releases.', @@ -1590,6 +1697,7 @@ final headlinesFixturesData = [ // Example for PR Newswire: Headline( id: kHeadlineId401, + isBreaking: false, title: 'Global Tech Corp Announces Record Quarterly Earnings', excerpt: 'Global Tech Corp today announced financial results for its fiscal third quarter, reporting record revenue and profit.', @@ -1605,6 +1713,7 @@ final headlinesFixturesData = [ // Example for The Lancet: Headline( id: kHeadlineId411, + isBreaking: false, title: 'Phase 3 Trial Results for New Diabetes Drug Published', excerpt: 'A new study in The Lancet details the successful phase 3 clinical trial results for a novel type 2 diabetes treatment.', diff --git a/lib/src/fixtures/user_content_preferences.dart b/lib/src/fixtures/user_content_preferences.dart index 23756029..37b19448 100644 --- a/lib/src/fixtures/user_content_preferences.dart +++ b/lib/src/fixtures/user_content_preferences.dart @@ -23,6 +23,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[7], // Travel ], savedHeadlines: [headlinesFixturesData[0], headlinesFixturesData[10]], + notificationSubscriptions: const {}, savedFilters: [ SavedFilter( id: kSavedFilterId1, @@ -76,6 +77,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[6], // Business ], savedHeadlines: [headlinesFixturesData[2], headlinesFixturesData[3]], + notificationSubscriptions: const {}, savedFilters: [ SavedFilter( id: 'pub_saved_1', @@ -98,6 +100,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[4], // Health ], savedHeadlines: [headlinesFixturesData[4], headlinesFixturesData[5]], + notificationSubscriptions: const {}, savedFilters: const [], ), // Add preferences for users 3-10 @@ -127,6 +130,7 @@ final List userContentPreferencesFixturesData = [ headlinesFixturesData[index * 2], headlinesFixturesData[index * 2 + 1], ], + notificationSubscriptions: const {}, savedFilters: [ SavedFilter( id: 'user_${index + 3}_saved_1', diff --git a/lib/src/fixtures/users.dart b/lib/src/fixtures/users.dart index 718bf6ca..dd8a6da0 100644 --- a/lib/src/fixtures/users.dart +++ b/lib/src/fixtures/users.dart @@ -12,6 +12,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.admin, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser1Id, @@ -20,6 +21,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.publisher, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser2Id, @@ -28,6 +30,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.publisher, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser3Id, @@ -36,6 +39,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser4Id, @@ -44,6 +48,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser5Id, @@ -52,6 +57,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser6Id, @@ -60,6 +66,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser7Id, @@ -68,6 +75,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser8Id, @@ -76,6 +84,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser9Id, @@ -84,6 +93,7 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), User( id: kUser10Id, @@ -92,5 +102,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, + pushNotificationSubscriptions: const {}, ), ]; diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index d7d23703..bdb276a7 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -1,10 +1,6 @@ -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/enums/dashboard_user_role.dart'; import 'package:core/src/enums/enums.dart'; -import 'package:core/src/enums/feed_decorator_type.dart'; -import 'package:core/src/enums/notifications.dart'; -import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:core/src/models/auth/user_feed_decorator_status.dart'; +import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -72,7 +68,7 @@ class User extends Equatable { fromJson: _pushNotificationSubscriptionsFromJson, toJson: _pushNotificationSubscriptionsToJson, ) - final Map + final Map pushNotificationSubscriptions; /// Converts this User instance to JSON data. @@ -105,7 +101,7 @@ class User extends Equatable { DashboardUserRole? dashboardRole, DateTime? createdAt, Map? feedDecoratorStatus, - Map? + Map? pushNotificationSubscriptions, }) { return User( @@ -193,7 +189,7 @@ Map /// Serializes the push notification subscriptions map to JSON. Map _pushNotificationSubscriptionsToJson( - Map subscriptions, + Map subscriptions, ) { return subscriptions.map((key, value) => MapEntry(key.name, value.toJson())); } diff --git a/lib/src/models/config/feed_ad_configuration.dart b/lib/src/models/config/feed_ad_configuration.dart index 53b8ca96..140b2f18 100644 --- a/lib/src/models/config/feed_ad_configuration.dart +++ b/lib/src/models/config/feed_ad_configuration.dart @@ -1,5 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/models/config/config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 9c8f037b..85df21ff 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,5 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/models/config/config.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; diff --git a/lib/src/models/user_preferences/user_content_preferences.dart b/lib/src/models/user_preferences/user_content_preferences.dart index eed8abbe..3bb60228 100644 --- a/lib/src/models/user_preferences/user_content_preferences.dart +++ b/lib/src/models/user_preferences/user_content_preferences.dart @@ -1,4 +1,4 @@ -import 'package:core/src/enums/notifications.dart'; +import 'package:core/src/enums/enums.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/headline.dart'; import 'package:core/src/models/entities/source.dart'; @@ -62,7 +62,7 @@ class UserContentPreferences extends Equatable { final List savedFilters; /// A map defining the user's subscription status for each notification type. - final Map + final Map notificationSubscriptions; /// Converts this [UserContentPreferences] instance to a JSON map. @@ -91,7 +91,7 @@ class UserContentPreferences extends Equatable { List? followedTopics, List? savedHeadlines, List? savedFilters, - Map? + Map? notificationSubscriptions, }) { return UserContentPreferences( From 78bb9c925e9a0add88586a89c919d09ec0717bb7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 10:54:38 +0100 Subject: [PATCH 33/78] build(user_preferences): generate UserContentPreferences serializer - Add generated code for UserContentPreferences JSON serialization - Include serializers for related models: Country, Source, Topic, Headline, SavedFilter, PushNotificationSubscription - Implement enum mapping for PushNotificationSubscriptionDeliveryType --- .../user_content_preferences.g.dart | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 lib/src/models/user_preferences/user_content_preferences.g.dart diff --git a/lib/src/models/user_preferences/user_content_preferences.g.dart b/lib/src/models/user_preferences/user_content_preferences.g.dart new file mode 100644 index 00000000..d80e3803 --- /dev/null +++ b/lib/src/models/user_preferences/user_content_preferences.g.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_content_preferences.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserContentPreferences _$UserContentPreferencesFromJson( + Map json, +) => $checkedCreate('UserContentPreferences', json, ($checkedConvert) { + final val = UserContentPreferences( + id: $checkedConvert('id', (v) => v as String), + followedCountries: $checkedConvert( + 'followedCountries', + (v) => (v as List) + .map((e) => Country.fromJson(e as Map)) + .toList(), + ), + followedSources: $checkedConvert( + 'followedSources', + (v) => (v as List) + .map((e) => Source.fromJson(e as Map)) + .toList(), + ), + followedTopics: $checkedConvert( + 'followedTopics', + (v) => (v as List) + .map((e) => Topic.fromJson(e as Map)) + .toList(), + ), + savedHeadlines: $checkedConvert( + 'savedHeadlines', + (v) => (v as List) + .map((e) => Headline.fromJson(e as Map)) + .toList(), + ), + savedFilters: $checkedConvert( + 'savedFilters', + (v) => (v as List) + .map((e) => SavedFilter.fromJson(e as Map)) + .toList(), + ), + notificationSubscriptions: $checkedConvert( + 'notificationSubscriptions', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, k), + PushNotificationSubscription.fromJson(e as Map), + ), + ), + ), + ); + return val; +}); + +Map _$UserContentPreferencesToJson( + UserContentPreferences instance, +) => { + 'id': instance.id, + 'followedCountries': instance.followedCountries + .map((e) => e.toJson()) + .toList(), + 'followedSources': instance.followedSources.map((e) => e.toJson()).toList(), + 'followedTopics': instance.followedTopics.map((e) => e.toJson()).toList(), + 'savedHeadlines': instance.savedHeadlines.map((e) => e.toJson()).toList(), + 'savedFilters': instance.savedFilters.map((e) => e.toJson()).toList(), + 'notificationSubscriptions': instance.notificationSubscriptions.map( + (k, e) => MapEntry( + _$PushNotificationSubscriptionDeliveryTypeEnumMap[k]!, + e.toJson(), + ), + ), +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; From db9bd83324d7f6cb49dd39bde28e980209b8eeea Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 11:53:24 +0100 Subject: [PATCH 34/78] refactor(user): remove pushNotificationSubscriptions from User model - Remove pushNotificationSubscriptions field from User class - Remove related JSON serialization/deserialization methods - Update fixtures and constructor to reflect the removal --- lib/src/fixtures/users.dart | 11 ------- lib/src/models/auth/user.dart | 52 +-------------------------------- lib/src/models/auth/user.g.dart | 8 ----- 3 files changed, 1 insertion(+), 70 deletions(-) diff --git a/lib/src/fixtures/users.dart b/lib/src/fixtures/users.dart index dd8a6da0..718bf6ca 100644 --- a/lib/src/fixtures/users.dart +++ b/lib/src/fixtures/users.dart @@ -12,7 +12,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.admin, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser1Id, @@ -21,7 +20,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.publisher, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser2Id, @@ -30,7 +28,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.publisher, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser3Id, @@ -39,7 +36,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser4Id, @@ -48,7 +44,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser5Id, @@ -57,7 +52,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser6Id, @@ -66,7 +60,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser7Id, @@ -75,7 +68,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser8Id, @@ -84,7 +76,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser9Id, @@ -93,7 +84,6 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), User( id: kUser10Id, @@ -102,6 +92,5 @@ final List usersFixturesData = [ dashboardRole: DashboardUserRole.none, createdAt: DateTime.now(), feedDecoratorStatus: const {}, - pushNotificationSubscriptions: const {}, ), ]; diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index bdb276a7..06dcec8e 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -17,9 +17,6 @@ class User extends Equatable { /// [email], and [createdAt]. The [feedDecoratorStatus] tracks user /// interactions with in-feed actions and is guaranteed to be complete for /// all [FeedDecoratorType] values. - /// - /// The [pushNotificationSubscriptions] map holds the user's current - /// notification subscription status. const User({ required this.id, required this.appRole, @@ -27,7 +24,6 @@ class User extends Equatable { required this.email, required this.createdAt, required this.feedDecoratorStatus, - required this.pushNotificationSubscriptions, }); /// Creates a User from JSON data. @@ -63,14 +59,6 @@ class User extends Equatable { ) final Map feedDecoratorStatus; - /// A map defining the user's subscription status for each notification type. - @JsonKey( - fromJson: _pushNotificationSubscriptionsFromJson, - toJson: _pushNotificationSubscriptionsToJson, - ) - final Map - pushNotificationSubscriptions; - /// Converts this User instance to JSON data. Map toJson() => _$UserToJson(this); @@ -82,7 +70,6 @@ class User extends Equatable { dashboardRole, createdAt, feedDecoratorStatus, - pushNotificationSubscriptions, ]; @override @@ -102,7 +89,7 @@ class User extends Equatable { DateTime? createdAt, Map? feedDecoratorStatus, Map? - pushNotificationSubscriptions, + pushNotificationSubscriptions, }) { return User( id: id ?? this.id, @@ -111,8 +98,6 @@ class User extends Equatable { dashboardRole: dashboardRole ?? this.dashboardRole, createdAt: createdAt ?? this.createdAt, feedDecoratorStatus: feedDecoratorStatus ?? this.feedDecoratorStatus, - pushNotificationSubscriptions: - pushNotificationSubscriptions ?? this.pushNotificationSubscriptions, ); } } @@ -158,38 +143,3 @@ Map _feedDecoratorStatusToJson( (key, value) => MapEntry(key.name, value.toJson()), ); } - -/// Deserializes the push notification subscriptions map from JSON and ensures -/// it's complete. -Map - _pushNotificationSubscriptionsFromJson( - Map json, -) { - final existingSubscriptions = json.map((key, value) { - final deliveryType = PushNotificationSubscriptionDeliveryType.values.byName(key); - return MapEntry( - deliveryType, - PushNotificationSubscription.fromJson(value as Map), - ); - }); - - return Map.fromEntries( - PushNotificationSubscriptionDeliveryType.values.map( - (type) => MapEntry( - type, - existingSubscriptions[type] ?? - PushNotificationSubscription( - isEnabled: false, - subscribesTo: type, - ), - ), - ), - ); -} - -/// Serializes the push notification subscriptions map to JSON. -Map _pushNotificationSubscriptionsToJson( - Map subscriptions, -) { - return subscriptions.map((key, value) => MapEntry(key.name, value.toJson())); -} diff --git a/lib/src/models/auth/user.g.dart b/lib/src/models/auth/user.g.dart index 1297e010..eb2857ce 100644 --- a/lib/src/models/auth/user.g.dart +++ b/lib/src/models/auth/user.g.dart @@ -27,11 +27,6 @@ User _$UserFromJson(Map json) => 'feedDecoratorStatus', (v) => _feedDecoratorStatusFromJson(v as Map), ), - pushNotificationSubscriptions: $checkedConvert( - 'pushNotificationSubscriptions', - (v) => - _pushNotificationSubscriptionsFromJson(v as Map), - ), ); return val; }); @@ -45,9 +40,6 @@ Map _$UserToJson(User instance) => { 'feedDecoratorStatus': _feedDecoratorStatusToJson( instance.feedDecoratorStatus, ), - 'pushNotificationSubscriptions': _pushNotificationSubscriptionsToJson( - instance.pushNotificationSubscriptions, - ), }; const _$AppUserRoleEnumMap = { From 8b6f491a0747336953cae57a5af963c50a5b1dba Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 12:06:19 +0100 Subject: [PATCH 35/78] refactor(push_notification): improve serialization and deserialization - Rename `providerName` to `provider` for better naming consistency - Refactor JSON serialization/deserialization process - Add support for additional push notification providers - Improve error handling for unknown provider types --- .../config/firebase_provider_config.dart | 4 +- .../config/firebase_provider_config.g.dart | 2 +- .../config/one_signal_provider_config.dart | 4 +- .../config/one_signal_provider_config.g.dart | 2 +- .../push_notification_provider_config.dart | 56 +++++++++++-------- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart index 33d30159..1bf0b517 100644 --- a/lib/src/models/config/firebase_provider_config.dart +++ b/lib/src/models/config/firebase_provider_config.dart @@ -19,7 +19,7 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { required this.projectId, required this.clientEmail, required this.privateKey, - }) : super(providerName: PushNotificationProvider.firebase); + }) : super(provider: PushNotificationProvider.firebase); /// Creates a [FirebaseProviderConfig] from JSON data. factory FirebaseProviderConfig.fromJson(Map json) => @@ -38,7 +38,7 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { Map toJson() => _$FirebaseProviderConfigToJson(this); @override - List get props => [providerName, projectId, clientEmail, privateKey]; + List get props => [provider, projectId, clientEmail, privateKey]; /// Creates a copy of this instance with the given fields replaced. FirebaseProviderConfig copyWith({ diff --git a/lib/src/models/config/firebase_provider_config.g.dart b/lib/src/models/config/firebase_provider_config.g.dart index 9e447a1d..c900be2f 100644 --- a/lib/src/models/config/firebase_provider_config.g.dart +++ b/lib/src/models/config/firebase_provider_config.g.dart @@ -20,7 +20,7 @@ FirebaseProviderConfig _$FirebaseProviderConfigFromJson( Map _$FirebaseProviderConfigToJson( FirebaseProviderConfig instance, ) => { - 'providerName': _$PushNotificationProviderEnumMap[instance.providerName]!, + 'providerName': _$PushNotificationProviderEnumMap[instance.provider]!, 'projectId': instance.projectId, 'clientEmail': instance.clientEmail, 'privateKey': instance.privateKey, diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart index 9554d6ad..440ea093 100644 --- a/lib/src/models/config/one_signal_provider_config.dart +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -16,7 +16,7 @@ part 'one_signal_provider_config.g.dart'; class OneSignalProviderConfig extends PushNotificationProviderConfig { /// {@macro one_signal_provider_config} const OneSignalProviderConfig({required this.appId, required this.restApiKey}) - : super(providerName: PushNotificationProvider.oneSignal); + : super(provider: PushNotificationProvider.oneSignal); /// Creates a [OneSignalProviderConfig] from JSON data. factory OneSignalProviderConfig.fromJson(Map json) => @@ -32,7 +32,7 @@ class OneSignalProviderConfig extends PushNotificationProviderConfig { Map toJson() => _$OneSignalProviderConfigToJson(this); @override - List get props => [providerName, appId, restApiKey]; + List get props => [provider, appId, restApiKey]; /// Creates a copy of this instance with the given fields replaced. OneSignalProviderConfig copyWith({String? appId, String? restApiKey}) { diff --git a/lib/src/models/config/one_signal_provider_config.g.dart b/lib/src/models/config/one_signal_provider_config.g.dart index a331a791..d964f1d5 100644 --- a/lib/src/models/config/one_signal_provider_config.g.dart +++ b/lib/src/models/config/one_signal_provider_config.g.dart @@ -19,7 +19,7 @@ OneSignalProviderConfig _$OneSignalProviderConfigFromJson( Map _$OneSignalProviderConfigToJson( OneSignalProviderConfig instance, ) => { - 'providerName': _$PushNotificationProviderEnumMap[instance.providerName]!, + 'providerName': _$PushNotificationProviderEnumMap[instance.provider]!, 'appId': instance.appId, 'restApiKey': instance.restApiKey, }; diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index bbd7b0e4..01860670 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -15,44 +15,56 @@ import 'package:json_annotation/json_annotation.dart'; @JsonSerializable(createToJson: false, checked: true) abstract class PushNotificationProviderConfig extends Equatable { /// {@macro push_notification_provider_config} - const PushNotificationProviderConfig({required this.providerName}); + const PushNotificationProviderConfig({required this.provider}); - /// Creates a [PushNotificationProviderConfig] instance from a JSON map. + /// Factory method to create a [PushNotificationProviderConfig] instance from a JSON map. /// - /// This factory uses the `providerName` field in the JSON map to dispatch - /// to the correct concrete `fromJson` constructor. + /// This factory uses the `provider` field in the JSON map to dispatch to the + /// correct concrete `fromJson` constructor. /// - /// Throws [FormatException] if the `providerName` field is missing or unknown. + /// Throws [FormatException] if the `provider` field is missing or unknown. factory PushNotificationProviderConfig.fromJson(Map json) { - final providerName = $enumDecodeNullable( - PushNotificationProvider.values, - json['providerName'], - ); - - if (providerName == null) { + final provider = json['provider'] as String?; + if (provider == null) { throw const FormatException( - 'Missing or unknown "providerName" in PushNotificationProviderConfig JSON.', + 'Missing "providerName" field in FeedItem JSON.', ); } - switch (providerName) { - case PushNotificationProvider.firebase: + switch (provider) { + case 'firebase': return FirebaseProviderConfig.fromJson(json); - case PushNotificationProvider.oneSignal: + case 'oneSignal': return OneSignalProviderConfig.fromJson(json); + default: + throw FormatException('Unknown push notification provider: $provider'); } } /// The name of the provider, used as a discriminator for deserialization. - @JsonKey(includeToJson: true) - final PushNotificationProvider providerName; + final String provider; - /// Converts this instance to JSON data. + /// Static factory method to serialize a [PushNotificationProviderConfig] instance to a JSON map. + /// + /// This factory uses the `provider` field of the provided [providerConfig] to dispatch + /// to the correct concrete `toJson` method. /// - /// Concrete implementations are responsible for providing the full JSON - /// representation, including the `providerName` field. - Map toJson(); + /// Throws [FormatException] if the `provider` field is missing or unknown. + Map toJson(PushNotificationProviderConfig providerConfig) { + switch (providerConfig.provider) { + case 'firebase': + final provider = providerConfig as FirebaseProviderConfig; + return provider.toJson(); + case 'oneSignal': + final provider = providerConfig as OneSignalProviderConfig; + return provider.toJson(); + default: + throw FormatException( + 'Unknown PushNotificationProviderConfig type: ${providerConfig.provider}', + ); + } + } @override - List get props => [providerName]; + List get props => [provider]; } From 024110b8d16b979cc22453c7955823944f242639 Mon Sep 17 00:00:00 2001 From: fulleni Date: Wed, 5 Nov 2025 12:10:46 +0100 Subject: [PATCH 36/78] fix(push_notification_configs): correct JSON serialization of provider configs - Update provider field to string literal in FirebaseProviderConfig and OneSignalProviderConfig - Override toJson method to include provider field in JSON output - Improve code consistency and ensure correct JSON structure for both configurations --- lib/src/models/config/firebase_provider_config.dart | 11 ++++++++--- lib/src/models/config/one_signal_provider_config.dart | 10 +++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart index 1bf0b517..f4e1540e 100644 --- a/lib/src/models/config/firebase_provider_config.dart +++ b/lib/src/models/config/firebase_provider_config.dart @@ -19,7 +19,7 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { required this.projectId, required this.clientEmail, required this.privateKey, - }) : super(provider: PushNotificationProvider.firebase); + }) : super(provider: 'firebase'); /// Creates a [FirebaseProviderConfig] from JSON data. factory FirebaseProviderConfig.fromJson(Map json) => @@ -34,8 +34,13 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { /// The private key from the Firebase service account credentials. final String privateKey; - @override - Map toJson() => _$FirebaseProviderConfigToJson(this); + + /// Converts this [FirebaseProviderConfig] instance to a JSON map. + Map toJson() { + final json = _$FirebaseProviderConfigToJson(this); + json['provider'] = provider; + return json; + } @override List get props => [provider, projectId, clientEmail, privateKey]; diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart index 440ea093..803a2452 100644 --- a/lib/src/models/config/one_signal_provider_config.dart +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -16,7 +16,7 @@ part 'one_signal_provider_config.g.dart'; class OneSignalProviderConfig extends PushNotificationProviderConfig { /// {@macro one_signal_provider_config} const OneSignalProviderConfig({required this.appId, required this.restApiKey}) - : super(provider: PushNotificationProvider.oneSignal); + : super(provider: 'oneSignal'); /// Creates a [OneSignalProviderConfig] from JSON data. factory OneSignalProviderConfig.fromJson(Map json) => @@ -28,8 +28,12 @@ class OneSignalProviderConfig extends PushNotificationProviderConfig { /// The OneSignal REST API Key for server-side operations. final String restApiKey; - @override - Map toJson() => _$OneSignalProviderConfigToJson(this); + /// Converts this [FirebaseProviderConfig] instance to a JSON map. + Map toJson() { + final json = _$OneSignalProviderConfigToJson(this); + json['provider'] = provider; + return json; + } @override List get props => [provider, appId, restApiKey]; From 90328ff611f359796ebc94031558a6f3d8b5e530 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:33:20 +0100 Subject: [PATCH 37/78] fix(config): override toJson to include provider in Firebase config - Remove unused import - Add @override annotation to toJson method - Include provider discriminator in JSON output --- lib/src/models/config/firebase_provider_config.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart index f4e1540e..f8e29907 100644 --- a/lib/src/models/config/firebase_provider_config.dart +++ b/lib/src/models/config/firebase_provider_config.dart @@ -1,4 +1,3 @@ -import 'package:core/src/enums/enums.dart'; import 'package:core/src/models/config/config.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -34,8 +33,9 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { /// The private key from the Firebase service account credentials. final String privateKey; - - /// Converts this [FirebaseProviderConfig] instance to a JSON map. + /// Converts this [FirebaseProviderConfig] instance to a JSON map, + /// including the provider discriminator. + @override Map toJson() { final json = _$FirebaseProviderConfigToJson(this); json['provider'] = provider; From 9fb704a79b7ba40ffec4d3a390b943289eeb1308 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:33:37 +0100 Subject: [PATCH 38/78] refactor(firebase): improve OneSignalProviderConfig toJson implementation - Remove unused import - Add @override annotation to toJson method - Update method documentation to reflect that it includes the provider discriminator --- lib/src/models/config/one_signal_provider_config.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart index 803a2452..f13f5f02 100644 --- a/lib/src/models/config/one_signal_provider_config.dart +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -1,4 +1,3 @@ -import 'package:core/src/enums/enums.dart'; import 'package:core/src/models/config/config.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -28,7 +27,9 @@ class OneSignalProviderConfig extends PushNotificationProviderConfig { /// The OneSignal REST API Key for server-side operations. final String restApiKey; - /// Converts this [FirebaseProviderConfig] instance to a JSON map. + /// Converts this [OneSignalProviderConfig] instance to a JSON map, + /// including the provider discriminator. + @override Map toJson() { final json = _$OneSignalProviderConfigToJson(this); json['provider'] = provider; From 2debe2cb0d96f66af32dcafb3c8486e7f487c5e7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:34:12 +0100 Subject: [PATCH 39/78] refactor(core): improve PushNotificationProviderConfig design - Remove unnecessary imports and annotations - Simplify error handling in fromJson method - Refactor toJson method to be an instance method - Add documentation for abstract class and its methods --- .../push_notification_provider_config.dart | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index 01860670..2e34387b 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -1,8 +1,6 @@ -import 'package:core/src/enums/push_notification_provider.dart'; import 'package:core/src/models/config/firebase_provider_config.dart'; import 'package:core/src/models/config/one_signal_provider_config.dart'; import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; /// {@template push_notification_provider_config} /// An abstract base class for push notification provider configurations. @@ -12,7 +10,6 @@ import 'package:json_annotation/json_annotation.dart'; /// [FirebaseProviderConfig], [OneSignalProviderConfig]) based on a /// `providerName` discriminator field in the JSON data. /// {@endtemplate} -@JsonSerializable(createToJson: false, checked: true) abstract class PushNotificationProviderConfig extends Equatable { /// {@macro push_notification_provider_config} const PushNotificationProviderConfig({required this.provider}); @@ -26,9 +23,7 @@ abstract class PushNotificationProviderConfig extends Equatable { factory PushNotificationProviderConfig.fromJson(Map json) { final provider = json['provider'] as String?; if (provider == null) { - throw const FormatException( - 'Missing "providerName" field in FeedItem JSON.', - ); + throw const FormatException('Missing "provider" field in FeedItem JSON.'); } switch (provider) { @@ -41,16 +36,17 @@ abstract class PushNotificationProviderConfig extends Equatable { } } - /// The name of the provider, used as a discriminator for deserialization. - final String provider; - - /// Static factory method to serialize a [PushNotificationProviderConfig] instance to a JSON map. + /// Static method to serialize a [PushNotificationProviderConfig] instance + /// to a JSON map. /// - /// This factory uses the `provider` field of the provided [providerConfig] to dispatch - /// to the correct concrete `toJson` method. + /// This method acts as a dispatcher, using the `provider` field of the + /// provided [providerConfig] to delegate to the correct concrete `toJson` + /// instance method. /// /// Throws [FormatException] if the `provider` field is missing or unknown. - Map toJson(PushNotificationProviderConfig providerConfig) { + static Map toJson( + PushNotificationProviderConfig providerConfig, + ) { switch (providerConfig.provider) { case 'firebase': final provider = providerConfig as FirebaseProviderConfig; @@ -65,6 +61,15 @@ abstract class PushNotificationProviderConfig extends Equatable { } } + /// Converts this [PushNotificationProviderConfig] instance to a JSON map. + /// + /// Concrete implementations must override this method to provide their + /// specific serialization logic. + Map toJson(); + + /// The name of the provider, used as a discriminator for deserialization. + final String provider; + @override List get props => [provider]; } From 651182e0172f28b14dfaaba3b4c75cbbba2e60a0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:34:28 +0100 Subject: [PATCH 40/78] build(serialization): generate --- .../config/firebase_provider_config.g.dart | 6 -- .../config/one_signal_provider_config.g.dart | 6 -- .../config/push_notification_config.g.dart | 66 ------------------- 3 files changed, 78 deletions(-) delete mode 100644 lib/src/models/config/push_notification_config.g.dart diff --git a/lib/src/models/config/firebase_provider_config.g.dart b/lib/src/models/config/firebase_provider_config.g.dart index c900be2f..43853d5d 100644 --- a/lib/src/models/config/firebase_provider_config.g.dart +++ b/lib/src/models/config/firebase_provider_config.g.dart @@ -20,13 +20,7 @@ FirebaseProviderConfig _$FirebaseProviderConfigFromJson( Map _$FirebaseProviderConfigToJson( FirebaseProviderConfig instance, ) => { - 'providerName': _$PushNotificationProviderEnumMap[instance.provider]!, 'projectId': instance.projectId, 'clientEmail': instance.clientEmail, 'privateKey': instance.privateKey, }; - -const _$PushNotificationProviderEnumMap = { - PushNotificationProvider.firebase: 'firebase', - PushNotificationProvider.oneSignal: 'oneSignal', -}; diff --git a/lib/src/models/config/one_signal_provider_config.g.dart b/lib/src/models/config/one_signal_provider_config.g.dart index d964f1d5..0262ecd4 100644 --- a/lib/src/models/config/one_signal_provider_config.g.dart +++ b/lib/src/models/config/one_signal_provider_config.g.dart @@ -19,12 +19,6 @@ OneSignalProviderConfig _$OneSignalProviderConfigFromJson( Map _$OneSignalProviderConfigToJson( OneSignalProviderConfig instance, ) => { - 'providerName': _$PushNotificationProviderEnumMap[instance.provider]!, 'appId': instance.appId, 'restApiKey': instance.restApiKey, }; - -const _$PushNotificationProviderEnumMap = { - PushNotificationProvider.firebase: 'firebase', - PushNotificationProvider.oneSignal: 'oneSignal', -}; diff --git a/lib/src/models/config/push_notification_config.g.dart b/lib/src/models/config/push_notification_config.g.dart deleted file mode 100644 index 241809c7..00000000 --- a/lib/src/models/config/push_notification_config.g.dart +++ /dev/null @@ -1,66 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'push_notification_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PushNotificationConfig _$PushNotificationConfigFromJson( - Map json, -) => $checkedCreate('PushNotificationConfig', json, ($checkedConvert) { - final val = PushNotificationConfig( - enabled: $checkedConvert('enabled', (v) => v as bool), - primaryProvider: $checkedConvert( - 'primaryProvider', - (v) => $enumDecode(_$PushNotificationProviderEnumMap, v), - ), - providerConfigs: $checkedConvert( - 'providerConfigs', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$PushNotificationProviderEnumMap, k), - PushNotificationProviderConfig.fromJson(e as Map), - ), - ), - ), - deliveryConfigs: $checkedConvert( - 'deliveryConfigs', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, k), - PushNotificationDeliveryConfig.fromJson(e as Map), - ), - ), - ), - ); - return val; -}); - -Map _$PushNotificationConfigToJson( - PushNotificationConfig instance, -) => { - 'enabled': instance.enabled, - 'primaryProvider': - _$PushNotificationProviderEnumMap[instance.primaryProvider]!, - 'providerConfigs': instance.providerConfigs.map( - (k, e) => MapEntry(_$PushNotificationProviderEnumMap[k]!, e.toJson()), - ), - 'deliveryConfigs': instance.deliveryConfigs.map( - (k, e) => MapEntry( - _$PushNotificationSubscriptionDeliveryTypeEnumMap[k]!, - e.toJson(), - ), - ), -}; - -const _$PushNotificationProviderEnumMap = { - PushNotificationProvider.firebase: 'firebase', - PushNotificationProvider.oneSignal: 'oneSignal', -}; - -const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { - PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', - PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', -}; From c541d30f2cbad3d0fb3f875000a0918989ecd0a9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:53:08 +0100 Subject: [PATCH 41/78] refactor(config): remove redundant toJson method from PushNotificationProviderConfig - Removed the abstract toJson method from PushNotificationProviderConfig class - This change simplifies the class structure and removes unnecessary code - Concrete implementations can still provide their specific serialization logic --- .../models/config/push_notification_provider_config.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index 2e34387b..94ef72d8 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -61,12 +61,6 @@ abstract class PushNotificationProviderConfig extends Equatable { } } - /// Converts this [PushNotificationProviderConfig] instance to a JSON map. - /// - /// Concrete implementations must override this method to provide their - /// specific serialization logic. - Map toJson(); - /// The name of the provider, used as a discriminator for deserialization. final String provider; From 692958e52cf9490dd1c72f01481172e5570c90a8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 06:53:32 +0100 Subject: [PATCH 42/78] feat(push_notification_config): add json serialization for providerConfigs - Add @JsonKey annotation to providerConfigs map - Implement toJson and fromJson methods for PushNotificationProviderConfig --- lib/src/models/config/push_notification_config.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index 2f152f31..621c90dc 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -42,6 +42,10 @@ class PushNotificationConfig extends Equatable { /// /// The key is the provider type, and the value is the corresponding /// polymorphic configuration object (e.g., [FirebaseProviderConfig]). + @JsonKey( + toJson: PushNotificationProviderConfig.toJson, + fromJson: PushNotificationProviderConfig.fromJson, + ) final Map providerConfigs; From dba28188ea14fa7d8ea06924c5a14a9dc7271127 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 07:30:29 +0100 Subject: [PATCH 43/78] refactor(push): remove redundant comments and annotations - Remove unnecessary comments above providerConfigs map - Remove @JsonKey annotation as it's not needed when using freezed --- lib/src/models/config/push_notification_config.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index 621c90dc..0f395c0e 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -39,13 +39,6 @@ class PushNotificationConfig extends Equatable { final PushNotificationProvider primaryProvider; /// A map holding the credentials for each potential push provider. - /// - /// The key is the provider type, and the value is the corresponding - /// polymorphic configuration object (e.g., [FirebaseProviderConfig]). - @JsonKey( - toJson: PushNotificationProviderConfig.toJson, - fromJson: PushNotificationProviderConfig.fromJson, - ) final Map providerConfigs; From f6966417e57fdc4a8815b247992ece17749b7d58 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 07:30:42 +0100 Subject: [PATCH 44/78] style(push): remove unnecessary override annotations - Removed @override annotations from toJson methods in FirebaseProviderConfig and OneSignalProviderConfig classes. - This change simplifies the code without affecting functionality. --- lib/src/models/config/firebase_provider_config.dart | 1 - lib/src/models/config/one_signal_provider_config.dart | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/src/models/config/firebase_provider_config.dart b/lib/src/models/config/firebase_provider_config.dart index f8e29907..b1ec5dbe 100644 --- a/lib/src/models/config/firebase_provider_config.dart +++ b/lib/src/models/config/firebase_provider_config.dart @@ -35,7 +35,6 @@ class FirebaseProviderConfig extends PushNotificationProviderConfig { /// Converts this [FirebaseProviderConfig] instance to a JSON map, /// including the provider discriminator. - @override Map toJson() { final json = _$FirebaseProviderConfigToJson(this); json['provider'] = provider; diff --git a/lib/src/models/config/one_signal_provider_config.dart b/lib/src/models/config/one_signal_provider_config.dart index f13f5f02..8f128133 100644 --- a/lib/src/models/config/one_signal_provider_config.dart +++ b/lib/src/models/config/one_signal_provider_config.dart @@ -29,7 +29,6 @@ class OneSignalProviderConfig extends PushNotificationProviderConfig { /// Converts this [OneSignalProviderConfig] instance to a JSON map, /// including the provider discriminator. - @override Map toJson() { final json = _$OneSignalProviderConfigToJson(this); json['provider'] = provider; From 6e7a5bfb980b66e7540a1724e52cb403444f2192 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 07:30:54 +0100 Subject: [PATCH 45/78] fix(remote_configs): update push notification delivery config models and fixtures - Add 'enabled' field to PushNotificationDeliveryConfig - Update visibleTo map to use PushNotificationDeliveryRoleConfig - Refactor remoteConfigsFixturesData to use new PushNotificationSubscriptionDeliveryType enum - Update JSON serialization and deserialization for new fields and types --- lib/src/fixtures/remote_configs.dart | 75 +++++++++++-------- .../push_notification_delivery_config.dart | 15 +++- .../push_notification_delivery_config.g.dart | 2 + ...ush_notification_delivery_role_config.dart | 13 ++-- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index e74ef587..1196205c 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -1,8 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/enums/push_notification_provider.dart'; -import 'package:core/src/models/config/firebase_provider_config.dart'; -import 'package:core/src/models/config/one_signal_provider_config.dart'; -import 'package:core/src/models/config/push_notification_config.dart'; /// A list of initial remote config data to be loaded into the in-memory /// remote config repository. @@ -129,36 +125,53 @@ final List remoteConfigsFixturesData = [ ), }, deliveryConfigs: { - SubscriptionDeliveryType.dailyDigest : NotificationDeliveryConfig( - enabled: true, - visibleTo: { - AppUserRole.guestUser: NotificationSubscriptionLimit( - isAllowed: true, - maxSubscriptions: 1, + PushNotificationSubscriptionDeliveryType.breakingOnly: + PushNotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 1, + ), + AppUserRole.standardUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 3, + ), + AppUserRole.premiumUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 10, + ), + }, ), - AppUserRole.standardUser: NotificationSubscriptionLimit( - isAllowed: true, - maxSubscriptions: 3, - ), - AppUserRole.premiumUser: NotificationSubscriptionLimit( - isAllowed: true, - maxSubscriptions: 10, + + PushNotificationSubscriptionDeliveryType.dailyDigest: + PushNotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 0, + ), + AppUserRole.standardUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 2, + ), + AppUserRole.premiumUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 10, + ), + }, ), - }, - ), - SubscriptionDeliveryType.breakingOnly: NotificationDeliveryConfig( - enabled: true, - visibleTo: { - AppUserRole.guestUser: - NotificationSubscriptionLimit(isAllowed: false), - AppUserRole.standardUser: NotificationSubscriptionLimit( - isAllowed: true, - maxSubscriptions: 5, + + PushNotificationSubscriptionDeliveryType.weeklyRoundup: + PushNotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 0, + ), + AppUserRole.standardUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 2, + ), + AppUserRole.premiumUser: PushNotificationDeliveryRoleConfig( + subscriptionLimit: 10, + ), + }, ), - AppUserRole.premiumUser: - NotificationSubscriptionLimit(isAllowed: true), - }, - ), }, ), ), diff --git a/lib/src/models/config/push_notification_delivery_config.dart b/lib/src/models/config/push_notification_delivery_config.dart index 4257875b..14ed31e4 100644 --- a/lib/src/models/config/push_notification_delivery_config.dart +++ b/lib/src/models/config/push_notification_delivery_config.dart @@ -18,12 +18,17 @@ part 'push_notification_delivery_config.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class PushNotificationDeliveryConfig extends Equatable { /// {@macro push_notification_delivery_config} - const PushNotificationDeliveryConfig({required this.visibleTo}); + const PushNotificationDeliveryConfig({ + required this.enabled, + required this.visibleTo, + }); /// Creates a [PushNotificationDeliveryConfig] from JSON data. factory PushNotificationDeliveryConfig.fromJson(Map json) => _$PushNotificationDeliveryConfigFromJson(json); + final bool enabled; + /// A map that defines the visibility and limits for this delivery type /// based on user roles. /// @@ -35,12 +40,16 @@ class PushNotificationDeliveryConfig extends Equatable { Map toJson() => _$PushNotificationDeliveryConfigToJson(this); @override - List get props => [visibleTo]; + List get props => [enabled, visibleTo]; /// Creates a copy of this instance with the given fields replaced. PushNotificationDeliveryConfig copyWith({ + bool? enabled, Map? visibleTo, }) { - return PushNotificationDeliveryConfig(visibleTo: visibleTo ?? this.visibleTo); + return PushNotificationDeliveryConfig( + enabled: enabled ?? this.enabled, + visibleTo: visibleTo ?? this.visibleTo, + ); } } diff --git a/lib/src/models/config/push_notification_delivery_config.g.dart b/lib/src/models/config/push_notification_delivery_config.g.dart index 3e21896b..114d46c3 100644 --- a/lib/src/models/config/push_notification_delivery_config.g.dart +++ b/lib/src/models/config/push_notification_delivery_config.g.dart @@ -10,6 +10,7 @@ PushNotificationDeliveryConfig _$PushNotificationDeliveryConfigFromJson( Map json, ) => $checkedCreate('PushNotificationDeliveryConfig', json, ($checkedConvert) { final val = PushNotificationDeliveryConfig( + enabled: $checkedConvert('enabled', (v) => v as bool), visibleTo: $checkedConvert( 'visibleTo', (v) => (v as Map).map( @@ -28,6 +29,7 @@ PushNotificationDeliveryConfig _$PushNotificationDeliveryConfigFromJson( Map _$PushNotificationDeliveryConfigToJson( PushNotificationDeliveryConfig instance, ) => { + 'enabled': instance.enabled, 'visibleTo': instance.visibleTo.map( (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), ), diff --git a/lib/src/models/config/push_notification_delivery_role_config.dart b/lib/src/models/config/push_notification_delivery_role_config.dart index 18ce4e46..9e10a2ee 100644 --- a/lib/src/models/config/push_notification_delivery_role_config.dart +++ b/lib/src/models/config/push_notification_delivery_role_config.dart @@ -19,18 +19,19 @@ class PushNotificationDeliveryRoleConfig extends Equatable { const PushNotificationDeliveryRoleConfig({required this.subscriptionLimit}); /// Creates a [PushNotificationDeliveryRoleConfig] from JSON data. - factory PushNotificationDeliveryRoleConfig.fromJson(Map json) => - _$PushNotificationDeliveryRoleConfigFromJson(json); + factory PushNotificationDeliveryRoleConfig.fromJson( + Map json, + ) => _$PushNotificationDeliveryRoleConfigFromJson(json); - /// The maximum number of subscriptions of this specific delivery type that - /// a user with this role is allowed to create. + /// The subscription limit for a user with this role. final int subscriptionLimit; /// Converts this instance to JSON data. - Map toJson() => _$PushNotificationDeliveryRoleConfigToJson(this); + Map toJson() => + _$PushNotificationDeliveryRoleConfigToJson(this); @override - List get props => [subscriptionLimit]; + List get props => [subscriptionLimit]; /// Creates a copy of this instance with the given fields replaced. PushNotificationDeliveryRoleConfig copyWith({int? subscriptionLimit}) { From 122ca7c906228b9f8f1a74d2ef846e9aa98e36f5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 07:55:54 +0100 Subject: [PATCH 46/78] feat(PushNotificationConfig): implement custom serializers for providerConfigs map - Add custom fromJson and toJson methods for the providerConfigs map - Implement manual deserialization and serialization of PushNotificationProvider enum keys - Delegate value deserialization/serialization to PushNotificationProviderConfig - Update class documentation to explain the polymorphic nature of providerConfigs --- .../config/push_notification_config.dart | 48 +++++++++++++- .../config/push_notification_config.g.dart | 63 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 lib/src/models/config/push_notification_config.g.dart diff --git a/lib/src/models/config/push_notification_config.dart b/lib/src/models/config/push_notification_config.dart index 0f395c0e..38260625 100644 --- a/lib/src/models/config/push_notification_config.dart +++ b/lib/src/models/config/push_notification_config.dart @@ -39,17 +39,57 @@ class PushNotificationConfig extends Equatable { final PushNotificationProvider primaryProvider; /// A map holding the credentials for each potential push provider. + /// + /// This uses custom fromJson/toJson helpers to handle the polymorphic nature + /// of [PushNotificationProviderConfig] within a map structure, which is not + /// natively supported by `json_serializable`. + @JsonKey(fromJson: _providerConfigsFromJson, toJson: _providerConfigsToJson) final Map providerConfigs; /// A map to globally enable or disable each specific notification type /// and define its role-based limits using the `visibleTo` pattern. - final Map + final Map< + PushNotificationSubscriptionDeliveryType, + PushNotificationDeliveryConfig + > deliveryConfigs; /// Converts this [PushNotificationConfig] instance to JSON data. Map toJson() => _$PushNotificationConfigToJson(this); + /// A custom deserializer for the `providerConfigs` map. + /// + /// This function manually iterates through the incoming JSON map, converting + /// string keys into [PushNotificationProvider] enum values and delegating + /// the value deserialization to the polymorphic + /// [PushNotificationProviderConfig.fromJson] factory. + static Map + _providerConfigsFromJson(Map json) { + return json.map((key, value) { + final provider = PushNotificationProvider.values.byName(key); + return MapEntry( + provider, + PushNotificationProviderConfig.fromJson(value as Map), + ); + }); + } + + /// A custom serializer for the `providerConfigs` map. + /// + /// This function manually iterates through the map, converting the + /// [PushNotificationProvider] enum keys into strings and delegating the + /// value serialization to the polymorphic + /// [PushNotificationProviderConfig.toJson] static method. + static Map _providerConfigsToJson( + Map configs, + ) { + return configs.map( + (key, value) => + MapEntry(key.name, PushNotificationProviderConfig.toJson(value)), + ); + } + @override List get props => [ enabled, @@ -65,7 +105,11 @@ class PushNotificationConfig extends Equatable { PushNotificationProvider? primaryProvider, Map? providerConfigs, - Map? deliveryConfigs, + Map< + PushNotificationSubscriptionDeliveryType, + PushNotificationDeliveryConfig + >? + deliveryConfigs, }) { return PushNotificationConfig( enabled: enabled ?? this.enabled, diff --git a/lib/src/models/config/push_notification_config.g.dart b/lib/src/models/config/push_notification_config.g.dart new file mode 100644 index 00000000..7dd46398 --- /dev/null +++ b/lib/src/models/config/push_notification_config.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'push_notification_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PushNotificationConfig _$PushNotificationConfigFromJson( + Map json, +) => $checkedCreate('PushNotificationConfig', json, ($checkedConvert) { + final val = PushNotificationConfig( + enabled: $checkedConvert('enabled', (v) => v as bool), + primaryProvider: $checkedConvert( + 'primaryProvider', + (v) => $enumDecode(_$PushNotificationProviderEnumMap, v), + ), + providerConfigs: $checkedConvert( + 'providerConfigs', + (v) => PushNotificationConfig._providerConfigsFromJson( + v as Map, + ), + ), + deliveryConfigs: $checkedConvert( + 'deliveryConfigs', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, k), + PushNotificationDeliveryConfig.fromJson(e as Map), + ), + ), + ), + ); + return val; +}); + +Map _$PushNotificationConfigToJson( + PushNotificationConfig instance, +) => { + 'enabled': instance.enabled, + 'primaryProvider': + _$PushNotificationProviderEnumMap[instance.primaryProvider]!, + 'providerConfigs': PushNotificationConfig._providerConfigsToJson( + instance.providerConfigs, + ), + 'deliveryConfigs': instance.deliveryConfigs.map( + (k, e) => MapEntry( + _$PushNotificationSubscriptionDeliveryTypeEnumMap[k]!, + e.toJson(), + ), + ), +}; + +const _$PushNotificationProviderEnumMap = { + PushNotificationProvider.firebase: 'firebase', + PushNotificationProvider.oneSignal: 'oneSignal', +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; From 5e52e03cbd7a83f9940426f8a7b18843d2432ed4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:02:39 +0100 Subject: [PATCH 47/78] test(core): add unit test for PushNotificationSubscriptionDeliveryType enum - Create new test file for PushNotificationSubscriptionDeliveryType enum - Implement test to verify the presence of all expected enum values - Ensure the enum contains exactly 3 values --- ...ation_subscription_delivery_type_test.dart | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/src/enums/push_notification_subscription_delivery_type_test.dart diff --git a/test/src/enums/push_notification_subscription_delivery_type_test.dart b/test/src/enums/push_notification_subscription_delivery_type_test.dart new file mode 100644 index 00000000..c27713dc --- /dev/null +++ b/test/src/enums/push_notification_subscription_delivery_type_test.dart @@ -0,0 +1,27 @@ +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationSubscriptionDeliveryType', () { + test('should contain all expected values', () { + // Arrange: The expected set of enum values. + const expectedValues = { + PushNotificationSubscriptionDeliveryType.breakingOnly, + PushNotificationSubscriptionDeliveryType.dailyDigest, + PushNotificationSubscriptionDeliveryType.weeklyRoundup, + }; + + // Assert + // Check that the number of enum values is correct. + expect(PushNotificationSubscriptionDeliveryType.values.length, 3); + + // Check that all expected values are present in the enum's values list. + for (final value in expectedValues) { + expect( + PushNotificationSubscriptionDeliveryType.values, + contains(value), + ); + } + }); + }); +} From 73e4a399cc9a90dbe0784f30e6750e8bf2746d16 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:03:34 +0100 Subject: [PATCH 48/78] test(core): add unit test for PushNotificationProvider enum - Create a new test file for PushNotificationProvider enum - Add a test case to verify the presence of all expected enum values - Ensure the enum contains exactly two values: firebase and oneSignal --- .../push_notification_provider_test.dart | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 test/src/enums/push_notification_provider_test.dart diff --git a/test/src/enums/push_notification_provider_test.dart b/test/src/enums/push_notification_provider_test.dart new file mode 100644 index 00000000..effc2fa7 --- /dev/null +++ b/test/src/enums/push_notification_provider_test.dart @@ -0,0 +1,21 @@ +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationProvider', () { + test('should contain all expected values', () { + // Arrange: The expected set of enum values. + const expectedValues = { + PushNotificationProvider.firebase, + PushNotificationProvider.oneSignal, + }; + + // Assert + // Check that the number of enum values is correct. + expect(PushNotificationProvider.values.length, 2); + + // Check that all expected values are present in the enum's values list. + expect(PushNotificationProvider.values, containsAll(expectedValues)); + }); + }); +} From 6d5cb6233b3db4057b2650508b402429646e9cf5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:12:02 +0100 Subject: [PATCH 49/78] test(core): add PushNotificationSubscription model tests - Create unit tests for PushNotificationSubscription model - Test value equality, props, JSON serialization/deserialization - Implement tests for copyWith functionality --- .../push_notification_subscription_test.dart | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/src/models/push_notifications/push_notification_subscription_test.dart diff --git a/test/src/models/push_notifications/push_notification_subscription_test.dart b/test/src/models/push_notifications/push_notification_subscription_test.dart new file mode 100644 index 00000000..48eb5216 --- /dev/null +++ b/test/src/models/push_notifications/push_notification_subscription_test.dart @@ -0,0 +1,114 @@ +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationSubscription', () { + // Since a specific fixture file for PushNotificationSubscription was not + // provided, sample data is created here for testing purposes. + const id = 'subscription-id-1'; + const userId = 'user-id-1'; + const name = 'Tech News'; + const topics = ['topic-1', 'topic-2']; + const sources = ['source-1']; + const countries = ['country-1']; + const deliveryTypes = { + PushNotificationSubscriptionDeliveryType.breakingOnly, + PushNotificationSubscriptionDeliveryType.dailyDigest, + }; + + const subscription = PushNotificationSubscription( + id: id, + userId: userId, + name: name, + topics: topics, + sources: sources, + countries: countries, + deliveryTypes: deliveryTypes, + ); + + final json = { + 'id': id, + 'userId': userId, + 'name': name, + 'topics': topics, + 'sources': sources, + 'countries': countries, + 'deliveryTypes': ['breakingOnly', 'dailyDigest'], + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherSubscription = PushNotificationSubscription( + id: id, + userId: userId, + name: name, + topics: topics, + sources: sources, + countries: countries, + deliveryTypes: deliveryTypes, + ); + + // Assert: The two instances should be equal. + expect(subscription, equals(anotherSubscription)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields. + expect( + subscription.props, + equals([id, userId, name, topics, sources, countries, deliveryTypes]), + ); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = PushNotificationSubscription.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(subscription)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = subscription.toJson(); + + // Assert: The resulting map should match the original JSON map. + // We sort the deliveryTypes list to ensure consistent comparison. + (toJson['deliveryTypes'] as List).sort(); + (json['deliveryTypes'] as List).sort(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define the updated values. + const newName = 'Updated Tech News'; + const newTopics = ['topic-3']; + + // Act: Create a copy with the updated values. + final copiedSubscription = subscription.copyWith( + name: newName, + topics: newTopics, + ); + + // Assert: The new instance should have the updated values. + expect(copiedSubscription.name, equals(newName)); + expect(copiedSubscription.topics, equals(newTopics)); + + // Assert: The original instance should remain unchanged. + expect(subscription.name, equals(name)); + expect(subscription.topics, equals(topics)); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedSubscription = subscription.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedSubscription, equals(subscription)); + }, + ); + }); +} From 3fa58e346ec40561abb1857baeddc72a7e140635 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:12:19 +0100 Subject: [PATCH 50/78] test(push_notifications): add unit tests for PushNotificationPayload - Implement comprehensive unit tests for PushNotificationPayload model - Cover value equality, props, JSON serialization/deserialization, and copyWith functionality --- .../push_notification_payload_test.dart | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 test/src/models/push_notifications/push_notification_payload_test.dart diff --git a/test/src/models/push_notifications/push_notification_payload_test.dart b/test/src/models/push_notifications/push_notification_payload_test.dart new file mode 100644 index 00000000..25f88d2b --- /dev/null +++ b/test/src/models/push_notifications/push_notification_payload_test.dart @@ -0,0 +1,92 @@ +import 'package:core/src/models/push_notifications/push_notification_payload.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationPayload', () { + const title = 'Breaking News'; + const body = 'This is a test breaking news notification.'; + const imageUrl = 'https://example.com/image.jpg'; + const data = { + 'contentType': 'headline', + 'id': 'headline-123', + }; + + const payload = PushNotificationPayload( + title: title, + body: body, + imageUrl: imageUrl, + data: data, + ); + + final json = { + 'title': title, + 'body': body, + 'imageUrl': imageUrl, + 'data': data, + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherPayload = PushNotificationPayload( + title: title, + body: body, + imageUrl: imageUrl, + data: data, + ); + + // Assert: The two instances should be equal. + expect(payload, equals(anotherPayload)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields. + expect(payload.props, equals([title, body, imageUrl, data])); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = PushNotificationPayload.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(payload)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = payload.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define the updated values. + const newTitle = 'Updated News'; + const newBody = 'Updated body content.'; + + // Act: Create a copy with the updated values. + final copiedPayload = payload.copyWith(title: newTitle, body: newBody); + + // Assert: The new instance should have the updated values. + expect(copiedPayload.title, equals(newTitle)); + expect(copiedPayload.body, equals(newBody)); + expect(copiedPayload.imageUrl, equals(imageUrl)); // Unchanged + expect(copiedPayload.data, equals(data)); // Unchanged + + // Assert: The original instance should remain unchanged. + expect(payload.title, equals(title)); + expect(payload.body, equals(body)); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedPayload = payload.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedPayload, equals(payload)); + }, + ); + }); +} From 0585e19f2e8173a0faf91575ffaa13885ab761f6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:12:25 +0100 Subject: [PATCH 51/78] style(test): remove unnecessary comment in push notification subscription test - Deleted comment about missing fixture file, as sample data is being used - This change simplifies the test file and removes outdated information --- .../push_notification_subscription_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/src/models/push_notifications/push_notification_subscription_test.dart b/test/src/models/push_notifications/push_notification_subscription_test.dart index 48eb5216..82c97bce 100644 --- a/test/src/models/push_notifications/push_notification_subscription_test.dart +++ b/test/src/models/push_notifications/push_notification_subscription_test.dart @@ -4,9 +4,6 @@ import 'package:test/test.dart'; void main() { group('PushNotificationSubscription', () { - // Since a specific fixture file for PushNotificationSubscription was not - // provided, sample data is created here for testing purposes. - const id = 'subscription-id-1'; const userId = 'user-id-1'; const name = 'Tech News'; const topics = ['topic-1', 'topic-2']; From 682ada0a71ff08cba308da7b417c740abeedf6c2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 08:13:51 +0100 Subject: [PATCH 52/78] test(core): add PushNotificationDevice model tests - Implement comprehensive unit tests for PushNotificationDevice model - Cover value equality, property correctness, JSON serialization/deserialization - Test copyWith functionality with and without argument updates --- .../push_notification_device_test.dart | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 test/src/models/push_notifications/push_notification_device_test.dart diff --git a/test/src/models/push_notifications/push_notification_device_test.dart b/test/src/models/push_notifications/push_notification_device_test.dart new file mode 100644 index 00000000..9f3d39a4 --- /dev/null +++ b/test/src/models/push_notifications/push_notification_device_test.dart @@ -0,0 +1,107 @@ +import 'package:core/src/enums/device_platform.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/models/push_notifications/push_notification_device.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationDevice', () { + const id = 'device-id-1'; + const userId = 'user-id-1'; + const token = 'device-token-string'; + const provider = PushNotificationProvider.firebase; + const platform = DevicePlatform.android; + final createdAt = DateTime.parse('2023-01-01T10:00:00.000Z'); + final updatedAt = DateTime.parse('2023-01-01T11:00:00.000Z'); + + final device = PushNotificationDevice( + id: id, + userId: userId, + token: token, + provider: provider, + platform: platform, + createdAt: createdAt, + updatedAt: updatedAt, + ); + + final json = { + 'id': id, + 'userId': userId, + 'token': token, + 'provider': 'firebase', + 'platform': 'android', + 'createdAt': '2023-01-01T10:00:00.000Z', + 'updatedAt': '2023-01-01T11:00:00.000Z', + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + final anotherDevice = PushNotificationDevice( + id: id, + userId: userId, + token: token, + provider: provider, + platform: platform, + createdAt: createdAt, + updatedAt: updatedAt, + ); + + // Assert: The two instances should be equal. + expect(device, equals(anotherDevice)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields. + expect( + device.props, + equals([id, userId, token, provider, platform, createdAt, updatedAt]), + ); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = PushNotificationDevice.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(device)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = device.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define the updated values. + const newToken = 'new-device-token'; + final newUpdatedAt = DateTime.parse('2023-02-01T12:00:00.000Z'); + + // Act: Create a copy with the updated values. + final copiedDevice = device.copyWith( + token: newToken, + updatedAt: newUpdatedAt, + ); + + // Assert: The new instance should have the updated values. + expect(copiedDevice.token, equals(newToken)); + expect(copiedDevice.updatedAt, equals(newUpdatedAt)); + + // Assert: The original instance should remain unchanged. + expect(device.token, equals(token)); + expect(device.updatedAt, equals(updatedAt)); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedDevice = device.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedDevice, equals(device)); + }, + ); + }); +} From 1fc17fe2dd87ea6faebbfc1b444c8cc7c9fcbf8a Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:01:58 +0100 Subject: [PATCH 53/78] test: add constant for push notification subscription ID - Introduce a new constant 'id' to represent a push notification subscription ID - This change improves test readability and maintains consistency with other test constants --- .../push_notifications/push_notification_subscription_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/models/push_notifications/push_notification_subscription_test.dart b/test/src/models/push_notifications/push_notification_subscription_test.dart index 82c97bce..9a65cf98 100644 --- a/test/src/models/push_notifications/push_notification_subscription_test.dart +++ b/test/src/models/push_notifications/push_notification_subscription_test.dart @@ -4,6 +4,7 @@ import 'package:test/test.dart'; void main() { group('PushNotificationSubscription', () { + const id = 'push-notification-subscription-id-1'; const userId = 'user-id-1'; const name = 'Tech News'; const topics = ['topic-1', 'topic-2']; From ce6737f472d127b03d03967cbcfd37b3bafe7fc5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:13:39 +0100 Subject: [PATCH 54/78] test(core): add FirebaseProviderConfig model tests - Implement comprehensive tests for FirebaseProviderConfig model - Cover value equality, property correctness, JSON serialization/deserialization, and copyWith functionality - Use sample data for testing purposes as no specific fixture file was provided --- .../config/firebase_provider_config_test.dart | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 test/src/models/config/firebase_provider_config_test.dart diff --git a/test/src/models/config/firebase_provider_config_test.dart b/test/src/models/config/firebase_provider_config_test.dart new file mode 100644 index 00000000..2aef2762 --- /dev/null +++ b/test/src/models/config/firebase_provider_config_test.dart @@ -0,0 +1,88 @@ +import 'package:core/src/models/config/firebase_provider_config.dart'; +import 'package:test/test.dart'; + +void main() { + group('FirebaseProviderConfig', () { + // Since a specific fixture file was not provided, sample data is + // created here for testing purposes. + const config = FirebaseProviderConfig( + projectId: 'test-project-id', + clientEmail: 'test-client-email@example.com', + privateKey: 'test-private-key', + ); + + final json = { + 'projectId': 'test-project-id', + 'clientEmail': 'test-client-email@example.com', + 'privateKey': 'test-private-key', + 'provider': 'firebase', // Manually added by toJson + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherConfig = FirebaseProviderConfig( + projectId: 'test-project-id', + clientEmail: 'test-client-email@example.com', + privateKey: 'test-private-key', + ); + + // Assert: The two instances should be equal. + expect(config, equals(anotherConfig)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields including provider. + expect( + config.props, + equals([ + 'firebase', + 'test-project-id', + 'test-client-email@example.com', + 'test-private-key', + ]), + ); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = FirebaseProviderConfig.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(config)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = config.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define new values. + const newProjectId = 'new-project-id'; + + // Act: Create a copy with the new value. + final copiedConfig = config.copyWith(projectId: newProjectId); + + // Assert: The new instance should have the updated value. + expect(copiedConfig.projectId, equals(newProjectId)); + expect(copiedConfig.clientEmail, equals(config.clientEmail)); + + // Assert: The original instance should remain unchanged. + expect(config.projectId, isNot(equals(newProjectId))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedConfig = config.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(config)); + }, + ); + }); +} From 1814a9ad2cd3775c987df6716d17f2e9de450388 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:13:49 +0100 Subject: [PATCH 55/78] test(core): add OneSignalProviderConfig tests - Add unit tests for OneSignalProviderConfig model - Cover value equality, props, JSON serialization/deserialization, and copyWith functionality --- .../one_signal_provider_config_test.dart | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 test/src/models/config/one_signal_provider_config_test.dart diff --git a/test/src/models/config/one_signal_provider_config_test.dart b/test/src/models/config/one_signal_provider_config_test.dart new file mode 100644 index 00000000..0063c996 --- /dev/null +++ b/test/src/models/config/one_signal_provider_config_test.dart @@ -0,0 +1,80 @@ +import 'package:core/src/models/config/one_signal_provider_config.dart'; +import 'package:test/test.dart'; + +void main() { + group('OneSignalProviderConfig', () { + // Since a specific fixture file was not provided, sample data is + // created here for testing purposes. + const config = OneSignalProviderConfig( + appId: 'test-app-id', + restApiKey: 'test-rest-api-key', + ); + + final json = { + 'appId': 'test-app-id', + 'restApiKey': 'test-rest-api-key', + 'provider': 'oneSignal', // Manually added by toJson + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherConfig = OneSignalProviderConfig( + appId: 'test-app-id', + restApiKey: 'test-rest-api-key', + ); + + // Assert: The two instances should be equal. + expect(config, equals(anotherConfig)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields including provider. + expect( + config.props, + equals(['oneSignal', 'test-app-id', 'test-rest-api-key']), + ); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = OneSignalProviderConfig.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(config)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = config.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define new values. + const newAppId = 'new-app-id'; + + // Act: Create a copy with the new value. + final copiedConfig = config.copyWith(appId: newAppId); + + // Assert: The new instance should have the updated value. + expect(copiedConfig.appId, equals(newAppId)); + expect(copiedConfig.restApiKey, equals(config.restApiKey)); + + // Assert: The original instance should remain unchanged. + expect(config.appId, isNot(equals(newAppId))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedConfig = config.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(config)); + }, + ); + }); +} From 6a9d075e34894edd39bf8c67e2535a9efe00c5f1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:14:03 +0100 Subject: [PATCH 56/78] test(core): add PushNotificationConfig model tests - Create unit tests for PushNotificationConfig model - Test value equality, props, JSON serialization/deserialization - Verify copyWith functionality with and without updated values --- .../config/push_notification_config_test.dart | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 test/src/models/config/push_notification_config_test.dart diff --git a/test/src/models/config/push_notification_config_test.dart b/test/src/models/config/push_notification_config_test.dart new file mode 100644 index 00000000..1b855d6c --- /dev/null +++ b/test/src/models/config/push_notification_config_test.dart @@ -0,0 +1,188 @@ +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:core/src/models/config/firebase_provider_config.dart'; +import 'package:core/src/models/config/one_signal_provider_config.dart'; +import 'package:core/src/models/config/push_notification_config.dart'; +import 'package:core/src/models/config/push_notification_delivery_config.dart'; +import 'package:core/src/models/config/push_notification_delivery_role_config.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationConfig', () { + // Define sample provider configs + const firebaseProviderConfig = FirebaseProviderConfig( + projectId: 'test-firebase-project', + clientEmail: 'firebase@example.com', + privateKey: 'firebase-private-key', + ); + const oneSignalProviderConfig = OneSignalProviderConfig( + appId: 'test-onesignal-app-id', + restApiKey: 'test-onesignal-rest-api-key', + ); + + // Define sample delivery role configs + const guestDeliveryRoleConfig = PushNotificationDeliveryRoleConfig( + subscriptionLimit: 1, + ); + const standardDeliveryRoleConfig = PushNotificationDeliveryRoleConfig( + subscriptionLimit: 3, + ); + + // Define sample delivery configs + const breakingOnlyDeliveryConfig = PushNotificationDeliveryConfig( + enabled: true, + visibleTo: { + AppUserRole.guestUser: guestDeliveryRoleConfig, + AppUserRole.standardUser: standardDeliveryRoleConfig, + }, + ); + + const dailyDigestDeliveryConfig = PushNotificationDeliveryConfig( + enabled: false, + visibleTo: {AppUserRole.standardUser: standardDeliveryRoleConfig}, + ); + + // Main PushNotificationConfig instance + const pushNotificationConfig = PushNotificationConfig( + enabled: true, + primaryProvider: PushNotificationProvider.firebase, + providerConfigs: { + PushNotificationProvider.firebase: firebaseProviderConfig, + PushNotificationProvider.oneSignal: oneSignalProviderConfig, + }, + deliveryConfigs: { + PushNotificationSubscriptionDeliveryType.breakingOnly: + breakingOnlyDeliveryConfig, + PushNotificationSubscriptionDeliveryType.dailyDigest: + dailyDigestDeliveryConfig, + }, + ); + + // Corresponding JSON representation + final json = { + 'enabled': true, + 'primaryProvider': 'firebase', + 'providerConfigs': { + 'firebase': { + 'projectId': 'test-firebase-project', + 'clientEmail': 'firebase@example.com', + 'privateKey': 'firebase-private-key', + 'provider': 'firebase', + }, + 'oneSignal': { + 'appId': 'test-onesignal-app-id', + 'restApiKey': 'test-onesignal-rest-api-key', + 'provider': 'oneSignal', + }, + }, + 'deliveryConfigs': { + 'breakingOnly': { + 'enabled': true, + 'visibleTo': { + 'guestUser': {'subscriptionLimit': 1}, + 'standardUser': {'subscriptionLimit': 3}, + }, + }, + 'dailyDigest': { + 'enabled': false, + 'visibleTo': { + 'standardUser': {'subscriptionLimit': 3}, + }, + }, + }, + }; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherConfig = PushNotificationConfig( + enabled: true, + primaryProvider: PushNotificationProvider.firebase, + providerConfigs: { + PushNotificationProvider.firebase: firebaseProviderConfig, + PushNotificationProvider.oneSignal: oneSignalProviderConfig, + }, + deliveryConfigs: { + PushNotificationSubscriptionDeliveryType.breakingOnly: + breakingOnlyDeliveryConfig, + PushNotificationSubscriptionDeliveryType.dailyDigest: + dailyDigestDeliveryConfig, + }, + ); + + // Assert: The two instances should be equal. + expect(pushNotificationConfig, equals(anotherConfig)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields. + expect( + pushNotificationConfig.props, + equals([ + pushNotificationConfig.enabled, + pushNotificationConfig.primaryProvider, + pushNotificationConfig.providerConfigs, + pushNotificationConfig.deliveryConfigs, + ]), + ); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = PushNotificationConfig.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(pushNotificationConfig)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = pushNotificationConfig.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define updated values. + const newEnabled = false; + const newPrimaryProvider = PushNotificationProvider.oneSignal; + + // Act: Create a copy with updated values. + final copiedConfig = pushNotificationConfig.copyWith( + enabled: newEnabled, + primaryProvider: newPrimaryProvider, + ); + + // Assert: The new instance should have the updated values. + expect(copiedConfig.enabled, equals(newEnabled)); + expect(copiedConfig.primaryProvider, equals(newPrimaryProvider)); + expect( + copiedConfig.providerConfigs, + equals(pushNotificationConfig.providerConfigs), + ); + expect( + copiedConfig.deliveryConfigs, + equals(pushNotificationConfig.deliveryConfigs), + ); + + // Assert: The original instance should remain unchanged. + expect(pushNotificationConfig.enabled, isNot(equals(newEnabled))); + expect( + pushNotificationConfig.primaryProvider, + isNot(equals(newPrimaryProvider)), + ); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedConfig = pushNotificationConfig.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(pushNotificationConfig)); + }, + ); + }); +} From 42ae276d10ef1f86256336749cbc90b7b1778168 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:14:13 +0100 Subject: [PATCH 57/78] test(core): add tests for PushNotificationDeliveryRoleConfig - Create unit tests for PushNotificationDeliveryRoleConfig class - Test value equality, props, JSON serialization/deserialization - Verify copyWith functionality with and without updated arguments --- ...otification_delivery_role_config_test.dart | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 test/src/models/config/push_notification_delivery_role_config_test.dart diff --git a/test/src/models/config/push_notification_delivery_role_config_test.dart b/test/src/models/config/push_notification_delivery_role_config_test.dart new file mode 100644 index 00000000..65cfdfb1 --- /dev/null +++ b/test/src/models/config/push_notification_delivery_role_config_test.dart @@ -0,0 +1,68 @@ +import 'package:core/src/models/config/push_notification_delivery_role_config.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationDeliveryRoleConfig', () { + // Since a specific fixture file was not provided, sample data is + // created here for testing purposes. + const roleConfig = PushNotificationDeliveryRoleConfig(subscriptionLimit: 5); + + final json = {'subscriptionLimit': 5}; + + test('supports value equality', () { + // Arrange: Create another instance with the same values. + const anotherRoleConfig = PushNotificationDeliveryRoleConfig( + subscriptionLimit: 5, + ); + + // Assert: The two instances should be equal. + expect(roleConfig, equals(anotherRoleConfig)); + }); + + test('props are correct', () { + // Assert: The props list should contain the subscriptionLimit. + expect(roleConfig.props, equals([5])); + }); + + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = PushNotificationDeliveryRoleConfig.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(roleConfig)); + }); + + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = roleConfig.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + // Arrange: Define a new limit. + const newLimit = 10; + + // Act: Create a copy with the new limit. + final copiedConfig = roleConfig.copyWith(subscriptionLimit: newLimit); + + // Assert: The new instance should have the updated value. + expect(copiedConfig.subscriptionLimit, equals(newLimit)); + + // Assert: The original instance should remain unchanged. + expect(roleConfig.subscriptionLimit, isNot(equals(newLimit))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedConfig = roleConfig.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(roleConfig)); + }, + ); + }); +} From 1f35724c3955874e25e1bb1b103d09a1f25611a2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:14:26 +0100 Subject: [PATCH 58/78] test(core): add PushNotificationProviderConfig serialization tests - Create unit tests for polymorphic JSON serialization and deserialization - Verify correct handling of different provider types - Test error scenarios for missing or unknown provider fields - Ensure proper conversion between JSON maps and concrete config classes --- ...ush_notification_provider_config_test.dart | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/src/models/config/push_notification_provider_config_test.dart diff --git a/test/src/models/config/push_notification_provider_config_test.dart b/test/src/models/config/push_notification_provider_config_test.dart new file mode 100644 index 00000000..0f4adb7f --- /dev/null +++ b/test/src/models/config/push_notification_provider_config_test.dart @@ -0,0 +1,96 @@ +import 'package:core/src/models/config/firebase_provider_config.dart'; +import 'package:core/src/models/config/one_signal_provider_config.dart'; +import 'package:core/src/models/config/push_notification_provider_config.dart'; +import 'package:test/test.dart'; + +void main() { + group('PushNotificationProviderConfig', () { + // Arrange: Create sample concrete instances. + const firebaseConfig = FirebaseProviderConfig( + projectId: 'test-project', + clientEmail: 'test@example.com', + privateKey: 'test-key', + ); + + const oneSignalConfig = OneSignalProviderConfig( + appId: 'test-app-id', + restApiKey: 'test-api-key', + ); + + // Arrange: Create corresponding JSON maps with the 'provider' discriminator. + final firebaseJson = { + 'provider': 'firebase', + 'projectId': 'test-project', + 'clientEmail': 'test@example.com', + 'privateKey': 'test-key', + }; + + final oneSignalJson = { + 'provider': 'oneSignal', + 'appId': 'test-app-id', + 'restApiKey': 'test-api-key', + }; + + group('fromJson factory (polymorphic)', () { + test('correctly deserializes to FirebaseProviderConfig', () { + // Act: Deserialize the Firebase JSON using the base factory. + final result = PushNotificationProviderConfig.fromJson(firebaseJson); + + // Assert: The result should be the correct concrete type and value. + expect(result, isA()); + expect(result, equals(firebaseConfig)); + }); + + test('correctly deserializes to OneSignalProviderConfig', () { + // Act: Deserialize the OneSignal JSON using the base factory. + final result = PushNotificationProviderConfig.fromJson(oneSignalJson); + + // Assert: The result should be the correct concrete type and value. + expect(result, isA()); + expect(result, equals(oneSignalConfig)); + }); + + test('throws FormatException for missing provider field', () { + // Arrange: Create JSON without the discriminator field. + final jsonWithoutProvider = {'appId': 'test-app-id'}; + + // Assert: Deserializing should throw a FormatException. + expect( + () => PushNotificationProviderConfig.fromJson(jsonWithoutProvider), + throwsA(isA()), + ); + }); + + test('throws FormatException for unknown provider field', () { + // Arrange: Create JSON with an invalid discriminator. + final jsonWithUnknownProvider = { + 'provider': 'unknownProvider', + 'appId': 'test-app-id', + }; + + // Assert: Deserializing should throw a FormatException. + expect( + () => + PushNotificationProviderConfig.fromJson(jsonWithUnknownProvider), + throwsA(isA()), + ); + }); + }); + + group('toJson static method (polymorphic)', () { + test('correctly serializes a FirebaseProviderConfig instance', () { + expect( + PushNotificationProviderConfig.toJson(firebaseConfig), + equals(firebaseJson), + ); + }); + + test('correctly serializes a OneSignalProviderConfig instance', () { + expect( + PushNotificationProviderConfig.toJson(oneSignalConfig), + equals(oneSignalJson), + ); + }); + }); + }); +} From e6139ec185befef28e3eb3d3d092d3d75e66bb40 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:14:55 +0100 Subject: [PATCH 59/78] test(config): enhance OneSignalProviderConfig tests with fixture data - Replace hardcoded test data with OneSignalProviderConfig from remoteConfigsFixturesData - Update tests to use fixture data for consistency with application configurations - Improve test coverage for value equality, props, and copyWith functionality --- .../one_signal_provider_config_test.dart | 56 ++++++++++--------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/test/src/models/config/one_signal_provider_config_test.dart b/test/src/models/config/one_signal_provider_config_test.dart index 0063c996..f4953d2f 100644 --- a/test/src/models/config/one_signal_provider_config_test.dart +++ b/test/src/models/config/one_signal_provider_config_test.dart @@ -1,27 +1,30 @@ +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/fixtures/remote_configs.dart'; import 'package:core/src/models/config/one_signal_provider_config.dart'; import 'package:test/test.dart'; void main() { group('OneSignalProviderConfig', () { - // Since a specific fixture file was not provided, sample data is - // created here for testing purposes. - const config = OneSignalProviderConfig( - appId: 'test-app-id', - restApiKey: 'test-rest-api-key', - ); + // Retrieve the OneSignalProviderConfig from the remoteConfigsFixturesData. + // This ensures consistency with predefined application configurations. + final config = + remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.oneSignal]! + as OneSignalProviderConfig; - final json = { - 'appId': 'test-app-id', - 'restApiKey': 'test-rest-api-key', - 'provider': 'oneSignal', // Manually added by toJson - }; + // Generate the expected JSON from the fixture config for comparison. + final json = config.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherConfig = OneSignalProviderConfig( - appId: 'test-app-id', - restApiKey: 'test-rest-api-key', - ); + final anotherConfig = + remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.oneSignal]! + as OneSignalProviderConfig; // Assert: The two instances should be equal. expect(config, equals(anotherConfig)); @@ -31,7 +34,7 @@ void main() { // Assert: The props list should contain all the fields including provider. expect( config.props, - equals(['oneSignal', 'test-app-id', 'test-rest-api-key']), + equals([config.provider, config.appId, config.restApiKey]), ); }); @@ -53,10 +56,12 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define new values. - const newAppId = 'new-app-id'; + const newAppId = 'updated-app-id'; // Act: Create a copy with the new value. - final copiedConfig = config.copyWith(appId: newAppId); + final copiedConfig = config.copyWith( + appId: newAppId, + ); // Using a new value for appId // Assert: The new instance should have the updated value. expect(copiedConfig.appId, equals(newAppId)); @@ -66,15 +71,12 @@ void main() { expect(config.appId, isNot(equals(newAppId))); }); - test( - 'copyWith creates an identical copy when no arguments are provided', - () { - // Act: Create a copy without providing any arguments. - final copiedConfig = config.copyWith(); + test('copyWith creates an identical copy when no arguments are provided', () { + // Act: Create a copy without providing any arguments, expecting an identical instance. + final copiedConfig = config.copyWith(); - // Assert: The copied instance should be equal to the original. - expect(copiedConfig, equals(config)); - }, - ); + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(config)); + }); }); } From 8466f1a14b2fe528c9534264fee18f3695876511 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:18:15 +0100 Subject: [PATCH 60/78] test(config): migrate FirebaseProviderConfig tests to use fixtures - Replace hardcoded test data with FirebaseProviderConfig from remoteConfigsFixturesData - Update toJson() method to use fixture data - Modify tests to use fixture data for equality checks and copyWith method - Add import statements for required dependencies --- .../config/firebase_provider_config_test.dart | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/test/src/models/config/firebase_provider_config_test.dart b/test/src/models/config/firebase_provider_config_test.dart index 2aef2762..b6dcea2c 100644 --- a/test/src/models/config/firebase_provider_config_test.dart +++ b/test/src/models/config/firebase_provider_config_test.dart @@ -1,30 +1,30 @@ +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/fixtures/remote_configs.dart'; import 'package:core/src/models/config/firebase_provider_config.dart'; import 'package:test/test.dart'; void main() { group('FirebaseProviderConfig', () { - // Since a specific fixture file was not provided, sample data is - // created here for testing purposes. - const config = FirebaseProviderConfig( - projectId: 'test-project-id', - clientEmail: 'test-client-email@example.com', - privateKey: 'test-private-key', - ); + // Retrieve the FirebaseProviderConfig from the remoteConfigsFixturesData. + // This ensures consistency with predefined application configurations. + final config = + remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.firebase]! + as FirebaseProviderConfig; - final json = { - 'projectId': 'test-project-id', - 'clientEmail': 'test-client-email@example.com', - 'privateKey': 'test-private-key', - 'provider': 'firebase', // Manually added by toJson - }; + // Generate the expected JSON from the fixture config for comparison. + final json = config.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherConfig = FirebaseProviderConfig( - projectId: 'test-project-id', - clientEmail: 'test-client-email@example.com', - privateKey: 'test-private-key', - ); + final anotherConfig = + remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.firebase]! + as FirebaseProviderConfig; // Assert: The two instances should be equal. expect(config, equals(anotherConfig)); @@ -35,10 +35,10 @@ void main() { expect( config.props, equals([ - 'firebase', - 'test-project-id', - 'test-client-email@example.com', - 'test-private-key', + config.provider, + config.projectId, + config.clientEmail, + config.privateKey, ]), ); }); @@ -61,10 +61,12 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define new values. - const newProjectId = 'new-project-id'; + const newProjectId = 'updated-project-id'; // Act: Create a copy with the new value. - final copiedConfig = config.copyWith(projectId: newProjectId); + final copiedConfig = config.copyWith( + projectId: newProjectId, + ); // Using a new value for projectId // Assert: The new instance should have the updated value. expect(copiedConfig.projectId, equals(newProjectId)); From 97f04d8f8c66adeb11c82f79389b8ad4b731bff3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:18:24 +0100 Subject: [PATCH 61/78] test(config): update PushNotificationDeliveryRoleConfig tests - Replace hardcoded test data with fixture-based configuration - Improve test accuracy by using real-world JSON data - Enhance test maintainability through fixture reuse --- ...otification_delivery_role_config_test.dart | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/test/src/models/config/push_notification_delivery_role_config_test.dart b/test/src/models/config/push_notification_delivery_role_config_test.dart index 65cfdfb1..a1802678 100644 --- a/test/src/models/config/push_notification_delivery_role_config_test.dart +++ b/test/src/models/config/push_notification_delivery_role_config_test.dart @@ -1,18 +1,26 @@ +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:core/src/fixtures/remote_configs.dart'; import 'package:core/src/models/config/push_notification_delivery_role_config.dart'; import 'package:test/test.dart'; void main() { group('PushNotificationDeliveryRoleConfig', () { - // Since a specific fixture file was not provided, sample data is - // created here for testing purposes. - const roleConfig = PushNotificationDeliveryRoleConfig(subscriptionLimit: 5); + // Retrieve a sample PushNotificationDeliveryRoleConfig from the fixtures. + // This ensures consistency with predefined application configurations. + final roleConfig = remoteConfigsFixturesData + .first + .pushNotificationConfig + .deliveryConfigs[PushNotificationSubscriptionDeliveryType.breakingOnly]! + .visibleTo[AppUserRole.standardUser]!; - final json = {'subscriptionLimit': 5}; + // Generate the expected JSON from the fixture config for comparison. + final json = roleConfig.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherRoleConfig = PushNotificationDeliveryRoleConfig( - subscriptionLimit: 5, + final anotherRoleConfig = PushNotificationDeliveryRoleConfig( + subscriptionLimit: roleConfig.subscriptionLimit, ); // Assert: The two instances should be equal. @@ -21,7 +29,7 @@ void main() { test('props are correct', () { // Assert: The props list should contain the subscriptionLimit. - expect(roleConfig.props, equals([5])); + expect(roleConfig.props, equals([roleConfig.subscriptionLimit])); }); test('can be created from JSON', () { From ca5f08738e4e7405894070538d8a67489becbcb2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:21:18 +0100 Subject: [PATCH 62/78] test(config): refactor push notification config tests - Replace hardcoded config instances with fixtures - Remove manual JSON map creation, use toJson() instead - Import necessary packages for enums and fixtures --- ...ush_notification_provider_config_test.dart | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/test/src/models/config/push_notification_provider_config_test.dart b/test/src/models/config/push_notification_provider_config_test.dart index 0f4adb7f..5a97bde1 100644 --- a/test/src/models/config/push_notification_provider_config_test.dart +++ b/test/src/models/config/push_notification_provider_config_test.dart @@ -1,3 +1,5 @@ +import 'package:core/src/enums/push_notification_provider.dart'; +import 'package:core/src/fixtures/remote_configs.dart'; import 'package:core/src/models/config/firebase_provider_config.dart'; import 'package:core/src/models/config/one_signal_provider_config.dart'; import 'package:core/src/models/config/push_notification_provider_config.dart'; @@ -5,31 +7,20 @@ import 'package:test/test.dart'; void main() { group('PushNotificationProviderConfig', () { - // Arrange: Create sample concrete instances. - const firebaseConfig = FirebaseProviderConfig( - projectId: 'test-project', - clientEmail: 'test@example.com', - privateKey: 'test-key', - ); + // Arrange: Retrieve sample concrete instances from the fixtures. + final firebaseConfig = remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.firebase]!; - const oneSignalConfig = OneSignalProviderConfig( - appId: 'test-app-id', - restApiKey: 'test-api-key', - ); + final oneSignalConfig = remoteConfigsFixturesData + .first + .pushNotificationConfig + .providerConfigs[PushNotificationProvider.oneSignal]!; // Arrange: Create corresponding JSON maps with the 'provider' discriminator. - final firebaseJson = { - 'provider': 'firebase', - 'projectId': 'test-project', - 'clientEmail': 'test@example.com', - 'privateKey': 'test-key', - }; - - final oneSignalJson = { - 'provider': 'oneSignal', - 'appId': 'test-app-id', - 'restApiKey': 'test-api-key', - }; + final firebaseJson = (firebaseConfig as FirebaseProviderConfig).toJson(); + final oneSignalJson = (oneSignalConfig as OneSignalProviderConfig).toJson(); group('fromJson factory (polymorphic)', () { test('correctly deserializes to FirebaseProviderConfig', () { From eadb60e8259da4a541b744d6dae5182dfa58ab31 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:28:49 +0100 Subject: [PATCH 63/78] refactor(test): replace local push notification config with fixture - Remove hardcoded push notification config in test - Use remoteConfigsFixturesData from fixtures package instead - Simplify test by leveraging predefined application configuration --- .../config/push_notification_config_test.dart | 108 ++---------------- 1 file changed, 8 insertions(+), 100 deletions(-) diff --git a/test/src/models/config/push_notification_config_test.dart b/test/src/models/config/push_notification_config_test.dart index 1b855d6c..295529a9 100644 --- a/test/src/models/config/push_notification_config_test.dart +++ b/test/src/models/config/push_notification_config_test.dart @@ -1,114 +1,22 @@ -import 'package:core/src/enums/app_user_role.dart'; import 'package:core/src/enums/push_notification_provider.dart'; -import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; -import 'package:core/src/models/config/firebase_provider_config.dart'; -import 'package:core/src/models/config/one_signal_provider_config.dart'; +import 'package:core/src/fixtures/remote_configs.dart'; import 'package:core/src/models/config/push_notification_config.dart'; -import 'package:core/src/models/config/push_notification_delivery_config.dart'; -import 'package:core/src/models/config/push_notification_delivery_role_config.dart'; import 'package:test/test.dart'; void main() { group('PushNotificationConfig', () { - // Define sample provider configs - const firebaseProviderConfig = FirebaseProviderConfig( - projectId: 'test-firebase-project', - clientEmail: 'firebase@example.com', - privateKey: 'firebase-private-key', - ); - const oneSignalProviderConfig = OneSignalProviderConfig( - appId: 'test-onesignal-app-id', - restApiKey: 'test-onesignal-rest-api-key', - ); - - // Define sample delivery role configs - const guestDeliveryRoleConfig = PushNotificationDeliveryRoleConfig( - subscriptionLimit: 1, - ); - const standardDeliveryRoleConfig = PushNotificationDeliveryRoleConfig( - subscriptionLimit: 3, - ); - - // Define sample delivery configs - const breakingOnlyDeliveryConfig = PushNotificationDeliveryConfig( - enabled: true, - visibleTo: { - AppUserRole.guestUser: guestDeliveryRoleConfig, - AppUserRole.standardUser: standardDeliveryRoleConfig, - }, - ); - - const dailyDigestDeliveryConfig = PushNotificationDeliveryConfig( - enabled: false, - visibleTo: {AppUserRole.standardUser: standardDeliveryRoleConfig}, - ); - - // Main PushNotificationConfig instance - const pushNotificationConfig = PushNotificationConfig( - enabled: true, - primaryProvider: PushNotificationProvider.firebase, - providerConfigs: { - PushNotificationProvider.firebase: firebaseProviderConfig, - PushNotificationProvider.oneSignal: oneSignalProviderConfig, - }, - deliveryConfigs: { - PushNotificationSubscriptionDeliveryType.breakingOnly: - breakingOnlyDeliveryConfig, - PushNotificationSubscriptionDeliveryType.dailyDigest: - dailyDigestDeliveryConfig, - }, - ); + // Retrieve the PushNotificationConfig from the remoteConfigsFixturesData. + // This ensures consistency with predefined application configurations. + final pushNotificationConfig = + remoteConfigsFixturesData.first.pushNotificationConfig; // Corresponding JSON representation - final json = { - 'enabled': true, - 'primaryProvider': 'firebase', - 'providerConfigs': { - 'firebase': { - 'projectId': 'test-firebase-project', - 'clientEmail': 'firebase@example.com', - 'privateKey': 'firebase-private-key', - 'provider': 'firebase', - }, - 'oneSignal': { - 'appId': 'test-onesignal-app-id', - 'restApiKey': 'test-onesignal-rest-api-key', - 'provider': 'oneSignal', - }, - }, - 'deliveryConfigs': { - 'breakingOnly': { - 'enabled': true, - 'visibleTo': { - 'guestUser': {'subscriptionLimit': 1}, - 'standardUser': {'subscriptionLimit': 3}, - }, - }, - 'dailyDigest': { - 'enabled': false, - 'visibleTo': { - 'standardUser': {'subscriptionLimit': 3}, - }, - }, - }, - }; + final json = pushNotificationConfig.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherConfig = PushNotificationConfig( - enabled: true, - primaryProvider: PushNotificationProvider.firebase, - providerConfigs: { - PushNotificationProvider.firebase: firebaseProviderConfig, - PushNotificationProvider.oneSignal: oneSignalProviderConfig, - }, - deliveryConfigs: { - PushNotificationSubscriptionDeliveryType.breakingOnly: - breakingOnlyDeliveryConfig, - PushNotificationSubscriptionDeliveryType.dailyDigest: - dailyDigestDeliveryConfig, - }, - ); + final anotherConfig = + remoteConfigsFixturesData.first.pushNotificationConfig; // Assert: The two instances should be equal. expect(pushNotificationConfig, equals(anotherConfig)); From bc5d84125327aa40fda889928fd8d06f93e1e764 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:29:11 +0100 Subject: [PATCH 64/78] refactor(push-notification): use fixtures for topic, source, and country IDs - Replace hardcoded IDs with IDs from fixtures - Import necessary fixture files for countries, sources, and topics - Update test to ensure data integrity and consistency --- .../push_notification_subscription_test.dart | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/test/src/models/push_notifications/push_notification_subscription_test.dart b/test/src/models/push_notifications/push_notification_subscription_test.dart index 9a65cf98..8bf4259b 100644 --- a/test/src/models/push_notifications/push_notification_subscription_test.dart +++ b/test/src/models/push_notifications/push_notification_subscription_test.dart @@ -1,21 +1,25 @@ import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; +import 'package:core/src/fixtures/countries.dart'; +import 'package:core/src/fixtures/sources.dart'; +import 'package:core/src/fixtures/topics.dart'; import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:test/test.dart'; void main() { group('PushNotificationSubscription', () { + // Use IDs from fixtures to ensure data integrity and consistency. const id = 'push-notification-subscription-id-1'; const userId = 'user-id-1'; const name = 'Tech News'; - const topics = ['topic-1', 'topic-2']; - const sources = ['source-1']; - const countries = ['country-1']; + final topics = [topicsFixturesData[0].id, topicsFixturesData[1].id]; + final sources = [sourcesFixturesData[0].id]; + final countries = [countriesFixturesData[0].id]; const deliveryTypes = { PushNotificationSubscriptionDeliveryType.breakingOnly, PushNotificationSubscriptionDeliveryType.dailyDigest, }; - const subscription = PushNotificationSubscription( + final subscription = PushNotificationSubscription( id: id, userId: userId, name: name, @@ -37,7 +41,7 @@ void main() { test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherSubscription = PushNotificationSubscription( + final anotherSubscription = PushNotificationSubscription( id: id, userId: userId, name: name, From b6de7ed2cd1e5892a88c28663ff9bc715f5d615f Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:32:17 +0100 Subject: [PATCH 65/78] test(RemoteConfig): add and improve equality and JSON tests - Add test for value equality using fixture data - Implement props correctness test - Separate JSON serialization and deserialization tests - Enhance copyWith test to include more properties - Add test for inequality with different properties --- .../src/models/config/remote_config_test.dart | 148 ++++++++++-------- 1 file changed, 86 insertions(+), 62 deletions(-) diff --git a/test/src/models/config/remote_config_test.dart b/test/src/models/config/remote_config_test.dart index 2bc8127f..c22a115e 100644 --- a/test/src/models/config/remote_config_test.dart +++ b/test/src/models/config/remote_config_test.dart @@ -4,77 +4,101 @@ import 'package:test/test.dart'; void main() { group('RemoteConfig', () { final remoteConfigFixture = remoteConfigsFixturesData.first; + final json = remoteConfigFixture.toJson(); - group('constructor', () { - test('returns correct instance', () { - expect(remoteConfigFixture, isA()); - expect(remoteConfigFixture.id, isA()); - expect( + test('supports value equality', () { + // Arrange: Create another instance with the same values from the fixture. + final anotherConfig = remoteConfigsFixturesData.first.copyWith(); + + // Assert: The two instances should be equal. + expect(remoteConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + // Assert: The props list should contain all the fields for Equatable. + expect( + remoteConfigFixture.props, + equals([ + remoteConfigFixture.id, remoteConfigFixture.userPreferenceConfig, - isA(), - ); - expect(remoteConfigFixture.adConfig, isA()); - expect( + remoteConfigFixture.adConfig, remoteConfigFixture.feedDecoratorConfig, - isA>(), - ); - expect(remoteConfigFixture.appStatus, isA()); - }); + remoteConfigFixture.appStatus, + remoteConfigFixture.pushNotificationConfig, + remoteConfigFixture.createdAt, + remoteConfigFixture.updatedAt, + ]), + ); }); - group('fromJson/toJson', () { - test('round trip', () { - final json = remoteConfigFixture.toJson(); - final result = RemoteConfig.fromJson(json); - expect(result, remoteConfigFixture); - }); + test('can be created from JSON', () { + // Act: Create an instance from the JSON map. + final fromJson = RemoteConfig.fromJson(json); + + // Assert: The created instance should be equal to the original. + expect(fromJson, equals(remoteConfigFixture)); }); - group('copyWith', () { - test('returns a new instance with updated values', () { - final updatedConfig = remoteConfigFixture.copyWith( - id: 'new_app_config', - appStatus: remoteConfigFixture.appStatus.copyWith( - isUnderMaintenance: true, - ), - adConfig: remoteConfigFixture.adConfig.copyWith( - primaryAdPlatform: AdPlatformType.admob, - feedAdConfiguration: remoteConfigFixture - .adConfig - .feedAdConfiguration - .copyWith(enabled: false), - ), - ); - - expect(updatedConfig.id, 'new_app_config'); - expect( - updatedConfig.userPreferenceConfig, - remoteConfigFixture.userPreferenceConfig, - ); - expect(updatedConfig.appStatus.isUnderMaintenance, true); - expect(updatedConfig.adConfig.primaryAdPlatform, AdPlatformType.admob); - expect(updatedConfig.adConfig.feedAdConfiguration.enabled, isFalse); - expect(updatedConfig, isNot(equals(remoteConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = remoteConfigFixture.copyWith(); - expect(updatedConfig, equals(remoteConfigFixture)); - }); + test('can be converted to JSON', () { + // Act: Convert the instance to a JSON map. + final toJson = remoteConfigFixture.toJson(); + + // Assert: The resulting map should match the original JSON map. + expect(toJson, equals(json)); }); - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = remoteConfigFixture.copyWith(); - final config2 = remoteConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = remoteConfigFixture.copyWith(); - final config2 = remoteConfigFixture.copyWith(id: 'different_id'); - expect(config1, isNot(equals(config2))); - }); + test('copyWith creates a copy with updated values', () { + // Arrange: Define new values for various properties. + const newId = 'new_app_config'; + final newAppStatus = remoteConfigFixture.appStatus.copyWith( + isUnderMaintenance: true, + ); + final newPushConfig = remoteConfigFixture.pushNotificationConfig.copyWith( + primaryProvider: PushNotificationProvider.oneSignal, + ); + + // Act: Create a copy with the updated values. + final copiedConfig = remoteConfigFixture.copyWith( + id: newId, + appStatus: newAppStatus, + pushNotificationConfig: newPushConfig, + ); + + // Assert: The new instance should have the updated values. + expect(copiedConfig.id, equals(newId)); + expect(copiedConfig.appStatus, equals(newAppStatus)); + expect(copiedConfig.pushNotificationConfig, equals(newPushConfig)); + + // Assert: Unchanged properties remain the same. + expect( + copiedConfig.userPreferenceConfig, + equals(remoteConfigFixture.userPreferenceConfig), + ); + expect(copiedConfig.adConfig, equals(remoteConfigFixture.adConfig)); + + // Assert: The original instance remains unchanged. + expect(remoteConfigFixture.id, isNot(equals(newId))); + expect(remoteConfigFixture.appStatus, isNot(equals(newAppStatus))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + // Act: Create a copy without providing any arguments. + final copiedConfig = remoteConfigFixture.copyWith(); + + // Assert: The copied instance should be equal to the original. + expect(copiedConfig, equals(remoteConfigFixture)); + }, + ); + + test('instances with different properties are not equal', () { + // Arrange: Create two instances with a differing property. + final config1 = remoteConfigFixture.copyWith(); + final config2 = remoteConfigFixture.copyWith(id: 'different_id'); + + // Assert: The instances should not be equal. + expect(config1, isNot(equals(config2))); }); }); } From 4939d799cf469028ecbeada3e2871a8bf166c2ac Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:33:46 +0100 Subject: [PATCH 66/78] test(headline): update headline test expectations - Increment props length expectation from 12 to 13 - Add isBreaking field to props list test --- test/src/models/entities/headline_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/models/entities/headline_test.dart b/test/src/models/entities/headline_test.dart index 6f7850e3..be181d0a 100644 --- a/test/src/models/entities/headline_test.dart +++ b/test/src/models/entities/headline_test.dart @@ -63,7 +63,7 @@ void main() { }); test('props list should contain all relevant fields', () { - expect(headlineFixture.props.length, 12); + expect(headlineFixture.props.length, 13); expect(headlineFixture.props, [ headlineFixture.id, headlineFixture.title, @@ -76,6 +76,7 @@ void main() { headlineFixture.source, headlineFixture.eventCountry, headlineFixture.topic, + headlineFixture.isBreaking, headlineFixture.type, ]); }); From 39069bd90e545c63888299663b7eb4a9bb78a762 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:36:52 +0100 Subject: [PATCH 67/78] test(core): add unit test for DevicePlatform enum - Create a new test file for DevicePlatform enum - Add tests to verify the correct values of DevicePlatform enum - Ensure the enum has exactly two values: ios and android --- test/src/enums/device_platform_test.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 test/src/enums/device_platform_test.dart diff --git a/test/src/enums/device_platform_test.dart b/test/src/enums/device_platform_test.dart new file mode 100644 index 00000000..9207a3dd --- /dev/null +++ b/test/src/enums/device_platform_test.dart @@ -0,0 +1,15 @@ +import 'package:core/src/enums/device_platform.dart'; +import 'package:test/test.dart'; + +void main() { + group('DevicePlatform', () { + test('should contain the correct values', () { + // Assert: The enum should have exactly two values. + expect(DevicePlatform.values, hasLength(2)); + + // Assert: The enum should contain 'ios' and 'android'. + expect(DevicePlatform.values, contains(DevicePlatform.ios)); + expect(DevicePlatform.values, contains(DevicePlatform.android)); + }); + }); +} From 6e67d15545a1380c2f317a158a20a5db567eaf3e Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:52:54 +0100 Subject: [PATCH 68/78] refactor(user_preferences): update notification subscriptions structure - Change notificationSubscriptions type from Map to List - Remove unused import of enums package - Update toJson and copyWith methods to reflect the structural change --- .../user_preferences/user_content_preferences.dart | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/src/models/user_preferences/user_content_preferences.dart b/lib/src/models/user_preferences/user_content_preferences.dart index 3bb60228..614326ec 100644 --- a/lib/src/models/user_preferences/user_content_preferences.dart +++ b/lib/src/models/user_preferences/user_content_preferences.dart @@ -1,4 +1,3 @@ -import 'package:core/src/enums/enums.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/headline.dart'; import 'package:core/src/models/entities/source.dart'; @@ -61,9 +60,8 @@ class UserContentPreferences extends Equatable { /// List of filter combinations the user has saved. final List savedFilters; - /// A map defining the user's subscription status for each notification type. - final Map - notificationSubscriptions; + /// A list of the user's saved notification subscriptions. + final List notificationSubscriptions; /// Converts this [UserContentPreferences] instance to a JSON map. Map toJson() => _$UserContentPreferencesToJson(this); @@ -91,8 +89,7 @@ class UserContentPreferences extends Equatable { List? followedTopics, List? savedHeadlines, List? savedFilters, - Map? - notificationSubscriptions, + List? notificationSubscriptions, }) { return UserContentPreferences( id: id ?? this.id, From 34a3799e1b770fccfd05d69908fff32df6ed6560 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:55:26 +0100 Subject: [PATCH 69/78] test(user_preferences): add notificationSubscriptions tests - Add test to check if notificationSubscriptions is a List - Implement test for copyWith method to update notificationSubscriptions - Include notificationSubscriptions in props list test --- .../user_content_preferences.g.dart | 28 +++++++------------ .../user_content_preferences_test.dart | 20 +++++++++++++ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/src/models/user_preferences/user_content_preferences.g.dart b/lib/src/models/user_preferences/user_content_preferences.g.dart index d80e3803..aee5072a 100644 --- a/lib/src/models/user_preferences/user_content_preferences.g.dart +++ b/lib/src/models/user_preferences/user_content_preferences.g.dart @@ -43,12 +43,13 @@ UserContentPreferences _$UserContentPreferencesFromJson( ), notificationSubscriptions: $checkedConvert( 'notificationSubscriptions', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, k), - PushNotificationSubscription.fromJson(e as Map), - ), - ), + (v) => (v as List) + .map( + (e) => PushNotificationSubscription.fromJson( + e as Map, + ), + ) + .toList(), ), ); return val; @@ -65,16 +66,7 @@ Map _$UserContentPreferencesToJson( 'followedTopics': instance.followedTopics.map((e) => e.toJson()).toList(), 'savedHeadlines': instance.savedHeadlines.map((e) => e.toJson()).toList(), 'savedFilters': instance.savedFilters.map((e) => e.toJson()).toList(), - 'notificationSubscriptions': instance.notificationSubscriptions.map( - (k, e) => MapEntry( - _$PushNotificationSubscriptionDeliveryTypeEnumMap[k]!, - e.toJson(), - ), - ), -}; - -const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { - PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', - PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', + 'notificationSubscriptions': instance.notificationSubscriptions + .map((e) => e.toJson()) + .toList(), }; diff --git a/test/src/models/user_preferences/user_content_preferences_test.dart b/test/src/models/user_preferences/user_content_preferences_test.dart index 53b9bf96..94f9ca90 100644 --- a/test/src/models/user_preferences/user_content_preferences_test.dart +++ b/test/src/models/user_preferences/user_content_preferences_test.dart @@ -20,6 +20,7 @@ void main() { expect(preferences.followedSources, isNotEmpty); expect(preferences.followedTopics, isNotEmpty); expect(preferences.savedHeadlines, isNotEmpty); + expect(preferences.notificationSubscriptions, isA>()); expect(preferences.savedFilters, isNotEmpty); }); }); @@ -60,6 +61,13 @@ void main() { ); }); + test('returns a new instance with updated notificationSubscriptions', () { + final updatedPreferences = userContentPreferencesFixture.copyWith( + notificationSubscriptions: [], + ); + expect(updatedPreferences.notificationSubscriptions, isEmpty); + }); + test( 'returns a new instance with the same fields if no updates provided', () { @@ -84,5 +92,17 @@ void main() { expect(preferences1, isNot(equals(preferences2))); }); }); + + test('props list should contain all relevant fields', () { + expect(userContentPreferencesFixture.props, [ + userContentPreferencesFixture.id, + userContentPreferencesFixture.followedCountries, + userContentPreferencesFixture.followedSources, + userContentPreferencesFixture.followedTopics, + userContentPreferencesFixture.savedHeadlines, + userContentPreferencesFixture.savedFilters, + userContentPreferencesFixture.notificationSubscriptions, + ]); + }); }); } From d395d05174eeb60a07eff627cd2a1f7dd78ade29 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 09:55:39 +0100 Subject: [PATCH 70/78] fix(fixtures): use empty list instead of map for notification subscriptions Replace empty map with empty list in userContentPreferencesFixturesData for notificationSubscriptions field. This change ensures consistency with the UserContentPreferences class definition, which uses a list for notification subscriptions. - Updated three instances in the fixtures data - Changed from const {} to const [] --- lib/src/fixtures/user_content_preferences.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/fixtures/user_content_preferences.dart b/lib/src/fixtures/user_content_preferences.dart index 37b19448..f14a41c8 100644 --- a/lib/src/fixtures/user_content_preferences.dart +++ b/lib/src/fixtures/user_content_preferences.dart @@ -23,7 +23,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[7], // Travel ], savedHeadlines: [headlinesFixturesData[0], headlinesFixturesData[10]], - notificationSubscriptions: const {}, + notificationSubscriptions: const [], savedFilters: [ SavedFilter( id: kSavedFilterId1, @@ -77,7 +77,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[6], // Business ], savedHeadlines: [headlinesFixturesData[2], headlinesFixturesData[3]], - notificationSubscriptions: const {}, + notificationSubscriptions: const [], savedFilters: [ SavedFilter( id: 'pub_saved_1', @@ -100,7 +100,7 @@ final List userContentPreferencesFixturesData = [ topicsFixturesData[4], // Health ], savedHeadlines: [headlinesFixturesData[4], headlinesFixturesData[5]], - notificationSubscriptions: const {}, + notificationSubscriptions: const [], savedFilters: const [], ), // Add preferences for users 3-10 @@ -130,7 +130,7 @@ final List userContentPreferencesFixturesData = [ headlinesFixturesData[index * 2], headlinesFixturesData[index * 2 + 1], ], - notificationSubscriptions: const {}, + notificationSubscriptions: const [], savedFilters: [ SavedFilter( id: 'user_${index + 3}_saved_1', From cafcb081e1f8d12778ebc9ca6b97853814e3d6e0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:11:16 +0100 Subject: [PATCH 71/78] docs(CHANGELOG): update feature description for push notification system - Expand the description of the upcoming feature to include models, fixtures, and tests for the push notification system. - This change provides a more comprehensive overview of the implemented functionality. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 367d1259..52e4595a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Upcoming -- **feat**: add data models for push notification system. +- **feat**: add models/fixtures/tests for push notification system. - **BREAKING** feat!: Deprecate and remove `LocalAd` model and related fixtures. - **feat**: Add 10 new user fixtures, including publishers and standard users. - **chore**: Expand user-related fixtures with detailed settings and preferences. From f61022021aebaac9d211d24c273744d1d48a2688 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:13:57 +0100 Subject: [PATCH 72/78] docs(README): expand feature showcase and organize content structure - Refactor Feature Showcase section with added details on various features - Reorganize content into categorized sections for better clarity - Enhance readability with improved formatting and consistent structure - Expand descriptions to provide more detailed information on each feature - Reorder some sections to present content in a more logical flow --- README.md | 193 ++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 159 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index bc8f17bb..e74d1c8b 100644 --- a/README.md +++ b/README.md @@ -10,61 +10,186 @@ Main Project: Browse

-This `core` package serves as the foundational shared library for the [**Flutter News App Full Source Code Toolkit**](https://github.com/flutter-news-app-full-source-code). It defines the common language and data structures—including models for news content, user data, and remote configurations—that ensure consistency and accelerate development across the Flutter mobile app, web dashboard, and Dart Frog backend API. +This `core` package serves as the foundational shared library for the **Flutter News App Full Source Code Toolkit**. It defines the common language and data structures—including models for news content, user data, and remote configurations—that ensure consistency and accelerate development across the Flutter mobile app, web dashboard, and Dart Frog backend API. ## ⭐ Feature Showcase: The Foundation for a Robust News Platform This package provides the critical building blocks for a professional news application ecosystem.
-🧱 Unified Data Models +🏗️ Foundational Data Architecture -### 📰 News & Content Entities -- **`Headline`, `Topic`, `Source`, `Country`, `Language`:** Comprehensive models for all core news content, ensuring consistent representation across the entire toolkit. -- **`FeedItem`:** An abstract base class for all items that can appear in a mixed content feed, supporting diverse content types like headlines, ads, and suggested content. +--- -### 🔐 User & Authentication Data -- **`User`, `AppUserRole`, `DashboardUserRole`, `Permission`:** Robust models for user profiles, roles, and permissions, enabling secure and personalized experiences. -- **`UserContentPreferences`, `UserAppSettings`:** Detailed models for storing user-specific content preferences (e.g., followed topics, saved headlines) and application settings (e.g., theme, language). +### 📰 Core Content Entities -### 📲 Push Notifications & Subscriptions -- **`NotificationSubscription`:** A model for storing user-defined notification subscriptions. -- **`Device`:** Represents a user's device registered for push notifications. -- **`NotificationPayload`:** Defines the structure for a push notification message. +Provides comprehensive models for all core news content, ensuring consistent representation across the entire toolkit. -### 💾 User Presets -- **`SavedFilter`:** A model for storing user-defined filter combinations. +> **Your Advantage:** A unified and robust data schema that simplifies content management and ensures data integrity across all applications. -### ⚙️ Application Configuration -- **`RemoteConfig`:** A central container for all dynamic application settings, fetched from a remote source. This includes: - - **`AdConfig`:** Master configuration for all advertising, now featuring **highly flexible, role-based control** over ad visibility and frequency for feed, article, and interstitial ads. This allows for granular control over monetization strategies per user segment. - - **`PushNotificationConfig`:** Manages global settings for the push notification system. - - **`UserPreferenceConfig`:** Defines user preference limits (e.g., max followed items, saved headlines) tiered by user role. - - **`AppStatus`:** Manages application-wide status, including maintenance mode and force update directives. - - **`FeedDecoratorConfig`:** Configures dynamic in-feed elements like calls-to-action and content collections, with role-based visibility. +--- -### 📊 Dashboard & Analytics -- **`DashboardSummary`:** Provides a model for aggregated statistics, useful for administrative dashboards. +### 🔄 Polymorphic Feed Items -### 🔍 Querying & API Communication -- **`SortOption`, `PaginationOptions`:** Models for building flexible, server-side queries with sorting and pagination capabilities. -- **`PaginatedResponse`, `SuccessApiResponse`:** Standardized response wrappers for consistent API communication. -- **`HttpException` Hierarchy:** A comprehensive set of custom exceptions for predictable and standardized error handling across all layers of the application. +Enables diverse content types (headlines, ads, suggested content) to coexist seamlessly within a single feed. -> **💡 Your Advantage:** You get a meticulously designed, production-quality data layer that forms the backbone of a scalable news platform. This package eliminates the need to define core data structures from scratch, saving months of development time and ensuring architectural consistency. +> **Your Advantage:** Flexible feed construction that supports rich, mixed-content experiences without complex type handling. + +--- + +### 📊 Querying & API Communication + +Offers models for building flexible, server-side queries with sorting and pagination capabilities. + +> **Your Advantage:** Efficient data retrieval and reduced backend load through optimized query parameters. + +--- + +### ✅ Standardized API Responses + +Ensures consistent response wrappers for all API communication, guaranteeing predictable data structures. + +> **Your Advantage:** Simplified API client development and robust error handling due to a uniform response format. + +--- + +### 🚨 Predictable Error Handling + +Includes a comprehensive set of custom exceptions for predictable and standardized error handling across all layers of the application. + +> **Your Advantage:** Clear, actionable error messages that streamline debugging and improve application stability. + +--- + +### 📈 Dashboard Analytics Summary + +Provides a model for aggregated statistics, useful for administrative dashboards. + +> **Your Advantage:** Quick and easy access to key performance indicators for administrative oversight.
-🧪 Sample Data & Fixtures +👤 User Identity & Personalization + +--- + +### 🔐 Secure User Management + +Offers robust models for user profiles, roles, and permissions, enabling secure and personalized experiences. + +> **Your Advantage:** A secure and flexible user authentication and authorization system that supports tiered access and custom permissions. + +--- + +### ⚙️ User-Specific Settings + +Provides detailed models for storing user-specific content preferences (e.g., followed topics, saved headlines) and application settings (e.g., theme, language). + +> **Your Advantage:** Highly personalized user experiences through persistent storage of individual content interests and application configurations. + +--- + +### 💾 Reusable Content Filters + +Enables users to store and quickly re-apply complex content filters. + +> **Your Advantage:** Enhanced user engagement and efficiency by enabling quick access to frequently used content filters. + +
+ +
+🔔 Dynamic Push Notifications + +--- + +### 📩 User-Defined Subscriptions + +Allows users to create and manage their notification subscriptions, offering granular control over alert preferences. + +> **Your Advantage:** Empower users with precise control over the notifications they receive, reducing noise and increasing relevance. + +--- + +### 📱 Device Registration & Management + +Manages user device registrations for push notifications, linking user accounts to specific device tokens. + +> **Your Advantage:** Reliable and targeted delivery of notifications to the correct user devices across different platforms. + +--- + +### ✉️ Standardized Notification Payloads + +Defines a generic structure for push notification messages, ensuring consistent content delivery across various providers. + +> **Your Advantage:** Simplified development of notification sending logic with a clear, consistent payload structure for all alerts. + +--- + +### 🌐 Global Push System Configuration + +Provides centralized management for the entire push notification system, including provider credentials and feature availability. + +> **Your Advantage:** Centralized control over your push notification infrastructure, allowing for dynamic adjustments and provider switching without code changes. + +
+ +
+⚙️ Remote Configuration & Monetization + +--- + +### ☁️ Centralized App Control + +Offers a central container for all dynamic application settings, fetched from a remote source, enabling real-time adjustments to app behavior. + +> **Your Advantage:** Agility in managing application features and behavior without requiring app store updates. + +--- + +### 💰 Flexible Ad Monetization + +Provides master configuration for all advertising, featuring **highly flexible, role-based control** over ad visibility and frequency for various ad types. + +> **Your Advantage:** Granular control over monetization strategies, allowing optimization of ad revenue and user experience per user segment. + +--- + +### 🎯 Tiered User Feature Limits + +Defines user preference limits (e.g., max followed items, saved headlines) tiered by user role. + +> **Your Advantage:** Effective feature differentiation across user tiers (guest, standard, premium) and robust backend enforcement of limits. + +--- + +### 🚧 Application Status Management + +Manages application-wide status, including maintenance mode and force update directives. + +> **Your Advantage:** Proactive communication with users during maintenance and seamless enforcement of critical updates. + +--- + +### 🎨 Dynamic In-Feed Elements + +Configures dynamic in-feed elements like calls-to-action and content collections, with role-based visibility. + +> **Your Advantage:** Enhanced user engagement and promotional capabilities through dynamically injected content within the news feed. + +
+ +
+🧪 Development & Testing Accelerators + +--- -To further accelerate development and provide immediate demonstration capabilities, `core` includes a set of **pre-defined, in-memory fixture data** for core models like `Headline`, `Topic`, `Source`, `Country`, and `RemoteConfig`. These fixtures are directly embedded as Dart constants, enabling: +### ⚡ Ready-to-Use Sample Data -* **⚡ Instant Setup:** Quickly initialize in-memory data stores for testing or local development without needing a backend. -* **🧪 Reliable Testing:** Use consistent, realistic data for unit and integration tests. -* **🚀 Rapid Prototyping:** Jumpstart UI development and feature implementation with readily available sample content. +Includes a set of pre-defined, in-memory fixture data for core models, facilitating rapid development and testing. -> **💡 Your Advantage:** Leverage ready-to-use sample data for rapid prototyping, reliable testing, and instant setup of development environments, significantly accelerating your workflow. +> **Your Advantage:** Rapid prototyping, reliable testing, and instant setup of development environments, significantly accelerating your workflow.
From c09b9a3e8cf927174a18f8460f32976cbe2fd8e3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:15:38 +0100 Subject: [PATCH 73/78] style: format --- README.md | 2 +- lib/src/models/entities/headline.dart | 28 +++++++++---------- .../push_notification_device.dart | 3 +- .../user_content_preferences_test.dart | 5 +++- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index e74d1c8b..f0c57a8f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- coverage + coverage Live Docs: View Main Project: Browse

diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index db84510b..83164a12 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -92,20 +92,20 @@ class Headline extends FeedItem { @override List get props => [ - id, - title, - excerpt, - url, - imageUrl, - createdAt, - updatedAt, - status, - source, - eventCountry, - topic, - isBreaking, - type, - ]; + id, + title, + excerpt, + url, + imageUrl, + createdAt, + updatedAt, + status, + source, + eventCountry, + topic, + isBreaking, + type, + ]; @override bool get stringify => true; diff --git a/lib/src/models/push_notifications/push_notification_device.dart b/lib/src/models/push_notifications/push_notification_device.dart index 0cae03e1..55dcd6ed 100644 --- a/lib/src/models/push_notifications/push_notification_device.dart +++ b/lib/src/models/push_notifications/push_notification_device.dart @@ -28,7 +28,8 @@ class PushNotificationDevice extends Equatable { }); /// Creates a [PushNotificationDevice] from JSON data. - factory PushNotificationDevice.fromJson(Map json) => _$PushNotificationDeviceFromJson(json); + factory PushNotificationDevice.fromJson(Map json) => + _$PushNotificationDeviceFromJson(json); /// The unique identifier for this device registration. final String id; diff --git a/test/src/models/user_preferences/user_content_preferences_test.dart b/test/src/models/user_preferences/user_content_preferences_test.dart index 94f9ca90..d2cbcb13 100644 --- a/test/src/models/user_preferences/user_content_preferences_test.dart +++ b/test/src/models/user_preferences/user_content_preferences_test.dart @@ -20,7 +20,10 @@ void main() { expect(preferences.followedSources, isNotEmpty); expect(preferences.followedTopics, isNotEmpty); expect(preferences.savedHeadlines, isNotEmpty); - expect(preferences.notificationSubscriptions, isA>()); + expect( + preferences.notificationSubscriptions, + isA>(), + ); expect(preferences.savedFilters, isNotEmpty); }); }); From 4d7f24ea6f25b0d2fb052e6217bd92fdc530ea18 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:51:13 +0100 Subject: [PATCH 74/78] refactor(user): remove unused notification subscription property - Remove `pushNotificationSubscriptions` property from User class - This property is no longer used in the application and is redundant --- lib/src/models/auth/user.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index 06dcec8e..98c58b74 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -88,8 +88,6 @@ class User extends Equatable { DashboardUserRole? dashboardRole, DateTime? createdAt, Map? feedDecoratorStatus, - Map? - pushNotificationSubscriptions, }) { return User( id: id ?? this.id, From d5fb10f6b871dd772b97ef43cb60305146d527bc Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:51:59 +0100 Subject: [PATCH 75/78] docs: fix capitalization in CHANGELOG.md - Capitalize first letter of the feat description --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e4595a..37bc7965 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Upcoming -- **feat**: add models/fixtures/tests for push notification system. +- **feat**: Add models/fixtures/tests for push notification system. - **BREAKING** feat!: Deprecate and remove `LocalAd` model and related fixtures. - **feat**: Add 10 new user fixtures, including publishers and standard users. - **chore**: Expand user-related fixtures with detailed settings and preferences. From cafecf002fca5dc141de1373092ef6626956f620 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:53:09 +0100 Subject: [PATCH 76/78] fix(models): update error message in PushNotificationProviderConfig - Modify the error message for missing 'provider' field to be more specific - Change 'FeedItem JSON' to 'PushNotificationProviderConfig JSON' for clarity --- lib/src/models/config/push_notification_provider_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index 94ef72d8..134b8f4b 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -23,7 +23,7 @@ abstract class PushNotificationProviderConfig extends Equatable { factory PushNotificationProviderConfig.fromJson(Map json) { final provider = json['provider'] as String?; if (provider == null) { - throw const FormatException('Missing "provider" field in FeedItem JSON.'); + throw const FormatException('Missing "provider" field in PushNotificationProviderConfig JSON.'); } switch (provider) { From 971a88481bd70b8a6ea200a22a398c87b4b800f6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:53:26 +0100 Subject: [PATCH 77/78] style: format --- lib/src/models/config/push_notification_provider_config.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/models/config/push_notification_provider_config.dart b/lib/src/models/config/push_notification_provider_config.dart index 134b8f4b..582ea7a4 100644 --- a/lib/src/models/config/push_notification_provider_config.dart +++ b/lib/src/models/config/push_notification_provider_config.dart @@ -23,7 +23,9 @@ abstract class PushNotificationProviderConfig extends Equatable { factory PushNotificationProviderConfig.fromJson(Map json) { final provider = json['provider'] as String?; if (provider == null) { - throw const FormatException('Missing "provider" field in PushNotificationProviderConfig JSON.'); + throw const FormatException( + 'Missing "provider" field in PushNotificationProviderConfig JSON.', + ); } switch (provider) { From cdbc913bae1cc7696a7084e603781fc617db1303 Mon Sep 17 00:00:00 2001 From: fulleni Date: Fri, 7 Nov 2025 10:56:19 +0100 Subject: [PATCH 78/78] refactor(user): remove unused import - Removed unused import statement for PushNotificationSubscription - This change simplifies the code and removes an unnecessary dependency --- lib/src/models/auth/user.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/models/auth/user.dart b/lib/src/models/auth/user.dart index 98c58b74..a299c07b 100644 --- a/lib/src/models/auth/user.dart +++ b/lib/src/models/auth/user.dart @@ -1,6 +1,5 @@ import 'package:core/src/enums/enums.dart'; import 'package:core/src/models/auth/user_feed_decorator_status.dart'; -import 'package:core/src/models/push_notifications/push_notification_subscription.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart';