From dffb02017a0b8df3327040403d0473d08da3b549 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 07:55:27 +0100 Subject: [PATCH 01/93] feat(core): add EngagementMode enum Introduces the `EngagementMode` enum as part of the new User-Generated Content (UGC) feature. This enum will be used in the `EngagementConfig` to allow remote configuration of the available user engagement features, specifically toggling between `reactionsOnly` and `reactionsAndComments`. --- lib/src/enums/engagement_mode.dart | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/src/enums/engagement_mode.dart diff --git a/lib/src/enums/engagement_mode.dart b/lib/src/enums/engagement_mode.dart new file mode 100644 index 0000000..ef483c7 --- /dev/null +++ b/lib/src/enums/engagement_mode.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template engagement_mode} +/// Defines the engagement features available to users. +/// {@endtemplate} +@JsonEnum() +enum EngagementMode { + /// Users can only react to headlines. + @JsonValue('reactionsOnly') + reactionsOnly, + + /// Users can both react and comment on headlines. + @JsonValue('reactionsAndComments') + reactionsAndComments, +} From a4e17224bc28b93986d34aeaac266345dc466ed5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 07:56:46 +0100 Subject: [PATCH 02/93] feat(core): add CommentStatus enum Introduces the `CommentStatus` enum to manage the moderation workflow for user comments. This enum includes states like `pendingReview`, `approved`, `rejected`, `flaggedByAI`, and `hiddenByUser`, providing a robust foundation for both manual and future automated moderation systems. --- lib/src/enums/comment_status.dart | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 lib/src/enums/comment_status.dart diff --git a/lib/src/enums/comment_status.dart b/lib/src/enums/comment_status.dart new file mode 100644 index 0000000..95fb5a5 --- /dev/null +++ b/lib/src/enums/comment_status.dart @@ -0,0 +1,27 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template comment_status} +/// Defines the lifecycle status of a user-submitted comment. +/// {@endtemplate} +@JsonEnum() +enum CommentStatus { + /// The comment has been submitted and is awaiting moderation. + @JsonValue('pendingReview') + pendingReview, + + /// The comment has been approved by a moderator and is publicly visible. + @JsonValue('approved') + approved, + + /// The comment has been rejected by a moderator and is not visible. + @JsonValue('rejected') + rejected, + + /// The comment has been automatically flagged by an AI moderation service. + @JsonValue('flaggedByAI') + flaggedByAI, + + /// The comment has been hidden by the user who posted it. + @JsonValue('hiddenByUser') + hiddenByUser, +} From 602c8eb362c17b9d55e6834cf23a335463c3a9d1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:06:51 +0100 Subject: [PATCH 03/93] feat(core): add ReactionType enum Introduces the `ReactionType` enum, which defines the set of possible emoji-based reactions a user can apply to a headline. This allows for a structured and extensible way to manage user reactions. --- lib/src/enums/reaction_type.dart | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 lib/src/enums/reaction_type.dart diff --git a/lib/src/enums/reaction_type.dart b/lib/src/enums/reaction_type.dart new file mode 100644 index 0000000..0015529 --- /dev/null +++ b/lib/src/enums/reaction_type.dart @@ -0,0 +1,38 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template reaction_type} +/// Defines the types of reactions a user can have to a headline. +/// {@endtemplate} +@JsonEnum() +enum ReactionType { + /// Represents a "like" or "thumbs up" reaction. + /// General positive approval. + @JsonValue('like') + like, + + /// Represents an "insightful" or "lightbulb" reaction. + /// Signals the article provided new information or a valuable perspective. + @JsonValue('insightful') + insightful, + + /// Represents an "amusing" or "funny" reaction. + /// For lighthearted or humorous content. + @JsonValue('amusing') + amusing, + + /// Represents a "sad" reaction. + /// For news that evokes empathy or sadness. + @JsonValue('sad') + sad, + + /// Represents an "angry" or "outrageous" reaction. + /// For news that provokes a strong negative emotional response. + @JsonValue('angry') + angry, + + /// Represents a "skeptical" or "questionable" reaction. + /// Signals that the user questions the validity, bias, or sourcing of the + /// article without formally reporting it. + @JsonValue('skeptical') + skeptical, +} From 9224645b235b0ee4fa2341d1fd12fdef6908da00 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:07:56 +0100 Subject: [PATCH 04/93] feat(core): add ReportableEntity enum Introduces the `ReportableEntity` enum to create a strongly-typed discriminator for the reporting system. This enum identifies whether a report pertains to a `headline`, `source`, or `comment`, enabling flexible and type-safe handling of reports. --- lib/src/enums/reportable_entity.dart | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/src/enums/reportable_entity.dart diff --git a/lib/src/enums/reportable_entity.dart b/lib/src/enums/reportable_entity.dart new file mode 100644 index 0000000..403ea51 --- /dev/null +++ b/lib/src/enums/reportable_entity.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template reportable_entity} +/// Defines the types of entities that can be reported by users. +/// +/// This enum acts as a discriminator in the `Report` model to identify +/// what kind of content the report refers to. +/// {@endtemplate} +@JsonEnum() +enum ReportableEntity { + /// The report is for a news headline. + @JsonValue('headline') + headline, + + /// The report is for a news source. + @JsonValue('source') + source, + + /// The report is for a user-submitted comment. + @JsonValue('comment') + comment, +} From 18d4656790dc7e118f25848bec032af73e7b81f0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:09:23 +0100 Subject: [PATCH 05/93] feat(core): add ReportStatus enum Introduces the `ReportStatus` enum to manage the lifecycle of a user-submitted report. It includes states like `submitted`, `inReview`, and `resolved`, which are essential for building a moderation dashboard to process user reports. --- lib/src/enums/report_status.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/src/enums/report_status.dart diff --git a/lib/src/enums/report_status.dart b/lib/src/enums/report_status.dart new file mode 100644 index 0000000..6e0bd3b --- /dev/null +++ b/lib/src/enums/report_status.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template report_status} +/// Defines the moderation workflow status for a user-submitted report. +/// {@endtemplate} +@JsonEnum() +enum ReportStatus { + /// The report has been submitted by a user and is awaiting review. + @JsonValue('submitted') + submitted, + + /// A moderator is actively reviewing the report. + @JsonValue('inReview') + inReview, + + /// The report has been reviewed and a decision has been made. + @JsonValue('resolved') + resolved, +} From b86e3b1e4c11e8ff5386edd23925bac74ff327dc Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:10:39 +0100 Subject: [PATCH 06/93] feat(core): add HeadlineReportReason enum Introduces the `HeadlineReportReason` enum, providing a set of specific, actionable reasons for reporting a headline. This enhances the moderation process by giving clear context to why a headline was flagged, with reasons like `MISINFORMATION_OR_FAKE_NEWS` and `CLICKBAIT_TITLE`. --- lib/src/enums/headline_report_reason.dart | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 lib/src/enums/headline_report_reason.dart diff --git a/lib/src/enums/headline_report_reason.dart b/lib/src/enums/headline_report_reason.dart new file mode 100644 index 0000000..462d131 --- /dev/null +++ b/lib/src/enums/headline_report_reason.dart @@ -0,0 +1,32 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template headline_report_reason} +/// Defines the specific reasons a user can provide for reporting a headline. +/// {@endtemplate} +@JsonEnum() +enum HeadlineReportReason { + /// The content is factually incorrect or considered fake news. + @JsonValue('misinformationOrFakeNews') + misinformationOrFakeNews, + + /// The headline is clickbait or does not reflect the article's content. + @JsonValue('clickbaitTitle') + clickbaitTitle, + + /// The content contains hate speech, graphic violence, or other inappropriate + /// material. + @JsonValue('offensiveOrHateSpeech') + offensiveOrHateSpeech, + + /// The link leads to advertising, phishing, or fraudulent content. + @JsonValue('spamOrScam') + spamOrScam, + + /// The article URL does not work. + @JsonValue('brokenLink') + brokenLink, + + /// The content requires a subscription that was not disclosed. + @JsonValue('paywalled') + paywalled, +} From 9fb38918ee2ed5cd3dac5fae1d56fae3e7ac2c21 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:11:59 +0100 Subject: [PATCH 07/93] feat(core): add SourceReportReason enum Introduces the `SourceReportReason` enum, providing specific reasons for reporting a news source. These reasons, such as `LOW_QUALITY_JOURNALISM` and `HIGH_AD_DENSITY`, are designed to be actionable and can feed data into future systems like an automated content scraper. --- lib/src/enums/source_report_reason.dart | 30 +++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lib/src/enums/source_report_reason.dart diff --git a/lib/src/enums/source_report_reason.dart b/lib/src/enums/source_report_reason.dart new file mode 100644 index 0000000..8c1aa16 --- /dev/null +++ b/lib/src/enums/source_report_reason.dart @@ -0,0 +1,30 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template source_report_reason} +/// Defines the specific reasons a user can provide for reporting a news source. +/// +/// These reasons are designed to be actionable and can provide data to +/// influence other systems, like an automated content scraper's scoring. +/// {@endtemplate} +@JsonEnum() +enum SourceReportReason { + /// The source consistently produces poor, biased, or unreliable content. + @JsonValue('lowQualityJournalism') + lowQualityJournalism, + + /// The source's website is unusable due to excessive ads or popups. + @JsonValue('highAdDensity') + highAdDensity, + + /// The source often requires a subscription to view content. + @JsonValue('frequentPaywalls') + frequentPaywalls, + + /// The source is pretending to be another entity. + @JsonValue('impersonation') + impersonation, + + /// The source has a pattern of publishing fake news or misinformation. + @JsonValue('spreadsMisinformation') + spreadsMisinformation, +} From ed09db4a668faa17374feb8dd6330ff380382b72 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:13:20 +0100 Subject: [PATCH 08/93] feat(core): add CommentReportReason enum Introduces the `CommentReportReason` enum, which provides a clear and specific set of reasons for reporting a user comment. This is a critical component for community self-moderation, with reasons including `SPAM_OR_ADVERTISING` and `HARASSMENT_OR_BULLYING`. --- lib/src/enums/comment_report_reason.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/src/enums/comment_report_reason.dart diff --git a/lib/src/enums/comment_report_reason.dart b/lib/src/enums/comment_report_reason.dart new file mode 100644 index 0000000..492c178 --- /dev/null +++ b/lib/src/enums/comment_report_reason.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template comment_report_reason} +/// Defines the specific reasons a user can provide for reporting a comment. +/// {@endtemplate} +@JsonEnum() +enum CommentReportReason { + /// The comment is unsolicited advertising or promotion. + @JsonValue('spamOrAdvertising') + spamOrAdvertising, + + /// The comment contains abusive language, personal attacks, or bullying. + @JsonValue('harassmentOrBullying') + harassmentOrBullying, + + /// The comment targets certain groups with hateful language. + @JsonValue('hateSpeech') + hateSpeech, +} From a713143b0863989ca68bf6be714ab390c69a622c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:16:05 +0100 Subject: [PATCH 09/93] feat(core): add Comment model Introduces the `Comment` data model, which represents a user-submitted comment on a headline. The model is designed for a robust moderation system, including a `status` field (`CommentStatus`) to track its state through the review process. It also includes fields for `headlineId`, `userId`, and `content`. --- .../user_generated_content/comment.dart | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 lib/src/models/user_generated_content/comment.dart diff --git a/lib/src/models/user_generated_content/comment.dart b/lib/src/models/user_generated_content/comment.dart new file mode 100644 index 0000000..a08cd0f --- /dev/null +++ b/lib/src/models/user_generated_content/comment.dart @@ -0,0 +1,91 @@ +import 'package:core/src/enums/comment_status.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 'comment.g.dart'; + +/// {@template comment} +/// Represents a user-submitted comment on a headline. +/// +/// This model is designed to support a robust moderation system, with a `status` +/// field to track its state through the review process (manual or automated). +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class Comment extends Equatable { + /// {@macro comment} + const Comment({ + required this.id, + required this.headlineId, + required this.userId, + required this.content, + required this.status, + required this.createdAt, + required this.updatedAt, + }); + + /// Creates a [Comment] from JSON data. + factory Comment.fromJson(Map json) => + _$CommentFromJson(json); + + /// The unique identifier for the comment. + final String id; + + /// The ID of the headline this comment is associated with. + final String headlineId; + + /// The ID of the user who authored the comment. + final String userId; + + /// The text content of the comment. + final String content; + + /// The current moderation status of the comment. + final CommentStatus status; + + /// The timestamp when the comment was created. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// The timestamp when the comment was last updated. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime updatedAt; + + /// Converts this [Comment] instance to JSON data. + Map toJson() => _$CommentToJson(this); + + @override + List get props => [ + id, + headlineId, + userId, + content, + status, + createdAt, + updatedAt, + ]; + + /// Creates a copy of this [Comment] but with the given fields replaced + /// with the new values. + Comment copyWith({ + String? id, + String? headlineId, + String? userId, + String? content, + CommentStatus? status, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return Comment( + id: id ?? this.id, + headlineId: headlineId ?? this.headlineId, + userId: userId ?? this.userId, + content: content ?? this.content, + status: status ?? this.status, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } +} From cca56991dc09988ef78b31617443d092789b54ca Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:18:00 +0100 Subject: [PATCH 10/93] feat(core): add HeadlineReaction model Introduces the `HeadlineReaction` model to track individual user reactions to headlines. This model links a `userId` to a `headlineId` with a specific `ReactionType`, preventing duplicate reactions and providing a rich dataset for engagement analysis. --- .../headline_reaction.dart | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 lib/src/models/user_generated_content/headline_reaction.dart diff --git a/lib/src/models/user_generated_content/headline_reaction.dart b/lib/src/models/user_generated_content/headline_reaction.dart new file mode 100644 index 0000000..da94510 --- /dev/null +++ b/lib/src/models/user_generated_content/headline_reaction.dart @@ -0,0 +1,71 @@ +import 'package:core/src/enums/reaction_type.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 'headline_reaction.g.dart'; + +/// {@template headline_reaction} +/// Represents a single, unique reaction from a user to a headline. +/// +/// This model ensures that a user can only have one reaction of a specific +/// type on any given headline, providing a clear and non-duplicative dataset +/// for engagement analysis. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class HeadlineReaction extends Equatable { + /// {@macro headline_reaction} + const HeadlineReaction({ + required this.id, + required this.headlineId, + required this.userId, + required this.reactionType, + required this.createdAt, + }); + + /// Creates a [HeadlineReaction] from JSON data. + factory HeadlineReaction.fromJson(Map json) => + _$HeadlineReactionFromJson(json); + + /// The unique identifier for the reaction. + final String id; + + /// The ID of the headline being reacted to. + final String headlineId; + + /// The ID of the user who made the reaction. + final String userId; + + /// The type of reaction (e.g., like, insightful, etc). + final ReactionType reactionType; + + /// The timestamp when the reaction was created. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// Converts this [HeadlineReaction] instance to JSON data. + Map toJson() => _$HeadlineReactionToJson(this); + + @override + List get props => [id, headlineId, userId, reactionType, createdAt]; + + /// Creates a copy of this [HeadlineReaction] but with the given fields + /// replaced with the new values. + HeadlineReaction copyWith({ + String? id, + String? headlineId, + String? userId, + ReactionType? reactionType, + DateTime? createdAt, + }) { + return HeadlineReaction( + id: id ?? this.id, + headlineId: headlineId ?? this.headlineId, + userId: userId ?? this.userId, + reactionType: reactionType ?? this.reactionType, + createdAt: createdAt ?? this.createdAt, + ); + } +} From 1e46762100c13b690e40b07a703428f365c83dc8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:19:14 +0100 Subject: [PATCH 11/93] feat(core): add Report model Introduces the `Report` data model, a flexible structure for handling user reports across different entity types. It uses a `ReportableEntity` enum and `entityId` to dynamically target headlines, sources, or comments. The model includes the report reason, additional comments, and a `ReportStatus` for the moderation workflow. --- .../models/user_generated_content/report.dart | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 lib/src/models/user_generated_content/report.dart diff --git a/lib/src/models/user_generated_content/report.dart b/lib/src/models/user_generated_content/report.dart new file mode 100644 index 0000000..0cc6876 --- /dev/null +++ b/lib/src/models/user_generated_content/report.dart @@ -0,0 +1,104 @@ +import 'package:core/src/enums/comment_report_reason.dart'; +import 'package:core/src/enums/headline_report_reason.dart'; +import 'package:core/src/enums/report_status.dart'; +import 'package:core/src/enums/reportable_entity.dart'; +import 'package:core/src/enums/source_report_reason.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 'report.g.dart'; + +/// {@template report} +/// A flexible data model for handling user reports across different entity +/// types. +/// +/// It uses a `ReportableEntity` enum and `entityId` to dynamically target +/// headlines, sources, or comments. The model includes the report reason, +/// additional comments, and a `ReportStatus` for the moderation workflow. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class Report extends Equatable { + /// {@macro report} + const Report({ + required this.id, + required this.reporterUserId, + required this.entityType, + required this.entityId, + required this.reason, + required this.status, + required this.createdAt, + this.additionalComments, + }); + + /// Creates a [Report] from JSON data. + factory Report.fromJson(Map json) => _$ReportFromJson(json); + + /// The unique identifier for the report. + final String id; + + /// The ID of the user who made the report. + final String reporterUserId; + + /// The type of entity being reported (e.g., headline, source, comment). + final ReportableEntity entityType; + + /// The ID of the specific item being reported. + final String entityId; + + /// The specific reason for the report. This is a dynamic field that holds + /// the string value of the relevant reason enum (e.g., + /// [HeadlineReportReason], [SourceReportReason], [CommentReportReason]). + final String reason; + + /// The current moderation status of the report. + final ReportStatus status; + + /// Optional additional comments from the user providing more context. + final String? additionalComments; + + /// The timestamp when the report was created. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// Converts this [Report] instance to JSON data. + Map toJson() => _$ReportToJson(this); + + @override + List get props => [ + id, + reporterUserId, + entityType, + entityId, + reason, + status, + additionalComments, + createdAt, + ]; + + /// Creates a copy of this [Report] but with the given fields replaced + /// with the new values. + Report copyWith({ + String? id, + String? reporterUserId, + ReportableEntity? entityType, + String? entityId, + String? reason, + ReportStatus? status, + String? additionalComments, + DateTime? createdAt, + }) { + return Report( + id: id ?? this.id, + reporterUserId: reporterUserId ?? this.reporterUserId, + entityType: entityType ?? this.entityType, + entityId: entityId ?? this.entityId, + reason: reason ?? this.reason, + status: status ?? this.status, + additionalComments: additionalComments ?? this.additionalComments, + createdAt: createdAt ?? this.createdAt, + ); + } +} From 85e0f51a1b0c419b28de6864f163910dcd159695 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:22:00 +0100 Subject: [PATCH 12/93] feat(core): add EngagementConfig model Introduces the `EngagementConfig` model to be part of the remote configuration. This model allows administrators to remotely enable/disable the entire engagement system, switch between `reactionsOnly` and `reactionsAndComments` modes, and toggle a flag for future AI moderation. --- lib/src/models/config/engagement_config.dart | 58 ++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/src/models/config/engagement_config.dart diff --git a/lib/src/models/config/engagement_config.dart b/lib/src/models/config/engagement_config.dart new file mode 100644 index 0000000..c149b83 --- /dev/null +++ b/lib/src/models/config/engagement_config.dart @@ -0,0 +1,58 @@ +import 'package:core/src/enums/engagement_mode.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'engagement_config.g.dart'; + +/// {@template engagement_config} +/// Defines the remote configuration for user engagement features. +/// +/// This model allows administrators to remotely enable/disable the entire +/// engagement system, switch between `reactionsOnly` and `reactionsAndComments` +/// modes, and toggle a flag for future AI moderation. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class EngagementConfig extends Equatable { + /// {@macro engagement_config} + const EngagementConfig({ + required this.enabled, + required this.engagementMode, + required this.aiModerationEnabled, + }); + + /// Creates an [EngagementConfig] from JSON data. + factory EngagementConfig.fromJson(Map json) => + _$EngagementConfigFromJson(json); + + /// A master switch to enable or disable the entire engagement system. + final bool enabled; + + /// Defines the available engagement features (e.g., reactions only or both + /// reactions and comments). + final EngagementMode engagementMode; + + /// A flag to enable or disable AI-powered comment moderation. + final bool aiModerationEnabled; + + /// Converts this [EngagementConfig] instance to JSON data. + Map toJson() => _$EngagementConfigToJson(this); + + @override + List get props => [enabled, engagementMode, aiModerationEnabled]; + + /// Creates a copy of this [EngagementConfig] but with the given fields + /// replaced with the new values. + EngagementConfig copyWith({ + bool? enabled, + EngagementMode? engagementMode, + bool? aiModerationEnabled, + }) { + return EngagementConfig( + enabled: enabled ?? this.enabled, + engagementMode: engagementMode ?? this.engagementMode, + aiModerationEnabled: aiModerationEnabled ?? this.aiModerationEnabled, + ); + } +} From 6969934be048b9b6af92605da601c8701e11a1d4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:22:45 +0100 Subject: [PATCH 13/93] feat(core): add ReportingConfig model Introduces the `ReportingConfig` model for remote configuration. This allows administrators to enable or disable the reporting functionality for each reportable entity (`headline`, `source`, `comment`) individually. --- lib/src/models/config/reporting_config.dart | 62 +++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/src/models/config/reporting_config.dart diff --git a/lib/src/models/config/reporting_config.dart b/lib/src/models/config/reporting_config.dart new file mode 100644 index 0000000..566d3a7 --- /dev/null +++ b/lib/src/models/config/reporting_config.dart @@ -0,0 +1,62 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'reporting_config.g.dart'; + +/// {@template reporting_config} +/// Defines the remote configuration for the user content reporting system. +/// +/// This allows administrators to enable or disable the reporting functionality +/// for each reportable entity (`headline`, `source`, `comment`) individually. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class ReportingConfig extends Equatable { + /// {@macro reporting_config} + const ReportingConfig({ + required this.headlineReportingEnabled, + required this.sourceReportingEnabled, + required this.commentReportingEnabled, + }); + + /// Creates a [ReportingConfig] from JSON data. + factory ReportingConfig.fromJson(Map json) => + _$ReportingConfigFromJson(json); + + /// A switch to enable or disable reporting for headlines. + final bool headlineReportingEnabled; + + /// A switch to enable or disable reporting for news sources. + final bool sourceReportingEnabled; + + /// A switch to enable or disable reporting for user comments. + final bool commentReportingEnabled; + + /// Converts this [ReportingConfig] instance to JSON data. + Map toJson() => _$ReportingConfigToJson(this); + + @override + List get props => [ + headlineReportingEnabled, + sourceReportingEnabled, + commentReportingEnabled, + ]; + + /// Creates a copy of this [ReportingConfig] but with the given fields + /// replaced with the new values. + ReportingConfig copyWith({ + bool? headlineReportingEnabled, + bool? sourceReportingEnabled, + bool? commentReportingEnabled, + }) { + return ReportingConfig( + headlineReportingEnabled: + headlineReportingEnabled ?? this.headlineReportingEnabled, + sourceReportingEnabled: + sourceReportingEnabled ?? this.sourceReportingEnabled, + commentReportingEnabled: + commentReportingEnabled ?? this.commentReportingEnabled, + ); + } +} From dcec3f9d253af8f85970dbf7e982066b39499a41 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:24:34 +0100 Subject: [PATCH 14/93] feat(core): add ReviewFunnelConfig model Introduces the `ReviewFunnelConfig` model for remote configuration. This model makes the "Smart Review Funnel" behavior configurable from a dashboard, allowing admins to set the `positiveInteractionThreshold` required to trigger the prompt and the `initialPromptCooldownDays`. --- .../models/config/review_funnel_config.dart | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/src/models/config/review_funnel_config.dart diff --git a/lib/src/models/config/review_funnel_config.dart b/lib/src/models/config/review_funnel_config.dart new file mode 100644 index 0000000..73d0d4b --- /dev/null +++ b/lib/src/models/config/review_funnel_config.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'review_funnel_config.g.dart'; + +/// {@template review_funnel_config} +/// Defines the remote configuration for the "Smart Review Funnel" feature. +/// +/// This model makes the review funnel's behavior configurable from a dashboard, +/// allowing admins to set the `positiveInteractionThreshold` required to +/// trigger the prompt and the `initialPromptCooldownDays`. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class ReviewFunnelConfig extends Equatable { + /// {@macro review_funnel_config} + const ReviewFunnelConfig({ + required this.positiveInteractionThreshold, + required this.initialPromptCooldownDays, + }); + + /// Creates a [ReviewFunnelConfig] from JSON data. + factory ReviewFunnelConfig.fromJson(Map json) => + _$ReviewFunnelConfigFromJson(json); + + /// The number of positive interactions (e.g., saving a headline) required + /// to trigger the initial review prompt. + final int positiveInteractionThreshold; + + /// The number of days to wait before showing the initial prompt again if the + /// user dismisses it. + final int initialPromptCooldownDays; + + /// Converts this [ReviewFunnelConfig] instance to JSON data. + Map toJson() => _$ReviewFunnelConfigToJson(this); + + @override + List get props => [ + positiveInteractionThreshold, + initialPromptCooldownDays, + ]; + + /// Creates a copy of this [ReviewFunnelConfig] but with the given fields + /// replaced with the new values. + ReviewFunnelConfig copyWith({ + int? positiveInteractionThreshold, + int? initialPromptCooldownDays, + }) { + return ReviewFunnelConfig( + positiveInteractionThreshold: + positiveInteractionThreshold ?? this.positiveInteractionThreshold, + initialPromptCooldownDays: + initialPromptCooldownDays ?? this.initialPromptCooldownDays, + ); + } +} From 782cb2d8a3c47abd458491fe815d304dfb8e103b Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:25:17 +0100 Subject: [PATCH 15/93] feat(core): integrate UGC configs into FeaturesConfig Updates the `FeaturesConfig` model to include the new configuration models for user-generated content: `EngagementConfig`, `ReportingConfig`, and `ReviewFunnelConfig`. This centralizes all feature-related remote configurations. --- lib/src/models/config/features_config.dart | 30 +++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/src/models/config/features_config.dart b/lib/src/models/config/features_config.dart index 5aa1c9f..9a09ff3 100644 --- a/lib/src/models/config/features_config.dart +++ b/lib/src/models/config/features_config.dart @@ -1,6 +1,9 @@ import 'package:core/src/models/config/ad_config.dart'; +import 'package:core/src/models/config/engagement_config.dart'; import 'package:core/src/models/config/feed_config.dart'; import 'package:core/src/models/config/push_notification_config.dart'; +import 'package:core/src/models/config/reporting_config.dart'; +import 'package:core/src/models/config/review_funnel_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -18,6 +21,9 @@ class FeaturesConfig extends Equatable { required this.ads, required this.pushNotifications, required this.feed, + required this.engagement, + required this.reporting, + required this.reviewFunnel, }); /// Creates a [FeaturesConfig] from JSON data. @@ -33,11 +39,27 @@ class FeaturesConfig extends Equatable { /// Configuration for all feed-related features. final FeedConfig feed; + /// Configuration for user engagement features (reactions, comments). + final EngagementConfig engagement; + + /// Configuration for user content reporting features. + final ReportingConfig reporting; + + /// Configuration for the smart app review funnel. + final ReviewFunnelConfig reviewFunnel; + /// Converts this [FeaturesConfig] instance to JSON data. Map toJson() => _$FeaturesConfigToJson(this); @override - List get props => [ads, pushNotifications, feed]; + List get props => [ + ads, + pushNotifications, + feed, + engagement, + reporting, + reviewFunnel, + ]; /// Creates a copy of this [FeaturesConfig] but with the given fields /// replaced with the new values. @@ -45,11 +67,17 @@ class FeaturesConfig extends Equatable { AdConfig? ads, PushNotificationConfig? pushNotifications, FeedConfig? feed, + EngagementConfig? engagement, + ReportingConfig? reporting, + ReviewFunnelConfig? reviewFunnel, }) { return FeaturesConfig( ads: ads ?? this.ads, pushNotifications: pushNotifications ?? this.pushNotifications, feed: feed ?? this.feed, + engagement: engagement ?? this.engagement, + reporting: reporting ?? this.reporting, + reviewFunnel: reviewFunnel ?? this.reviewFunnel, ); } } From 0b998b132cc74b4ad003b393fb07c1f1f253452f Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:26:35 +0100 Subject: [PATCH 16/93] feat(core): extend UserLimitsConfig with UGC limits Updates the `UserLimitsConfig` model to include new role-based limits for user-generated content. This adds `commentsPerDay` and `reportsPerDay` as configurable limits per user role, ensuring the system can be scaled and managed effectively. --- lib/src/models/config/user_limits_config.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart index c871b7e..27bd743 100644 --- a/lib/src/models/config/user_limits_config.dart +++ b/lib/src/models/config/user_limits_config.dart @@ -22,6 +22,8 @@ class UserLimitsConfig extends Equatable { required this.savedHeadlines, required this.savedHeadlineFilters, required this.savedSourceFilters, + required this.commentsPerDay, + required this.reportsPerDay, }); /// Creates a [UserLimitsConfig] from JSON data. @@ -45,6 +47,12 @@ class UserLimitsConfig extends Equatable { /// defines the limits per user role. final Map savedSourceFilters; + /// Role-based limits for the number of comments a user can post per day. + final Map commentsPerDay; + + /// Role-based limits for the number of reports a user can submit per day. + final Map reportsPerDay; + /// Converts this [UserLimitsConfig] instance to JSON data. Map toJson() => _$UserLimitsConfigToJson(this); @@ -54,6 +62,8 @@ class UserLimitsConfig extends Equatable { savedHeadlines, savedHeadlineFilters, savedSourceFilters, + commentsPerDay, + reportsPerDay, ]; /// Creates a copy of this [UserLimitsConfig] but with the given fields @@ -63,12 +73,16 @@ class UserLimitsConfig extends Equatable { Map? savedHeadlines, Map? savedHeadlineFilters, Map? savedSourceFilters, + Map? commentsPerDay, + Map? reportsPerDay, }) { return UserLimitsConfig( followedItems: followedItems ?? this.followedItems, savedHeadlines: savedHeadlines ?? this.savedHeadlines, savedHeadlineFilters: savedHeadlineFilters ?? this.savedHeadlineFilters, savedSourceFilters: savedSourceFilters ?? this.savedSourceFilters, + commentsPerDay: commentsPerDay ?? this.commentsPerDay, + reportsPerDay: reportsPerDay ?? this.reportsPerDay, ); } } From d997c411660747e6c9231a4727a9d9fd8f9544b9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:27:12 +0100 Subject: [PATCH 17/93] chore: barrels --- lib/src/enums/enums.dart | 8 ++++++++ lib/src/models/config/config.dart | 3 +++ lib/src/models/models.dart | 1 + 3 files changed, 12 insertions(+) diff --git a/lib/src/enums/enums.dart b/lib/src/enums/enums.dart index f917dba..fe0d2e6 100644 --- a/lib/src/enums/enums.dart +++ b/lib/src/enums/enums.dart @@ -6,16 +6,24 @@ export 'app_font_weight.dart'; export 'app_text_scale_factor.dart'; export 'app_user_role.dart'; export 'banner_ad_shape.dart'; +export 'comment_report_reason.dart'; +export 'comment_status.dart'; export 'content_status.dart'; export 'content_type.dart'; export 'dashboard_user_role.dart'; export 'device_platform.dart'; +export 'engagement_mode.dart'; export 'feed_decorator_category.dart'; export 'feed_decorator_type.dart'; export 'feed_item_click_behavior.dart'; export 'feed_item_density.dart'; export 'feed_item_image_style.dart'; +export 'headline_report_reason.dart'; export 'push_notification_provider.dart'; export 'push_notification_subscription_delivery_type.dart'; +export 'reaction_type.dart'; +export 'report_status.dart'; +export 'reportable_entity.dart'; export 'sort_order.dart'; +export 'source_report_reason.dart'; export 'source_type.dart'; diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index a1aa0e8..0b7551e 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,6 +1,7 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; export 'app_config.dart'; +export 'engagement_config.dart'; export 'features_config.dart'; export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; @@ -13,6 +14,8 @@ export 'navigation_ad_configuration.dart'; export 'navigation_ad_frequency_config.dart'; export 'push_notification_config.dart'; export 'remote_config.dart'; +export 'reporting_config.dart'; +export 'review_funnel_config.dart'; export 'saved_filter_limits.dart'; export 'update_config.dart'; export 'user_config.dart'; diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index 08c3081..ee6b2c1 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -8,5 +8,6 @@ export 'notifications/notifications.dart'; export 'push_notifications/push_notifications.dart'; export 'query/query.dart'; export 'responses/responses.dart'; +export 'user_generated_content/user_generated_content.dart'; export 'user_preferences/user_preferences.dart'; export 'user_settings/user_settings.dart'; From 2273e7cd480082db2c1909130a8263ac338c0f43 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:27:20 +0100 Subject: [PATCH 18/93] chore: barrels --- .../models/user_generated_content/user_generated_content.dart | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 lib/src/models/user_generated_content/user_generated_content.dart diff --git a/lib/src/models/user_generated_content/user_generated_content.dart b/lib/src/models/user_generated_content/user_generated_content.dart new file mode 100644 index 0000000..b4e6231 --- /dev/null +++ b/lib/src/models/user_generated_content/user_generated_content.dart @@ -0,0 +1,3 @@ +export 'comment.dart'; +export 'headline_reaction.dart'; +export 'report.dart'; From f35f9346fd753ee52569c9136f8d1054e39cfcf1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:28:08 +0100 Subject: [PATCH 19/93] build(serialization): generate --- lib/src/models/config/app_config.g.dart | 32 ----------- .../models/config/engagement_config.g.dart | 35 ++++++++++++ lib/src/models/config/features_config.g.dart | 15 +++++ lib/src/models/config/feed_config.g.dart | 51 ----------------- .../models/config/general_app_config.g.dart | 23 -------- .../models/config/maintenance_config.g.dart | 21 ------- .../config/navigation_ad_configuration.g.dart | 52 ----------------- .../navigation_ad_frequency_config.g.dart | 32 ----------- lib/src/models/config/update_config.g.dart | 30 ---------- .../models/config/user_limits_config.g.dart | 20 +++++++ .../user_generated_content/comment.g.dart | 48 ++++++++++++++++ .../headline_reaction.g.dart | 43 ++++++++++++++ .../user_generated_content/report.g.dart | 57 +++++++++++++++++++ 13 files changed, 218 insertions(+), 241 deletions(-) delete mode 100644 lib/src/models/config/app_config.g.dart create mode 100644 lib/src/models/config/engagement_config.g.dart delete mode 100644 lib/src/models/config/feed_config.g.dart delete mode 100644 lib/src/models/config/general_app_config.g.dart delete mode 100644 lib/src/models/config/maintenance_config.g.dart delete mode 100644 lib/src/models/config/navigation_ad_configuration.g.dart delete mode 100644 lib/src/models/config/navigation_ad_frequency_config.g.dart delete mode 100644 lib/src/models/config/update_config.g.dart create mode 100644 lib/src/models/user_generated_content/comment.g.dart create mode 100644 lib/src/models/user_generated_content/headline_reaction.g.dart create mode 100644 lib/src/models/user_generated_content/report.g.dart diff --git a/lib/src/models/config/app_config.g.dart b/lib/src/models/config/app_config.g.dart deleted file mode 100644 index a49f838..0000000 --- a/lib/src/models/config/app_config.g.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'app_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AppConfig _$AppConfigFromJson(Map json) => - $checkedCreate('AppConfig', json, ($checkedConvert) { - final val = AppConfig( - maintenance: $checkedConvert( - 'maintenance', - (v) => MaintenanceConfig.fromJson(v as Map), - ), - update: $checkedConvert( - 'update', - (v) => UpdateConfig.fromJson(v as Map), - ), - general: $checkedConvert( - 'general', - (v) => GeneralAppConfig.fromJson(v as Map), - ), - ); - return val; - }); - -Map _$AppConfigToJson(AppConfig instance) => { - 'maintenance': instance.maintenance.toJson(), - 'update': instance.update.toJson(), - 'general': instance.general.toJson(), -}; diff --git a/lib/src/models/config/engagement_config.g.dart b/lib/src/models/config/engagement_config.g.dart new file mode 100644 index 0000000..f4face3 --- /dev/null +++ b/lib/src/models/config/engagement_config.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'engagement_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +EngagementConfig _$EngagementConfigFromJson(Map json) => + $checkedCreate('EngagementConfig', json, ($checkedConvert) { + final val = EngagementConfig( + enabled: $checkedConvert('enabled', (v) => v as bool), + engagementMode: $checkedConvert( + 'engagementMode', + (v) => $enumDecode(_$EngagementModeEnumMap, v), + ), + aiModerationEnabled: $checkedConvert( + 'aiModerationEnabled', + (v) => v as bool, + ), + ); + return val; + }); + +Map _$EngagementConfigToJson(EngagementConfig instance) => + { + 'enabled': instance.enabled, + 'engagementMode': _$EngagementModeEnumMap[instance.engagementMode]!, + 'aiModerationEnabled': instance.aiModerationEnabled, + }; + +const _$EngagementModeEnumMap = { + EngagementMode.reactionsOnly: 'reactionsOnly', + EngagementMode.reactionsAndComments: 'reactionsAndComments', +}; diff --git a/lib/src/models/config/features_config.g.dart b/lib/src/models/config/features_config.g.dart index d6b1a61..e6560bd 100644 --- a/lib/src/models/config/features_config.g.dart +++ b/lib/src/models/config/features_config.g.dart @@ -21,6 +21,18 @@ FeaturesConfig _$FeaturesConfigFromJson(Map json) => 'feed', (v) => FeedConfig.fromJson(v as Map), ), + engagement: $checkedConvert( + 'engagement', + (v) => EngagementConfig.fromJson(v as Map), + ), + reporting: $checkedConvert( + 'reporting', + (v) => ReportingConfig.fromJson(v as Map), + ), + reviewFunnel: $checkedConvert( + 'reviewFunnel', + (v) => ReviewFunnelConfig.fromJson(v as Map), + ), ); return val; }); @@ -30,4 +42,7 @@ Map _$FeaturesConfigToJson(FeaturesConfig instance) => 'ads': instance.ads.toJson(), 'pushNotifications': instance.pushNotifications.toJson(), 'feed': instance.feed.toJson(), + 'engagement': instance.engagement.toJson(), + 'reporting': instance.reporting.toJson(), + 'reviewFunnel': instance.reviewFunnel.toJson(), }; diff --git a/lib/src/models/config/feed_config.g.dart b/lib/src/models/config/feed_config.g.dart deleted file mode 100644 index d83ac51..0000000 --- a/lib/src/models/config/feed_config.g.dart +++ /dev/null @@ -1,51 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'feed_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FeedConfig _$FeedConfigFromJson(Map json) => - $checkedCreate('FeedConfig', json, ($checkedConvert) { - final val = FeedConfig( - itemClickBehavior: $checkedConvert( - 'itemClickBehavior', - (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), - ), - decorators: $checkedConvert( - 'decorators', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$FeedDecoratorTypeEnumMap, k), - FeedDecoratorConfig.fromJson(e as Map), - ), - ), - ), - ); - return val; - }); - -Map _$FeedConfigToJson(FeedConfig instance) => - { - 'itemClickBehavior': - _$FeedItemClickBehaviorEnumMap[instance.itemClickBehavior]!, - 'decorators': instance.decorators.map( - (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), - ), - }; - -const _$FeedItemClickBehaviorEnumMap = { - FeedItemClickBehavior.defaultBehavior: 'default', - FeedItemClickBehavior.internalNavigation: 'internalNavigation', - FeedItemClickBehavior.externalNavigation: 'externalNavigation', -}; - -const _$FeedDecoratorTypeEnumMap = { - FeedDecoratorType.linkAccount: 'linkAccount', - FeedDecoratorType.upgrade: 'upgrade', - FeedDecoratorType.rateApp: 'rateApp', - FeedDecoratorType.enableNotifications: 'enableNotifications', - FeedDecoratorType.suggestedTopics: 'suggestedTopics', - FeedDecoratorType.suggestedSources: 'suggestedSources', -}; diff --git a/lib/src/models/config/general_app_config.g.dart b/lib/src/models/config/general_app_config.g.dart deleted file mode 100644 index 86d9ab8..0000000 --- a/lib/src/models/config/general_app_config.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'general_app_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -GeneralAppConfig _$GeneralAppConfigFromJson( - Map json, -) => $checkedCreate('GeneralAppConfig', json, ($checkedConvert) { - final val = GeneralAppConfig( - termsOfServiceUrl: $checkedConvert('termsOfServiceUrl', (v) => v as String), - privacyPolicyUrl: $checkedConvert('privacyPolicyUrl', (v) => v as String), - ); - return val; -}); - -Map _$GeneralAppConfigToJson(GeneralAppConfig instance) => - { - 'termsOfServiceUrl': instance.termsOfServiceUrl, - 'privacyPolicyUrl': instance.privacyPolicyUrl, - }; diff --git a/lib/src/models/config/maintenance_config.g.dart b/lib/src/models/config/maintenance_config.g.dart deleted file mode 100644 index fe64745..0000000 --- a/lib/src/models/config/maintenance_config.g.dart +++ /dev/null @@ -1,21 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'maintenance_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -MaintenanceConfig _$MaintenanceConfigFromJson(Map json) => - $checkedCreate('MaintenanceConfig', json, ($checkedConvert) { - final val = MaintenanceConfig( - isUnderMaintenance: $checkedConvert( - 'isUnderMaintenance', - (v) => v as bool, - ), - ); - return val; - }); - -Map _$MaintenanceConfigToJson(MaintenanceConfig instance) => - {'isUnderMaintenance': instance.isUnderMaintenance}; diff --git a/lib/src/models/config/navigation_ad_configuration.g.dart b/lib/src/models/config/navigation_ad_configuration.g.dart deleted file mode 100644 index 25ac008..0000000 --- a/lib/src/models/config/navigation_ad_configuration.g.dart +++ /dev/null @@ -1,52 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'navigation_ad_configuration.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -NavigationAdConfiguration _$NavigationAdConfigurationFromJson( - Map json, -) => $checkedCreate('NavigationAdConfiguration', json, ($checkedConvert) { - final val = NavigationAdConfiguration( - enabled: $checkedConvert('enabled', (v) => v as bool), - visibleTo: $checkedConvert( - 'visibleTo', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$AppUserRoleEnumMap, k), - NavigationAdFrequencyConfig.fromJson(e as Map), - ), - ), - ), - adType: $checkedConvert( - 'adType', - (v) => $enumDecodeNullable(_$AdTypeEnumMap, v) ?? AdType.interstitial, - ), - ); - return val; -}); - -Map _$NavigationAdConfigurationToJson( - NavigationAdConfiguration instance, -) => { - 'enabled': instance.enabled, - 'adType': _$AdTypeEnumMap[instance.adType]!, - 'visibleTo': instance.visibleTo.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), - ), -}; - -const _$AppUserRoleEnumMap = { - AppUserRole.premiumUser: 'premiumUser', - AppUserRole.standardUser: 'standardUser', - AppUserRole.guestUser: 'guestUser', -}; - -const _$AdTypeEnumMap = { - AdType.banner: 'banner', - AdType.native: 'native', - AdType.video: 'video', - AdType.interstitial: 'interstitial', -}; diff --git a/lib/src/models/config/navigation_ad_frequency_config.g.dart b/lib/src/models/config/navigation_ad_frequency_config.g.dart deleted file mode 100644 index 956455c..0000000 --- a/lib/src/models/config/navigation_ad_frequency_config.g.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'navigation_ad_frequency_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -NavigationAdFrequencyConfig _$NavigationAdFrequencyConfigFromJson( - Map json, -) => $checkedCreate('NavigationAdFrequencyConfig', json, ($checkedConvert) { - final val = NavigationAdFrequencyConfig( - internalNavigationsBeforeShowingInterstitialAd: $checkedConvert( - 'internalNavigationsBeforeShowingInterstitialAd', - (v) => (v as num).toInt(), - ), - externalNavigationsBeforeShowingInterstitialAd: $checkedConvert( - 'externalNavigationsBeforeShowingInterstitialAd', - (v) => (v as num).toInt(), - ), - ); - return val; -}); - -Map _$NavigationAdFrequencyConfigToJson( - NavigationAdFrequencyConfig instance, -) => { - 'internalNavigationsBeforeShowingInterstitialAd': - instance.internalNavigationsBeforeShowingInterstitialAd, - 'externalNavigationsBeforeShowingInterstitialAd': - instance.externalNavigationsBeforeShowingInterstitialAd, -}; diff --git a/lib/src/models/config/update_config.g.dart b/lib/src/models/config/update_config.g.dart deleted file mode 100644 index 66cee4a..0000000 --- a/lib/src/models/config/update_config.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'update_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UpdateConfig _$UpdateConfigFromJson( - Map json, -) => $checkedCreate('UpdateConfig', json, ($checkedConvert) { - final val = UpdateConfig( - latestAppVersion: $checkedConvert('latestAppVersion', (v) => v as String), - isLatestVersionOnly: $checkedConvert( - 'isLatestVersionOnly', - (v) => v as bool, - ), - iosUpdateUrl: $checkedConvert('iosUpdateUrl', (v) => v as String), - androidUpdateUrl: $checkedConvert('androidUpdateUrl', (v) => v as String), - ); - return val; -}); - -Map _$UpdateConfigToJson(UpdateConfig instance) => - { - 'latestAppVersion': instance.latestAppVersion, - 'isLatestVersionOnly': instance.isLatestVersionOnly, - 'iosUpdateUrl': instance.iosUpdateUrl, - 'androidUpdateUrl': instance.androidUpdateUrl, - }; diff --git a/lib/src/models/config/user_limits_config.g.dart b/lib/src/models/config/user_limits_config.g.dart index 4f39818..e20c750 100644 --- a/lib/src/models/config/user_limits_config.g.dart +++ b/lib/src/models/config/user_limits_config.g.dart @@ -42,6 +42,20 @@ UserLimitsConfig _$UserLimitsConfigFromJson( ), ), ), + commentsPerDay: $checkedConvert( + 'commentsPerDay', + (v) => (v as Map).map( + (k, e) => + MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), + ), + ), + reportsPerDay: $checkedConvert( + 'reportsPerDay', + (v) => (v as Map).map( + (k, e) => + MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), + ), + ), ); return val; }); @@ -60,6 +74,12 @@ Map _$UserLimitsConfigToJson(UserLimitsConfig instance) => 'savedSourceFilters': instance.savedSourceFilters.map( (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), ), + 'commentsPerDay': instance.commentsPerDay.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), + 'reportsPerDay': instance.reportsPerDay.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), }; const _$AppUserRoleEnumMap = { diff --git a/lib/src/models/user_generated_content/comment.g.dart b/lib/src/models/user_generated_content/comment.g.dart new file mode 100644 index 0000000..f680f6b --- /dev/null +++ b/lib/src/models/user_generated_content/comment.g.dart @@ -0,0 +1,48 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'comment.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Comment _$CommentFromJson(Map json) => + $checkedCreate('Comment', json, ($checkedConvert) { + final val = Comment( + id: $checkedConvert('id', (v) => v as String), + headlineId: $checkedConvert('headlineId', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), + content: $checkedConvert('content', (v) => v as String), + status: $checkedConvert( + 'status', + (v) => $enumDecode(_$CommentStatusEnumMap, v), + ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + updatedAt: $checkedConvert( + 'updatedAt', + (v) => dateTimeFromJson(v as String?), + ), + ); + return val; + }); + +Map _$CommentToJson(Comment instance) => { + 'id': instance.id, + 'headlineId': instance.headlineId, + 'userId': instance.userId, + 'content': instance.content, + 'status': _$CommentStatusEnumMap[instance.status]!, + 'createdAt': dateTimeToJson(instance.createdAt), + 'updatedAt': dateTimeToJson(instance.updatedAt), +}; + +const _$CommentStatusEnumMap = { + CommentStatus.pendingReview: 'pendingReview', + CommentStatus.approved: 'approved', + CommentStatus.rejected: 'rejected', + CommentStatus.flaggedByAI: 'flaggedByAI', + CommentStatus.hiddenByUser: 'hiddenByUser', +}; diff --git a/lib/src/models/user_generated_content/headline_reaction.g.dart b/lib/src/models/user_generated_content/headline_reaction.g.dart new file mode 100644 index 0000000..8feba8c --- /dev/null +++ b/lib/src/models/user_generated_content/headline_reaction.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'headline_reaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +HeadlineReaction _$HeadlineReactionFromJson(Map json) => + $checkedCreate('HeadlineReaction', json, ($checkedConvert) { + final val = HeadlineReaction( + id: $checkedConvert('id', (v) => v as String), + headlineId: $checkedConvert('headlineId', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), + reactionType: $checkedConvert( + 'reactionType', + (v) => $enumDecode(_$ReactionTypeEnumMap, v), + ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + ); + return val; + }); + +Map _$HeadlineReactionToJson(HeadlineReaction instance) => + { + 'id': instance.id, + 'headlineId': instance.headlineId, + 'userId': instance.userId, + 'reactionType': _$ReactionTypeEnumMap[instance.reactionType]!, + 'createdAt': dateTimeToJson(instance.createdAt), + }; + +const _$ReactionTypeEnumMap = { + ReactionType.like: 'like', + ReactionType.insightful: 'insightful', + ReactionType.amusing: 'amusing', + ReactionType.sad: 'sad', + ReactionType.angry: 'angry', + ReactionType.skeptical: 'skeptical', +}; diff --git a/lib/src/models/user_generated_content/report.g.dart b/lib/src/models/user_generated_content/report.g.dart new file mode 100644 index 0000000..3016fda --- /dev/null +++ b/lib/src/models/user_generated_content/report.g.dart @@ -0,0 +1,57 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'report.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Report _$ReportFromJson(Map json) => + $checkedCreate('Report', json, ($checkedConvert) { + final val = Report( + id: $checkedConvert('id', (v) => v as String), + reporterUserId: $checkedConvert('reporterUserId', (v) => v as String), + entityType: $checkedConvert( + 'entityType', + (v) => $enumDecode(_$ReportableEntityEnumMap, v), + ), + entityId: $checkedConvert('entityId', (v) => v as String), + reason: $checkedConvert('reason', (v) => v as String), + status: $checkedConvert( + 'status', + (v) => $enumDecode(_$ReportStatusEnumMap, v), + ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + additionalComments: $checkedConvert( + 'additionalComments', + (v) => v as String?, + ), + ); + return val; + }); + +Map _$ReportToJson(Report instance) => { + 'id': instance.id, + 'reporterUserId': instance.reporterUserId, + 'entityType': _$ReportableEntityEnumMap[instance.entityType]!, + 'entityId': instance.entityId, + 'reason': instance.reason, + 'status': _$ReportStatusEnumMap[instance.status]!, + 'additionalComments': instance.additionalComments, + 'createdAt': dateTimeToJson(instance.createdAt), +}; + +const _$ReportableEntityEnumMap = { + ReportableEntity.headline: 'headline', + ReportableEntity.source: 'source', + ReportableEntity.comment: 'comment', +}; + +const _$ReportStatusEnumMap = { + ReportStatus.submitted: 'submitted', + ReportStatus.inReview: 'inReview', + ReportStatus.resolved: 'resolved', +}; From 9f3cdd27ac61112b89bd0e9fa7068feff2d94345 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:37:48 +0100 Subject: [PATCH 20/93] refactor(models): rename ReviewFunnelConfig to AppReviewConfig - Updated class name from ReviewFunnelConfig to AppReviewConfig - Updated file name from review_funnel_config.dart to app_review_config.dart - Updated documentation to reflect new class name and feature description --- ...nel_config.dart => app_review_config.dart} | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) rename lib/src/models/config/{review_funnel_config.dart => app_review_config.dart} (65%) diff --git a/lib/src/models/config/review_funnel_config.dart b/lib/src/models/config/app_review_config.dart similarity index 65% rename from lib/src/models/config/review_funnel_config.dart rename to lib/src/models/config/app_review_config.dart index 73d0d4b..f470b94 100644 --- a/lib/src/models/config/review_funnel_config.dart +++ b/lib/src/models/config/app_review_config.dart @@ -2,10 +2,10 @@ import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; -part 'review_funnel_config.g.dart'; +part 'app_review_config.g.dart'; -/// {@template review_funnel_config} -/// Defines the remote configuration for the "Smart Review Funnel" feature. +/// {@template app_review_config} +/// Defines the remote configuration for the "Smart App Review" feature. /// /// This model makes the review funnel's behavior configurable from a dashboard, /// allowing admins to set the `positiveInteractionThreshold` required to @@ -13,16 +13,16 @@ part 'review_funnel_config.g.dart'; /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class ReviewFunnelConfig extends Equatable { - /// {@macro review_funnel_config} - const ReviewFunnelConfig({ +class AppReviewConfig extends Equatable { + /// {@macro app_review_config} + const AppReviewConfig({ required this.positiveInteractionThreshold, required this.initialPromptCooldownDays, }); - /// Creates a [ReviewFunnelConfig] from JSON data. - factory ReviewFunnelConfig.fromJson(Map json) => - _$ReviewFunnelConfigFromJson(json); + /// Creates a [AppReviewConfig] from JSON data. + factory AppReviewConfig.fromJson(Map json) => + _$AppReviewConfigFromJson(json); /// The number of positive interactions (e.g., saving a headline) required /// to trigger the initial review prompt. @@ -32,8 +32,8 @@ class ReviewFunnelConfig extends Equatable { /// user dismisses it. final int initialPromptCooldownDays; - /// Converts this [ReviewFunnelConfig] instance to JSON data. - Map toJson() => _$ReviewFunnelConfigToJson(this); + /// Converts this [AppReviewConfig] instance to JSON data. + Map toJson() => _$AppReviewConfigToJson(this); @override List get props => [ @@ -41,13 +41,13 @@ class ReviewFunnelConfig extends Equatable { initialPromptCooldownDays, ]; - /// Creates a copy of this [ReviewFunnelConfig] but with the given fields + /// Creates a copy of this [AppReviewConfig] but with the given fields /// replaced with the new values. - ReviewFunnelConfig copyWith({ + AppReviewConfig copyWith({ int? positiveInteractionThreshold, int? initialPromptCooldownDays, }) { - return ReviewFunnelConfig( + return AppReviewConfig( positiveInteractionThreshold: positiveInteractionThreshold ?? this.positiveInteractionThreshold, initialPromptCooldownDays: From 08066dd24961865ccfb06629b580e1cc4ccc0f8a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:38:04 +0100 Subject: [PATCH 21/93] refactor(config): introduce CommunityConfig and deprecate related models - Introduce CommunityConfig to encapsulate engagement, reporting, and app review configurations - Replace EngagementConfig, ReportingConfig, and ReviewFunnelConfig with CommunityConfig in FeaturesConfig - Update imports and usages accordingly --- lib/src/models/config/community_config.dart | 58 +++++++++++++++++++++ lib/src/models/config/features_config.dart | 35 +++---------- 2 files changed, 65 insertions(+), 28 deletions(-) create mode 100644 lib/src/models/config/community_config.dart diff --git a/lib/src/models/config/community_config.dart b/lib/src/models/config/community_config.dart new file mode 100644 index 0000000..db39d32 --- /dev/null +++ b/lib/src/models/config/community_config.dart @@ -0,0 +1,58 @@ +import 'package:core/src/models/config/app_review_config.dart'; +import 'package:core/src/models/config/engagement_config.dart'; +import 'package:core/src/models/config/reporting_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'community_config.g.dart'; + +/// {@template community_config} +/// A container for all community and user-generated content features. +/// +/// This includes configurations for engagement (reactions, comments), +/// content reporting, and the app review funnel. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class CommunityConfig extends Equatable { + /// {@macro community_config} + const CommunityConfig({ + required this.engagement, + required this.reporting, + required this.appReview, + }); + + /// Creates a [CommunityConfig] from JSON data. + factory CommunityConfig.fromJson(Map json) => + _$CommunityConfigFromJson(json); + + /// Configuration for user engagement features (reactions, comments). + final EngagementConfig engagement; + + /// Configuration for user content reporting features. + final ReportingConfig reporting; + + /// Configuration for the smart app review funnel. + final AppReviewConfig appReview; + + /// Converts this [CommunityConfig] instance to JSON data. + Map toJson() => _$CommunityConfigToJson(this); + + @override + List get props => [engagement, reporting, appReview]; + + /// Creates a copy of this [CommunityConfig] but with the given fields + /// replaced with the new values. + CommunityConfig copyWith({ + EngagementConfig? engagement, + ReportingConfig? reporting, + AppReviewConfig? appReview, + }) { + return CommunityConfig( + engagement: engagement ?? this.engagement, + reporting: reporting ?? this.reporting, + appReview: appReview ?? this.appReview, + ); + } +} diff --git a/lib/src/models/config/features_config.dart b/lib/src/models/config/features_config.dart index 9a09ff3..88bd7de 100644 --- a/lib/src/models/config/features_config.dart +++ b/lib/src/models/config/features_config.dart @@ -1,9 +1,7 @@ import 'package:core/src/models/config/ad_config.dart'; -import 'package:core/src/models/config/engagement_config.dart'; +import 'package:core/src/models/config/community_config.dart'; import 'package:core/src/models/config/feed_config.dart'; import 'package:core/src/models/config/push_notification_config.dart'; -import 'package:core/src/models/config/reporting_config.dart'; -import 'package:core/src/models/config/review_funnel_config.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -21,9 +19,7 @@ class FeaturesConfig extends Equatable { required this.ads, required this.pushNotifications, required this.feed, - required this.engagement, - required this.reporting, - required this.reviewFunnel, + required this.community, }); /// Creates a [FeaturesConfig] from JSON data. @@ -39,27 +35,14 @@ class FeaturesConfig extends Equatable { /// Configuration for all feed-related features. final FeedConfig feed; - /// Configuration for user engagement features (reactions, comments). - final EngagementConfig engagement; - - /// Configuration for user content reporting features. - final ReportingConfig reporting; - - /// Configuration for the smart app review funnel. - final ReviewFunnelConfig reviewFunnel; + /// Configuration for community and user-generated content features. + final CommunityConfig community; /// Converts this [FeaturesConfig] instance to JSON data. Map toJson() => _$FeaturesConfigToJson(this); @override - List get props => [ - ads, - pushNotifications, - feed, - engagement, - reporting, - reviewFunnel, - ]; + List get props => [ads, pushNotifications, feed, community]; /// Creates a copy of this [FeaturesConfig] but with the given fields /// replaced with the new values. @@ -67,17 +50,13 @@ class FeaturesConfig extends Equatable { AdConfig? ads, PushNotificationConfig? pushNotifications, FeedConfig? feed, - EngagementConfig? engagement, - ReportingConfig? reporting, - ReviewFunnelConfig? reviewFunnel, + CommunityConfig? community, }) { return FeaturesConfig( ads: ads ?? this.ads, pushNotifications: pushNotifications ?? this.pushNotifications, feed: feed ?? this.feed, - engagement: engagement ?? this.engagement, - reporting: reporting ?? this.reporting, - reviewFunnel: reviewFunnel ?? this.reviewFunnel, + community: community ?? this.community, ); } } From 543269d598c0032e0365bce145f4583045c83065 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:46:46 +0100 Subject: [PATCH 22/93] docs(app_review_config): update model documentation for two-layer app review funnel - Expand documentation to describe the two-layer app review funnel system - Detail the trigger, prompt, and action steps of the review process - Clarify the behavior of the system in response to user actions - Update the description to reflect the strategic prompting of engaged users --- lib/src/models/config/app_review_config.dart | 26 +++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/src/models/config/app_review_config.dart b/lib/src/models/config/app_review_config.dart index f470b94..b9bb401 100644 --- a/lib/src/models/config/app_review_config.dart +++ b/lib/src/models/config/app_review_config.dart @@ -5,11 +5,29 @@ import 'package:meta/meta.dart'; part 'app_review_config.g.dart'; /// {@template app_review_config} -/// Defines the remote configuration for the "Smart App Review" feature. +/// Defines the remote configuration for the two-layer App Review Funnel. /// -/// This model makes the review funnel's behavior configurable from a dashboard, -/// allowing admins to set the `positiveInteractionThreshold` required to -/// trigger the prompt and the `initialPromptCooldownDays`. +/// This system strategically prompts engaged users for feedback to maximize +/// positive public reviews while capturing constructive criticism privately. +/// +/// ### How It Works +/// +/// 1. **Trigger**: A user becomes eligible to see the prompt after reaching +/// the [positiveInteractionThreshold] of positive actions (e.g., saves). +/// +/// 2. **Prompt**: The `FeedDecoratorType.rateApp` decorator asks the user +/// "Are you enjoying the app?". The display logic is managed by the user's +/// `UserFeedDecoratorStatus` for `rateApp`, which respects the +/// [initialPromptCooldownDays]. +/// +/// 3. **Action**: +/// - **On "Yes"**: The client sets `isCompleted` to `true` on the user's +/// `UserFeedDecoratorStatus` for `rateApp` and immediately triggers the +/// native OS in-app review dialog if applicable ie the app is hosted in +/// google play or apple store. The prompt will not be shown again. +/// - **On "No"**: The client only updates the `lastShownAt` timestamp on +/// the status object. The prompt will not be shown again until the +/// cooldown period has passed. No public review is requested. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) From 049a73546fbb5e087dbc9c346f9d29802f3d963a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:51:43 +0100 Subject: [PATCH 23/93] docs(README): add community and engagement system description - Add detailed information about the community & engagement system - Include models for reactions, comments, and reporting - Describe the smart app review funnel feature - Explain the advantages of implementing these systems --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 534fd00..7a9ac88 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,27 @@ Provides data structures for persisting all user-specific configurations, from a +
+💬 Community & Engagement System + +--- + +### 💬 Reactions, Comments & Reporting + +Provides a complete suite of models for building a rich community interaction layer. This includes individualized reactions, a robust commenting system with a built-in moderation workflow, and a flexible reporting system for headlines, sources, and comments. + +> **Your Advantage:** Foster a vibrant and safe user community, maintain high content quality through effective moderation, and gather direct user feedback on your content. + +--- + +### ⭐ Smart App Review Funnel + +Implements the data structures for a strategic, two-layer review funnel. This system intelligently prompts engaged users for public reviews while channeling critical feedback from dissatisfied users into private channels. + +> **Your Advantage:** Maximize positive app store ratings and improve your app's reputation by proactively managing user feedback and preventing negative public reviews. + +
+
🔔 Notification & Alerting System From 991fb8db73c7522ba090fbd7f8ce04c1afe055db Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 08:55:57 +0100 Subject: [PATCH 24/93] feat(community): implement comprehensive Community & Engagement System - Add foundational data models for user reactions, comments, and reporting - Introduce multi-entity reporting system and app review funnel - Implement remote configuration via Unified `CommunityConfig` model - Extend `UserLimitsConfig` for role-based comment and report limits - Include previously committed notification system features - Refactor `UserPreferenceConfig` for scalable, role-based user limits - Add comprehensive unit tests for new and refactored models --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8526f3d..ffe83ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Upcoming +- **feat**: Added a comprehensive **Community & Engagement System**. This major feature introduces the foundational data models for user reactions, comments, a multi-entity reporting system, and a smart app review funnel. The entire system is remotely configurable via a new unified `CommunityConfig` model and extends `UserLimitsConfig` to support role-based limits for comments and reports. - **feat**: Introduce data models to support a filter-based push notification system. This includes `SavedHeadlineFilter`, `SavedSourceFilter`, and related configuration models, providing the architectural foundation for clients to implement notification subscriptions. - **BREAKING** refactor!: Rework `UserPreferenceConfig` to support the new notification system with a more scalable, role-based map structure for all user limits. - **test**: Add comprehensive unit tests for all new and refactored models. From 4a3a6f542424d75f4b163ed25378d623b426b209 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 09:02:34 +0100 Subject: [PATCH 25/93] docs(CHANGELOG): update changelog for upcoming release - Add breaking change note for identity pivot refactor - Consolidate feat entries into a single comprehensive entry - Remove duplicate entries for notification system changes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ffe83ff..e64b982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Upcoming - **feat**: Added a comprehensive **Community & Engagement System**. This major feature introduces the foundational data models for user reactions, comments, a multi-entity reporting system, and a smart app review funnel. The entire system is remotely configurable via a new unified `CommunityConfig` model and extends `UserLimitsConfig` to support role-based limits for comments and reports. +- **BREAKING** refactor!: Overhauled data models and configuration to align with the new identity pivot toward news aggregator. This major refactor introduces a more scalable remote configuration structure, standardizes enums and models for broader use (e.g., `FeedItem` settings), and simplifies ad, notification, and headline data structures for improved clarity and maintainability. - **feat**: Introduce data models to support a filter-based push notification system. This includes `SavedHeadlineFilter`, `SavedSourceFilter`, and related configuration models, providing the architectural foundation for clients to implement notification subscriptions. - **BREAKING** refactor!: Rework `UserPreferenceConfig` to support the new notification system with a more scalable, role-based map structure for all user limits. - **test**: Add comprehensive unit tests for all new and refactored models. From d54269c87e5cc4d11064e428caa6da8dc2362cad Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 09:05:23 +0100 Subject: [PATCH 26/93] build(serialization): sync --- .../models/config/app_review_config.g.dart | 28 ++++++++++++++++ lib/src/models/config/community_config.g.dart | 33 +++++++++++++++++++ lib/src/models/config/features_config.g.dart | 18 +++------- 3 files changed, 65 insertions(+), 14 deletions(-) create mode 100644 lib/src/models/config/app_review_config.g.dart create mode 100644 lib/src/models/config/community_config.g.dart diff --git a/lib/src/models/config/app_review_config.g.dart b/lib/src/models/config/app_review_config.g.dart new file mode 100644 index 0000000..ad1387d --- /dev/null +++ b/lib/src/models/config/app_review_config.g.dart @@ -0,0 +1,28 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_review_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppReviewConfig _$AppReviewConfigFromJson(Map json) => + $checkedCreate('AppReviewConfig', json, ($checkedConvert) { + final val = AppReviewConfig( + positiveInteractionThreshold: $checkedConvert( + 'positiveInteractionThreshold', + (v) => (v as num).toInt(), + ), + initialPromptCooldownDays: $checkedConvert( + 'initialPromptCooldownDays', + (v) => (v as num).toInt(), + ), + ); + return val; + }); + +Map _$AppReviewConfigToJson(AppReviewConfig instance) => + { + 'positiveInteractionThreshold': instance.positiveInteractionThreshold, + 'initialPromptCooldownDays': instance.initialPromptCooldownDays, + }; diff --git a/lib/src/models/config/community_config.g.dart b/lib/src/models/config/community_config.g.dart new file mode 100644 index 0000000..b418250 --- /dev/null +++ b/lib/src/models/config/community_config.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'community_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CommunityConfig _$CommunityConfigFromJson(Map json) => + $checkedCreate('CommunityConfig', json, ($checkedConvert) { + final val = CommunityConfig( + engagement: $checkedConvert( + 'engagement', + (v) => EngagementConfig.fromJson(v as Map), + ), + reporting: $checkedConvert( + 'reporting', + (v) => ReportingConfig.fromJson(v as Map), + ), + appReview: $checkedConvert( + 'appReview', + (v) => AppReviewConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$CommunityConfigToJson(CommunityConfig instance) => + { + 'engagement': instance.engagement.toJson(), + 'reporting': instance.reporting.toJson(), + 'appReview': instance.appReview.toJson(), + }; diff --git a/lib/src/models/config/features_config.g.dart b/lib/src/models/config/features_config.g.dart index e6560bd..a67a4c1 100644 --- a/lib/src/models/config/features_config.g.dart +++ b/lib/src/models/config/features_config.g.dart @@ -21,17 +21,9 @@ FeaturesConfig _$FeaturesConfigFromJson(Map json) => 'feed', (v) => FeedConfig.fromJson(v as Map), ), - engagement: $checkedConvert( - 'engagement', - (v) => EngagementConfig.fromJson(v as Map), - ), - reporting: $checkedConvert( - 'reporting', - (v) => ReportingConfig.fromJson(v as Map), - ), - reviewFunnel: $checkedConvert( - 'reviewFunnel', - (v) => ReviewFunnelConfig.fromJson(v as Map), + community: $checkedConvert( + 'community', + (v) => CommunityConfig.fromJson(v as Map), ), ); return val; @@ -42,7 +34,5 @@ Map _$FeaturesConfigToJson(FeaturesConfig instance) => 'ads': instance.ads.toJson(), 'pushNotifications': instance.pushNotifications.toJson(), 'feed': instance.feed.toJson(), - 'engagement': instance.engagement.toJson(), - 'reporting': instance.reporting.toJson(), - 'reviewFunnel': instance.reviewFunnel.toJson(), + 'community': instance.community.toJson(), }; From cae7c39b72c116a959708ce3c7eae602424b81c2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 09:12:03 +0100 Subject: [PATCH 27/93] refactor(models): reorganize feed-related models and imports - Move feed-related models to a new 'feed' directory - Update imports for feed-related models - Rename 'feed_decorators' to 'feed' for consistency - Remove redundant 'core' exports - Update 'models.dart' to reflect new directory structure --- lib/src/models/config/config.dart | 3 ++- lib/src/models/core/core.dart | 1 - lib/src/models/entities/country.dart | 2 +- lib/src/models/entities/headline.dart | 2 +- lib/src/models/entities/source.dart | 2 +- lib/src/models/entities/topic.dart | 2 +- .../models/{feed_decorators => feed}/call_to_action_item.dart | 2 +- .../{feed_decorators => feed}/call_to_action_item.g.dart | 0 .../{feed_decorators => feed}/content_collection_item.dart | 2 +- .../{feed_decorators => feed}/content_collection_item.g.dart | 0 lib/src/models/feed/feed.dart | 4 ++++ lib/src/models/{feed_decorators => feed}/feed_decorators.dart | 0 lib/src/models/{core => feed}/feed_item.dart | 2 +- lib/src/models/models.dart | 3 +-- 14 files changed, 14 insertions(+), 11 deletions(-) delete mode 100644 lib/src/models/core/core.dart rename lib/src/models/{feed_decorators => feed}/call_to_action_item.dart (97%) rename lib/src/models/{feed_decorators => feed}/call_to_action_item.g.dart (100%) rename lib/src/models/{feed_decorators => feed}/content_collection_item.dart (97%) rename lib/src/models/{feed_decorators => feed}/content_collection_item.g.dart (100%) create mode 100644 lib/src/models/feed/feed.dart rename lib/src/models/{feed_decorators => feed}/feed_decorators.dart (100%) rename lib/src/models/{core => feed}/feed_item.dart (98%) diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index 0b7551e..3477f16 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,6 +1,8 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; export 'app_config.dart'; +export 'app_review_config.dart'; +export 'community_config.dart'; export 'engagement_config.dart'; export 'features_config.dart'; export 'feed_ad_configuration.dart'; @@ -15,7 +17,6 @@ export 'navigation_ad_frequency_config.dart'; export 'push_notification_config.dart'; export 'remote_config.dart'; export 'reporting_config.dart'; -export 'review_funnel_config.dart'; export 'saved_filter_limits.dart'; export 'update_config.dart'; export 'user_config.dart'; diff --git a/lib/src/models/core/core.dart b/lib/src/models/core/core.dart deleted file mode 100644 index 3201493..0000000 --- a/lib/src/models/core/core.dart +++ /dev/null @@ -1 +0,0 @@ -export 'feed_item.dart'; diff --git a/lib/src/models/entities/country.dart b/lib/src/models/entities/country.dart index 112d44f..1364457 100644 --- a/lib/src/models/entities/country.dart +++ b/lib/src/models/entities/country.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/utils/utils.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index 1b340c3..c6a0c1e 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/source.dart'; import 'package:core/src/models/entities/topic.dart'; diff --git a/lib/src/models/entities/source.dart b/lib/src/models/entities/source.dart index a325f1c..eb20139 100644 --- a/lib/src/models/entities/source.dart +++ b/lib/src/models/entities/source.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/language.dart'; import 'package:core/src/utils/utils.dart'; diff --git a/lib/src/models/entities/topic.dart b/lib/src/models/entities/topic.dart index 90a3c0d..d300761 100644 --- a/lib/src/models/entities/topic.dart +++ b/lib/src/models/entities/topic.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/utils/utils.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/feed_decorators/call_to_action_item.dart b/lib/src/models/feed/call_to_action_item.dart similarity index 97% rename from lib/src/models/feed_decorators/call_to_action_item.dart rename to lib/src/models/feed/call_to_action_item.dart index 6063a02..caa16a8 100644 --- a/lib/src/models/feed_decorators/call_to_action_item.dart +++ b/lib/src/models/feed/call_to_action_item.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/feed_decorator_type.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/feed_decorators/call_to_action_item.g.dart b/lib/src/models/feed/call_to_action_item.g.dart similarity index 100% rename from lib/src/models/feed_decorators/call_to_action_item.g.dart rename to lib/src/models/feed/call_to_action_item.g.dart diff --git a/lib/src/models/feed_decorators/content_collection_item.dart b/lib/src/models/feed/content_collection_item.dart similarity index 97% rename from lib/src/models/feed_decorators/content_collection_item.dart rename to lib/src/models/feed/content_collection_item.dart index 862d452..1ba9394 100644 --- a/lib/src/models/feed_decorators/content_collection_item.dart +++ b/lib/src/models/feed/content_collection_item.dart @@ -1,5 +1,5 @@ import 'package:core/src/enums/feed_decorator_type.dart'; -import 'package:core/src/models/core/feed_item.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/feed_decorators/content_collection_item.g.dart b/lib/src/models/feed/content_collection_item.g.dart similarity index 100% rename from lib/src/models/feed_decorators/content_collection_item.g.dart rename to lib/src/models/feed/content_collection_item.g.dart diff --git a/lib/src/models/feed/feed.dart b/lib/src/models/feed/feed.dart new file mode 100644 index 0000000..72a0b54 --- /dev/null +++ b/lib/src/models/feed/feed.dart @@ -0,0 +1,4 @@ +export 'call_to_action_item.dart'; +export 'content_collection_item.dart'; +export 'feed_decorators.dart'; +export 'feed_item.dart'; diff --git a/lib/src/models/feed_decorators/feed_decorators.dart b/lib/src/models/feed/feed_decorators.dart similarity index 100% rename from lib/src/models/feed_decorators/feed_decorators.dart rename to lib/src/models/feed/feed_decorators.dart diff --git a/lib/src/models/core/feed_item.dart b/lib/src/models/feed/feed_item.dart similarity index 98% rename from lib/src/models/core/feed_item.dart rename to lib/src/models/feed/feed_item.dart index 2e3d08e..9b106b7 100644 --- a/lib/src/models/core/feed_item.dart +++ b/lib/src/models/feed/feed_item.dart @@ -1,5 +1,5 @@ import 'package:core/src/models/entities/entities.dart'; -import 'package:core/src/models/feed_decorators/feed_decorators.dart'; +import 'package:core/src/models/feed/feed_decorators.dart'; import 'package:equatable/equatable.dart'; /// {@template feed_item} diff --git a/lib/src/models/models.dart b/lib/src/models/models.dart index ee6b2c1..49f8f5f 100644 --- a/lib/src/models/models.dart +++ b/lib/src/models/models.dart @@ -1,9 +1,8 @@ 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 'feed/feed.dart'; export 'notifications/notifications.dart'; export 'push_notifications/push_notifications.dart'; export 'query/query.dart'; From 2d5be91a9e8c54f4505acc442e492bf166d8a16d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 09:24:30 +0100 Subject: [PATCH 28/93] fix(remote-config): add missing community config values for standard and premium users - Add commentsPerDay and reportsPerDay limits for different user roles - Implement community engagement and reporting configurations - Set up app review configuration with positive interaction threshold and initial prompt cooldown --- lib/src/fixtures/remote_configs.dart | 28 +++++++++++++++++++ lib/src/models/config/user_limits_config.dart | 3 ++ 2 files changed, 31 insertions(+) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 46b642c..26c228e 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -65,6 +65,16 @@ final remoteConfigsFixturesData = [ AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), }, + commentsPerDay: { + AppUserRole.guestUser: 0, + AppUserRole.standardUser: 10, + AppUserRole.premiumUser: 50, + }, + reportsPerDay: { + AppUserRole.guestUser: 1, + AppUserRole.standardUser: 5, + AppUserRole.premiumUser: 20, + }, ), ), features: const FeaturesConfig( @@ -199,6 +209,24 @@ final remoteConfigsFixturesData = [ PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), + community: CommunityConfig( + engagement: EngagementConfig( + enabled: true, + engagementMode: EngagementMode.reactionsAndComments, + aiModerationEnabled: false, + ), + reporting: ReportingConfig( + headlineReportingEnabled: true, + sourceReportingEnabled: true, + commentReportingEnabled: true, + ), + appReview: AppReviewConfig( + // User must perform 5 positive actions (e.g., save headline) + // to become eligible for the review prompt. + positiveInteractionThreshold: 5, + initialPromptCooldownDays: 14, + ), + ), ), ), ]; diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart index 27bd743..b6f179d 100644 --- a/lib/src/models/config/user_limits_config.dart +++ b/lib/src/models/config/user_limits_config.dart @@ -48,6 +48,9 @@ class UserLimitsConfig extends Equatable { final Map savedSourceFilters; /// Role-based limits for the number of comments a user can post per day. + /// + /// This limit applies specifically to the creation of new comments and does + /// not include other interactions like reactions. final Map commentsPerDay; /// Role-based limits for the number of reports a user can submit per day. From 35413f593b2f8cb331d12a2fe6c4aae4b0574971 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:08:54 +0100 Subject: [PATCH 29/93] test(core): add EngagementMode enum tests - Add unit tests for EngagementMode enum - Verify correct enum values and string representations - Test enum creation from string values --- test/src/enums/engagement_mode_test.dart | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/src/enums/engagement_mode_test.dart diff --git a/test/src/enums/engagement_mode_test.dart b/test/src/enums/engagement_mode_test.dart new file mode 100644 index 0000000..ba41d64 --- /dev/null +++ b/test/src/enums/engagement_mode_test.dart @@ -0,0 +1,28 @@ +import 'package:core/src/enums/engagement_mode.dart'; +import 'package:test/test.dart'; + +void main() { + group('EngagementMode', () { + test('has correct values', () { + expect( + EngagementMode.values, + containsAll([ + EngagementMode.reactionsOnly, + EngagementMode.reactionsAndComments, + ]), + ); + }); + + test('has correct string values', () { + expect(EngagementMode.reactionsOnly.name, 'reactionsOnly'); + expect(EngagementMode.reactionsAndComments.name, 'reactionsAndComments'); + }); + + test('can be created from string values', () { + expect( + EngagementMode.values.byName('reactionsOnly'), + EngagementMode.reactionsOnly, + ); + }); + }); +} From ccb8a0b533306a54eea23d5024e8c2a668cdea5d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:12:47 +0100 Subject: [PATCH 30/93] test(core): add CommentStatus enum tests - Add unit tests for CommentStatus enum values - Verify correct string representation of enum values - Test creation of enum values from string representations --- test/src/enums/comment_status_test.dart | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/src/enums/comment_status_test.dart diff --git a/test/src/enums/comment_status_test.dart b/test/src/enums/comment_status_test.dart new file mode 100644 index 0000000..e1a711f --- /dev/null +++ b/test/src/enums/comment_status_test.dart @@ -0,0 +1,40 @@ +import 'package:core/src/enums/comment_status.dart'; +import 'package:test/test.dart'; + +void main() { + group('CommentStatus', () { + test('has correct values', () { + expect( + CommentStatus.values, + containsAll([ + CommentStatus.pendingReview, + CommentStatus.approved, + CommentStatus.rejected, + CommentStatus.flaggedByAI, + CommentStatus.hiddenByUser, + ]), + ); + }); + + test('has correct string values', () { + expect(CommentStatus.pendingReview.name, 'pendingReview'); + expect(CommentStatus.approved.name, 'approved'); + expect(CommentStatus.rejected.name, 'rejected'); + expect(CommentStatus.flaggedByAI.name, 'flaggedByAI'); + expect(CommentStatus.hiddenByUser.name, 'hiddenByUser'); + }); + + test('can be created from string values', () { + expect( + CommentStatus.values.byName('pendingReview'), + CommentStatus.pendingReview, + ); + expect(CommentStatus.values.byName('approved'), CommentStatus.approved); + expect(CommentStatus.values.byName('rejected'), CommentStatus.rejected); + expect( + CommentStatus.values.byName('flaggedByAI'), + CommentStatus.flaggedByAI, + ); + }); + }); +} From 380fdcb11eb0e5f15c42b11e1788446f2361d737 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:13:22 +0100 Subject: [PATCH 31/93] test(core): add ReactionType enum tests - Add unit tests for ReactionType enum values - Verify correct string representations of enum values - Test creation of enum instances from string values --- test/src/enums/reaction_type_test.dart | 38 ++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 test/src/enums/reaction_type_test.dart diff --git a/test/src/enums/reaction_type_test.dart b/test/src/enums/reaction_type_test.dart new file mode 100644 index 0000000..7f45b0b --- /dev/null +++ b/test/src/enums/reaction_type_test.dart @@ -0,0 +1,38 @@ +import 'package:core/src/enums/reaction_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('ReactionType', () { + test('has correct values', () { + expect( + ReactionType.values, + containsAll([ + ReactionType.like, + ReactionType.insightful, + ReactionType.amusing, + ReactionType.sad, + ReactionType.angry, + ReactionType.skeptical, + ]), + ); + }); + + test('has correct string values', () { + expect(ReactionType.like.name, 'like'); + expect(ReactionType.insightful.name, 'insightful'); + expect(ReactionType.amusing.name, 'amusing'); + expect(ReactionType.sad.name, 'sad'); + expect(ReactionType.angry.name, 'angry'); + expect(ReactionType.skeptical.name, 'skeptical'); + }); + + test('can be created from string values', () { + expect(ReactionType.values.byName('like'), ReactionType.like); + expect(ReactionType.values.byName('insightful'), ReactionType.insightful); + expect(ReactionType.values.byName('amusing'), ReactionType.amusing); + expect(ReactionType.values.byName('sad'), ReactionType.sad); + expect(ReactionType.values.byName('angry'), ReactionType.angry); + expect(ReactionType.values.byName('skeptical'), ReactionType.skeptical); + }); + }); +} From 707c2321714cfebcafca53b5fd0c46bbc3438028 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:13:44 +0100 Subject: [PATCH 32/93] test(core): add ReportableEntity enum tests - Create new test file for ReportableEntity enum - Add tests for enum values and string representations - Implement test cases for enum creation from string values --- test/src/enums/reportable_entity_test.dart | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 test/src/enums/reportable_entity_test.dart diff --git a/test/src/enums/reportable_entity_test.dart b/test/src/enums/reportable_entity_test.dart new file mode 100644 index 0000000..ce9e261 --- /dev/null +++ b/test/src/enums/reportable_entity_test.dart @@ -0,0 +1,35 @@ +import 'package:core/src/enums/reportable_entity.dart'; +import 'package:test/test.dart'; + +void main() { + group('ReportableEntity', () { + test('has correct values', () { + expect( + ReportableEntity.values, + containsAll([ + ReportableEntity.headline, + ReportableEntity.source, + ReportableEntity.comment, + ]), + ); + }); + + test('has correct string values', () { + expect(ReportableEntity.headline.name, 'headline'); + expect(ReportableEntity.source.name, 'source'); + expect(ReportableEntity.comment.name, 'comment'); + }); + + test('can be created from string values', () { + expect( + ReportableEntity.values.byName('headline'), + ReportableEntity.headline, + ); + expect(ReportableEntity.values.byName('source'), ReportableEntity.source); + expect( + ReportableEntity.values.byName('comment'), + ReportableEntity.comment, + ); + }); + }); +} From 871452ff0563514e8511534f9423dc133edddb29 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:14:17 +0100 Subject: [PATCH 33/93] test(core): add unit tests for ReportStatus enum - Create new test file for ReportStatus enum - Add tests for enum values, string representations, and creation from string values - Ensure all cases and their string versions are covered --- test/src/enums/report_status_test.dart | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 test/src/enums/report_status_test.dart diff --git a/test/src/enums/report_status_test.dart b/test/src/enums/report_status_test.dart new file mode 100644 index 0000000..9be135f --- /dev/null +++ b/test/src/enums/report_status_test.dart @@ -0,0 +1,29 @@ +import 'package:core/src/enums/report_status.dart'; +import 'package:test/test.dart'; + +void main() { + group('ReportStatus', () { + test('has correct values', () { + expect( + ReportStatus.values, + containsAll([ + ReportStatus.submitted, + ReportStatus.inReview, + ReportStatus.resolved, + ]), + ); + }); + + test('has correct string values', () { + expect(ReportStatus.submitted.name, 'submitted'); + expect(ReportStatus.inReview.name, 'inReview'); + expect(ReportStatus.resolved.name, 'resolved'); + }); + + test('can be created from string values', () { + expect(ReportStatus.values.byName('submitted'), ReportStatus.submitted); + expect(ReportStatus.values.byName('inReview'), ReportStatus.inReview); + expect(ReportStatus.values.byName('resolved'), ReportStatus.resolved); + }); + }); +} From 38a08af958aefc49feddc62a03598890d596ab8e Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:14:52 +0100 Subject: [PATCH 34/93] test: add HeadlineReportReason enum tests - Create new test file for HeadlineReportReason enum - Add tests for enum values and string representations - Verify correct functioning of enum value retrieval from string --- .../enums/headline_report_reason_test.dart | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/src/enums/headline_report_reason_test.dart diff --git a/test/src/enums/headline_report_reason_test.dart b/test/src/enums/headline_report_reason_test.dart new file mode 100644 index 0000000..6eb7848 --- /dev/null +++ b/test/src/enums/headline_report_reason_test.dart @@ -0,0 +1,46 @@ +import 'package:core/src/enums/headline_report_reason.dart'; +import 'package:test/test.dart'; + +void main() { + group('HeadlineReportReason', () { + test('has correct values', () { + expect( + HeadlineReportReason.values, + containsAll([ + HeadlineReportReason.misinformationOrFakeNews, + HeadlineReportReason.clickbaitTitle, + HeadlineReportReason.offensiveOrHateSpeech, + HeadlineReportReason.spamOrScam, + HeadlineReportReason.brokenLink, + HeadlineReportReason.paywalled, + ]), + ); + }); + + test('has correct string values', () { + expect( + HeadlineReportReason.misinformationOrFakeNews.name, + 'misinformationOrFakeNews', + ); + expect(HeadlineReportReason.clickbaitTitle.name, 'clickbaitTitle'); + expect( + HeadlineReportReason.offensiveOrHateSpeech.name, + 'offensiveOrHateSpeech', + ); + expect(HeadlineReportReason.spamOrScam.name, 'spamOrScam'); + expect(HeadlineReportReason.brokenLink.name, 'brokenLink'); + expect(HeadlineReportReason.paywalled.name, 'paywalled'); + }); + + test('can be created from string values', () { + expect( + HeadlineReportReason.values.byName('misinformationOrFakeNews'), + HeadlineReportReason.misinformationOrFakeNews, + ); + expect( + HeadlineReportReason.values.byName('clickbaitTitle'), + HeadlineReportReason.clickbaitTitle, + ); + }); + }); +} From cb8affa6edd9881485c37afe01f1f7df094d36d0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:16:19 +0100 Subject: [PATCH 35/93] test(core): add SourceReportReason enum tests - Add unit tests for SourceReportReason enum - Verify correct values and string representations - Test enum creation from string values --- test/src/enums/source_report_reason_test.dart | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 test/src/enums/source_report_reason_test.dart diff --git a/test/src/enums/source_report_reason_test.dart b/test/src/enums/source_report_reason_test.dart new file mode 100644 index 0000000..fbe1cf0 --- /dev/null +++ b/test/src/enums/source_report_reason_test.dart @@ -0,0 +1,44 @@ +import 'package:core/src/enums/source_report_reason.dart'; +import 'package:test/test.dart'; + +void main() { + group('SourceReportReason', () { + test('has correct values', () { + expect( + SourceReportReason.values, + containsAll([ + SourceReportReason.lowQualityJournalism, + SourceReportReason.highAdDensity, + SourceReportReason.frequentPaywalls, + SourceReportReason.impersonation, + SourceReportReason.spreadsMisinformation, + ]), + ); + }); + + test('has correct string values', () { + expect( + SourceReportReason.lowQualityJournalism.name, + 'lowQualityJournalism', + ); + expect(SourceReportReason.highAdDensity.name, 'highAdDensity'); + expect(SourceReportReason.frequentPaywalls.name, 'frequentPaywalls'); + expect(SourceReportReason.impersonation.name, 'impersonation'); + expect( + SourceReportReason.spreadsMisinformation.name, + 'spreadsMisinformation', + ); + }); + + test('can be created from string values', () { + expect( + SourceReportReason.values.byName('lowQualityJournalism'), + SourceReportReason.lowQualityJournalism, + ); + expect( + SourceReportReason.values.byName('highAdDensity'), + SourceReportReason.highAdDensity, + ); + }); + }); +} From 4fbf35d7e0c610bfffb77ede405319c518f579a7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:16:37 +0100 Subject: [PATCH 36/93] test(core): add CommentReportReason enum tests - Create new test file for CommentReportReason enum - Add tests for enum values and string representations - Implement test for creating enum from string values --- .../src/enums/comment_report_reason_test.dart | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 test/src/enums/comment_report_reason_test.dart diff --git a/test/src/enums/comment_report_reason_test.dart b/test/src/enums/comment_report_reason_test.dart new file mode 100644 index 0000000..f6ceec2 --- /dev/null +++ b/test/src/enums/comment_report_reason_test.dart @@ -0,0 +1,33 @@ +import 'package:core/src/enums/comment_report_reason.dart'; +import 'package:test/test.dart'; + +void main() { + group('CommentReportReason', () { + test('has correct values', () { + expect( + CommentReportReason.values, + containsAll([ + CommentReportReason.spamOrAdvertising, + CommentReportReason.harassmentOrBullying, + CommentReportReason.hateSpeech, + ]), + ); + }); + + test('has correct string values', () { + expect(CommentReportReason.spamOrAdvertising.name, 'spamOrAdvertising'); + expect( + CommentReportReason.harassmentOrBullying.name, + 'harassmentOrBullying', + ); + expect(CommentReportReason.hateSpeech.name, 'hateSpeech'); + }); + + test('can be created from string values', () { + expect( + CommentReportReason.values.byName('spamOrAdvertising'), + CommentReportReason.spamOrAdvertising, + ); + }); + }); +} From d1deea9d5e135fdead6cdcb007115bda6b9aabf4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:17:34 +0100 Subject: [PATCH 37/93] test(core): add Comment model tests - Instantiate Comment object - Check value equality - Create Comment from JSON - Convert Comment to JSON - Use copyWith to update Comment properties --- comment_test.dart | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 comment_test.dart diff --git a/comment_test.dart b/comment_test.dart new file mode 100644 index 0000000..9acb9c1 --- /dev/null +++ b/comment_test.dart @@ -0,0 +1,64 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Comment', () { + final now = DateTime.now(); + final commentFixture = Comment( + id: 'comment_1', + headlineId: 'headline_1', + userId: 'user_1', + content: 'This is a test comment.', + status: CommentStatus.approved, + createdAt: now, + updatedAt: now, + ); + + test('can be instantiated', () { + expect(commentFixture, isA()); + }); + + test('supports value equality', () { + final anotherComment = Comment( + id: 'comment_1', + headlineId: 'headline_1', + userId: 'user_1', + content: 'This is a test comment.', + status: CommentStatus.approved, + createdAt: now, + updatedAt: now, + ); + expect(commentFixture, equals(anotherComment)); + }); + + test('can be created from JSON', () { + final json = commentFixture.toJson(); + final fromJson = Comment.fromJson(json); + expect(fromJson, equals(commentFixture)); + }); + + test('can be converted to JSON', () { + final json = commentFixture.toJson(); + final expectedJson = { + 'id': 'comment_1', + 'headlineId': 'headline_1', + 'userId': 'user_1', + 'content': 'This is a test comment.', + 'status': 'approved', + 'createdAt': now.toIso8601String(), + 'updatedAt': now.toIso8601String(), + }; + expect(json, equals(expectedJson)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedComment = commentFixture.copyWith( + content: 'This is an updated comment.', + status: CommentStatus.rejected, + ); + expect(updatedComment.content, 'This is an updated comment.'); + expect(updatedComment.status, CommentStatus.rejected); + expect(updatedComment, isNot(equals(commentFixture))); + }); + }); +} \ No newline at end of file From 3de2a819f874f0bff57e89fd5f60da568404a2cb Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:19:22 +0100 Subject: [PATCH 38/93] test(headline_reaction): add tests for HeadlineReaction model - Instantiate test - Value equality test - JSON serialization and deserialization tests - copyWith method test --- headline_reaction_test.dart | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 headline_reaction_test.dart diff --git a/headline_reaction_test.dart b/headline_reaction_test.dart new file mode 100644 index 0000000..dd7cbea --- /dev/null +++ b/headline_reaction_test.dart @@ -0,0 +1,56 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('HeadlineReaction', () { + final now = DateTime.now(); + final reactionFixture = HeadlineReaction( + id: 'reaction_1', + headlineId: 'headline_1', + userId: 'user_1', + reactionType: ReactionType.like, + createdAt: now, + ); + + test('can be instantiated', () { + expect(reactionFixture, isA()); + }); + + test('supports value equality', () { + final anotherReaction = HeadlineReaction( + id: 'reaction_1', + headlineId: 'headline_1', + userId: 'user_1', + reactionType: ReactionType.like, + createdAt: now, + ); + expect(reactionFixture, equals(anotherReaction)); + }); + + test('can be created from JSON', () { + final json = reactionFixture.toJson(); + final fromJson = HeadlineReaction.fromJson(json); + expect(fromJson, equals(reactionFixture)); + }); + + test('can be converted to JSON', () { + final json = reactionFixture.toJson(); + final expectedJson = { + 'id': 'reaction_1', + 'headlineId': 'headline_1', + 'userId': 'user_1', + 'reactionType': 'like', + 'createdAt': now.toIso8601String(), + }; + expect(json, equals(expectedJson)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedReaction = reactionFixture.copyWith( + reactionType: ReactionType.insightful, + ); + expect(updatedReaction.reactionType, ReactionType.insightful); + expect(updatedReaction, isNot(equals(reactionFixture))); + }); + }); +} \ No newline at end of file From b5b8854ca1cfe66a6f6a58e1cdb9026db5f72e53 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:19:55 +0100 Subject: [PATCH 39/93] test(core): add report model test cases - Create report_test.dart to add unit tests for Report model - Implement tests for instantiation, value equality, JSON conversion, and copyWith method - Cover all properties including id, reporterUserId, entityType, entityId, reason, status, createdAt, and additionalComments --- report_test.dart | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 report_test.dart diff --git a/report_test.dart b/report_test.dart new file mode 100644 index 0000000..cd28602 --- /dev/null +++ b/report_test.dart @@ -0,0 +1,67 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Report', () { + final now = DateTime.now(); + final reportFixture = Report( + id: 'report_1', + reporterUserId: 'user_1', + entityType: ReportableEntity.headline, + entityId: 'headline_1', + reason: HeadlineReportReason.clickbaitTitle.name, + status: ReportStatus.submitted, + createdAt: now, + additionalComments: 'The title is misleading.', + ); + + test('can be instantiated', () { + expect(reportFixture, isA()); + }); + + test('supports value equality', () { + final anotherReport = Report( + id: 'report_1', + reporterUserId: 'user_1', + entityType: ReportableEntity.headline, + entityId: 'headline_1', + reason: HeadlineReportReason.clickbaitTitle.name, + status: ReportStatus.submitted, + createdAt: now, + additionalComments: 'The title is misleading.', + ); + expect(reportFixture, equals(anotherReport)); + }); + + test('can be created from JSON', () { + final json = reportFixture.toJson(); + final fromJson = Report.fromJson(json); + expect(fromJson, equals(reportFixture)); + }); + + test('can be converted to JSON', () { + final json = reportFixture.toJson(); + final expectedJson = { + 'id': 'report_1', + 'reporterUserId': 'user_1', + 'entityType': 'headline', + 'entityId': 'headline_1', + 'reason': 'clickbaitTitle', + 'status': 'submitted', + 'additionalComments': 'The title is misleading.', + 'createdAt': now.toIso8601String(), + }; + expect(json, equals(expectedJson)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedReport = reportFixture.copyWith( + status: ReportStatus.resolved, + additionalComments: null, + ); + expect(updatedReport.status, ReportStatus.resolved); + expect(updatedReport.additionalComments, isNull); + expect(updatedReport, isNot(equals(reportFixture))); + }); + }); +} \ No newline at end of file From a9e4db57576668a3c4f458156b9ed8828103c084 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:20:33 +0100 Subject: [PATCH 40/93] test(core): add EngagementConfig model tests - Add unit tests for EngagementConfig model - Verify instantiation, equality, JSON serialization/deserialization - Test copyWith method for creating updated instances --- .../models/config/engagement_config_test.dart | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/src/models/config/engagement_config_test.dart diff --git a/test/src/models/config/engagement_config_test.dart b/test/src/models/config/engagement_config_test.dart new file mode 100644 index 0000000..da0e785 --- /dev/null +++ b/test/src/models/config/engagement_config_test.dart @@ -0,0 +1,41 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('EngagementConfig', () { + final engagementConfigFixture = + remoteConfigsFixturesData.first.features.community.engagement; + final json = engagementConfigFixture.toJson(); + + test('can be instantiated', () { + expect(engagementConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = + remoteConfigsFixturesData.first.features.community.engagement; + expect(engagementConfigFixture, equals(anotherConfig)); + }); + + test('can be created from JSON', () { + final fromJson = EngagementConfig.fromJson(json); + expect(fromJson, equals(engagementConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = engagementConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = engagementConfigFixture.copyWith( + enabled: false, + engagementMode: EngagementMode.reactionsOnly, + ); + + expect(updatedConfig.enabled, isFalse); + expect(updatedConfig.engagementMode, EngagementMode.reactionsOnly); + expect(updatedConfig, isNot(equals(engagementConfigFixture))); + }); + }); +} \ No newline at end of file From d8b26c620489bff280e0c8b6bddb6daed2c89cd6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:21:02 +0100 Subject: [PATCH 41/93] test(core): add ReportingConfig model tests - Add unit tests for ReportingConfig model - Verify instantiation, equality, JSON serialization/deserialization - Test copyWith functionality --- .../models/config/reporting_config_test.dart | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 test/src/models/config/reporting_config_test.dart diff --git a/test/src/models/config/reporting_config_test.dart b/test/src/models/config/reporting_config_test.dart new file mode 100644 index 0000000..ce7cde5 --- /dev/null +++ b/test/src/models/config/reporting_config_test.dart @@ -0,0 +1,42 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('ReportingConfig', () { + final reportingConfigFixture = + remoteConfigsFixturesData.first.features.community.reporting; + final json = reportingConfigFixture.toJson(); + + test('can be instantiated', () { + expect(reportingConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = + remoteConfigsFixturesData.first.features.community.reporting; + expect(reportingConfigFixture, equals(anotherConfig)); + }); + + test('can be created from JSON', () { + final fromJson = ReportingConfig.fromJson(json); + expect(fromJson, equals(reportingConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = reportingConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = reportingConfigFixture.copyWith( + headlineReportingEnabled: false, + ); + + expect(updatedConfig.headlineReportingEnabled, isFalse); + expect( + updatedConfig, + isNot(equals(reportingConfigFixture)), + ); + }); + }); +} \ No newline at end of file From 832c3ae9e72348e15446b0288d7d2af6e3f4a157 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:21:35 +0100 Subject: [PATCH 42/93] test(core): add AppReviewConfig model tests - Add unit tests for AppReviewConfig model - Verify instantiation, equality, JSON serialization/deserialization - Test copyWith method functionality --- .../models/config/app_review_config_test.dart | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/src/models/config/app_review_config_test.dart diff --git a/test/src/models/config/app_review_config_test.dart b/test/src/models/config/app_review_config_test.dart new file mode 100644 index 0000000..a2f845c --- /dev/null +++ b/test/src/models/config/app_review_config_test.dart @@ -0,0 +1,39 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('AppReviewConfig', () { + final appReviewConfigFixture = + remoteConfigsFixturesData.first.features.community.appReview; + final json = appReviewConfigFixture.toJson(); + + test('can be instantiated', () { + expect(appReviewConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = + remoteConfigsFixturesData.first.features.community.appReview; + expect(appReviewConfigFixture, equals(anotherConfig)); + }); + + test('can be created from JSON', () { + final fromJson = AppReviewConfig.fromJson(json); + expect(fromJson, equals(appReviewConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = appReviewConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = appReviewConfigFixture.copyWith( + positiveInteractionThreshold: 10, + ); + + expect(updatedConfig.positiveInteractionThreshold, 10); + expect(updatedConfig, isNot(equals(appReviewConfigFixture))); + }); + }); +} \ No newline at end of file From fe2ae6e9f4c78a69cdf22390cd6767de6cc00b05 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:22:11 +0100 Subject: [PATCH 43/93] test(config): add community feature flag to FeaturesConfig fixture - Include community feature in the list of feature flags - Ensures proper testing of community-related functionalities --- test/src/models/config/features_config_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test/src/models/config/features_config_test.dart b/test/src/models/config/features_config_test.dart index 74329eb..e9bf8eb 100644 --- a/test/src/models/config/features_config_test.dart +++ b/test/src/models/config/features_config_test.dart @@ -22,6 +22,7 @@ void main() { featuresConfigFixture.ads, featuresConfigFixture.pushNotifications, featuresConfigFixture.feed, + featuresConfigFixture.community, ]), ); }); From 0016a3c0fb234c58326ed41a0144798d939ab5fe Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:22:58 +0100 Subject: [PATCH 44/93] test(config): update UserLimitsConfig test to include new fields - Add commentsPerDay and reportsPerDay to the list of properties being tested - Ensures that the new fields are properly included in the UserLimitsConfig fixture --- test/src/models/config/user_limits_config_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/src/models/config/user_limits_config_test.dart b/test/src/models/config/user_limits_config_test.dart index 761e3ea..d2d09d7 100644 --- a/test/src/models/config/user_limits_config_test.dart +++ b/test/src/models/config/user_limits_config_test.dart @@ -23,6 +23,8 @@ void main() { userLimitsConfigFixture.savedHeadlines, userLimitsConfigFixture.savedHeadlineFilters, userLimitsConfigFixture.savedSourceFilters, + userLimitsConfigFixture.commentsPerDay, + userLimitsConfigFixture.reportsPerDay, ]), ); }); From 7d36b9c44d8dc4740616751ffb052d65d9da89a2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:28:21 +0100 Subject: [PATCH 45/93] feat(remote_configs): add role-based reactionsPerDay limits - Add reactionsPerDay field to UserLimitsConfig model - Include reactionsPerDay data in remote configs fixture - Update UserLimitsConfig copyWith method to include reactionsPerDay - Modify UserLimitsConfig test to include reactionsPerDay --- lib/src/fixtures/remote_configs.dart | 5 +++++ lib/src/models/config/user_limits_config.dart | 7 +++++++ test/src/models/config/user_limits_config_test.dart | 1 + 3 files changed, 13 insertions(+) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 26c228e..46c2625 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -65,6 +65,11 @@ final remoteConfigsFixturesData = [ AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), }, + reactionsPerDay: { + AppUserRole.guestUser: 20, + AppUserRole.standardUser: 100, + AppUserRole.premiumUser: 500, + }, commentsPerDay: { AppUserRole.guestUser: 0, AppUserRole.standardUser: 10, diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart index b6f179d..b316f8e 100644 --- a/lib/src/models/config/user_limits_config.dart +++ b/lib/src/models/config/user_limits_config.dart @@ -22,6 +22,7 @@ class UserLimitsConfig extends Equatable { required this.savedHeadlines, required this.savedHeadlineFilters, required this.savedSourceFilters, + required this.reactionsPerDay, required this.commentsPerDay, required this.reportsPerDay, }); @@ -47,6 +48,9 @@ class UserLimitsConfig extends Equatable { /// defines the limits per user role. final Map savedSourceFilters; + /// Role-based limits for the number of reactions a user can perform per day. + final Map reactionsPerDay; + /// Role-based limits for the number of comments a user can post per day. /// /// This limit applies specifically to the creation of new comments and does @@ -65,6 +69,7 @@ class UserLimitsConfig extends Equatable { savedHeadlines, savedHeadlineFilters, savedSourceFilters, + reactionsPerDay, commentsPerDay, reportsPerDay, ]; @@ -78,6 +83,7 @@ class UserLimitsConfig extends Equatable { Map? savedSourceFilters, Map? commentsPerDay, Map? reportsPerDay, + Map? reactionsPerDay, }) { return UserLimitsConfig( followedItems: followedItems ?? this.followedItems, @@ -86,6 +92,7 @@ class UserLimitsConfig extends Equatable { savedSourceFilters: savedSourceFilters ?? this.savedSourceFilters, commentsPerDay: commentsPerDay ?? this.commentsPerDay, reportsPerDay: reportsPerDay ?? this.reportsPerDay, + reactionsPerDay: reactionsPerDay ?? this.reactionsPerDay, ); } } diff --git a/test/src/models/config/user_limits_config_test.dart b/test/src/models/config/user_limits_config_test.dart index d2d09d7..8a8a4f9 100644 --- a/test/src/models/config/user_limits_config_test.dart +++ b/test/src/models/config/user_limits_config_test.dart @@ -23,6 +23,7 @@ void main() { userLimitsConfigFixture.savedHeadlines, userLimitsConfigFixture.savedHeadlineFilters, userLimitsConfigFixture.savedSourceFilters, + userLimitsConfigFixture.reactionsPerDay, userLimitsConfigFixture.commentsPerDay, userLimitsConfigFixture.reportsPerDay, ]), From 2f5d2effe5e4b4672b5b7fa4341c6ecc515707bc Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:30:46 +0100 Subject: [PATCH 46/93] test: restructure test files to reflect models package changes - Remove core.dart export statement for feed_item_test.dart - Move test files from feed_decorators to feed package - Update file paths for call_to_action_item_test.dart, content_collection_item_test.dart, and feed_item_test.dart --- test/src/models/core/core.dart | 1 - .../{feed_decorators => feed}/call_to_action_item_test.dart | 0 .../{feed_decorators => feed}/content_collection_item_test.dart | 0 test/src/models/{core => feed}/feed_item_test.dart | 0 4 files changed, 1 deletion(-) delete mode 100644 test/src/models/core/core.dart rename test/src/models/{feed_decorators => feed}/call_to_action_item_test.dart (100%) rename test/src/models/{feed_decorators => feed}/content_collection_item_test.dart (100%) rename test/src/models/{core => feed}/feed_item_test.dart (100%) diff --git a/test/src/models/core/core.dart b/test/src/models/core/core.dart deleted file mode 100644 index af5e08f..0000000 --- a/test/src/models/core/core.dart +++ /dev/null @@ -1 +0,0 @@ -export 'feed_item_test.dart'; diff --git a/test/src/models/feed_decorators/call_to_action_item_test.dart b/test/src/models/feed/call_to_action_item_test.dart similarity index 100% rename from test/src/models/feed_decorators/call_to_action_item_test.dart rename to test/src/models/feed/call_to_action_item_test.dart diff --git a/test/src/models/feed_decorators/content_collection_item_test.dart b/test/src/models/feed/content_collection_item_test.dart similarity index 100% rename from test/src/models/feed_decorators/content_collection_item_test.dart rename to test/src/models/feed/content_collection_item_test.dart diff --git a/test/src/models/core/feed_item_test.dart b/test/src/models/feed/feed_item_test.dart similarity index 100% rename from test/src/models/core/feed_item_test.dart rename to test/src/models/feed/feed_item_test.dart From fbd41ae509490dc3b5da3fd54514bc312765859c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 10:32:41 +0100 Subject: [PATCH 47/93] build(serialization): sync --- lib/src/models/config/user_limits_config.g.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/models/config/user_limits_config.g.dart b/lib/src/models/config/user_limits_config.g.dart index e20c750..4893d71 100644 --- a/lib/src/models/config/user_limits_config.g.dart +++ b/lib/src/models/config/user_limits_config.g.dart @@ -42,6 +42,13 @@ UserLimitsConfig _$UserLimitsConfigFromJson( ), ), ), + reactionsPerDay: $checkedConvert( + 'reactionsPerDay', + (v) => (v as Map).map( + (k, e) => + MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), + ), + ), commentsPerDay: $checkedConvert( 'commentsPerDay', (v) => (v as Map).map( @@ -74,6 +81,9 @@ Map _$UserLimitsConfigToJson(UserLimitsConfig instance) => 'savedSourceFilters': instance.savedSourceFilters.map( (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), ), + 'reactionsPerDay': instance.reactionsPerDay.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), 'commentsPerDay': instance.commentsPerDay.map( (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), ), From 4bfe5102a7374d1b60ac2f845fa42127661dc17a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:10:07 +0100 Subject: [PATCH 48/93] style: format --- comment_test.dart | 2 +- headline_reaction_test.dart | 2 +- lib/src/models/entities/source.dart | 2 +- report_test.dart | 2 +- test/src/models/config/app_review_config_test.dart | 2 +- test/src/models/config/engagement_config_test.dart | 2 +- test/src/models/config/reporting_config_test.dart | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/comment_test.dart b/comment_test.dart index 9acb9c1..b6f5a74 100644 --- a/comment_test.dart +++ b/comment_test.dart @@ -61,4 +61,4 @@ void main() { expect(updatedComment, isNot(equals(commentFixture))); }); }); -} \ No newline at end of file +} diff --git a/headline_reaction_test.dart b/headline_reaction_test.dart index dd7cbea..1c63b73 100644 --- a/headline_reaction_test.dart +++ b/headline_reaction_test.dart @@ -53,4 +53,4 @@ void main() { expect(updatedReaction, isNot(equals(reactionFixture))); }); }); -} \ No newline at end of file +} diff --git a/lib/src/models/entities/source.dart b/lib/src/models/entities/source.dart index eb20139..d199388 100644 --- a/lib/src/models/entities/source.dart +++ b/lib/src/models/entities/source.dart @@ -1,7 +1,7 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/language.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/utils/utils.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/report_test.dart b/report_test.dart index cd28602..c0e0051 100644 --- a/report_test.dart +++ b/report_test.dart @@ -64,4 +64,4 @@ void main() { expect(updatedReport, isNot(equals(reportFixture))); }); }); -} \ No newline at end of file +} diff --git a/test/src/models/config/app_review_config_test.dart b/test/src/models/config/app_review_config_test.dart index a2f845c..e911e10 100644 --- a/test/src/models/config/app_review_config_test.dart +++ b/test/src/models/config/app_review_config_test.dart @@ -36,4 +36,4 @@ void main() { expect(updatedConfig, isNot(equals(appReviewConfigFixture))); }); }); -} \ No newline at end of file +} diff --git a/test/src/models/config/engagement_config_test.dart b/test/src/models/config/engagement_config_test.dart index da0e785..64e2d63 100644 --- a/test/src/models/config/engagement_config_test.dart +++ b/test/src/models/config/engagement_config_test.dart @@ -38,4 +38,4 @@ void main() { expect(updatedConfig, isNot(equals(engagementConfigFixture))); }); }); -} \ No newline at end of file +} diff --git a/test/src/models/config/reporting_config_test.dart b/test/src/models/config/reporting_config_test.dart index ce7cde5..5df60a8 100644 --- a/test/src/models/config/reporting_config_test.dart +++ b/test/src/models/config/reporting_config_test.dart @@ -39,4 +39,4 @@ void main() { ); }); }); -} \ No newline at end of file +} From 722dc3d5a45e08c69350f881e66eccffb0bb47cb Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:12:22 +0100 Subject: [PATCH 49/93] chore: update fixtures ids --- lib/src/fixtures/fixture_ids.dart | 210 ++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/lib/src/fixtures/fixture_ids.dart b/lib/src/fixtures/fixture_ids.dart index 32886ae..979db06 100644 --- a/lib/src/fixtures/fixture_ids.dart +++ b/lib/src/fixtures/fixture_ids.dart @@ -1066,3 +1066,213 @@ const String kInAppNotificationId18 = 'in_app_notification_18'; const String kInAppNotificationId19 = 'in_app_notification_19'; const String kInAppNotificationId20 = 'in_app_notification_20'; const String kInAppNotificationId21 = 'in_app_notification_21'; +const String kCommentId1 = 'c00000000000000000000001'; +const String kCommentId2 = 'c00000000000000000000002'; +const String kCommentId3 = 'c00000000000000000000003'; +const String kCommentId4 = 'c00000000000000000000004'; +const String kCommentId5 = 'c00000000000000000000005'; +const String kCommentId6 = 'c00000000000000000000006'; +const String kCommentId7 = 'c00000000000000000000007'; +const String kCommentId8 = 'c00000000000000000000008'; +const String kCommentId9 = 'c00000000000000000000009'; +const String kCommentId10 = 'c00000000000000000000010'; +const String kCommentId11 = 'c00000000000000000000011'; +const String kCommentId12 = 'c00000000000000000000012'; +const String kCommentId13 = 'c00000000000000000000013'; +const String kCommentId14 = 'c00000000000000000000014'; +const String kCommentId15 = 'c00000000000000000000015'; +const String kCommentId16 = 'c00000000000000000000016'; +const String kCommentId17 = 'c00000000000000000000017'; +const String kCommentId18 = 'c00000000000000000000018'; +const String kCommentId19 = 'c00000000000000000000019'; +const String kCommentId20 = 'c00000000000000000000020'; +const String kCommentId21 = 'c00000000000000000000021'; +const String kCommentId22 = 'c00000000000000000000022'; +const String kCommentId23 = 'c00000000000000000000023'; +const String kCommentId24 = 'c00000000000000000000024'; +const String kCommentId25 = 'c00000000000000000000025'; +const String kCommentId26 = 'c00000000000000000000026'; +const String kCommentId27 = 'c00000000000000000000027'; +const String kCommentId28 = 'c00000000000000000000028'; +const String kCommentId29 = 'c00000000000000000000029'; +const String kCommentId30 = 'c00000000000000000000030'; +const String kCommentId31 = 'c00000000000000000000031'; +const String kCommentId32 = 'c00000000000000000000032'; +const String kCommentId33 = 'c00000000000000000000033'; +const String kCommentId34 = 'c00000000000000000000034'; +const String kCommentId35 = 'c00000000000000000000035'; +const String kCommentId36 = 'c00000000000000000000036'; +const String kCommentId37 = 'c00000000000000000000037'; +const String kCommentId38 = 'c00000000000000000000038'; +const String kCommentId39 = 'c00000000000000000000039'; +const String kCommentId40 = 'c00000000000000000000040'; +const String kCommentId41 = 'c00000000000000000000041'; +const String kCommentId42 = 'c00000000000000000000042'; +const String kCommentId43 = 'c00000000000000000000043'; +const String kCommentId44 = 'c00000000000000000000044'; +const String kCommentId45 = 'c00000000000000000000045'; +const String kCommentId46 = 'c00000000000000000000046'; +const String kCommentId47 = 'c00000000000000000000047'; +const String kCommentId48 = 'c00000000000000000000048'; +const String kCommentId49 = 'c00000000000000000000049'; +const String kCommentId50 = 'c00000000000000000000050'; +const String kCommentId51 = 'c00000000000000000000051'; +const String kCommentId52 = 'c00000000000000000000052'; +const String kCommentId53 = 'c00000000000000000000053'; +const String kCommentId54 = 'c00000000000000000000054'; +const String kCommentId55 = 'c00000000000000000000055'; +const String kCommentId56 = 'c00000000000000000000056'; +const String kCommentId57 = 'c00000000000000000000057'; +const String kCommentId58 = 'c00000000000000000000058'; +const String kCommentId59 = 'c00000000000000000000059'; +const String kCommentId60 = 'c00000000000000000000060'; +const String kCommentId61 = 'c00000000000000000000061'; +const String kCommentId62 = 'c00000000000000000000062'; +const String kCommentId63 = 'c00000000000000000000063'; +const String kCommentId64 = 'c00000000000000000000064'; +const String kCommentId65 = 'c00000000000000000000065'; +const String kCommentId66 = 'c00000000000000000000066'; +const String kCommentId67 = 'c00000000000000000000067'; +const String kCommentId68 = 'c00000000000000000000068'; +const String kCommentId69 = 'c00000000000000000000069'; +const String kCommentId70 = 'c00000000000000000000070'; +const String kCommentId71 = 'c00000000000000000000071'; +const String kCommentId72 = 'c00000000000000000000072'; +const String kCommentId73 = 'c00000000000000000000073'; +const String kCommentId74 = 'c00000000000000000000074'; +const String kCommentId75 = 'c00000000000000000000075'; +const String kCommentId76 = 'c00000000000000000000076'; +const String kCommentId77 = 'c00000000000000000000077'; +const String kCommentId78 = 'c00000000000000000000078'; +const String kCommentId79 = 'c00000000000000000000079'; +const String kCommentId80 = 'c00000000000000000000080'; +const String kCommentId81 = 'c00000000000000000000081'; +const String kCommentId82 = 'c00000000000000000000082'; +const String kCommentId83 = 'c00000000000000000000083'; +const String kCommentId84 = 'c00000000000000000000084'; +const String kCommentId85 = 'c00000000000000000000085'; +const String kCommentId86 = 'c00000000000000000000086'; +const String kCommentId87 = 'c00000000000000000000087'; +const String kCommentId88 = 'c00000000000000000000088'; +const String kCommentId89 = 'c00000000000000000000089'; +const String kCommentId90 = 'c00000000000000000000090'; +const String kCommentId91 = 'c00000000000000000000091'; +const String kCommentId92 = 'c00000000000000000000092'; +const String kCommentId93 = 'c00000000000000000000093'; +const String kCommentId94 = 'c00000000000000000000094'; +const String kCommentId95 = 'c00000000000000000000095'; +const String kCommentId96 = 'c00000000000000000000096'; +const String kCommentId97 = 'c00000000000000000000097'; +const String kCommentId98 = 'c00000000000000000000098'; +const String kCommentId99 = 'c00000000000000000000099'; +const String kCommentId100 = 'c00000000000000000000100'; +const String kReactionId1 = 'r00000000000000000000001'; +const String kReactionId2 = 'r00000000000000000000002'; +const String kReactionId3 = 'r00000000000000000000003'; +const String kReactionId4 = 'r00000000000000000000004'; +const String kReactionId5 = 'r00000000000000000000005'; +const String kReactionId6 = 'r00000000000000000000006'; +const String kReactionId7 = 'r00000000000000000000007'; +const String kReactionId8 = 'r00000000000000000000008'; +const String kReactionId9 = 'r00000000000000000000009'; +const String kReactionId10 = 'r00000000000000000000010'; +const String kReactionId11 = 'r00000000000000000000011'; +const String kReactionId12 = 'r00000000000000000000012'; +const String kReactionId13 = 'r00000000000000000000013'; +const String kReactionId14 = 'r00000000000000000000014'; +const String kReactionId15 = 'r00000000000000000000015'; +const String kReactionId16 = 'r00000000000000000000016'; +const String kReactionId17 = 'r00000000000000000000017'; +const String kReactionId18 = 'r00000000000000000000018'; +const String kReactionId19 = 'r00000000000000000000019'; +const String kReactionId20 = 'r00000000000000000000020'; +const String kReactionId21 = 'r00000000000000000000021'; +const String kReactionId22 = 'r00000000000000000000022'; +const String kReactionId23 = 'r00000000000000000000023'; +const String kReactionId24 = 'r00000000000000000000024'; +const String kReactionId25 = 'r00000000000000000000025'; +const String kReactionId26 = 'r00000000000000000000026'; +const String kReactionId27 = 'r00000000000000000000027'; +const String kReactionId28 = 'r00000000000000000000028'; +const String kReactionId29 = 'r00000000000000000000029'; +const String kReactionId30 = 'r00000000000000000000030'; +const String kReactionId31 = 'r00000000000000000000031'; +const String kReactionId32 = 'r00000000000000000000032'; +const String kReactionId33 = 'r00000000000000000000033'; +const String kReactionId34 = 'r00000000000000000000034'; +const String kReactionId35 = 'r00000000000000000000035'; +const String kReactionId36 = 'r00000000000000000000036'; +const String kReactionId37 = 'r00000000000000000000037'; +const String kReactionId38 = 'r00000000000000000000038'; +const String kReactionId39 = 'r00000000000000000000039'; +const String kReactionId40 = 'r00000000000000000000040'; +const String kReactionId41 = 'r00000000000000000000041'; +const String kReactionId42 = 'r00000000000000000000042'; +const String kReactionId43 = 'r00000000000000000000043'; +const String kReactionId44 = 'r00000000000000000000044'; +const String kReactionId45 = 'r00000000000000000000045'; +const String kReactionId46 = 'r00000000000000000000046'; +const String kReactionId47 = 'r00000000000000000000047'; +const String kReactionId48 = 'r00000000000000000000048'; +const String kReactionId49 = 'r00000000000000000000049'; +const String kReactionId50 = 'r00000000000000000000050'; +const String kReactionId51 = 'r00000000000000000000051'; +const String kReactionId52 = 'r00000000000000000000052'; +const String kReactionId53 = 'r00000000000000000000053'; +const String kReactionId54 = 'r00000000000000000000054'; +const String kReactionId55 = 'r00000000000000000000055'; +const String kReactionId56 = 'r00000000000000000000056'; +const String kReactionId57 = 'r00000000000000000000057'; +const String kReactionId58 = 'r00000000000000000000058'; +const String kReactionId59 = 'r00000000000000000000059'; +const String kReactionId60 = 'r00000000000000000000060'; +const String kReactionId61 = 'r00000000000000000000061'; +const String kReactionId62 = 'r00000000000000000000062'; +const String kReactionId63 = 'r00000000000000000000063'; +const String kReactionId64 = 'r00000000000000000000064'; +const String kReactionId65 = 'r00000000000000000000065'; +const String kReactionId66 = 'r00000000000000000000066'; +const String kReactionId67 = 'r00000000000000000000067'; +const String kReactionId68 = 'r00000000000000000000068'; +const String kReactionId69 = 'r00000000000000000000069'; +const String kReactionId70 = 'r00000000000000000000070'; +const String kReactionId71 = 'r00000000000000000000071'; +const String kReactionId72 = 'r00000000000000000000072'; +const String kReactionId73 = 'r00000000000000000000073'; +const String kReactionId74 = 'r00000000000000000000074'; +const String kReactionId75 = 'r00000000000000000000075'; +const String kReactionId76 = 'r00000000000000000000076'; +const String kReactionId77 = 'r00000000000000000000077'; +const String kReactionId78 = 'r00000000000000000000078'; +const String kReactionId79 = 'r00000000000000000000079'; +const String kReactionId80 = 'r00000000000000000000080'; +const String kReactionId81 = 'r00000000000000000000081'; +const String kReactionId82 = 'r00000000000000000000082'; +const String kReactionId83 = 'r00000000000000000000083'; +const String kReactionId84 = 'r00000000000000000000084'; +const String kReactionId85 = 'r00000000000000000000085'; +const String kReactionId86 = 'r00000000000000000000086'; +const String kReactionId87 = 'r00000000000000000000087'; +const String kReactionId88 = 'r00000000000000000000088'; +const String kReactionId89 = 'r00000000000000000000089'; +const String kReactionId90 = 'r00000000000000000000090'; +const String kReactionId91 = 'r00000000000000000000091'; +const String kReactionId92 = 'r00000000000000000000092'; +const String kReactionId93 = 'r00000000000000000000093'; +const String kReactionId94 = 'r00000000000000000000094'; +const String kReactionId95 = 'r00000000000000000000095'; +const String kReactionId96 = 'r00000000000000000000096'; +const String kReactionId97 = 'r00000000000000000000097'; +const String kReactionId98 = 'r00000000000000000000098'; +const String kReactionId99 = 'r00000000000000000000099'; +const String kReactionId100 = 'r00000000000000000000100'; +const String kReportId1 = 'rep0000000000000000000001'; +const String kReportId2 = 'rep0000000000000000000002'; +const String kReportId3 = 'rep0000000000000000000003'; +const String kReportId4 = 'rep0000000000000000000004'; +const String kReportId5 = 'rep0000000000000000000005'; +const String kReportId6 = 'rep0000000000000000000006'; +const String kReportId7 = 'rep0000000000000000000007'; +const String kReportId8 = 'rep0000000000000000000008'; +const String kReportId9 = 'rep0000000000000000000009'; +const String kReportId10 = 'rep0000000000000000000010'; From 3e66b0e7a88863eca79c35c37d0a0667882fa66f Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:12:34 +0100 Subject: [PATCH 50/93] fix(lib): add comment fixtures for testing and development - Create a new file with predefined comment data for testing and development purposes - Generate 10 comments --- lib/src/fixtures/comments.dart | 155 +++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 lib/src/fixtures/comments.dart diff --git a/lib/src/fixtures/comments.dart b/lib/src/fixtures/comments.dart new file mode 100644 index 0000000..879d8a9 --- /dev/null +++ b/lib/src/fixtures/comments.dart @@ -0,0 +1,155 @@ +import 'package:core/core.dart'; + +/// A list of predefined comments for fixture data. +/// This creates 10 comments for each of the first 10 users, with each +/// comment targeting a unique headline. +final List commentsFixturesData = () { + final comments = []; + final users = usersFixturesData.take(10).toList(); + final headlines = headlinesFixturesData.take(100).toList(); + final commentIds = [ + kCommentId1, + kCommentId2, + kCommentId3, + kCommentId4, + kCommentId5, + kCommentId6, + kCommentId7, + kCommentId8, + kCommentId9, + kCommentId10, + kCommentId11, + kCommentId12, + kCommentId13, + kCommentId14, + kCommentId15, + kCommentId16, + kCommentId17, + kCommentId18, + kCommentId19, + kCommentId20, + kCommentId21, + kCommentId22, + kCommentId23, + kCommentId24, + kCommentId25, + kCommentId26, + kCommentId27, + kCommentId28, + kCommentId29, + kCommentId30, + kCommentId31, + kCommentId32, + kCommentId33, + kCommentId34, + kCommentId35, + kCommentId36, + kCommentId37, + kCommentId38, + kCommentId39, + kCommentId40, + kCommentId41, + kCommentId42, + kCommentId43, + kCommentId44, + kCommentId45, + kCommentId46, + kCommentId47, + kCommentId48, + kCommentId49, + kCommentId50, + kCommentId51, + kCommentId52, + kCommentId53, + kCommentId54, + kCommentId55, + kCommentId56, + kCommentId57, + kCommentId58, + kCommentId59, + kCommentId60, + kCommentId61, + kCommentId62, + kCommentId63, + kCommentId64, + kCommentId65, + kCommentId66, + kCommentId67, + kCommentId68, + kCommentId69, + kCommentId70, + kCommentId71, + kCommentId72, + kCommentId73, + kCommentId74, + kCommentId75, + kCommentId76, + kCommentId77, + kCommentId78, + kCommentId79, + kCommentId80, + kCommentId81, + kCommentId82, + kCommentId83, + kCommentId84, + kCommentId85, + kCommentId86, + kCommentId87, + kCommentId88, + kCommentId89, + kCommentId90, + kCommentId91, + kCommentId92, + kCommentId93, + kCommentId94, + kCommentId95, + kCommentId96, + kCommentId97, + kCommentId98, + kCommentId99, + kCommentId100, + ]; + final commentContents = [ + 'This is a really insightful article. It completely changed my perspective.', + "I'm not sure I agree with the author's conclusion, but it's a well-argued piece.", + 'Finally, someone is talking about this! More people need to read this.', + 'A bit simplistic, but a good introduction to the topic for beginners.', + 'The data presented here is fascinating. I wonder what the long-term implications are.', + 'This is exactly what I was looking for. Thank you for sharing!', + 'I have a few questions about the methodology used in this study.', + 'This made me laugh out loud. Great writing!', + "A powerful and moving story. It's important to hear these voices.", + 'I think there are some key facts missing from this analysis.', + ]; + + for (var i = 0; i < users.length; i++) { + for (var j = 0; j < 10; j++) { + final user = users[i]; + final headlineIndex = i * 10 + j; + final headline = headlines[headlineIndex]; + final commentIndex = i * 10 + j; + + // Vary the status for realism + var status = CommentStatus.approved; + if (commentIndex % 15 == 0) { + status = CommentStatus.pendingReview; + } else if (commentIndex % 25 == 0) { + status = CommentStatus.rejected; + } + + comments.add( + Comment( + id: commentIds[commentIndex], + headlineId: headline.id, + userId: user.id, + content: commentContents[j], + status: status, + createdAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), + updatedAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), + ), + ); + } + } + + return comments; +}(); From 6f9597be46a76052ab57d5fef9e6c3d325251170 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:13:27 +0100 Subject: [PATCH 51/93] fix(lib): add reactions fixtures for testing and development - Create a new file with predefined reactions data for testing and development purposes - Generate 100 reaction --- lib/src/fixtures/headline_reactions.dart | 134 +++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 lib/src/fixtures/headline_reactions.dart diff --git a/lib/src/fixtures/headline_reactions.dart b/lib/src/fixtures/headline_reactions.dart new file mode 100644 index 0000000..6024ac4 --- /dev/null +++ b/lib/src/fixtures/headline_reactions.dart @@ -0,0 +1,134 @@ +import 'package:core/core.dart'; + +/// A list of predefined headline reactions for fixture data. +/// This creates 10 reactions for each of the first 10 users, with each +/// reaction targeting a unique headline. +final List headlineReactionsFixturesData = () { + final reactions = []; + final users = usersFixturesData.take(10).toList(); + final headlines = headlinesFixturesData.take(100).toList(); + final reactionIds = [ + kReactionId1, + kReactionId2, + kReactionId3, + kReactionId4, + kReactionId5, + kReactionId6, + kReactionId7, + kReactionId8, + kReactionId9, + kReactionId10, + kReactionId11, + kReactionId12, + kReactionId13, + kReactionId14, + kReactionId15, + kReactionId16, + kReactionId17, + kReactionId18, + kReactionId19, + kReactionId20, + kReactionId21, + kReactionId22, + kReactionId23, + kReactionId24, + kReactionId25, + kReactionId26, + kReactionId27, + kReactionId28, + kReactionId29, + kReactionId30, + kReactionId31, + kReactionId32, + kReactionId33, + kReactionId34, + kReactionId35, + kReactionId36, + kReactionId37, + kReactionId38, + kReactionId39, + kReactionId40, + kReactionId41, + kReactionId42, + kReactionId43, + kReactionId44, + kReactionId45, + kReactionId46, + kReactionId47, + kReactionId48, + kReactionId49, + kReactionId50, + kReactionId51, + kReactionId52, + kReactionId53, + kReactionId54, + kReactionId55, + kReactionId56, + kReactionId57, + kReactionId58, + kReactionId59, + kReactionId60, + kReactionId61, + kReactionId62, + kReactionId63, + kReactionId64, + kReactionId65, + kReactionId66, + kReactionId67, + kReactionId68, + kReactionId69, + kReactionId70, + kReactionId71, + kReactionId72, + kReactionId73, + kReactionId74, + kReactionId75, + kReactionId76, + kReactionId77, + kReactionId78, + kReactionId79, + kReactionId80, + kReactionId81, + kReactionId82, + kReactionId83, + kReactionId84, + kReactionId85, + kReactionId86, + kReactionId87, + kReactionId88, + kReactionId89, + kReactionId90, + kReactionId91, + kReactionId92, + kReactionId93, + kReactionId94, + kReactionId95, + kReactionId96, + kReactionId97, + kReactionId98, + kReactionId99, + kReactionId100, + ]; + const reactionTypes = ReactionType.values; + + for (var i = 0; i < users.length; i++) { + for (var j = 0; j < 10; j++) { + final user = users[i]; + final headlineIndex = i * 10 + j; + final headline = headlines[headlineIndex]; + final reactionIndex = i * 10 + j; + + reactions.add( + HeadlineReaction( + id: reactionIds[reactionIndex], + headlineId: headline.id, + userId: user.id, + reactionType: reactionTypes[reactionIndex % reactionTypes.length], + createdAt: DateTime.now().subtract(Duration(days: i, hours: j)), + ), + ); + } + } + + return reactions; +}(); From 38d61d8e51db449d2b139c457fc2984e75b699fc Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:14:07 +0100 Subject: [PATCH 52/93] fix(lib): add reports fixtures for testing and development - Create a new file with predefined reports data for testing and development purposes - Generate 10 report --- lib/src/fixtures/reports.dart | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 lib/src/fixtures/reports.dart diff --git a/lib/src/fixtures/reports.dart b/lib/src/fixtures/reports.dart new file mode 100644 index 0000000..03e6f66 --- /dev/null +++ b/lib/src/fixtures/reports.dart @@ -0,0 +1,84 @@ +import 'package:core/core.dart'; +import 'package:core/src/fixtures/comments.dart'; + +/// A list of predefined reports for fixture data. +/// This creates 1 report for each of the first 10 users, targeting a mix of +/// headlines, sources, and comments. +final List reportsFixturesData = () { + final reports = []; + final users = usersFixturesData.take(10).toList(); + final headlines = headlinesFixturesData.take(10).toList(); + final reportIds = [ + kReportId1, + kReportId2, + kReportId3, + kReportId4, + kReportId5, + kReportId6, + kReportId7, + kReportId8, + kReportId9, + kReportId10, + ]; + const headlineReasons = HeadlineReportReason.values; + const sourceReasons = SourceReportReason.values; + const commentReasons = CommentReportReason.values; + + for (var i = 0; i < users.length; i++) { + final user = users[i]; + final headline = headlines[i]; + var status = ReportStatus.submitted; + if (i % 3 == 0) { + status = ReportStatus.inReview; + } else if (i % 5 == 0) { + status = ReportStatus.resolved; + } + + // Create a mix of report types + if (i < 5) { + // Report on Headlines + reports.add( + Report( + id: reportIds[i], + reporterUserId: user.id, + entityType: ReportableEntity.headline, + entityId: headline.id, + reason: headlineReasons[i % headlineReasons.length].name, + additionalComments: 'This headline seems misleading.', + status: status, + createdAt: DateTime.now().subtract(Duration(days: i)), + ), + ); + } else if (i < 8) { + // Report on Sources + reports.add( + Report( + id: reportIds[i], + reporterUserId: user.id, + entityType: ReportableEntity.source, + entityId: sourcesFixturesData[i].id, + reason: sourceReasons[i % sourceReasons.length].name, + additionalComments: 'This source has too many ads.', + status: status, + createdAt: DateTime.now().subtract(Duration(days: i)), + ), + ); + } else { + // Report on Comments + reports.add( + Report( + id: reportIds[i], + reporterUserId: user.id, + entityType: ReportableEntity.comment, + entityId: commentsFixturesData[i].id, + reason: commentReasons[i % commentReasons.length].name, + additionalComments: 'This comment is spam.', + status: status, + createdAt: DateTime.now().subtract(Duration(days: i)), + ), + ); + } + } + + return reports; +}(); From cf08a8f245967745f5163ff0498a69ad0f17cabf Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:14:14 +0100 Subject: [PATCH 53/93] style: format --- lib/src/models/entities/headline.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index c6a0c1e..8fd8e46 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -1,8 +1,8 @@ import 'package:core/src/enums/enums.dart'; -import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/models/entities/country.dart'; import 'package:core/src/models/entities/source.dart'; import 'package:core/src/models/entities/topic.dart'; +import 'package:core/src/models/feed/feed_item.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; From 0fb9a1c8fcc04a921a83d8c12e1d5dc55b416eae Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:41:58 +0100 Subject: [PATCH 54/93] feat(enums): add EngageableType enum - Defines the types of entities that can be engaged with (reacted to or commented on) - Adds 'headline' as the first value --- lib/src/enums/engageable_type.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 lib/src/enums/engageable_type.dart diff --git a/lib/src/enums/engageable_type.dart b/lib/src/enums/engageable_type.dart new file mode 100644 index 0000000..a3f5384 --- /dev/null +++ b/lib/src/enums/engageable_type.dart @@ -0,0 +1,12 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template engageable_type} +/// Defines the types of entities that can be engaged with (reacted to or +/// commented on). +/// {@endtemplate} +@JsonEnum() +enum EngageableType { + /// The engagement is for a news headline. + @JsonValue('headline') + headline, +} From 896f77cfda8e7ca4d8af167f4c70ff05481b306d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:42:15 +0100 Subject: [PATCH 55/93] chore: delete absolete file --- .../headline_reaction.dart | 71 ------------------- .../headline_reaction.g.dart | 43 ----------- 2 files changed, 114 deletions(-) delete mode 100644 lib/src/models/user_generated_content/headline_reaction.dart delete mode 100644 lib/src/models/user_generated_content/headline_reaction.g.dart diff --git a/lib/src/models/user_generated_content/headline_reaction.dart b/lib/src/models/user_generated_content/headline_reaction.dart deleted file mode 100644 index da94510..0000000 --- a/lib/src/models/user_generated_content/headline_reaction.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:core/src/enums/reaction_type.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 'headline_reaction.g.dart'; - -/// {@template headline_reaction} -/// Represents a single, unique reaction from a user to a headline. -/// -/// This model ensures that a user can only have one reaction of a specific -/// type on any given headline, providing a clear and non-duplicative dataset -/// for engagement analysis. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class HeadlineReaction extends Equatable { - /// {@macro headline_reaction} - const HeadlineReaction({ - required this.id, - required this.headlineId, - required this.userId, - required this.reactionType, - required this.createdAt, - }); - - /// Creates a [HeadlineReaction] from JSON data. - factory HeadlineReaction.fromJson(Map json) => - _$HeadlineReactionFromJson(json); - - /// The unique identifier for the reaction. - final String id; - - /// The ID of the headline being reacted to. - final String headlineId; - - /// The ID of the user who made the reaction. - final String userId; - - /// The type of reaction (e.g., like, insightful, etc). - final ReactionType reactionType; - - /// The timestamp when the reaction was created. - @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) - final DateTime createdAt; - - /// Converts this [HeadlineReaction] instance to JSON data. - Map toJson() => _$HeadlineReactionToJson(this); - - @override - List get props => [id, headlineId, userId, reactionType, createdAt]; - - /// Creates a copy of this [HeadlineReaction] but with the given fields - /// replaced with the new values. - HeadlineReaction copyWith({ - String? id, - String? headlineId, - String? userId, - ReactionType? reactionType, - DateTime? createdAt, - }) { - return HeadlineReaction( - id: id ?? this.id, - headlineId: headlineId ?? this.headlineId, - userId: userId ?? this.userId, - reactionType: reactionType ?? this.reactionType, - createdAt: createdAt ?? this.createdAt, - ); - } -} diff --git a/lib/src/models/user_generated_content/headline_reaction.g.dart b/lib/src/models/user_generated_content/headline_reaction.g.dart deleted file mode 100644 index 8feba8c..0000000 --- a/lib/src/models/user_generated_content/headline_reaction.g.dart +++ /dev/null @@ -1,43 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'headline_reaction.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -HeadlineReaction _$HeadlineReactionFromJson(Map json) => - $checkedCreate('HeadlineReaction', json, ($checkedConvert) { - final val = HeadlineReaction( - id: $checkedConvert('id', (v) => v as String), - headlineId: $checkedConvert('headlineId', (v) => v as String), - userId: $checkedConvert('userId', (v) => v as String), - reactionType: $checkedConvert( - 'reactionType', - (v) => $enumDecode(_$ReactionTypeEnumMap, v), - ), - createdAt: $checkedConvert( - 'createdAt', - (v) => dateTimeFromJson(v as String?), - ), - ); - return val; - }); - -Map _$HeadlineReactionToJson(HeadlineReaction instance) => - { - 'id': instance.id, - 'headlineId': instance.headlineId, - 'userId': instance.userId, - 'reactionType': _$ReactionTypeEnumMap[instance.reactionType]!, - 'createdAt': dateTimeToJson(instance.createdAt), - }; - -const _$ReactionTypeEnumMap = { - ReactionType.like: 'like', - ReactionType.insightful: 'insightful', - ReactionType.amusing: 'amusing', - ReactionType.sad: 'sad', - ReactionType.angry: 'angry', - ReactionType.skeptical: 'skeptical', -}; From df87f084cd5d39b1af14b0ddab06a568b2f3e0b1 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:42:47 +0100 Subject: [PATCH 56/93] refactor(comment): update model for broader engageable content types - Remove headlineId in favor of generic entityId and entityType - Simplify copyWith method by removing unnecessary parameters - Update class documentation to reflect new functionality - Add default status value in constructor for better usability --- .../user_generated_content/comment.dart | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/src/models/user_generated_content/comment.dart b/lib/src/models/user_generated_content/comment.dart index a08cd0f..ecc683a 100644 --- a/lib/src/models/user_generated_content/comment.dart +++ b/lib/src/models/user_generated_content/comment.dart @@ -1,4 +1,5 @@ import 'package:core/src/enums/comment_status.dart'; +import 'package:core/src/enums/engageable_type.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -7,10 +8,7 @@ import 'package:meta/meta.dart'; part 'comment.g.dart'; /// {@template comment} -/// Represents a user-submitted comment on a headline. -/// -/// This model is designed to support a robust moderation system, with a `status` -/// field to track its state through the review process (manual or automated). +/// Represents a user-submitted comment on a specific piece of content. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) @@ -18,12 +16,13 @@ class Comment extends Equatable { /// {@macro comment} const Comment({ required this.id, - required this.headlineId, required this.userId, + required this.entityId, + required this.entityType, required this.content, - required this.status, required this.createdAt, required this.updatedAt, + this.status = CommentStatus.pendingReview, }); /// Creates a [Comment] from JSON data. @@ -33,12 +32,15 @@ class Comment extends Equatable { /// The unique identifier for the comment. final String id; - /// The ID of the headline this comment is associated with. - final String headlineId; - /// The ID of the user who authored the comment. final String userId; + /// The ID of the entity being commented on (e.g., a headline ID). + final String entityId; + + /// The type of entity being commented on. + final EngageableType entityType; + /// The text content of the comment. final String content; @@ -59,33 +61,26 @@ class Comment extends Equatable { @override List get props => [ id, - headlineId, userId, + entityId, + entityType, content, status, createdAt, updatedAt, ]; - /// Creates a copy of this [Comment] but with the given fields replaced - /// with the new values. - Comment copyWith({ - String? id, - String? headlineId, - String? userId, - String? content, - CommentStatus? status, - DateTime? createdAt, - DateTime? updatedAt, - }) { + /// Creates a copy of this [Comment] with updated values. + Comment copyWith({String? content, CommentStatus? status}) { return Comment( - id: id ?? this.id, - headlineId: headlineId ?? this.headlineId, - userId: userId ?? this.userId, + id: id, + userId: userId, + entityId: entityId, + entityType: entityType, content: content ?? this.content, status: status ?? this.status, - createdAt: createdAt ?? this.createdAt, - updatedAt: updatedAt ?? this.updatedAt, + createdAt: createdAt, + updatedAt: DateTime.now(), ); } } From 34ee8232e9b883b41562e9c9386a86c932815ce7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:43:43 +0100 Subject: [PATCH 57/93] feat(user-generated-content): add Reaction model - Create Reaction class to represent user's reaction to content - Include properties for id, userId, entityId, entityType, reactionType, and createdAt - Add factory constructor for creating Reaction from JSON - Implement toJson method for serializing Reaction to JSON - Override props for Equatable comparison - Include copyWith method for creating modified copies of Reaction --- .../user_generated_content/reaction.dart | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 lib/src/models/user_generated_content/reaction.dart diff --git a/lib/src/models/user_generated_content/reaction.dart b/lib/src/models/user_generated_content/reaction.dart new file mode 100644 index 0000000..a67017c --- /dev/null +++ b/lib/src/models/user_generated_content/reaction.dart @@ -0,0 +1,73 @@ +import 'package:core/src/enums/engageable_type.dart'; +import 'package:core/src/enums/reaction_type.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 'reaction.g.dart'; + +/// {@template reaction} +/// Represents a user's reaction to a specific piece of content. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class Reaction extends Equatable { + /// {@macro reaction} + const Reaction({ + required this.id, + required this.userId, + required this.entityId, + required this.entityType, + required this.reactionType, + required this.createdAt, + }); + + /// Creates a [Reaction] from JSON data. + factory Reaction.fromJson(Map json) => + _$ReactionFromJson(json); + + /// The unique identifier for the reaction. + final String id; + + /// The ID of the user who made the reaction. + final String userId; + + /// The ID of the entity being reacted to (e.g., a headline ID). + final String entityId; + + /// The type of entity being reacted to. + final EngageableType entityType; + + /// The type of reaction (e.g., like, insightful). + final ReactionType reactionType; + + /// The timestamp when the reaction was created. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// Converts this [Reaction] instance to JSON data. + Map toJson() => _$ReactionToJson(this); + + @override + List get props => [ + id, + userId, + entityId, + entityType, + reactionType, + createdAt, + ]; + + /// Creates a copy of this [Reaction] with updated values. + Reaction copyWith({ReactionType? reactionType}) { + return Reaction( + id: id, + userId: userId, + entityId: entityId, + entityType: entityType, + reactionType: reactionType ?? this.reactionType, + createdAt: createdAt, + ); + } +} From 7355a64facb000537db2f2ae58e6455c59d64b40 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sat, 29 Nov 2025 11:44:40 +0100 Subject: [PATCH 58/93] feat(user_generated_content): add Engagement model - Represents a user's complete engagement action - Includes a mandatory reaction and an optional comment - Tied to a specific entity - Intended to be sent from client to backend in a single request --- .../user_generated_content/engagement.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 lib/src/models/user_generated_content/engagement.dart diff --git a/lib/src/models/user_generated_content/engagement.dart b/lib/src/models/user_generated_content/engagement.dart new file mode 100644 index 0000000..75af0ca --- /dev/null +++ b/lib/src/models/user_generated_content/engagement.dart @@ -0,0 +1,59 @@ +import 'package:core/src/enums/engageable_type.dart'; +import 'package:core/src/models/user_generated_content/comment.dart'; +import 'package:core/src/models/user_generated_content/reaction.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'engagement.g.dart'; + +/// {@template engagement} +/// Represents a user's complete engagement action, which includes a mandatory +/// reaction and an optional comment, tied to a specific entity. +/// +/// This model is intended to be sent from the client to the backend in a +/// single request to record a user's interaction. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class Engagement extends Equatable { + /// {@macro engagement} + const Engagement({ + required this.entityId, + required this.entityType, + required this.reaction, + this.comment, + }); + + /// Creates an [Engagement] from JSON data. + factory Engagement.fromJson(Map json) => + _$EngagementFromJson(json); + + /// The ID of the entity being engaged with (e.g., a headline ID). + final String entityId; + + /// The type of entity being engaged with. + final EngageableType entityType; + + /// The user's reaction. This is a mandatory part of the engagement. + final Reaction reaction; + + /// The user's optional comment, provided along with the reaction. + final Comment? comment; + + /// Converts this [Engagement] instance to JSON data. + Map toJson() => _$EngagementToJson(this); + + @override + List get props => [entityId, entityType, reaction, comment]; + + /// Creates a copy of this [Engagement] with updated values. + Engagement copyWith({Reaction? reaction, Comment? comment}) { + return Engagement( + entityId: entityId, + entityType: entityType, + reaction: reaction ?? this.reaction, + comment: comment ?? this.comment, + ); + } +} From ee2555cf3e04bd5fb07a37012bca968984a41d3b Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:15:01 +0100 Subject: [PATCH 59/93] chore: delete bsolete files --- comment_test.dart | 64 -------------- headline_reaction_test.dart | 56 ------------ lib/src/fixtures/comments.dart | 155 --------------------------------- report_test.dart | 67 -------------- 4 files changed, 342 deletions(-) delete mode 100644 comment_test.dart delete mode 100644 headline_reaction_test.dart delete mode 100644 lib/src/fixtures/comments.dart delete mode 100644 report_test.dart diff --git a/comment_test.dart b/comment_test.dart deleted file mode 100644 index b6f5a74..0000000 --- a/comment_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('Comment', () { - final now = DateTime.now(); - final commentFixture = Comment( - id: 'comment_1', - headlineId: 'headline_1', - userId: 'user_1', - content: 'This is a test comment.', - status: CommentStatus.approved, - createdAt: now, - updatedAt: now, - ); - - test('can be instantiated', () { - expect(commentFixture, isA()); - }); - - test('supports value equality', () { - final anotherComment = Comment( - id: 'comment_1', - headlineId: 'headline_1', - userId: 'user_1', - content: 'This is a test comment.', - status: CommentStatus.approved, - createdAt: now, - updatedAt: now, - ); - expect(commentFixture, equals(anotherComment)); - }); - - test('can be created from JSON', () { - final json = commentFixture.toJson(); - final fromJson = Comment.fromJson(json); - expect(fromJson, equals(commentFixture)); - }); - - test('can be converted to JSON', () { - final json = commentFixture.toJson(); - final expectedJson = { - 'id': 'comment_1', - 'headlineId': 'headline_1', - 'userId': 'user_1', - 'content': 'This is a test comment.', - 'status': 'approved', - 'createdAt': now.toIso8601String(), - 'updatedAt': now.toIso8601String(), - }; - expect(json, equals(expectedJson)); - }); - - test('copyWith creates a copy with updated values', () { - final updatedComment = commentFixture.copyWith( - content: 'This is an updated comment.', - status: CommentStatus.rejected, - ); - expect(updatedComment.content, 'This is an updated comment.'); - expect(updatedComment.status, CommentStatus.rejected); - expect(updatedComment, isNot(equals(commentFixture))); - }); - }); -} diff --git a/headline_reaction_test.dart b/headline_reaction_test.dart deleted file mode 100644 index 1c63b73..0000000 --- a/headline_reaction_test.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('HeadlineReaction', () { - final now = DateTime.now(); - final reactionFixture = HeadlineReaction( - id: 'reaction_1', - headlineId: 'headline_1', - userId: 'user_1', - reactionType: ReactionType.like, - createdAt: now, - ); - - test('can be instantiated', () { - expect(reactionFixture, isA()); - }); - - test('supports value equality', () { - final anotherReaction = HeadlineReaction( - id: 'reaction_1', - headlineId: 'headline_1', - userId: 'user_1', - reactionType: ReactionType.like, - createdAt: now, - ); - expect(reactionFixture, equals(anotherReaction)); - }); - - test('can be created from JSON', () { - final json = reactionFixture.toJson(); - final fromJson = HeadlineReaction.fromJson(json); - expect(fromJson, equals(reactionFixture)); - }); - - test('can be converted to JSON', () { - final json = reactionFixture.toJson(); - final expectedJson = { - 'id': 'reaction_1', - 'headlineId': 'headline_1', - 'userId': 'user_1', - 'reactionType': 'like', - 'createdAt': now.toIso8601String(), - }; - expect(json, equals(expectedJson)); - }); - - test('copyWith creates a copy with updated values', () { - final updatedReaction = reactionFixture.copyWith( - reactionType: ReactionType.insightful, - ); - expect(updatedReaction.reactionType, ReactionType.insightful); - expect(updatedReaction, isNot(equals(reactionFixture))); - }); - }); -} diff --git a/lib/src/fixtures/comments.dart b/lib/src/fixtures/comments.dart deleted file mode 100644 index 879d8a9..0000000 --- a/lib/src/fixtures/comments.dart +++ /dev/null @@ -1,155 +0,0 @@ -import 'package:core/core.dart'; - -/// A list of predefined comments for fixture data. -/// This creates 10 comments for each of the first 10 users, with each -/// comment targeting a unique headline. -final List commentsFixturesData = () { - final comments = []; - final users = usersFixturesData.take(10).toList(); - final headlines = headlinesFixturesData.take(100).toList(); - final commentIds = [ - kCommentId1, - kCommentId2, - kCommentId3, - kCommentId4, - kCommentId5, - kCommentId6, - kCommentId7, - kCommentId8, - kCommentId9, - kCommentId10, - kCommentId11, - kCommentId12, - kCommentId13, - kCommentId14, - kCommentId15, - kCommentId16, - kCommentId17, - kCommentId18, - kCommentId19, - kCommentId20, - kCommentId21, - kCommentId22, - kCommentId23, - kCommentId24, - kCommentId25, - kCommentId26, - kCommentId27, - kCommentId28, - kCommentId29, - kCommentId30, - kCommentId31, - kCommentId32, - kCommentId33, - kCommentId34, - kCommentId35, - kCommentId36, - kCommentId37, - kCommentId38, - kCommentId39, - kCommentId40, - kCommentId41, - kCommentId42, - kCommentId43, - kCommentId44, - kCommentId45, - kCommentId46, - kCommentId47, - kCommentId48, - kCommentId49, - kCommentId50, - kCommentId51, - kCommentId52, - kCommentId53, - kCommentId54, - kCommentId55, - kCommentId56, - kCommentId57, - kCommentId58, - kCommentId59, - kCommentId60, - kCommentId61, - kCommentId62, - kCommentId63, - kCommentId64, - kCommentId65, - kCommentId66, - kCommentId67, - kCommentId68, - kCommentId69, - kCommentId70, - kCommentId71, - kCommentId72, - kCommentId73, - kCommentId74, - kCommentId75, - kCommentId76, - kCommentId77, - kCommentId78, - kCommentId79, - kCommentId80, - kCommentId81, - kCommentId82, - kCommentId83, - kCommentId84, - kCommentId85, - kCommentId86, - kCommentId87, - kCommentId88, - kCommentId89, - kCommentId90, - kCommentId91, - kCommentId92, - kCommentId93, - kCommentId94, - kCommentId95, - kCommentId96, - kCommentId97, - kCommentId98, - kCommentId99, - kCommentId100, - ]; - final commentContents = [ - 'This is a really insightful article. It completely changed my perspective.', - "I'm not sure I agree with the author's conclusion, but it's a well-argued piece.", - 'Finally, someone is talking about this! More people need to read this.', - 'A bit simplistic, but a good introduction to the topic for beginners.', - 'The data presented here is fascinating. I wonder what the long-term implications are.', - 'This is exactly what I was looking for. Thank you for sharing!', - 'I have a few questions about the methodology used in this study.', - 'This made me laugh out loud. Great writing!', - "A powerful and moving story. It's important to hear these voices.", - 'I think there are some key facts missing from this analysis.', - ]; - - for (var i = 0; i < users.length; i++) { - for (var j = 0; j < 10; j++) { - final user = users[i]; - final headlineIndex = i * 10 + j; - final headline = headlines[headlineIndex]; - final commentIndex = i * 10 + j; - - // Vary the status for realism - var status = CommentStatus.approved; - if (commentIndex % 15 == 0) { - status = CommentStatus.pendingReview; - } else if (commentIndex % 25 == 0) { - status = CommentStatus.rejected; - } - - comments.add( - Comment( - id: commentIds[commentIndex], - headlineId: headline.id, - userId: user.id, - content: commentContents[j], - status: status, - createdAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), - updatedAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), - ), - ); - } - } - - return comments; -}(); diff --git a/report_test.dart b/report_test.dart deleted file mode 100644 index c0e0051..0000000 --- a/report_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('Report', () { - final now = DateTime.now(); - final reportFixture = Report( - id: 'report_1', - reporterUserId: 'user_1', - entityType: ReportableEntity.headline, - entityId: 'headline_1', - reason: HeadlineReportReason.clickbaitTitle.name, - status: ReportStatus.submitted, - createdAt: now, - additionalComments: 'The title is misleading.', - ); - - test('can be instantiated', () { - expect(reportFixture, isA()); - }); - - test('supports value equality', () { - final anotherReport = Report( - id: 'report_1', - reporterUserId: 'user_1', - entityType: ReportableEntity.headline, - entityId: 'headline_1', - reason: HeadlineReportReason.clickbaitTitle.name, - status: ReportStatus.submitted, - createdAt: now, - additionalComments: 'The title is misleading.', - ); - expect(reportFixture, equals(anotherReport)); - }); - - test('can be created from JSON', () { - final json = reportFixture.toJson(); - final fromJson = Report.fromJson(json); - expect(fromJson, equals(reportFixture)); - }); - - test('can be converted to JSON', () { - final json = reportFixture.toJson(); - final expectedJson = { - 'id': 'report_1', - 'reporterUserId': 'user_1', - 'entityType': 'headline', - 'entityId': 'headline_1', - 'reason': 'clickbaitTitle', - 'status': 'submitted', - 'additionalComments': 'The title is misleading.', - 'createdAt': now.toIso8601String(), - }; - expect(json, equals(expectedJson)); - }); - - test('copyWith creates a copy with updated values', () { - final updatedReport = reportFixture.copyWith( - status: ReportStatus.resolved, - additionalComments: null, - ); - expect(updatedReport.status, ReportStatus.resolved); - expect(updatedReport.additionalComments, isNull); - expect(updatedReport, isNot(equals(reportFixture))); - }); - }); -} From 97cbdab946aad33867df214496178a38dd607201 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:17:51 +0100 Subject: [PATCH 60/93] refactor(fixtures): update reaction fixtures for generic use - Rename `headlineReactionsFixturesData` to `reactionsFixturesData` - Change `HeadlineReaction` to `Reaction` for broader applicability - Add `entityId` and `entityType` fields to `Reaction` constructor - Update fixture documentation to reflect changes --- lib/src/fixtures/headline_reactions.dart | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/src/fixtures/headline_reactions.dart b/lib/src/fixtures/headline_reactions.dart index 6024ac4..f0c9b3b 100644 --- a/lib/src/fixtures/headline_reactions.dart +++ b/lib/src/fixtures/headline_reactions.dart @@ -1,10 +1,10 @@ import 'package:core/core.dart'; -/// A list of predefined headline reactions for fixture data. +/// A list of predefined reactions for fixture data. /// This creates 10 reactions for each of the first 10 users, with each /// reaction targeting a unique headline. -final List headlineReactionsFixturesData = () { - final reactions = []; +final List reactionsFixturesData = () { + final reactions = []; final users = usersFixturesData.take(10).toList(); final headlines = headlinesFixturesData.take(100).toList(); final reactionIds = [ @@ -119,10 +119,11 @@ final List headlineReactionsFixturesData = () { final reactionIndex = i * 10 + j; reactions.add( - HeadlineReaction( + Reaction( id: reactionIds[reactionIndex], - headlineId: headline.id, userId: user.id, + entityId: headline.id, + entityType: EngageableType.headline, reactionType: reactionTypes[reactionIndex % reactionTypes.length], createdAt: DateTime.now().subtract(Duration(days: i, hours: j)), ), From 245e9829ac4f7dafd1e80fdb632d799392783968 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:19:49 +0100 Subject: [PATCH 61/93] test: update headline reactions fixture to use getHeadlinesFixturesData - Replace direct access to headlinesFixturesData with a call to getHeadlinesFixturesData --- lib/src/fixtures/headline_reactions.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/fixtures/headline_reactions.dart b/lib/src/fixtures/headline_reactions.dart index f0c9b3b..fad3219 100644 --- a/lib/src/fixtures/headline_reactions.dart +++ b/lib/src/fixtures/headline_reactions.dart @@ -6,7 +6,9 @@ import 'package:core/core.dart'; final List reactionsFixturesData = () { final reactions = []; final users = usersFixturesData.take(10).toList(); - final headlines = headlinesFixturesData.take(100).toList(); + final headlines = getHeadlinesFixturesData( + languageCode: 'en', + ).take(100).toList(); final reactionIds = [ kReactionId1, kReactionId2, From a64abf80462cd1e04f2bcb1414732da969ca88d2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:20:40 +0100 Subject: [PATCH 62/93] test: update headline reactions fixture to use getHeadlinesFixturesData - Replace direct access to headlinesFixturesData with a call to getHeadlinesFixturesData - Add languageCode parameter set to 'en' (English) for consistency --- lib/src/fixtures/headline_comments.dart | 187 ++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 lib/src/fixtures/headline_comments.dart diff --git a/lib/src/fixtures/headline_comments.dart b/lib/src/fixtures/headline_comments.dart new file mode 100644 index 0000000..52c979c --- /dev/null +++ b/lib/src/fixtures/headline_comments.dart @@ -0,0 +1,187 @@ +import 'package:core/core.dart'; + +/// A list of predefined comments for fixture data. +/// +/// This function can be configured to generate comments in either English or +/// Arabic. It creates 10 comments for each of the first 10 users, with each +/// comment targeting a unique headline. +List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { + final comments = []; + final users = usersFixturesData.take(10).toList(); + final headlines = headlinesFixturesData.take(100).toList(); + + // Ensure only approved languages are used, default to 'en'. + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + final language = languagesFixturesData.firstWhere( + (lang) => lang.code == resolvedLanguageCode, + orElse: () => + languagesFixturesData.firstWhere((lang) => lang.code == 'en'), + ); + final commentIds = [ + kCommentId1, + kCommentId2, + kCommentId3, + kCommentId4, + kCommentId5, + kCommentId6, + kCommentId7, + kCommentId8, + kCommentId9, + kCommentId10, + kCommentId11, + kCommentId12, + kCommentId13, + kCommentId14, + kCommentId15, + kCommentId16, + kCommentId17, + kCommentId18, + kCommentId19, + kCommentId20, + kCommentId21, + kCommentId22, + kCommentId23, + kCommentId24, + kCommentId25, + kCommentId26, + kCommentId27, + kCommentId28, + kCommentId29, + kCommentId30, + kCommentId31, + kCommentId32, + kCommentId33, + kCommentId34, + kCommentId35, + kCommentId36, + kCommentId37, + kCommentId38, + kCommentId39, + kCommentId40, + kCommentId41, + kCommentId42, + kCommentId43, + kCommentId44, + kCommentId45, + kCommentId46, + kCommentId47, + kCommentId48, + kCommentId49, + kCommentId50, + kCommentId51, + kCommentId52, + kCommentId53, + kCommentId54, + kCommentId55, + kCommentId56, + kCommentId57, + kCommentId58, + kCommentId59, + kCommentId60, + kCommentId61, + kCommentId62, + kCommentId63, + kCommentId64, + kCommentId65, + kCommentId66, + kCommentId67, + kCommentId68, + kCommentId69, + kCommentId70, + kCommentId71, + kCommentId72, + kCommentId73, + kCommentId74, + kCommentId75, + kCommentId76, + kCommentId77, + kCommentId78, + kCommentId79, + kCommentId80, + kCommentId81, + kCommentId82, + kCommentId83, + kCommentId84, + kCommentId85, + kCommentId86, + kCommentId87, + kCommentId88, + kCommentId89, + kCommentId90, + kCommentId91, + kCommentId92, + kCommentId93, + kCommentId94, + kCommentId95, + kCommentId96, + kCommentId97, + kCommentId98, + kCommentId99, + kCommentId100, + ]; + + final Map> commentContentsByLang = { + 'en': [ + 'This is a really insightful article. It completely changed my perspective.', + "I'm not sure I agree with the author's conclusion, but it's a well-argued piece.", + 'Finally, someone is talking about this! More people need to read this.', + 'A bit simplistic, but a good introduction to the topic for beginners.', + 'The data presented here is fascinating. I wonder what the long-term implications are.', + 'This is exactly what I was looking for. Thank you for sharing!', + 'I have a few questions about the methodology used in this study.', + 'This made me laugh out loud. Great writing!', + "A powerful and moving story. It's important to hear these voices.", + 'I think there are some key facts missing from this analysis.', + ], + 'ar': [ + 'هذا مقال ثاقب حقًا. لقد غير وجهة نظري تمامًا.', + 'لست متأكدًا من أنني أتفق مع استنتاج المؤلف، لكنها قطعة جيدة الحجة.', + 'أخيرًا، هناك من يتحدث عن هذا! المزيد من الناس بحاجة إلى قراءة هذا.', + 'مبسط بعض الشيء، لكنه مقدمة جيدة للمبتدئين.', + 'البيانات المقدمة هنا رائعة. أتساءل ما هي الآثار طويلة المدى.', + 'هذا هو بالضبط ما كنت أبحث عنه. شكرًا لك على المشاركة!', + 'لدي بعض الأسئلة حول المنهجية المستخدمة في هذه الدراسة.', + 'هذا جعلني أضحك بصوت عال. كتابة رائعة!', + 'قصة قوية ومؤثرة. من المهم سماع هذه الأصوات.', + 'أعتقد أن هناك بعض الحقائق الأساسية المفقودة من هذا التحليل.', + ], + }; + + final commentContents = commentContentsByLang[resolvedLanguageCode]!; + + for (var i = 0; i < users.length; i++) { + for (var j = 0; j < 10; j++) { + final user = users[i]; + final headlineIndex = i * 10 + j; + final headline = headlines[headlineIndex]; + final commentIndex = i * 10 + j; + + // Vary the status for realism + var status = CommentStatus.approved; + if (commentIndex % 15 == 0) { + status = CommentStatus.pendingReview; + } else if (commentIndex % 25 == 0) { + status = CommentStatus.rejected; + } + + comments.add( + Comment( + id: commentIds[commentIndex], + userId: user.id, + entityId: headline.id, + entityType: EngageableType.headline, + language: language, + content: commentContents[j], + status: status, + createdAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), + updatedAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), + ), + ); + } + } + + return comments; +} + +/// A list of predefined comments for fixture data, defaulting to English. +final List headlineCommentsFixturesData = getHeadlineCommentsFixturesData(); From eb87e086e6bc3cd50170142fd55aa354b0fd5717 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:21:38 +0100 Subject: [PATCH 63/93] fix(core): add engagement fixtures for testing and demo purposes - Create new file with engagement fixtures - Add function to generate predefined engagements - Include reactions and optional comments in engagements - Define a list of engagement IDs for consistent fixture data --- lib/src/fixtures/engagements.dart | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/src/fixtures/engagements.dart diff --git a/lib/src/fixtures/engagements.dart b/lib/src/fixtures/engagements.dart new file mode 100644 index 0000000..589a68c --- /dev/null +++ b/lib/src/fixtures/engagements.dart @@ -0,0 +1,136 @@ +import 'package:core/core.dart'; +import 'package:core/src/fixtures/headline_reactions.dart'; + +/// Generates a list of predefined engagements for fixture data. +/// +/// This function can be configured to generate data in either English or +/// Arabic. It pairs reactions with comments to create realistic engagement +/// scenarios. +List getEngagementsFixturesData({String languageCode = 'en'}) { + final engagements = []; + final reactions = reactionsFixturesData; + final comments = getHeadlineCommentsFixturesData(languageCode: languageCode); + + for (var i = 0; i < reactions.length; i++) { + final reaction = reactions[i]; + // Pair every other reaction with a comment for variety + final comment = i.isEven ? comments[i] : null; + + engagements.add( + Engagement( + entityId: reaction.entityId, + entityType: reaction.entityType, + reaction: reaction, + comment: comment, + ), + ); + } + + return engagements; +} + +/// A list of predefined engagements for fixture data, defaulting to English. +final List engagementsFixturesData = getEngagementsFixturesData(); + +const _engagementIds = [ + kEngagementId1, + kEngagementId2, + kEngagementId3, + kEngagementId4, + kEngagementId5, + kEngagementId6, + kEngagementId7, + kEngagementId8, + kEngagementId9, + kEngagementId10, + kEngagementId11, + kEngagementId12, + kEngagementId13, + kEngagementId14, + kEngagementId15, + kEngagementId16, + kEngagementId17, + kEngagementId18, + kEngagementId19, + kEngagementId20, + kEngagementId21, + kEngagementId22, + kEngagementId23, + kEngagementId24, + kEngagementId25, + kEngagementId26, + kEngagementId27, + kEngagementId28, + kEngagementId29, + kEngagementId30, + kEngagementId31, + kEngagementId32, + kEngagementId33, + kEngagementId34, + kEngagementId35, + kEngagementId36, + kEngagementId37, + kEngagementId38, + kEngagementId39, + kEngagementId40, + kEngagementId41, + kEngagementId42, + kEngagementId43, + kEngagementId44, + kEngagementId45, + kEngagementId46, + kEngagementId47, + kEngagementId48, + kEngagementId49, + kEngagementId50, + kEngagementId51, + kEngagementId52, + kEngagementId53, + kEngagementId54, + kEngagementId55, + kEngagementId56, + kEngagementId57, + kEngagementId58, + kEngagementId59, + kEngagementId60, + kEngagementId61, + kEngagementId62, + kEngagementId63, + kEngagementId64, + kEngagementId65, + kEngagementId66, + kEngagementId67, + kEngagementId68, + kEngagementId69, + kEngagementId70, + kEngagementId71, + kEngagementId72, + kEngagementId73, + kEngagementId74, + kEngagementId75, + kEngagementId76, + kEngagementId77, + kEngagementId78, + kEngagementId79, + kEngagementId80, + kEngagementId81, + kEngagementId82, + kEngagementId83, + kEngagementId84, + kEngagementId85, + kEngagementId86, + kEngagementId87, + kEngagementId88, + kEngagementId89, + kEngagementId90, + kEngagementId91, + kEngagementId92, + kEngagementId93, + kEngagementId94, + kEngagementId95, + kEngagementId96, + kEngagementId97, + kEngagementId98, + kEngagementId99, + kEngagementId100, +]; From be00b389a88282c22753cb30cfd4c82f99a8425c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:23:18 +0100 Subject: [PATCH 64/93] refactor: enance headlines fixtures generation --- lib/src/fixtures/headlines.dart | 1670 ++++--------------------------- 1 file changed, 169 insertions(+), 1501 deletions(-) diff --git a/lib/src/fixtures/headlines.dart b/lib/src/fixtures/headlines.dart index 721179e..d8b0c6b 100644 --- a/lib/src/fixtures/headlines.dart +++ b/lib/src/fixtures/headlines.dart @@ -1,1511 +1,179 @@ import 'package:core/src/enums/enums.dart'; import 'package:core/src/fixtures/countries.dart'; import 'package:core/src/fixtures/fixture_ids.dart'; -import 'package:core/src/fixtures/sources.dart'; -import 'package:core/src/fixtures/topics.dart'; +import 'package:core/src/fixtures/sources.dart' as source_fixtures; +import 'package:core/src/fixtures/topics.dart' as topic_fixtures; import 'package:core/src/models/entities/headline.dart'; -/// A list of predefined headlines for fixture data. -final headlinesFixturesData = [ - Headline( - id: kHeadlineId1, - isBreaking: false, - title: 'AI Breakthrough: New Model Achieves Human-Level Performance', - url: 'https://example.com/news/ai-breakthrough-1', - imageUrl: 'https://picsum.photos/seed/kHeadlineId1/800/600', - source: sourcesFixturesData[0], // TechCrunch - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(minutes: 15)), - updatedAt: DateTime.now().subtract(const Duration(minutes: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId2, - isBreaking: false, - title: 'Local Team Wins Championship in Thrilling Final', - url: 'https://example.com/news/sports-championship-2', - imageUrl: 'https://picsum.photos/seed/kHeadlineId2/800/600', - source: sourcesFixturesData[1], // BBC News - eventCountry: countriesFixturesData[1], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(minutes: 15)), - updatedAt: DateTime.now().subtract(const Duration(minutes: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId3, - isBreaking: false, - title: 'Global Leaders Meet to Discuss Climate Change Policies', - url: 'https://example.com/news/politics-climate-3', - imageUrl: 'https://picsum.photos/seed/kHeadlineId3/800/600', - source: sourcesFixturesData[2], // The New York Times - eventCountry: countriesFixturesData[2], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(minutes: 15)), - updatedAt: DateTime.now().subtract(const Duration(minutes: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId4, - isBreaking: true, - title: 'New Planet Discovered in Distant Galaxy', - url: 'https://example.com/news/science-planet-4', - imageUrl: 'https://picsum.photos/seed/kHeadlineId4/800/600', - source: sourcesFixturesData[3], // The Guardian - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(minutes: 15)), - updatedAt: DateTime.now().subtract(const Duration(minutes: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId5, - isBreaking: false, - title: 'Breakthrough in Cancer Research Offers New Hope', - url: 'https://example.com/news/health-cancer-5', - imageUrl: 'https://picsum.photos/seed/kHeadlineId5/800/600', - source: sourcesFixturesData[4], // CNN - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(minutes: 15)), - updatedAt: DateTime.now().subtract(const Duration(minutes: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId6, - isBreaking: false, - title: 'Blockbuster Movie Breaks Box Office Records', - url: 'https://example.com/news/entertainment-movie-6', - imageUrl: 'https://picsum.photos/seed/kHeadlineId6/800/600', - source: sourcesFixturesData[5], // Reuters - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 1)), - updatedAt: DateTime.now().subtract(const Duration(days: 1)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId7, - isBreaking: false, - title: 'Stock Market Reaches All-Time High Amid Economic Boom', - url: 'https://example.com/news/business-market-7', - imageUrl: 'https://picsum.photos/seed/kHeadlineId7/800/600', - source: sourcesFixturesData[6], // Al Jazeera English - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 1)), - updatedAt: DateTime.now().subtract(const Duration(days: 1)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId8, - isBreaking: false, - title: 'New Travel Restrictions Lifted for Popular Destinations', - url: 'https://example.com/news/travel-restrictions-8', - imageUrl: 'https://picsum.photos/seed/kHeadlineId8/800/600', - source: sourcesFixturesData[7], // Xinhua News Agency - eventCountry: countriesFixturesData[7], // China - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 1)), - updatedAt: DateTime.now().subtract(const Duration(days: 1)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId9, - isBreaking: false, - title: 'Michelin Star Chef Opens New Restaurant in City Center', - url: 'https://example.com/news/food-restaurant-9', - imageUrl: 'https://picsum.photos/seed/kHeadlineId9/800/600', - source: sourcesFixturesData[8], // The Times of India - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 1)), - updatedAt: DateTime.now().subtract(const Duration(days: 1)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId10, - isBreaking: false, - title: 'Innovative Teaching Methods Boost Student Engagement', - url: 'https://example.com/news/education-methods-10', - imageUrl: 'https://picsum.photos/seed/kHeadlineId10/800/600', - source: sourcesFixturesData[9], // Folha de S.Paulo - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[9], // Education - createdAt: DateTime.now().subtract(const Duration(days: 1)), - updatedAt: DateTime.now().subtract(const Duration(days: 1)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId11, - isBreaking: false, - title: 'Cybersecurity Firms Warn of New Global Threat', - url: 'https://example.com/news/cybersecurity-threat-11', - imageUrl: 'https://picsum.photos/seed/kHeadlineId11/800/600', - source: sourcesFixturesData[0], // TechCrunch - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 2)), - updatedAt: DateTime.now().subtract(const Duration(days: 2)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId12, - isBreaking: false, - title: 'Olympics Committee Announces Host City for 2032 Games', - url: 'https://example.com/news/sports-olympics-12', - imageUrl: 'https://picsum.photos/seed/kHeadlineId12/800/600', - source: sourcesFixturesData[1], // BBC News - eventCountry: countriesFixturesData[1], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 2)), - updatedAt: DateTime.now().subtract(const Duration(days: 2)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId13, - isBreaking: false, - title: 'New Bill Aims to Reform Healthcare System', - url: 'https://example.com/news/politics-healthcare-13', - imageUrl: 'https://picsum.photos/seed/kHeadlineId13/800/600', - source: sourcesFixturesData[2], // The New York Times - eventCountry: countriesFixturesData[2], // Canada - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 2)), - updatedAt: DateTime.now().subtract(const Duration(days: 2)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId14, - isBreaking: false, - title: 'Archaeologists Uncover Ancient City Ruins', - url: 'https://example.com/news/science-archaeology-14', - imageUrl: 'https://picsum.photos/seed/kHeadlineId14/800/600', - source: sourcesFixturesData[3], // The Guardian - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(days: 2)), - updatedAt: DateTime.now().subtract(const Duration(days: 2)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId15, - isBreaking: false, - title: 'Dietary Guidelines Updated for Public Health', - url: 'https://example.com/news/health-diet-15', - imageUrl: 'https://picsum.photos/seed/kHeadlineId15/800/600', - source: sourcesFixturesData[4], // CNN - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 2)), - updatedAt: DateTime.now().subtract(const Duration(days: 2)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId16, - isBreaking: false, - title: 'Music Festival Announces Star-Studded Lineup', - url: 'https://example.com/news/entertainment-music-16', - imageUrl: 'https://picsum.photos/seed/kHeadlineId16/800/600', - source: sourcesFixturesData[5], // Reuters - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 3)), - updatedAt: DateTime.now().subtract(const Duration(days: 3)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId17, - isBreaking: false, - title: 'Tech Giant Acquires Startup in Multi-Billion Dollar Deal', - url: 'https://example.com/news/business-acquisition-17', - imageUrl: 'https://picsum.photos/seed/kHeadlineId17/800/600', - source: sourcesFixturesData[6], // Al Jazeera English - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 3)), - updatedAt: DateTime.now().subtract(const Duration(days: 3)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId18, - isBreaking: false, - title: 'Space Tourism Takes Off: First Commercial Flights Announced', - url: 'https://example.com/news/travel-space-18', - imageUrl: 'https://picsum.photos/seed/kHeadlineId18/800/600', - source: sourcesFixturesData[7], // Xinhua News Agency - eventCountry: countriesFixturesData[7], // China - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 3)), - updatedAt: DateTime.now().subtract(const Duration(days: 3)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId19, - isBreaking: false, - title: 'Future of Food: Lab-Grown Meat Gains Popularity', - url: 'https://example.com/news/food-lab-meat-19', - imageUrl: 'https://picsum.photos/seed/kHeadlineId19/800/600', - source: sourcesFixturesData[8], // The Times of India - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 3)), - updatedAt: DateTime.now().subtract(const Duration(days: 3)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId20, - isBreaking: false, - title: 'Online Learning Platforms See Surge in Enrollment', - url: 'https://example.com/news/education-online-20', - imageUrl: 'https://picsum.photos/seed/kHeadlineId20/800/600', - source: sourcesFixturesData[9], // Folha de S.Paulo - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[9], // Education - createdAt: DateTime.now().subtract(const Duration(days: 3)), - updatedAt: DateTime.now().subtract(const Duration(days: 3)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId21, - isBreaking: false, - title: 'Quantum Computing Achieves New Milestone', - url: 'https://example.com/news/tech-quantum-21', - imageUrl: 'https://picsum.photos/seed/kHeadlineId21/800/600', - source: sourcesFixturesData[0], // TechCrunch - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 4)), - updatedAt: DateTime.now().subtract(const Duration(days: 4)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId22, - isBreaking: false, - title: 'World Cup Qualifiers: Unexpected Upsets Shake Rankings', - url: 'https://example.com/news/sports-worldcup-22', - imageUrl: 'https://picsum.photos/seed/kHeadlineId22/800/600', - source: sourcesFixturesData[1], // BBC News - eventCountry: countriesFixturesData[1], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 4)), - updatedAt: DateTime.now().subtract(const Duration(days: 4)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId23, - isBreaking: false, - title: 'Election Results: New Government Takes Power', - url: 'https://example.com/news/politics-election-23', - imageUrl: 'https://picsum.photos/seed/kHeadlineId23/800/600', - source: sourcesFixturesData[2], // The New York Times - eventCountry: countriesFixturesData[2], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 4)), - updatedAt: DateTime.now().subtract(const Duration(days: 4)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId24, - isBreaking: false, - title: 'Breakthrough in Fusion Energy Research Announced', - url: 'https://example.com/news/science-fusion-24', - imageUrl: 'https://picsum.photos/seed/kHeadlineId24/800/600', - source: sourcesFixturesData[3], // The Guardian - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(days: 4)), - updatedAt: DateTime.now().subtract(const Duration(days: 4)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId25, - isBreaking: false, - title: 'Mental Health Awareness Campaign Launched Globally', - url: 'https://example.com/news/health-mental-25', - imageUrl: 'https://picsum.photos/seed/kHeadlineId25/800/600', - source: sourcesFixturesData[4], // CNN - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 4)), - updatedAt: DateTime.now().subtract(const Duration(days: 4)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId26, - isBreaking: false, - title: 'Gaming Industry Sees Record Growth in Virtual Reality', - url: 'https://example.com/news/entertainment-vr-26', - imageUrl: 'https://picsum.photos/seed/kHeadlineId26/800/600', - source: sourcesFixturesData[5], // Reuters - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 5)), - updatedAt: DateTime.now().subtract(const Duration(days: 5)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId27, - isBreaking: false, - title: 'Global Supply Chain Disruptions Impacting Consumer Goods', - url: 'https://example.com/news/business-supplychain-27', - imageUrl: 'https://picsum.photos/seed/kHeadlineId27/800/600', - source: sourcesFixturesData[6], // Al Jazeera English - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 5)), - updatedAt: DateTime.now().subtract(const Duration(days: 5)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId28, - isBreaking: false, - title: 'Arctic Expedition Discovers New Marine Species', - url: 'https://example.com/news/travel-arctic-28', - imageUrl: 'https://picsum.photos/seed/kHeadlineId28/800/600', - source: sourcesFixturesData[7], // Xinhua News Agency - eventCountry: countriesFixturesData[7], // China - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 5)), - updatedAt: DateTime.now().subtract(const Duration(days: 5)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId29, - isBreaking: false, - title: 'Rise of Plant-Based Cuisine: New Restaurants Open', - url: 'https://example.com/news/food-plantbased-29', - imageUrl: 'https://picsum.photos/seed/kHeadlineId29/800/600', - source: sourcesFixturesData[8], // The Times of India - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 5)), - updatedAt: DateTime.now().subtract(const Duration(days: 5)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId30, - isBreaking: false, - title: 'Education Technology Transforms Classrooms', - url: 'https://example.com/news/education-edtech-30', - imageUrl: 'https://picsum.photos/seed/kHeadlineId30/800/600', - source: sourcesFixturesData[9], // Folha de S.Paulo - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[9], // Education - createdAt: DateTime.now().subtract(const Duration(days: 5)), - updatedAt: DateTime.now().subtract(const Duration(days: 5)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId31, - isBreaking: false, - title: 'SpaceX Launches New Satellite Constellation', - url: 'https://example.com/news/tech-spacex-31', - imageUrl: 'https://picsum.photos/seed/kHeadlineId31/800/600', - source: sourcesFixturesData[0], // TechCrunch - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 6)), - updatedAt: DateTime.now().subtract(const Duration(days: 6)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId32, - isBreaking: false, - title: 'Football Legend Announces Retirement', - url: 'https://example.com/news/sports-retirement-32', - imageUrl: 'https://picsum.photos/seed/kHeadlineId32/800/600', - source: sourcesFixturesData[1], // BBC News - eventCountry: countriesFixturesData[1], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 6)), - updatedAt: DateTime.now().subtract(const Duration(days: 6)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId33, - isBreaking: false, - title: 'G7 Summit Concludes with Joint Statement on Global Economy', - url: 'https://example.com/news/politics-g7-33', - imageUrl: 'https://picsum.photos/seed/kHeadlineId33/800/600', - source: sourcesFixturesData[2], // The New York Times - eventCountry: countriesFixturesData[2], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 6)), - updatedAt: DateTime.now().subtract(const Duration(days: 6)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId34, - isBreaking: false, - title: "Breakthrough in Alzheimer's Research Offers New Treatment Path", - url: 'https://example.com/news/science-alzheimers-34', - imageUrl: 'https://picsum.photos/seed/kHeadlineId34/800/600', - source: sourcesFixturesData[3], // The Guardian - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 6)), - updatedAt: DateTime.now().subtract(const Duration(days: 6)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId35, - isBreaking: false, - title: 'Global Vaccination Campaign Reaches Billions', - url: 'https://example.com/news/health-vaccine-35', - imageUrl: 'https://picsum.photos/seed/kHeadlineId35/800/600', - source: sourcesFixturesData[4], // CNN - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 6)), - updatedAt: DateTime.now().subtract(const Duration(days: 6)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId36, - isBreaking: false, - title: 'Streaming Wars Intensify with New Platform Launches', - url: 'https://example.com/news/entertainment-streaming-36', - imageUrl: 'https://picsum.photos/seed/kHeadlineId36/800/600', - source: sourcesFixturesData[5], // Reuters - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 7)), - updatedAt: DateTime.now().subtract(const Duration(days: 7)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId37, - isBreaking: false, - title: 'Cryptocurrency Market Experiences Major Volatility', - url: 'https://example.com/news/business-crypto-37', - imageUrl: 'https://picsum.photos/seed/kHeadlineId37/800/600', - source: sourcesFixturesData[6], // Al Jazeera English - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 7)), - updatedAt: DateTime.now().subtract(const Duration(days: 7)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId38, - isBreaking: false, - title: 'Sustainable Tourism Initiatives Gain Momentum', - url: 'https://example.com/news/travel-sustainable-38', - imageUrl: 'https://picsum.photos/seed/kHeadlineId38/800/600', - source: sourcesFixturesData[7], // Xinhua News Agency - eventCountry: countriesFixturesData[7], // China - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 7)), - updatedAt: DateTime.now().subtract(const Duration(days: 7)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId39, - isBreaking: false, - title: 'Food Security Summit Addresses Global Hunger', - url: 'https://example.com/news/food-security-39', - imageUrl: 'https://picsum.photos/seed/kHeadlineId39/800/600', - source: sourcesFixturesData[8], // The Times of India - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 7)), - updatedAt: DateTime.now().subtract(const Duration(days: 7)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId40, - isBreaking: false, - title: 'Robotics in Education: New Tools for Learning', - url: 'https://example.com/news/education-robotics-40', - imageUrl: 'https://picsum.photos/seed/kHeadlineId40/800/600', - source: sourcesFixturesData[9], // Folha de S.Paulo - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[9], // Education - createdAt: DateTime.now().subtract(const Duration(days: 7)), - updatedAt: DateTime.now().subtract(const Duration(days: 7)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId41, - isBreaking: false, - title: 'AI Ethics Debate Intensifies Among Tech Leaders', - url: 'https://example.com/news/tech-ethics-41', - imageUrl: 'https://picsum.photos/seed/kHeadlineId41/800/600', - source: sourcesFixturesData[0], // TechCrunch - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 8)), - updatedAt: DateTime.now().subtract(const Duration(days: 8)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId42, - isBreaking: false, - title: 'Esports Industry Sees Massive Investment Boom', - url: 'https://example.com/news/sports-esports-42', - imageUrl: 'https://picsum.photos/seed/kHeadlineId42/800/600', - source: sourcesFixturesData[1], // BBC News - eventCountry: countriesFixturesData[1], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 8)), - updatedAt: DateTime.now().subtract(const Duration(days: 8)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId43, - isBreaking: false, - title: 'International Sanctions Imposed on Rogue State', - url: 'https://example.com/news/politics-sanctions-43', - imageUrl: 'https://picsum.photos/seed/kHeadlineId43/800/600', - source: sourcesFixturesData[2], // The New York Times - eventCountry: countriesFixturesData[2], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 8)), - updatedAt: DateTime.now().subtract(const Duration(days: 8)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId44, - isBreaking: false, - title: 'New Species of Deep-Sea Creature Discovered', - url: 'https://example.com/news/science-deepsea-44', - imageUrl: 'https://picsum.photos/seed/kHeadlineId44/800/600', - source: sourcesFixturesData[3], // The Guardian - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(days: 8)), - updatedAt: DateTime.now().subtract(const Duration(days: 8)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId45, - isBreaking: false, - title: 'Global Health Crisis: New Pandemic Preparedness Plan', - url: 'https://example.com/news/health-pandemic-45', - imageUrl: 'https://picsum.photos/seed/kHeadlineId45/800/600', - source: sourcesFixturesData[4], // CNN - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 8)), - updatedAt: DateTime.now().subtract(const Duration(days: 8)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId46, - isBreaking: false, - title: 'Hollywood Strikes Continue: Impact on Film Production', - url: 'https://example.com/news/entertainment-strikes-46', - imageUrl: 'https://picsum.photos/seed/kHeadlineId46/800/600', - source: sourcesFixturesData[5], // Reuters - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 9)), - updatedAt: DateTime.now().subtract(const Duration(days: 9)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId47, - isBreaking: false, - title: 'Emerging Markets Show Strong Economic Resilience', - url: 'https://example.com/news/business-emerging-47', - imageUrl: 'https://picsum.photos/seed/kHeadlineId47/800/600', - source: sourcesFixturesData[6], // Al Jazeera English - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 9)), - updatedAt: DateTime.now().subtract(const Duration(days: 9)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId48, - isBreaking: false, - title: 'Adventure Tourism Booms in Remote Regions', - url: 'https://example.com/news/travel-adventure-48', - imageUrl: 'https://picsum.photos/seed/kHeadlineId48/800/600', - source: sourcesFixturesData[7], // Xinhua News Agency - eventCountry: countriesFixturesData[7], // China - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 9)), - updatedAt: DateTime.now().subtract(const Duration(days: 9)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId49, - isBreaking: false, - title: 'The Rise of Sustainable Food Packaging', - url: 'https://example.com/news/food-packaging-49', - imageUrl: 'https://picsum.photos/seed/kHeadlineId49/800/600', - source: sourcesFixturesData[8], // The Times of India - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 9)), - updatedAt: DateTime.now().subtract(const Duration(days: 9)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId50, - isBreaking: false, - title: 'Personalized Learning: Tailoring Education to Individual Needs', - url: 'https://example.com/news/education-personalized-50', - imageUrl: 'https://picsum.photos/seed/kHeadlineId50/800/600', - source: sourcesFixturesData[9], // Folha de S.Paulo - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[9], // Education - createdAt: DateTime.now().subtract(const Duration(days: 9)), - updatedAt: DateTime.now().subtract(const Duration(days: 9)), - status: ContentStatus.active, - ), +/// Generates a list of predefined headlines for fixture data. +/// +/// This function can be configured to generate headlines in either English or +/// Arabic. +List getHeadlinesFixturesData({String languageCode = 'en'}) { + // Ensure only approved languages are used, default to 'en'. + final resolvedLanguageCode = + ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; - // --- Headlines for New Sources (5 per source) --- + final sources = + source_fixtures.getSourcesFixturesData(languageCode: resolvedLanguageCode); + final topics = + topic_fixtures.getTopicsFixturesData(languageCode: resolvedLanguageCode); - // --- Local News Outlets (kSourceId11 - kSourceId20) --- - Headline( - id: kHeadlineId51, - isBreaking: false, - title: 'City Council Approves New Downtown Development Plan', - url: 'https://example.com/news/sf-downtown-plan', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId51/800/600', // San Francisco Chronicle - source: sourcesFixturesData[10], // San Francisco Chronicle - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 10)), - updatedAt: DateTime.now().subtract(const Duration(days: 10)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId52, - isBreaking: false, - title: 'Tech Startups Flourish in the Bay Area', - url: 'https://example.com/news/sf-tech-boom', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId52/800/600', // San Francisco Chronicle - source: sourcesFixturesData[10], // San Francisco Chronicle - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 10)), - updatedAt: DateTime.now().subtract(const Duration(days: 10)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId53, - isBreaking: false, - title: 'Golden Gate Bridge Retrofit Project Begins', - url: 'https://example.com/news/ggb-retrofit', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId53/800/600', // San Francisco Chronicle - source: sourcesFixturesData[10], // San Francisco Chronicle - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 11)), - updatedAt: DateTime.now().subtract(const Duration(days: 11)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId54, - isBreaking: false, - title: 'Local Chef Wins Prestigious Culinary Award', - url: 'https://example.com/news/sf-chef-award', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId54/800/600', // San Francisco Chronicle - source: sourcesFixturesData[10], // San Francisco Chronicle - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 11)), - updatedAt: DateTime.now().subtract(const Duration(days: 11)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId55, - isBreaking: false, - title: 'Warriors Secure Victory in Season Opener', - url: 'https://example.com/news/warriors-win', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId55/800/600', // San Francisco Chronicle - source: sourcesFixturesData[10], // San Francisco Chronicle - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 12)), - updatedAt: DateTime.now().subtract(const Duration(days: 12)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId56, - isBreaking: false, - title: 'Manchester United Announces New Stadium Expansion Plans', - url: 'https://example.com/news/mu-stadium-expansion', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId56/800/600', // Manchester Evening News - source: sourcesFixturesData[11], // Manchester Evening News - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 12)), - updatedAt: DateTime.now().subtract(const Duration(days: 12)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId57, - isBreaking: false, - title: 'New Tram Line Opens in Greater Manchester', - url: 'https://example.com/news/manchester-tram-line', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId57/800/600', // Manchester Evening News - source: sourcesFixturesData[11], // Manchester Evening News - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 13)), - updatedAt: DateTime.now().subtract(const Duration(days: 13)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId58, - isBreaking: false, - title: 'Manchester Tech Hub Attracts Global Talent', - url: 'https://example.com/news/manchester-tech-hub', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId58/800/600', // Manchester Evening News - source: sourcesFixturesData[11], // Manchester Evening News - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 13)), - updatedAt: DateTime.now().subtract(const Duration(days: 13)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId59, - isBreaking: false, - title: 'Coronation Street Filming Causes Local Buzz', - url: 'https://example.com/news/corrie-filming', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId59/800/600', // Manchester Evening News - source: sourcesFixturesData[11], // Manchester Evening News - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 14)), - updatedAt: DateTime.now().subtract(const Duration(days: 14)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId60, - isBreaking: false, - title: 'Council Debates Clean Air Zone Implementation', - url: 'https://example.com/news/manc-caz-debate', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId60/800/600', // Manchester Evening News - source: sourcesFixturesData[11], // Manchester Evening News - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 14)), - updatedAt: DateTime.now().subtract(const Duration(days: 14)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId61, - isBreaking: false, - title: 'Sydney Opera House Announces New Season Lineup', - url: 'https://example.com/news/sydney-opera-season', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId61/800/600', // The Sydney Morning Herald - source: sourcesFixturesData[12], // The Sydney Morning Herald - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 15)), - updatedAt: DateTime.now().subtract(const Duration(days: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId62, - isBreaking: false, - title: 'Housing Prices in Sydney Continue to Climb', - url: 'https://example.com/news/sydney-housing-prices', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId62/800/600', // The Sydney Morning Herald - source: sourcesFixturesData[12], // The Sydney Morning Herald - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 15)), - updatedAt: DateTime.now().subtract(const Duration(days: 15)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId63, - isBreaking: false, - title: 'NSW Government Unveils New Infrastructure Projects', - url: 'https://example.com/news/nsw-infrastructure', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId63/800/600', // The Sydney Morning Herald - source: sourcesFixturesData[12], // The Sydney Morning Herald - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 16)), - updatedAt: DateTime.now().subtract(const Duration(days: 16)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId64, - isBreaking: false, - title: 'Swans Triumph in AFL Derby Match', - url: 'https://example.com/news/swans-afl-win', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId64/800/600', // The Sydney Morning Herald - source: sourcesFixturesData[12], // The Sydney Morning Herald - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 16)), - updatedAt: DateTime.now().subtract(const Duration(days: 16)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId65, - isBreaking: false, - title: 'Bondi Beach Erosion Concerns Prompt Action', - url: 'https://example.com/news/bondi-erosion', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId65/800/600', // The Sydney Morning Herald - source: sourcesFixturesData[12], // The Sydney Morning Herald - eventCountry: countriesFixturesData[3], // Australia - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(days: 17)), - updatedAt: DateTime.now().subtract(const Duration(days: 17)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId66, - isBreaking: false, - title: 'Paris Metro Expansion: New Stations Opened', - url: 'https://example.com/news/paris-metro-expansion', - imageUrl: 'https://picsum.photos/seed/kHeadlineId66/800/600', // Le Parisien - source: sourcesFixturesData[13], // Le Parisien - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 17)), - updatedAt: DateTime.now().subtract(const Duration(days: 17)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId67, - isBreaking: false, - title: 'Louvre Museum Unveils New Egyptian Antiquities Wing', - url: 'https://example.com/news/louvre-egyptian-wing', - imageUrl: 'https://picsum.photos/seed/kHeadlineId67/800/600', // Le Parisien - source: sourcesFixturesData[13], // Le Parisien - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 18)), - updatedAt: DateTime.now().subtract(const Duration(days: 18)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId68, - isBreaking: false, - title: 'Paris Saint-Germain Secures Ligue 1 Title', - url: 'https://example.com/news/psg-ligue1-title', - imageUrl: 'https://picsum.photos/seed/kHeadlineId68/800/600', // Le Parisien - source: sourcesFixturesData[13], // Le Parisien - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 18)), - updatedAt: DateTime.now().subtract(const Duration(days: 18)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId69, - isBreaking: false, - title: 'Mayor of Paris Announces New Green Initiatives', - url: 'https://example.com/news/paris-green-initiatives', - imageUrl: 'https://picsum.photos/seed/kHeadlineId69/800/600', // Le Parisien - source: sourcesFixturesData[13], // Le Parisien - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 19)), - updatedAt: DateTime.now().subtract(const Duration(days: 19)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId70, - isBreaking: false, - title: 'Paris Fashion Week Highlights New Trends', - url: 'https://example.com/news/paris-fashion-week', - imageUrl: 'https://picsum.photos/seed/kHeadlineId70/800/600', // Le Parisien - source: sourcesFixturesData[13], // Le Parisien - eventCountry: countriesFixturesData[5], // France - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 19)), - updatedAt: DateTime.now().subtract(const Duration(days: 19)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId71, - isBreaking: false, - title: 'Toronto Raptors Make Key Trade Ahead of Deadline', - url: 'https://example.com/news/raptors-trade', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId71/800/600', // The Toronto Star - source: sourcesFixturesData[14], // The Toronto Star - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 20)), - updatedAt: DateTime.now().subtract(const Duration(days: 20)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId72, - isBreaking: false, - title: 'TTC Announces Service Changes for Summer', - url: 'https://example.com/news/ttc-summer-changes', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId72/800/600', // The Toronto Star - source: sourcesFixturesData[14], // The Toronto Star - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 20)), - updatedAt: DateTime.now().subtract(const Duration(days: 20)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId73, - isBreaking: false, - title: 'Toronto International Film Festival (TIFF) Lineup Revealed', - url: 'https://example.com/news/tiff-lineup', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId73/800/600', // The Toronto Star - source: sourcesFixturesData[14], // The Toronto Star - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 21)), - updatedAt: DateTime.now().subtract(const Duration(days: 21)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId74, - isBreaking: false, - title: 'City of Toronto Grapples with Housing Affordability', - url: 'https://example.com/news/toronto-housing-crisis', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId74/800/600', // The Toronto Star - source: sourcesFixturesData[14], // The Toronto Star - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 21)), - updatedAt: DateTime.now().subtract(const Duration(days: 21)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId75, - isBreaking: false, - title: 'New Waterfront Development Project Approved', - url: 'https://example.com/news/toronto-waterfront-project', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId75/800/600', // The Toronto Star - source: sourcesFixturesData[14], // The Toronto Star - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 22)), - updatedAt: DateTime.now().subtract(const Duration(days: 22)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId76, - isBreaking: false, - title: 'Berlin Philharmonic Announces New Conductor', - url: 'https://example.com/news/berlin-phil-conductor', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId76/800/600', // Berliner Morgenpost - source: sourcesFixturesData[15], // Berliner Morgenpost - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 22)), - updatedAt: DateTime.now().subtract(const Duration(days: 22)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId77, - isBreaking: false, - title: 'Remnants of Berlin Wall Unearthed During Construction', - url: 'https://example.com/news/berlin-wall-discovery', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId77/800/600', // Berliner Morgenpost - source: sourcesFixturesData[15], // Berliner Morgenpost - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[3], // Science - createdAt: DateTime.now().subtract(const Duration(days: 23)), - updatedAt: DateTime.now().subtract(const Duration(days: 23)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId78, - isBreaking: false, - title: 'Hertha BSC Faces Relegation Battle', - url: 'https://example.com/news/hertha-bsc-relegation', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId78/800/600', // Berliner Morgenpost - source: sourcesFixturesData[15], // Berliner Morgenpost - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 23)), - updatedAt: DateTime.now().subtract(const Duration(days: 23)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId79, - isBreaking: false, - title: 'Berlin Senate Approves Rent Control Measures', - url: 'https://example.com/news/berlin-rent-control', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId79/800/600', // Berliner Morgenpost - source: sourcesFixturesData[15], // Berliner Morgenpost - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 24)), - updatedAt: DateTime.now().subtract(const Duration(days: 24)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId80, - isBreaking: false, - title: 'Brandenburg Airport Reports Record Passenger Numbers', - url: 'https://example.com/news/ber-airport-record', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId80/800/600', // Berliner Morgenpost - source: sourcesFixturesData[15], // Berliner Morgenpost - eventCountry: countriesFixturesData[4], // Germany - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 24)), - updatedAt: DateTime.now().subtract(const Duration(days: 24)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId81, - isBreaking: false, - title: 'Tokyo Government Tackles Aging Population Issues', - url: 'https://example.com/news/tokyo-aging-population', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId81/800/600', // The Asahi Shimbun (Tokyo) - source: sourcesFixturesData[16], // The Asahi Shimbun (Tokyo) - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 25)), - updatedAt: DateTime.now().subtract(const Duration(days: 25)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId82, - isBreaking: false, - title: 'New Shinkansen Line to Connect Tokyo and Tsuruga', - url: 'https://example.com/news/shinkansen-extension', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId82/800/600', // The Asahi Shimbun (Tokyo) - source: sourcesFixturesData[16], // The Asahi Shimbun (Tokyo) - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 25)), - updatedAt: DateTime.now().subtract(const Duration(days: 25)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId83, - isBreaking: false, - title: 'Yomiuri Giants Clinch Central League Pennant', - url: 'https://example.com/news/giants-win-pennant', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId83/800/600', // The Asahi Shimbun (Tokyo) - source: sourcesFixturesData[16], // The Asahi Shimbun (Tokyo) - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 26)), - updatedAt: DateTime.now().subtract(const Duration(days: 26)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId84, - isBreaking: false, - title: 'Studio Ghibli Announces New Film Project', - url: 'https://example.com/news/ghibli-new-film', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId84/800/600', // The Asahi Shimbun (Tokyo) - source: sourcesFixturesData[16], // The Asahi Shimbun (Tokyo) - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 26)), - updatedAt: DateTime.now().subtract(const Duration(days: 26)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId85, - isBreaking: false, - title: "Tokyo's Tsukiji Outer Market Thrives After Relocation", - url: 'https://example.com/news/tsukiji-market-thrives', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId85/800/600', // The Asahi Shimbun (Tokyo) - source: sourcesFixturesData[16], // The Asahi Shimbun (Tokyo) - eventCountry: countriesFixturesData[6], // Japan - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 27)), - updatedAt: DateTime.now().subtract(const Duration(days: 27)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId86, - isBreaking: false, - title: 'Mumbai Metro Expands with New Aqua Line', - url: 'https://example.com/news/mumbai-metro-aqua-line', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId86/800/600', // Hindustan Times (Mumbai) - source: sourcesFixturesData[17], // Hindustan Times (Mumbai) - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 27)), - updatedAt: DateTime.now().subtract(const Duration(days: 27)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId87, - isBreaking: false, - title: 'Bollywood Film Shoots Bring Stars to Mumbai Streets', - url: 'https://example.com/news/bollywood-mumbai-shoots', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId87/800/600', // Hindustan Times (Mumbai) - source: sourcesFixturesData[17], // Hindustan Times (Mumbai) - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 28)), - updatedAt: DateTime.now().subtract(const Duration(days: 28)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId88, - isBreaking: false, - title: 'Mumbai Indians Gear Up for IPL Season', - url: 'https://example.com/news/mumbai-indians-ipl', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId88/800/600', // Hindustan Times (Mumbai) - source: sourcesFixturesData[17], // Hindustan Times (Mumbai) - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 28)), - updatedAt: DateTime.now().subtract(const Duration(days: 28)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId89, - isBreaking: false, - title: 'BMC Tackles Monsoon Preparedness in Mumbai', - url: 'https://example.com/news/bmc-monsoon-prep', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId89/800/600', // Hindustan Times (Mumbai) - source: sourcesFixturesData[17], // Hindustan Times (Mumbai) - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 29)), - updatedAt: DateTime.now().subtract(const Duration(days: 29)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId90, - isBreaking: false, - title: "Mumbai's Financial District Sees New Investments", - url: 'https://example.com/news/mumbai-bkc-investments', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId90/800/600', // Hindustan Times (Mumbai) - source: sourcesFixturesData[17], // Hindustan Times (Mumbai) - eventCountry: countriesFixturesData[8], // India - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 29)), - updatedAt: DateTime.now().subtract(const Duration(days: 29)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId91, - isBreaking: false, - title: 'Rio Carnival Preparations in Full Swing', - url: 'https://example.com/news/rio-carnival-prep', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId91/800/600', // O Globo (Rio de Janeiro) - source: sourcesFixturesData[18], // O Globo (Rio de Janeiro) - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 30)), - updatedAt: DateTime.now().subtract(const Duration(days: 30)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId92, - isBreaking: false, - title: 'Flamengo Wins Key Match at Maracanã Stadium', - url: 'https://example.com/news/flamengo-maracana-win', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId92/800/600', // O Globo (Rio de Janeiro) - source: sourcesFixturesData[18], // O Globo (Rio de Janeiro) - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 30)), - updatedAt: DateTime.now().subtract(const Duration(days: 30)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId93, - isBreaking: false, - title: 'Security Boosted in Rio Ahead of Major Summit', - url: 'https://example.com/news/rio-security-boost', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId93/800/600', // O Globo (Rio de Janeiro) - source: sourcesFixturesData[18], // O Globo (Rio de Janeiro) - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 31)), - updatedAt: DateTime.now().subtract(const Duration(days: 31)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId94, - isBreaking: false, - title: 'Sugarloaf Mountain Cable Car Undergoes Modernization', - url: 'https://example.com/news/sugarloaf-cable-car-update', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId94/800/600', // O Globo (Rio de Janeiro) - source: sourcesFixturesData[18], // O Globo (Rio de Janeiro) - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 31)), - updatedAt: DateTime.now().subtract(const Duration(days: 31)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId95, - isBreaking: false, - title: "Bossa Nova Festival Celebrates Rio's Musical Heritage", - url: 'https://example.com/news/bossa-nova-festival', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId95/800/600', // O Globo (Rio de Janeiro) - source: sourcesFixturesData[18], // O Globo (Rio de Janeiro) - eventCountry: countriesFixturesData[9], // Brazil - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 32)), - updatedAt: DateTime.now().subtract(const Duration(days: 32)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId96, - isBreaking: false, - title: 'Sagrada Família Nears Completion After 140 Years', - url: 'https://example.com/news/sagrada-familia-completion', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId96/800/600', // La Vanguardia (Barcelona) - source: sourcesFixturesData[19], // La Vanguardia (Barcelona) - eventCountry: countriesFixturesData[10], // Spain - topic: topicsFixturesData[5], // Entertainment - createdAt: DateTime.now().subtract(const Duration(days: 32)), - updatedAt: DateTime.now().subtract(const Duration(days: 32)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId97, - isBreaking: false, - title: 'FC Barcelona Presents New Kit at Camp Nou', - url: 'https://example.com/news/fcb-new-kit', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId97/800/600', // La Vanguardia (Barcelona) - source: sourcesFixturesData[19], // La Vanguardia (Barcelona) - eventCountry: countriesFixturesData[10], // Spain - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 33)), - updatedAt: DateTime.now().subtract(const Duration(days: 33)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId98, - isBreaking: false, - title: 'Catalan Government Discusses Tourism Strategy', - url: 'https://example.com/news/catalan-tourism-strategy', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId98/800/600', // La Vanguardia (Barcelona) - source: sourcesFixturesData[19], // La Vanguardia (Barcelona) - eventCountry: countriesFixturesData[10], // Spain - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 33)), - updatedAt: DateTime.now().subtract(const Duration(days: 33)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId99, - isBreaking: false, - title: "Barcelona's Tech Scene Booms with New Hub", - url: 'https://example.com/news/barcelona-tech-hub', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId99/800/600', // La Vanguardia (Barcelona) - source: sourcesFixturesData[19], // La Vanguardia (Barcelona) - eventCountry: countriesFixturesData[10], // Spain - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 34)), - updatedAt: DateTime.now().subtract(const Duration(days: 34)), - status: ContentStatus.active, - ), - Headline( - id: kHeadlineId100, - isBreaking: false, - title: 'La Boqueria Market: A Taste of Barcelona', - url: 'https://example.com/news/la-boqueria-feature', - imageUrl: - 'https://picsum.photos/seed/kHeadlineId100/800/600', // La Vanguardia (Barcelona) - source: sourcesFixturesData[19], // La Vanguardia (Barcelona) - eventCountry: countriesFixturesData[10], // Spain - topic: topicsFixturesData[8], // Food - createdAt: DateTime.now().subtract(const Duration(days: 34)), - updatedAt: DateTime.now().subtract(const Duration(days: 34)), - status: ContentStatus.active, - ), + final headlines = []; + for (var i = 0; i < _headlineIds.length; i++) { + final id = _headlineIds[i]; + final title = + _titlesByLang[resolvedLanguageCode]![i % _titlesByLang['en']!.length]; + final source = sources[i % sources.length]; + final topic = topics[i % topics.length]; + final country = countriesFixturesData[i % countriesFixturesData.length]; - // --- National News Outlets (kSourceId21 - kSourceId30) --- - // ... (Headlines for kSourceId21 to kSourceId30 would follow the same pattern) - // To keep the response size manageable, I will add a placeholder comment here. - // In a real implementation, 50 headlines for these 10 sources would be added. - // Example for USA Today: - Headline( - id: kHeadlineId101, - isBreaking: false, - title: 'National Parks See Record Visitor Numbers', - url: 'https://example.com/news/national-parks-visitors', - imageUrl: 'https://picsum.photos/seed/kHeadlineId101/800/600', - source: sourcesFixturesData[20], // USA Today - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[7], // Travel - createdAt: DateTime.now().subtract(const Duration(days: 35)), - updatedAt: DateTime.now().subtract(const Duration(days: 35)), - status: ContentStatus.active, - ), - // ... 4 more for USA Today + headlines.add( + Headline( + id: id, + isBreaking: i % 10 == 3, // Make some headlines breaking + title: title, + url: 'https://example.com/news/${id.substring(0, 8)}', + imageUrl: 'https://picsum.photos/seed/$id/800/600', + source: source, + eventCountry: country, + topic: topic, + createdAt: DateTime.now().subtract(Duration(minutes: i * 15)), + updatedAt: DateTime.now().subtract(Duration(minutes: i * 15)), + status: ContentStatus.active, + ), + ); + } + return headlines; +} - // Example for The Globe and Mail: - Headline( - id: kHeadlineId106, - isBreaking: false, - title: 'Canadian Government Announces New Federal Budget', - url: 'https://example.com/news/canada-federal-budget', - imageUrl: 'https://picsum.photos/seed/kHeadlineId106/800/600', - source: sourcesFixturesData[21], // The Globe and Mail - eventCountry: countriesFixturesData[1], // Canada - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 36)), - updatedAt: DateTime.now().subtract(const Duration(days: 36)), - status: ContentStatus.active, - ), - // ... 4 more for The Globe and Mail - - // --- International News Outlets (kSourceId31 - kSourceId40) --- - // ... (Headlines for kSourceId31 to kSourceId40 would follow the same pattern) - // Example for CNN International: - Headline( - id: kHeadlineId151, - isBreaking: false, - title: 'Global Supply Chain Issues Persist', - url: 'https://example.com/news/global-supply-chain', - imageUrl: 'https://picsum.photos/seed/kHeadlineId151/800/600', - source: sourcesFixturesData[30], // CNN International - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 45)), - updatedAt: DateTime.now().subtract(const Duration(days: 45)), - status: ContentStatus.active, - ), - // ... 4 more for CNN International - - // --- Specialized Publishers (kSourceId41 - kSourceId50) --- - // Example for ESPN: - Headline( - id: kHeadlineId201, - isBreaking: false, - title: 'World Cup Finals: An Unforgettable Match', - url: 'https://example.com/news/world-cup-final', - imageUrl: 'https://picsum.photos/seed/kHeadlineId201/800/600', - source: sourcesFixturesData[40], // ESPN - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[1], // Sports - createdAt: DateTime.now().subtract(const Duration(days: 55)), - updatedAt: DateTime.now().subtract(const Duration(days: 55)), - status: ContentStatus.active, - ), - // ... 4 more for ESPN - - // --- Blogs (kSourceId51 - kSourceId60) --- - // Example for Stratechery: - Headline( - id: kHeadlineId251, - isBreaking: false, - title: 'The Future of Content and Aggregation', - url: 'https://example.com/news/stratechery-content-ai', - imageUrl: 'https://picsum.photos/seed/kHeadlineId251/800/600', - source: sourcesFixturesData[50], // Stratechery by Ben Thompson - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 65)), - updatedAt: DateTime.now().subtract(const Duration(days: 65)), - status: ContentStatus.active, - ), - // ... 4 more for Stratechery - - // --- Government Sources (kSourceId61 - kSourceId70) --- - // Example for WhiteHouse.gov: - Headline( - id: kHeadlineId301, - isBreaking: false, - title: 'President Signs Executive Order on Cybersecurity', - url: 'https://example.com/news/wh-cyber-order', - imageUrl: 'https://picsum.photos/seed/kHeadlineId301/800/600', - source: sourcesFixturesData[60], // WhiteHouse.gov - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[2], // Politics - createdAt: DateTime.now().subtract(const Duration(days: 75)), - updatedAt: DateTime.now().subtract(const Duration(days: 75)), - status: ContentStatus.active, - ), - // ... 4 more for WhiteHouse.gov - - // --- Aggregators (kSourceId71 - kSourceId80) --- - // Example for Google News: - Headline( - id: kHeadlineId351, - isBreaking: false, - title: 'This Week in Tech: A Google News Roundup', - url: 'https://example.com/news/gnews-tech-roundup', - imageUrl: 'https://picsum.photos/seed/kHeadlineId351/800/600', - source: sourcesFixturesData[70], // Google News - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[0], // Technology - createdAt: DateTime.now().subtract(const Duration(days: 85)), - updatedAt: DateTime.now().subtract(const Duration(days: 85)), - status: ContentStatus.active, - ), - // ... 4 more for Google News - - // --- Other (kSourceId81 - kSourceId90) --- - // Example for PR Newswire: - Headline( - id: kHeadlineId401, - isBreaking: false, - title: 'Global Tech Corp Announces Record Quarterly Earnings', - url: 'https://example.com/news/prn-earnings', - imageUrl: 'https://picsum.photos/seed/kHeadlineId401/800/600', - source: sourcesFixturesData[80], // PR Newswire - eventCountry: countriesFixturesData[0], // United States - topic: topicsFixturesData[6], // Business - createdAt: DateTime.now().subtract(const Duration(days: 95)), - updatedAt: DateTime.now().subtract(const Duration(days: 95)), - status: ContentStatus.active, - ), - // Example for The Lancet: - Headline( - id: kHeadlineId411, - isBreaking: false, - title: 'Phase 3 Trial Results for New Diabetes Drug Published', - url: 'https://example.com/news/lancet-diabetes-drug', - imageUrl: 'https://picsum.photos/seed/kHeadlineId411/800/600', - source: sourcesFixturesData[82], // The Lancet - eventCountry: countriesFixturesData[2], // United Kingdom - topic: topicsFixturesData[4], // Health - createdAt: DateTime.now().subtract(const Duration(days: 100)), - updatedAt: DateTime.now().subtract(const Duration(days: 100)), - status: ContentStatus.active, - ), +const List _headlineIds = [ + kHeadlineId1, kHeadlineId2, kHeadlineId3, kHeadlineId4, kHeadlineId5, + kHeadlineId6, kHeadlineId7, kHeadlineId8, kHeadlineId9, kHeadlineId10, + kHeadlineId11, kHeadlineId12, kHeadlineId13, kHeadlineId14, kHeadlineId15, + kHeadlineId16, kHeadlineId17, kHeadlineId18, kHeadlineId19, kHeadlineId20, + kHeadlineId21, kHeadlineId22, kHeadlineId23, kHeadlineId24, kHeadlineId25, + kHeadlineId26, kHeadlineId27, kHeadlineId28, kHeadlineId29, kHeadlineId30, + kHeadlineId31, kHeadlineId32, kHeadlineId33, kHeadlineId34, kHeadlineId35, + kHeadlineId36, kHeadlineId37, kHeadlineId38, kHeadlineId39, kHeadlineId40, + kHeadlineId41, kHeadlineId42, kHeadlineId43, kHeadlineId44, kHeadlineId45, + kHeadlineId46, kHeadlineId47, kHeadlineId48, kHeadlineId49, kHeadlineId50, + kHeadlineId51, kHeadlineId52, kHeadlineId53, kHeadlineId54, kHeadlineId55, + kHeadlineId56, kHeadlineId57, kHeadlineId58, kHeadlineId59, kHeadlineId60, + kHeadlineId61, kHeadlineId62, kHeadlineId63, kHeadlineId64, kHeadlineId65, + kHeadlineId66, kHeadlineId67, kHeadlineId68, kHeadlineId69, kHeadlineId70, + kHeadlineId71, kHeadlineId72, kHeadlineId73, kHeadlineId74, kHeadlineId75, + kHeadlineId76, kHeadlineId77, kHeadlineId78, kHeadlineId79, kHeadlineId80, + kHeadlineId81, kHeadlineId82, kHeadlineId83, kHeadlineId84, kHeadlineId85, + kHeadlineId86, kHeadlineId87, kHeadlineId88, kHeadlineId89, kHeadlineId90, + kHeadlineId91, kHeadlineId92, kHeadlineId93, kHeadlineId94, kHeadlineId95, + kHeadlineId96, kHeadlineId97, kHeadlineId98, kHeadlineId99, kHeadlineId100, + // Add more IDs if needed, up to kHeadlineId450 ]; + +final Map> _titlesByLang = { + 'en': [ + 'AI Breakthrough: New Model Achieves Human-Level Performance', + 'Local Team Wins Championship in Thrilling Final', + 'Global Leaders Meet to Discuss Climate Change Policies', + 'New Planet Discovered in Distant Galaxy', + 'Breakthrough in Cancer Research Offers New Hope', + 'Blockbuster Movie Breaks Box Office Records', + 'Stock Market Reaches All-Time High Amid Economic Boom', + 'New Travel Restrictions Lifted for Popular Destinations', + 'Michelin Star Chef Opens New Restaurant in City Center', + 'Innovative Teaching Methods Boost Student Engagement', + 'Cybersecurity Firms Warn of New Global Threat', + 'Olympics Committee Announces Host City for 2032 Games', + 'New Bill Aims to Reform Healthcare System', + 'Archaeologists Uncover Ancient City Ruins', + 'Dietary Guidelines Updated for Public Health', + 'Music Festival Announces Star-Studded Lineup', + 'Tech Giant Acquires Startup in Multi-Billion Dollar Deal', + 'Space Tourism Takes Off: First Commercial Flights Announced', + 'Future of Food: Lab-Grown Meat Gains Popularity', + 'Online Learning Platforms See Surge in Enrollment', + 'Quantum Computing Achieves New Milestone', + 'World Cup Qualifiers: Unexpected Upsets Shake Rankings', + 'Election Results: New Government Takes Power', + 'Breakthrough in Fusion Energy Research Announced', + 'Mental Health Awareness Campaign Launched Globally', + 'Gaming Industry Sees Record Growth in Virtual Reality', + 'Global Supply Chain Disruptions Impacting Consumer Goods', + 'Arctic Expedition Discovers New Marine Species', + 'Rise of Plant-Based Cuisine: New Restaurants Open', + 'Education Technology Transforms Classrooms', + 'SpaceX Launches New Satellite Constellation', + 'Football Legend Announces Retirement', + 'G7 Summit Concludes with Joint Statement on Global Economy', + "Breakthrough in Alzheimer's Research Offers New Treatment Path", + 'Global Vaccination Campaign Reaches Billions', + 'Streaming Wars Intensify with New Platform Launches', + 'Cryptocurrency Market Experiences Major Volatility', + 'Sustainable Tourism Initiatives Gain Momentum', + 'Food Security Summit Addresses Global Hunger', + 'Robotics in Education: New Tools for Learning', + 'AI Ethics Debate Intensifies Among Tech Leaders', + 'Esports Industry Sees Massive Investment Boom', + 'International Sanctions Imposed on Rogue State', + 'New Species of Deep-Sea Creature Discovered', + 'Global Health Crisis: New Pandemic Preparedness Plan', + 'Hollywood Strikes Continue: Impact on Film Production', + 'Emerging Markets Show Strong Economic Resilience', + 'Adventure Tourism Booms in Remote Regions', + 'The Rise of Sustainable Food Packaging', + 'Personalized Learning: Tailoring Education to Individual Needs', + ], + 'ar': [ + 'إنجاز في الذكاء الاصطناعي: نموذج جديد يحقق أداءً على المستوى البشري', + 'الفريق المحلي يفوز بالبطولة في نهائي مثير', + 'قادة العالم يجتمعون لمناقشة سياسات تغير المناخ', + 'اكتشاف كوكب جديد في مجرة بعيدة', + 'تقدم في أبحاث السرطان يقدم أملاً جديدًا', + 'فيلم ضخم يحطم الأرقام القياسية في شباك التذاكر', + 'سوق الأسهم يصل إلى أعلى مستوى له على الإطلاق وسط ازدهار اقتصادي', + 'رفع قيود السفر الجديدة عن وجهات شهيرة', + 'شيف حائز على نجمة ميشلان يفتتح مطعمًا جديدًا في وسط المدينة', + 'طرق التدريس المبتكرة تعزز مشاركة الطلاب', + 'شركات الأمن السيبراني تحذر من تهديد عالمي جديد', + 'اللجنة الأولمبية تعلن عن المدينة المضيفة لألعاب 2032', + 'مشروع قانون جديد يهدف إلى إصلاح نظام الرعاية الصحية', + 'علماء الآثار يكشفون عن أطلال مدينة قديمة', + 'تحديث المبادئ التوجيهية الغذائية للصحة العامة', + 'مهرجان موسيقي يعلن عن قائمة نجوم مرصعة بالنجوم', + 'عملاق التكنولوجيا يستحوذ على شركة ناشئة في صفقة بمليارات الدولارات', + 'السياحة الفضائية تنطلق: الإعلان عن أولى الرحلات التجارية', + 'مستقبل الغذاء: اللحوم المزروعة في المختبر تكتسب شعبية', + 'منصات التعلم عبر الإنترنت تشهد طفرة في التسجيل', + 'الحوسبة الكمومية تحقق إنجازًا جديدًا', + 'تصفيات كأس العالم: مفاجآت غير متوقعة تهز التصنيفات', + 'نتائج الانتخابات: حكومة جديدة تتولى السلطة', + 'الإعلان عن تقدم كبير في أبحاث طاقة الاندماج', + 'إطلاق حملة توعية بالصحة النفسية على مستوى العالم', + 'صناعة الألعاب تشهد نموًا قياسيًا في الواقع الافتراضي', + 'اضطرابات سلسلة التوريد العالمية تؤثر على السلع الاستهلاكية', + 'بعثة استكشافية في القطب الشمالي تكتشف أنواعًا بحرية جديدة', + 'صعود المطبخ النباتي: افتتاح مطاعم جديدة', + 'تكنولوجيا التعليم تغير الفصول الدراسية', + 'سبيس إكس تطلق كوكبة أقمار صناعية جديدة', + 'أسطورة كرة القدم يعلن اعتزاله', + 'قمة مجموعة السبع تختتم ببيان مشترك حول الاقتصاد العالمي', + 'تقدم في أبحاث الزهايمر يقدم مسارًا علاجيًا جديدًا', + 'حملة التطعيم العالمية تصل إلى المليارات', + 'حروب البث تشتد مع إطلاق منصات جديدة', + 'سوق العملات المشفرة يشهد تقلبات كبيرة', + 'مبادرات السياحة المستدامة تكتسب زخمًا', + 'قمة الأمن الغذائي تتناول الجوع العالمي', + 'الروبوتات في التعليم: أدوات جديدة للتعلم', + 'جدل أخلاقيات الذكاء الاصطناعي يشتد بين قادة التكنولوجيا', + 'صناعة الرياضات الإلكترونية تشهد طفرة استثمارية هائلة', + 'فرض عقوبات دولية على دولة مارقة', + 'اكتشاف أنواع جديدة من مخلوقات أعماق البحار', + 'أزمة صحية عالمية: خطة جديدة للتأهب للأوبئة', + 'إضرابات هوليوود مستمرة: التأثير على إنتاج الأفلام', + 'الأسواق الناشئة تظهر مرونة اقتصادية قوية', + 'ازدهار سياحة المغامرات في المناطق النائية', + 'صعود أغلفة المواد الغذائية المستدامة', + 'التعلم المخصص: تكييف التعليم مع الاحتياجات الفردية', + ], +}; From 90d1e4ec3697ccc8b11932561f50388b8777efd8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:23:55 +0100 Subject: [PATCH 65/93] refactor fixtures: make saved headline filters multilingual - Convert savedHeadlineFiltersFixturesData from a top-level variable to a function - Add language support for filter names (English and Arabic) - Use resolved language code to fetch appropriate topic data - Update names accordingly to the selected language --- lib/src/fixtures/saved_headline_filters.dart | 72 ++++++++++++-------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/lib/src/fixtures/saved_headline_filters.dart b/lib/src/fixtures/saved_headline_filters.dart index d90c14c..5a22c65 100644 --- a/lib/src/fixtures/saved_headline_filters.dart +++ b/lib/src/fixtures/saved_headline_filters.dart @@ -1,33 +1,47 @@ import 'package:core/core.dart'; -/// A list of predefined saved headline filters for fixture data. -final savedHeadlineFiltersFixturesData = [ - SavedHeadlineFilter( - id: kSavedHeadlineFilterId1, - userId: kAdminUserId, - name: 'US Tech News', - isPinned: true, - deliveryTypes: const { - PushNotificationSubscriptionDeliveryType.breakingOnly, - }, - criteria: HeadlineFilterCriteria( - topics: [topicsFixturesData[0]], // Technology - sources: const [], - countries: [countriesFixturesData[0]], // United States +/// Generates a list of predefined saved headline filters for fixture data. +List getSavedHeadlineFiltersFixturesData({ + String languageCode = 'en', +}) { + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; + final topics = getTopicsFixturesData(languageCode: resolvedLanguageCode); + + final namesByLang = { + 'en': ['US Tech News', 'Global Business'], + 'ar': ['أخبار التكنولوجيا الأمريكية', 'أعمال عالمية'], + }; + + return [ + SavedHeadlineFilter( + id: kSavedHeadlineFilterId1, + userId: kAdminUserId, + name: namesByLang[resolvedLanguageCode]![0], + isPinned: true, + deliveryTypes: const { + PushNotificationSubscriptionDeliveryType.breakingOnly, + }, + criteria: HeadlineFilterCriteria( + topics: [topics[0]], // Technology + sources: const [], + countries: [countriesFixturesData[0]], // United States + ), ), - ), - SavedHeadlineFilter( - id: kSavedHeadlineFilterId2, - userId: kUser1Id, - name: 'Global Business', - isPinned: false, - deliveryTypes: const { - PushNotificationSubscriptionDeliveryType.breakingOnly, - }, - criteria: HeadlineFilterCriteria( - topics: [topicsFixturesData[6]], // Business - sources: const [], - countries: const [], + SavedHeadlineFilter( + id: kSavedHeadlineFilterId2, + userId: kUser1Id, + name: namesByLang[resolvedLanguageCode]![1], + isPinned: false, + deliveryTypes: const { + PushNotificationSubscriptionDeliveryType.breakingOnly, + }, + criteria: HeadlineFilterCriteria( + topics: [topics[6]], // Business + sources: const [], + countries: const [], + ), ), - ), -]; + ]; +} From 49e4ec771adc4812ab41eaebea67bf353e02d43f Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:24:14 +0100 Subject: [PATCH 66/93] refactor(fixtures): enhance saved source filters fixture for internationalization - Convert saved source filters fixtures into a function - Add support for multiple language codes (en, ar) - Implement fallback to English for unsupported language codes - Update filter names based on the specified language --- lib/src/fixtures/saved_source_filters.dart | 59 +++++++++++++--------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/src/fixtures/saved_source_filters.dart b/lib/src/fixtures/saved_source_filters.dart index 3458301..b8dcc30 100644 --- a/lib/src/fixtures/saved_source_filters.dart +++ b/lib/src/fixtures/saved_source_filters.dart @@ -1,27 +1,40 @@ import 'package:core/core.dart'; -/// A list of predefined saved source filters for fixture data. -final savedSourceFiltersFixturesData = [ - SavedSourceFilter( - id: kSavedSourceFilterId1, - userId: kAdminUserId, - name: 'UK News Agencies', - isPinned: true, - criteria: SourceFilterCriteria( - sourceTypes: const [SourceType.newsAgency], - languages: const [], - countries: [countriesFixturesData[1]], // United Kingdom +/// Generates a list of predefined saved source filters for fixture data. +List getSavedSourceFiltersFixturesData({ + String languageCode = 'en', +}) { + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; + + final namesByLang = { + 'en': ['UK News Agencies', 'German Tech Blogs'], + 'ar': ['وكالات الأنباء البريطانية', 'مدونات التكنولوجيا الألمانية'], + }; + + return [ + SavedSourceFilter( + id: kSavedSourceFilterId1, + userId: kAdminUserId, + name: namesByLang[resolvedLanguageCode]![0], + isPinned: true, + criteria: SourceFilterCriteria( + sourceTypes: const [SourceType.newsAgency], + languages: const [], + countries: [countriesFixturesData[1]], // United Kingdom + ), ), - ), - SavedSourceFilter( - id: kSavedSourceFilterId2, - userId: kUser1Id, - name: 'German Tech Blogs', - isPinned: false, - criteria: SourceFilterCriteria( - sourceTypes: const [SourceType.blog, SourceType.specializedPublisher], - languages: [languagesFixturesData.firstWhere((l) => l.code == 'de')], - countries: [countriesFixturesData[4]], // Germany + SavedSourceFilter( + id: kSavedSourceFilterId2, + userId: kUser1Id, + name: namesByLang[resolvedLanguageCode]![1], + isPinned: false, + criteria: SourceFilterCriteria( + sourceTypes: const [SourceType.blog, SourceType.specializedPublisher], + languages: [languagesFixturesData.firstWhere((l) => l.code == 'de')], + countries: [countriesFixturesData[4]], // Germany + ), ), - ), -]; + ]; +} From 2d883723471113bc250ecddc5bfd440942675c87 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:26:20 +0100 Subject: [PATCH 67/93] refactor: make sources fixtures multilingual --- lib/src/fixtures/sources.dart | 1579 +++++++++------------------------ 1 file changed, 404 insertions(+), 1175 deletions(-) diff --git a/lib/src/fixtures/sources.dart b/lib/src/fixtures/sources.dart index ea562d9..9b5708b 100644 --- a/lib/src/fixtures/sources.dart +++ b/lib/src/fixtures/sources.dart @@ -4,1179 +4,408 @@ import 'package:core/src/fixtures/fixture_ids.dart'; import 'package:core/src/fixtures/languages.dart'; import 'package:core/src/models/entities/source.dart'; -/// A list of predefined sources for fixture data. -final sourcesFixturesData = [ - Source( - id: kSourceId1, - name: 'TechCrunch', - description: 'Leading online publisher of technology news.', - url: 'https://techcrunch.com', - logoUrl: 'https://api.companyenrich.com/logo/techcrunch.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-01-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId2, - name: 'BBC News', - description: 'Breaking news, sport, TV, radio and a whole lot more.', - url: 'https://www.bbc.com/news', - logoUrl: 'https://api.companyenrich.com/logo/bbc.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-01-02T11:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-02T11:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId3, - name: 'The New York Times', - description: 'Breaking News, World News & Multimedia.', - url: 'https://www.nytimes.com', - logoUrl: 'https://api.companyenrich.com/logo/nytimes.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-01-03T12:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-03T12:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId4, - name: 'The Guardian', - description: - 'Latest news, sport, business, comment and reviews from the Guardian.', - url: 'https://www.theguardian.com', - logoUrl: 'https://api.companyenrich.com/logo/theguardian.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-01-04T13:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-04T13:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId5, - name: 'CNN', - description: 'Breaking News, Latest News and Videos.', - url: 'https://edition.cnn.com', - logoUrl: 'https://api.companyenrich.com/logo/cnn.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-01-05T14:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-05T14:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId6, - name: 'Reuters', - description: 'Business, financial, national and international news.', - url: 'https://www.reuters.com', - logoUrl: 'https://api.companyenrich.com/logo/reuters.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-01-06T15:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-06T15:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId7, - name: 'Al Jazeera English', - description: - 'News, analysis, and opinion from the Middle East and around the world.', - url: 'https://www.aljazeera.com', - logoUrl: 'https://api.companyenrich.com/logo/aljazeera.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: - countriesFixturesData[0], // United States (assuming for simplicity, actual is Qatar) - createdAt: DateTime.parse('2023-01-07T16:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-07T16:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId8, - name: 'Xinhua News Agency', - description: "Official press agency of the People's Republic of China.", - url: 'http://www.xinhuanet.com/english/', - logoUrl: 'https://api.companyenrich.com/logo/xinhuanet.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[7], // China - createdAt: DateTime.parse('2023-01-08T17:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-08T17:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId9, - name: 'The Times of India', - description: 'Latest and Breaking News from India.', - url: 'https://timesofindia.indiatimes.com/', - logoUrl: 'https://api.companyenrich.com/logo/indiatimes.com', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[8], // India - createdAt: DateTime.parse('2023-01-09T18:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-09T18:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId10, - name: 'Folha de S.Paulo', - description: 'Brazilian daily newspaper.', - url: 'https://www.folha.uol.com.br/', - logoUrl: 'https://api.companyenrich.com/logo/uol.com.br', - sourceType: SourceType.newsAgency, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'pt'), - headquarters: countriesFixturesData[9], // Brazil - createdAt: DateTime.parse('2023-01-10T19:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-10T19:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId11, - name: 'San Francisco Chronicle', - description: 'News from the San Francisco Bay Area.', - url: 'https://www.sfchronicle.com', - logoUrl: 'https://api.companyenrich.com/logo/sfchronicle.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-02-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId12, - name: 'Manchester Evening News', - description: 'Covering Greater Manchester, UK.', - url: 'https://www.manchestereveningnews.co.uk', - logoUrl: 'https://api.companyenrich.com/logo/manchestereveningnews.co.uk', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-02-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId13, - name: 'The Sydney Morning Herald', - description: 'Independent journalism for Sydney, Australia.', - url: 'https://www.smh.com.au', - logoUrl: 'https://api.companyenrich.com/logo/smh.com.au', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[3], // Australia - createdAt: DateTime.parse('2023-02-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId14, - name: 'Le Parisien', - description: 'Local news for Paris and the Île-de-France region.', - url: 'https://www.leparisien.fr', - logoUrl: 'https://api.companyenrich.com/logo/leparisien.fr', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'fr'), - headquarters: countriesFixturesData[5], // France - createdAt: DateTime.parse('2023-02-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId15, - name: 'The Toronto Star', - description: 'News and stories for Toronto, Canada.', - url: 'https://www.thestar.com', - logoUrl: 'https://api.companyenrich.com/logo/thestar.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[1], // Canada - createdAt: DateTime.parse('2023-02-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId16, - name: 'Berliner Morgenpost', - description: 'Daily news for Berlin, Germany.', - url: 'https://www.morgenpost.de', - logoUrl: 'https://api.companyenrich.com/logo/morgenpost.de', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'de'), - headquarters: countriesFixturesData[4], // Germany - createdAt: DateTime.parse('2023-02-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId17, - name: 'The Asahi Shimbun (Tokyo)', - description: 'Local and national news from a Tokyo perspective.', - url: 'https://www.asahi.com/area/tokyo/', - logoUrl: 'https://api.companyenrich.com/logo/asahi.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'ja'), - headquarters: countriesFixturesData[6], // Japan - createdAt: DateTime.parse('2023-02-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId18, - name: 'Hindustan Times (Mumbai)', - description: 'Latest news from Mumbai, India.', - url: 'https://www.hindustantimes.com/mumbai-news', - logoUrl: 'https://api.companyenrich.com/logo/hindustantimes.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[8], // India - createdAt: DateTime.parse('2023-02-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId19, - name: 'O Globo (Rio de Janeiro)', - description: 'News from Rio de Janeiro, Brazil.', - url: 'https://oglobo.globo.com/rio/', - logoUrl: 'https://api.companyenrich.com/logo/globo.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'pt'), - headquarters: countriesFixturesData[9], // Brazil - createdAt: DateTime.parse('2023-02-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId20, - name: 'La Vanguardia (Barcelona)', - description: 'News from Barcelona and Catalonia, Spain.', - url: 'https://www.lavanguardia.com/local/barcelona', - logoUrl: 'https://api.companyenrich.com/logo/lavanguardia.com', - sourceType: SourceType.localNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'es'), - headquarters: countriesFixturesData[10], // Spain - createdAt: DateTime.parse('2023-02-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-02-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId21, - name: 'USA Today', - description: 'National news from across the United States.', - url: 'https://www.usatoday.com', - logoUrl: 'https://api.companyenrich.com/logo/usatoday.com', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-03-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId22, - name: 'The Globe and Mail', - description: "Canada's national newspaper.", - url: 'https://www.theglobeandmail.com', - logoUrl: 'https://api.companyenrich.com/logo/theglobeandmail.com', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[1], // Canada - createdAt: DateTime.parse('2023-03-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId23, - name: 'The Australian', - description: 'National news of Australia.', - url: 'https://www.theaustralian.com.au', - logoUrl: 'https://api.companyenrich.com/logo/theaustralian.com.au', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[3], // Australia - createdAt: DateTime.parse('2023-03-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId24, - name: 'Le Monde', - description: 'French national daily newspaper.', - url: 'https://www.lemonde.fr', - logoUrl: 'https://api.companyenrich.com/logo/lemonde.fr', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'fr'), - headquarters: countriesFixturesData[5], // France - createdAt: DateTime.parse('2023-03-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId25, - name: 'Frankfurter Allgemeine Zeitung', - description: 'German national newspaper.', - url: 'https://www.faz.net', - logoUrl: 'https://api.companyenrich.com/logo/faz.net', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'de'), - headquarters: countriesFixturesData[4], // Germany - createdAt: DateTime.parse('2023-03-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId26, - name: 'The Yomiuri Shimbun', - description: 'Japanese national newspaper.', - url: 'https://www.yomiuri.co.jp', - logoUrl: 'https://api.companyenrich.com/logo/yomiuri.co.jp', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'ja'), - headquarters: countriesFixturesData[6], // Japan - createdAt: DateTime.parse('2023-03-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId27, - name: "People's Daily", - description: 'Official newspaper of the Central Committee of the CCP.', - url: 'http://en.people.cn', - logoUrl: 'https://api.companyenrich.com/logo/people.cn', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[7], // China - createdAt: DateTime.parse('2023-03-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId28, - name: 'O Estado de S. Paulo', - description: 'Brazilian national newspaper.', - url: 'https://www.estadao.com.br', - logoUrl: 'https://api.companyenrich.com/logo/estadao.com.br', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'pt'), - headquarters: countriesFixturesData[9], // Brazil - createdAt: DateTime.parse('2023-03-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId29, - name: 'El País', - description: 'Spanish national daily newspaper.', - url: 'https://elpais.com', - logoUrl: 'https://api.companyenrich.com/logo/elpais.com', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'es'), - headquarters: countriesFixturesData[10], // Spain - createdAt: DateTime.parse('2023-03-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId30, - name: 'Corriere della Sera', - description: 'Italian national daily newspaper.', - url: 'https://www.corriere.it', - logoUrl: 'https://api.companyenrich.com/logo/corriere.it', - sourceType: SourceType.nationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'it'), - headquarters: countriesFixturesData[11], // Italy - createdAt: DateTime.parse('2023-03-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-03-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId31, - name: 'CNN International', - description: 'Global news coverage from CNN.', - url: 'https://edition.cnn.com', - logoUrl: 'https://api.companyenrich.com/logo/cnn.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-04-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId32, - name: 'BBC World News', - description: 'International news from the BBC.', - url: 'https://www.bbc.com/news/world', - logoUrl: 'https://api.companyenrich.com/logo/bbc.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-04-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId33, - name: 'The Economist', - description: 'In-depth analysis of international news and business.', - url: 'https://www.economist.com', - logoUrl: 'https://api.companyenrich.com/logo/economist.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-04-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId34, - name: 'France 24', - description: 'French perspective on international current events.', - url: 'https://www.france24.com/en/', - logoUrl: 'https://api.companyenrich.com/logo/france24.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[5], // France - createdAt: DateTime.parse('2023-04-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId35, - name: 'Deutsche Welle', - description: "Germany's international broadcaster.", - url: 'https://www.dw.com/en/', - logoUrl: 'https://api.companyenrich.com/logo/dw.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[4], // Germany - createdAt: DateTime.parse('2023-04-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId36, - name: 'The Wall Street Journal', - description: 'Global business and financial news.', - url: 'https://www.wsj.com', - logoUrl: 'https://api.companyenrich.com/logo/wsj.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-04-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId37, - name: 'Associated Press (AP)', - description: 'Global news network.', - url: 'https://apnews.com', - logoUrl: 'https://api.companyenrich.com/logo/apnews.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-04-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId38, - name: 'Agence France-Presse (AFP)', - description: 'International news agency based in Paris.', - url: 'https://www.afp.com/en', - logoUrl: 'https://api.companyenrich.com/logo/afp.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[5], // France - createdAt: DateTime.parse('2023-04-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId39, - name: 'RT', - description: 'Russian state-funded international television network.', - url: 'https://www.rt.com', - logoUrl: 'https://api.companyenrich.com/logo/rt.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[12], // Russia - createdAt: DateTime.parse('2023-04-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId40, - name: 'CGTN', - description: 'Chinese state-funded international television network.', - url: 'https://www.cgtn.com', - logoUrl: 'https://api.companyenrich.com/logo/cgtn.com', - sourceType: SourceType.internationalNewsOutlet, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[7], // China - createdAt: DateTime.parse('2023-04-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-04-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId41, - name: 'ESPN', - description: 'The worldwide leader in sports.', - url: 'https://www.espn.com', - logoUrl: 'https://api.companyenrich.com/logo/espn.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId42, - name: 'Nature', - description: 'International journal of science.', - url: 'https://www.nature.com', - logoUrl: 'https://api.companyenrich.com/logo/nature.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-05-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId43, - name: 'The Hollywood Reporter', - description: 'The definitive voice of the entertainment industry.', - url: 'https://www.hollywoodreporter.com', - logoUrl: 'https://api.companyenrich.com/logo/hollywoodreporter.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId44, - name: 'Vogue', - description: 'Fashion, beauty, and lifestyle.', - url: 'https://www.vogue.com', - logoUrl: 'https://api.companyenrich.com/logo/vogue.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId45, - name: 'National Geographic', - description: 'Science, exploration, and adventure.', - url: 'https://www.nationalgeographic.com', - logoUrl: 'https://api.companyenrich.com/logo/nationalgeographic.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId46, - name: 'Wired', - description: 'How technology is changing every aspect of our lives.', - url: 'https://www.wired.com', - logoUrl: 'https://api.companyenrich.com/logo/wired.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId47, - name: 'Bon Appétit', - description: 'Food and cooking magazine.', - url: 'https://www.bonappetit.com', - logoUrl: 'https://api.companyenrich.com/logo/bonappetit.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId48, - name: 'Architectural Digest', - description: 'The international design authority.', - url: 'https://www.architecturaldigest.com', - logoUrl: 'https://api.companyenrich.com/logo/architecturaldigest.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId49, - name: 'Car and Driver', - description: 'Automotive news and reviews.', - url: 'https://www.caranddriver.com', - logoUrl: 'https://api.companyenrich.com/logo/caranddriver.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-05-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId50, - name: 'PC Gamer', - description: 'Global authority on PC games.', - url: 'https://www.pcgamer.com', - logoUrl: 'https://api.companyenrich.com/logo/pcgamer.com', - sourceType: SourceType.specializedPublisher, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-05-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-05-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId51, - name: 'Stratechery by Ben Thompson', - description: 'Analysis of the strategy and business of technology.', - url: 'https://stratechery.com', - logoUrl: 'https://api.companyenrich.com/logo/stratechery.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId52, - name: 'Daring Fireball', - description: 'By John Gruber. On technology and Apple.', - url: 'https://daringfireball.net', - logoUrl: 'https://api.companyenrich.com/logo/daringfireball.net', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId53, - name: 'Wait But Why', - description: 'A popular long-form, stick-figure-illustrated blog.', - url: 'https://waitbutwhy.com', - logoUrl: 'https://api.companyenrich.com/logo/waitbutwhy.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId54, - name: 'Smitten Kitchen', - description: 'A home cooking blog from a tiny kitchen in New York City.', - url: 'https://smittenkitchen.com', - logoUrl: 'https://api.companyenrich.com/logo/smittenkitchen.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId55, - name: 'The Verge', - description: 'A technology news and media network operated by Vox Media.', - url: 'https://www.theverge.com', - logoUrl: 'https://api.companyenrich.com/logo/theverge.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId56, - name: 'Gizmodo', - description: 'A design, technology, science and science fiction website.', - url: 'https://gizmodo.com', - logoUrl: 'https://api.companyenrich.com/logo/gizmodo.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId57, - name: 'Kotaku', - description: 'A video game website and blog.', - url: 'https://kotaku.com', - logoUrl: 'https://api.companyenrich.com/logo/kotaku.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId58, - name: 'Lifehacker', - description: 'A weblog about life hacks and software.', - url: 'https://lifehacker.com', - logoUrl: 'https://api.companyenrich.com/logo/lifehacker.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId59, - name: 'Mashable', - description: 'A global, multi-platform media and entertainment company.', - url: 'https://mashable.com', - logoUrl: 'https://api.companyenrich.com/logo/mashable.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId60, - name: 'Engadget', - description: 'A multilingual technology blog network.', - url: 'https://www.engadget.com', - logoUrl: 'https://api.companyenrich.com/logo/engadget.com', - sourceType: SourceType.blog, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-06-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-06-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId61, - name: 'WhiteHouse.gov', - description: 'Official website of the White House.', - url: 'https://www.whitehouse.gov', - logoUrl: 'https://api.companyenrich.com/logo/whitehouse.gov', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-07-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId62, - name: 'GOV.UK', - description: 'The official website for UK government services.', - url: 'https://www.gov.uk', - logoUrl: 'https://api.companyenrich.com/logo/gov.uk', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-07-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId63, - name: 'Canada.ca', - description: 'Official website of the Government of Canada.', - url: 'https://www.canada.ca/en.html', - logoUrl: 'https://api.companyenrich.com/logo/canada.ca', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[1], // Canada - createdAt: DateTime.parse('2023-07-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId64, - name: 'Australia.gov.au', - description: 'Official website of the Australian Government.', - url: 'https://www.australia.gov.au', - logoUrl: 'https://api.companyenrich.com/logo/australia.gov.au', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[3], // Australia - createdAt: DateTime.parse('2023-07-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId65, - name: 'Bundesregierung.de', - description: 'Official website of the German Federal Government.', - url: 'https://www.bundesregierung.de/breg-de', - logoUrl: 'https://api.companyenrich.com/logo/bundesregierung.de', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'de'), - headquarters: countriesFixturesData[4], // Germany - createdAt: DateTime.parse('2023-07-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId66, - name: 'Gouvernement.fr', - description: 'Official website of the French Government.', - url: 'https://www.gouvernement.fr', - logoUrl: 'https://api.companyenrich.com/logo/gouvernement.fr', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'fr'), - headquarters: countriesFixturesData[5], // France - createdAt: DateTime.parse('2023-07-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId67, - name: 'Kantei.go.jp', - description: 'Official website of the Prime Minister of Japan.', - url: 'https://japan.kantei.go.jp', - logoUrl: 'https://api.companyenrich.com/logo/kantei.go.jp', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[6], // Japan - createdAt: DateTime.parse('2023-07-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId68, - name: 'India.gov.in', - description: 'National Portal of India.', - url: 'https://www.india.gov.in', - logoUrl: 'https://api.companyenrich.com/logo/india.gov.in', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[8], // India - createdAt: DateTime.parse('2023-07-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId69, - name: 'Gov.br', - description: 'Official website of the Brazilian Government.', - url: 'https://www.gov.br/pt-br', - logoUrl: 'https://api.companyenrich.com/logo/gov.br', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'pt'), - headquarters: countriesFixturesData[9], // Brazil - createdAt: DateTime.parse('2023-07-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId70, - name: 'English.gov.cn', - description: 'Official web portal of the Chinese Government.', - url: 'http://english.gov.cn', - logoUrl: 'https://api.companyenrich.com/logo/gov.cn', - sourceType: SourceType.governmentSource, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[7], // China - createdAt: DateTime.parse('2023-07-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-07-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId71, - name: 'Google News', - description: 'A news aggregator service developed by Google.', - url: 'https://news.google.com', - logoUrl: 'https://api.companyenrich.com/logo/google.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId72, - name: 'Apple News', - description: 'A news aggregator app by Apple Inc.', - url: 'https://www.apple.com/apple-news/', - logoUrl: 'https://api.companyenrich.com/logo/apple.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId73, - name: 'Feedly', - description: 'A news aggregator application for various web browsers.', - url: 'https://feedly.com', - logoUrl: 'https://api.companyenrich.com/logo/feedly.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId74, - name: 'Flipboard', - description: 'A social-network aggregation, magazine-format mobile app.', - url: 'https://flipboard.com', - logoUrl: 'https://api.companyenrich.com/logo/flipboard.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId75, - name: 'SmartNews', - description: 'A mobile app for discovering news.', - url: 'https://www.smartnews.com', - logoUrl: 'https://api.companyenrich.com/logo/smartnews.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[6], // Japan - createdAt: DateTime.parse('2023-08-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId76, - name: 'Inoreader', - description: 'A web-based content and RSS feed reader.', - url: 'https://www.inoreader.com', - logoUrl: 'https://api.companyenrich.com/logo/inoreader.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[13], // Bulgaria - createdAt: DateTime.parse('2023-08-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId77, - name: 'The Old Reader', - description: 'A simple, web-based RSS reader.', - url: 'https://theoldreader.com', - logoUrl: 'https://api.companyenrich.com/logo/theoldreader.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId78, - name: 'NewsBlur', - description: 'A personal news reader.', - url: 'https://newsblur.com', - logoUrl: 'https://api.companyenrich.com/logo/newsblur.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId79, - name: 'Pocket', - description: 'An application for managing a reading list of articles.', - url: 'https://getpocket.com', - logoUrl: 'https://api.companyenrich.com/logo/getpocket.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId80, - name: 'Digg', - description: 'A news aggregator with a curated front page.', - url: 'https://digg.com', - logoUrl: 'https://api.companyenrich.com/logo/digg.com', - sourceType: SourceType.aggregator, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-08-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-08-10T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId81, - name: 'PR Newswire', - description: 'A distributor of press releases.', - url: 'https://www.prnewswire.com', - logoUrl: 'https://api.companyenrich.com/logo/prnewswire.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId82, - name: 'arXiv', - description: 'An open-access archive for scholarly articles.', - url: 'https://arxiv.org', - logoUrl: 'https://api.companyenrich.com/logo/arxiv.org', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-02T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-02T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId83, - name: 'The Lancet', - description: 'A weekly peer-reviewed general medical journal.', - url: 'https://www.thelancet.com', - logoUrl: 'https://api.companyenrich.com/logo/thelancet.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[2], // United Kingdom - createdAt: DateTime.parse('2023-09-03T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-03T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId84, - name: 'Google AI Blog', - description: 'The latest news from Google AI.', - url: 'https://ai.googleblog.com', - logoUrl: 'https://api.companyenrich.com/logo/googleblog.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-04T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-04T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId85, - name: 'Microsoft PressPass', - description: 'Official news and information from Microsoft.', - url: 'https://news.microsoft.com', - logoUrl: 'https://api.companyenrich.com/logo/microsoft.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-05T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-05T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId86, - name: 'JSTOR', - description: 'A digital library of academic journals, books, and sources.', - url: 'https://www.jstor.org', - logoUrl: 'https://api.companyenrich.com/logo/jstor.org', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-06T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-06T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId87, - name: 'Business Wire', - description: 'A company that disseminates press releases.', - url: 'https://www.businesswire.com', - logoUrl: 'https://api.companyenrich.com/logo/businesswire.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-07T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-07T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId88, - name: 'PLOS ONE', - description: 'A peer-reviewed open access scientific journal.', - url: 'https://journals.plos.org/plosone/', - logoUrl: 'https://api.companyenrich.com/logo/plos.org', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-08T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-08T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId89, - name: 'Apple Newsroom', - description: 'Official press releases from Apple.', - url: 'https://www.apple.com/newsroom/', - logoUrl: 'https://api.companyenrich.com/logo/apple.com', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-09T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-09T10:00:00.000Z'), - status: ContentStatus.active, - ), - Source( - id: kSourceId90, - name: 'The New England Journal of Medicine', - description: 'A weekly medical journal.', - url: 'https://www.nejm.org', - logoUrl: 'https://api.companyenrich.com/logo/nejm.org', - sourceType: SourceType.other, - language: languagesFixturesData.firstWhere((lang) => lang.code == 'en'), - headquarters: countriesFixturesData[0], // United States - createdAt: DateTime.parse('2023-09-10T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-09-10T10:00:00.000Z'), - status: ContentStatus.active, - ), +/// Generates a list of predefined sources for fixture data. +/// +/// This function can be configured to generate sources in either English or +/// Arabic. +List getSourcesFixturesData({String languageCode = 'en'}) { + // Ensure only approved languages are used, default to 'en'. + final resolvedLanguageCode = + ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + + final sources = []; + for (var i = 0; i < _sourceIds.length; i++) { + final id = _sourceIds[i]; + final name = _namesByLang[resolvedLanguageCode]![i]; + final description = _descriptionsByLang[resolvedLanguageCode]![i]; + final url = _urls[i]; + final logoUrl = 'https://api.companyenrich.com/logo/${url.split('/')[2]}'; + final sourceType = _sourceTypes[i]; + final language = + languagesFixturesData.firstWhere((lang) => lang.code == _langCodes[i]); + final headquarters = countriesFixturesData[_countryIndexes[i]]; + + sources.add( + Source( + id: id, + name: name, + description: description, + url: url, + logoUrl: logoUrl, + sourceType: sourceType, + language: language, + headquarters: headquarters, + createdAt: DateTime.parse('2023-01-01T10:00:00.000Z') + .add(Duration(days: i)), + updatedAt: DateTime.parse('2023-01-01T10:00:00.000Z') + .add(Duration(days: i)), + status: ContentStatus.active, + ), + ); + } + return sources; +} + +const _sourceIds = [ + kSourceId1, kSourceId2, kSourceId3, kSourceId4, kSourceId5, kSourceId6, + kSourceId7, kSourceId8, kSourceId9, kSourceId10, kSourceId11, kSourceId12, + kSourceId13, kSourceId14, kSourceId15, kSourceId16, kSourceId17, kSourceId18, + kSourceId19, kSourceId20, kSourceId21, kSourceId22, kSourceId23, kSourceId24, + kSourceId25, kSourceId26, kSourceId27, kSourceId28, kSourceId29, kSourceId30, + kSourceId31, kSourceId32, kSourceId33, kSourceId34, kSourceId35, kSourceId36, + kSourceId37, kSourceId38, kSourceId39, kSourceId40, kSourceId41, kSourceId42, + kSourceId43, kSourceId44, kSourceId45, kSourceId46, kSourceId47, kSourceId48, + kSourceId49, kSourceId50, kSourceId51, kSourceId52, kSourceId53, kSourceId54, + kSourceId55, kSourceId56, kSourceId57, kSourceId58, kSourceId59, kSourceId60, + kSourceId61, kSourceId62, kSourceId63, kSourceId64, kSourceId65, kSourceId66, + kSourceId67, kSourceId68, kSourceId69, kSourceId70, kSourceId71, kSourceId72, + kSourceId73, kSourceId74, kSourceId75, kSourceId76, kSourceId77, kSourceId78, + kSourceId79, kSourceId80, kSourceId81, kSourceId82, kSourceId83, kSourceId84, + kSourceId85, kSourceId86, kSourceId87, kSourceId88, kSourceId89, kSourceId90, +]; + +final Map> _namesByLang = { + 'en': [ + 'TechCrunch', 'BBC News', 'The New York Times', 'The Guardian', 'CNN', + 'Reuters', 'Al Jazeera English', 'Xinhua News Agency', 'The Times of India', + 'Folha de S.Paulo', 'San Francisco Chronicle', 'Manchester Evening News', + 'The Sydney Morning Herald', 'Le Parisien', 'The Toronto Star', + 'Berliner Morgenpost', 'The Asahi Shimbun (Tokyo)', + 'Hindustan Times (Mumbai)', 'O Globo (Rio de Janeiro)', + 'La Vanguardia (Barcelona)', 'USA Today', 'The Globe and Mail', + 'The Australian', 'Le Monde', 'Frankfurter Allgemeine Zeitung', + 'The Yomiuri Shimbun', "People's Daily", 'O Estado de S. Paulo', 'El País', + 'Corriere della Sera', 'CNN International', 'BBC World News', + 'The Economist', 'France 24', 'Deutsche Welle', 'The Wall Street Journal', + 'Associated Press (AP)', 'Agence France-Presse (AFP)', 'RT', 'CGTN', + 'ESPN', 'Nature', 'The Hollywood Reporter', 'Vogue', 'National Geographic', + 'Wired', 'Bon Appétit', 'Architectural Digest', 'Car and Driver', + 'PC Gamer', 'Stratechery by Ben Thompson', 'Daring Fireball', + 'Wait But Why', 'Smitten Kitchen', 'The Verge', 'Gizmodo', 'Kotaku', + 'Lifehacker', 'Mashable', 'Engadget', 'WhiteHouse.gov', 'GOV.UK', + 'Canada.ca', 'Australia.gov.au', 'Bundesregierung.de', 'Gouvernement.fr', + 'Kantei.go.jp', 'India.gov.in', 'Gov.br', 'English.gov.cn', 'Google News', + 'Apple News', 'Feedly', 'Flipboard', 'SmartNews', 'Inoreader', + 'The Old Reader', 'NewsBlur', 'Pocket', 'Digg', 'PR Newswire', 'arXiv', + 'The Lancet', 'Google AI Blog', 'Microsoft PressPass', 'JSTOR', + 'Business Wire', 'PLOS ONE', 'Apple Newsroom', + 'The New England Journal of Medicine', + ], + 'ar': [ + 'تك كرانش', 'بي بي سي نيوز', 'نيويورك تايمز', 'الجارديان', 'سي إن إن', + 'رويترز', 'الجزيرة الإنجليزية', 'وكالة أنباء شينخوا', 'تايمز أوف إنديا', + 'فولها دي ساو باولو', 'سان فرانسيسكو كرونيكل', 'مانشستر إيفننغ نيوز', + 'سيدني مورنينغ هيرالد', 'لو باريزيان', 'تورنتو ستار', + 'برلينر مورغنبوست', 'أساهي شيمبون (طوكيو)', 'هندوستان تايمز (مومباي)', + 'أو جلوبو (ريو دي جانيرو)', 'لا فانجارديا (برشلونة)', 'يو إس إيه توداي', + 'ذا جلوب آند ميل', 'ذي أستراليان', 'لوموند', + 'فرانكفورتر ألجماينه تسايتונג', 'يomiuri Shimbun', 'صحيفة الشعب اليومية', + 'أو إستาดو دي ساو باولو', 'إل باييس', 'كورييري ديلا سيرا', + 'سي إن إن الدولية', 'بي بي سي وورلد نيوز', 'ذي إيكونوميست', 'فرانس 24', + 'دويتشه فيله', 'وول ستريت جورنال', 'أسوشيتد برس (AP)', + 'وكالة فرانس برس (AFP)', 'آر تي', 'سي جي تي إن', 'إي إس بي إن', 'نيتشر', + 'هوليوود ريبورتر', 'فوغ', 'ناشيونال جيوغرافيك', 'وايرد', 'بون أبيتيت', + 'أركيتكتشرال دايجست', 'كار آند درايفر', 'بي سي جيمر', + 'סטרטכרי بقلم بن طومسون', 'دارينج فايربول', 'ويت بات واي', + 'สมิตเทน คิทเช่น', 'ذا فيرج', 'جيزمودو', 'كوتاكو', 'لايف هاكر', 'ماشابل', + 'إنガジェット', 'WhiteHouse.gov', 'GOV.UK', 'Canada.ca', + 'Australia.gov.au', 'Bundesregierung.de', 'Gouvernement.fr', + 'Kantei.go.jp', 'India.gov.in', 'Gov.br', 'English.gov.cn', 'أخبار جوجل', + 'أخبار أبل', 'فيدلي', 'فليبورد', 'سمارت نيوز', 'إينوريدر', + 'ذا أولد ريدر', 'نيوز بلور', 'بوكيت', 'ديغ', 'بي آر نيوزواير', 'أرخايف', + 'ذا لانسيت', 'مدونة جوجل للذكاء الاصطناعي', 'مايكروسوفت بريس باس', + 'جيستور', 'بزنس واير', 'بلوس ون', 'غرفة أخبار أبل', + 'مجلة نيو إنجلاند الطبية', + ], +}; + +final Map> _descriptionsByLang = { + 'en': [ + 'Leading online publisher of technology news.', + 'Breaking news, sport, TV, radio and a whole lot more.', + 'Breaking News, World News & Multimedia.', + 'Latest news, sport, business, comment and reviews from the Guardian.', + 'Breaking News, Latest News and Videos.', + 'Business, financial, national and international news.', + 'News, analysis, and opinion from the Middle East and around the world.', + "Official press agency of the People's Republic of China.", + 'Latest and Breaking News from India.', + 'Brazilian daily newspaper.', + 'News from the San Francisco Bay Area.', + 'Covering Greater Manchester, UK.', + 'Independent journalism for Sydney, Australia.', + 'Local news for Paris and the Île-de-France region.', + 'News and stories for Toronto, Canada.', + 'Daily news for Berlin, Germany.', + 'Local and national news from a Tokyo perspective.', + 'Latest news from Mumbai, India.', + 'News from Rio de Janeiro, Brazil.', + 'News from Barcelona and Catalonia, Spain.', + 'National news from across the United States.', + "Canada's national newspaper.", + 'National news of Australia.', + 'French national daily newspaper.', + 'German national newspaper.', + 'Japanese national newspaper.', + 'Official newspaper of the Central Committee of the CCP.', + 'Brazilian national newspaper.', + 'Spanish national daily newspaper.', + 'Italian national daily newspaper.', + 'Global news coverage from CNN.', + 'International news from the BBC.', + 'In-depth analysis of international news and business.', + 'French perspective on international current events.', + "Germany's international broadcaster.", + 'Global business and financial news.', + 'Global news network.', + 'International news agency based in Paris.', + 'Russian state-funded international television network.', + 'Chinese state-funded international television network.', + 'The worldwide leader in sports.', + 'International journal of science.', + 'The definitive voice of the entertainment industry.', + 'Fashion, beauty, and lifestyle.', + 'Science, exploration, and adventure.', + 'How technology is changing every aspect of our lives.', + 'Food and cooking magazine.', + 'The international design authority.', + 'Automotive news and reviews.', + 'Global authority on PC games.', + 'Analysis of the strategy and business of technology.', + 'By John Gruber. On technology and Apple.', + 'A popular long-form, stick-figure-illustrated blog.', + 'A home cooking blog from a tiny kitchen in New York City.', + 'A technology news and media network operated by Vox Media.', + 'A design, technology, science and science fiction website.', + 'A video game website and blog.', + 'A weblog about life hacks and software.', + 'A global, multi-platform media and entertainment company.', + 'A multilingual technology blog network.', + 'Official website of the White House.', + 'The official website for UK government services.', + 'Official website of the Government of Canada.', + 'Official website of the Australian Government.', + 'Official website of the German Federal Government.', + 'Official website of the French Government.', + 'Official website of the Prime Minister of Japan.', + 'National Portal of India.', + 'Official website of the Brazilian Government.', + 'Official web portal of the Chinese Government.', + 'A news aggregator service developed by Google.', + 'A news aggregator app by Apple Inc.', + 'A news aggregator application for various web browsers.', + 'A social-network aggregation, magazine-format mobile app.', + 'A mobile app for discovering news.', + 'A web-based content and RSS feed reader.', + 'A simple, web-based RSS reader.', + 'A personal news reader.', + 'An application for managing a reading list of articles.', + 'A news aggregator with a curated front page.', + 'A distributor of press releases.', + 'An open-access archive for scholarly articles.', + 'A weekly peer-reviewed general medical journal.', + 'The latest news from Google AI.', + 'Official news and information from Microsoft.', + 'A digital library of academic journals, books, and sources.', + 'A company that disseminates press releases.', + 'A peer-reviewed open access scientific journal.', + 'Official press releases from Apple.', + 'A weekly medical journal.', + ], + 'ar': [ + 'الناشر الرائد عبر الإنترنت لأخبار التكنولوجيا.', + 'الأخبار العاجلة والرياضة والتلفزيون والراديو والكثير.', + 'الأخبار العاجلة والأخبار العالمية والوسائط المتعددة.', + 'آخر الأخبار والرياضة والأعمال والتعليقات والمراجعات من الجارديان.', + 'الأخبار العاجلة وآخر الأخبار ومقاطع الفيديو.', + 'الأعمال والأخبار المالية والوطنية والدولية.', + 'الأخبار والتحليلات والآراء من الشرق الأوسط وحول العالم.', + 'وكالة الأنباء الرسمية لجمهورية الصين الشعبية.', + 'آخر الأخبار العاجلة من الهند.', + 'صحيفة يومية برازيلية.', + 'أخبار من منطقة خليج سان فرانسيسكو.', + 'تغطية مانشستر الكبرى، المملكة المتحدة.', + 'صحافة مستقلة لسيدني، أستراليا.', + 'الأخبار المحلية لباريس ومنطقة إيل دو فرانس.', + 'الأخبار والقصص لتورنتو، كندا.', + 'الأخبار اليومية لبرلين، ألمانيا.', + 'الأخبار المحلية والوطنية من منظور طوكيو.', + 'آخر الأخبار من مومباي، الهند.', + 'أخبار من ريو دي جانيرو، البرازيل.', + 'أخبار من برشلونة وكاتالونيا، إسبانيا.', + 'الأخبار الوطنية من جميع أنحاء الولايات المتحدة.', + 'الصحيفة الوطنية الكندية.', + 'الأخبار الوطنية لأستراليا.', + 'صحيفة يومية وطنية فرنسية.', + 'صحيفة وطنية ألمانية.', + 'صحيفة وطنية يابانية.', + 'الصحيفة الرسمية للجنة المركزية للحزب الشيوعي الصيني.', + 'صحيفة وطنية برازيلية.', + 'صحيفة يومية وطنية إسبانية.', + 'صحيفة يومية وطنية إيطالية.', + 'تغطية إخبارية عالمية من CNN.', + 'الأخبار الدولية من بي بي سي.', + 'تحليل متعمق للأخبار الدولية والأعمال.', + 'منظور فرنسي للأحداث الجارية الدولية.', + 'المذيع الدولي لألمانيا.', + 'الأخبار التجارية والمالية العالمية.', + 'شبكة أخبار عالمية.', + 'وكالة أنباء دولية مقرها باريس.', + 'شبكة تلفزيونية دولية ممولة من الدولة الروسية.', + 'شبكة تلفزيونية دولية ممولة من الدولة الصينية.', + 'الشركة الرائدة عالميًا في مجال الرياضة.', + 'مجلة دولية للعلوم.', + 'الصوت النهائي لصناعة الترفيه.', + 'الموضة والجمال وأسلوب الحياة.', + 'العلوم والاستكشاف والمغامرة.', + 'كيف تغير التكنولوجيا كل جانب من جوانب حياتنا.', + 'مجلة طعام وطبخ.', + 'السلطة الدولية للتصميم.', + 'أخبار ومراجعات السيارات.', + 'السلطة العالمية في ألعاب الكمبيوتر.', + 'تحليل استراتيجية وأعمال التكنولوجيا.', + 'بقلم جون غروبر. عن التكنولوجيا وأبل.', + 'مدونة شهيرة طويلة ومصورة بشخصيات كرتونية.', + 'مدونة طبخ منزلية من مطبخ صغير في مدينة نيويورك.', + 'شبكة أخبار ووسائط تكنولوجية تديرها Vox Media.', + 'موقع للتصميم والتكنولوجيا والعلوم والخيال العلمي.', + 'موقع ومدونة لألعاب الفيديو.', + 'مدونة حول حيل الحياة والبرامج.', + 'شركة إعلام وترفيه عالمية متعددة المنصات.', + 'شبكة مدونات تكنولوجية متعددة اللغات.', + 'الموقع الرسمي للبيت الأبيض.', + 'الموقع الرسمي لخدمات حكومة المملكة المتحدة.', + 'الموقع الرسمي لحكومة كندا.', + 'الموقع الرسمي للحكومة الأسترالية.', + 'الموقع الرسمي للحكومة الفيدرالية الألمانية.', + 'الموقع الرسمي للحكومة الفرنسية.', + 'الموقع الرسمي لرئيس وزراء اليابان.', + 'البوابة الوطنية للهند.', + 'الموقع الرسمي للحكومة البرازيلية.', + 'البوابة الإلكترونية الرسمية للحكومة الصينية.', + 'خدمة تجميع الأخبار التي طورتها جوجل.', + 'تطبيق مجمع أخبار من شركة أبل.', + 'تطبيق مجمع أخبار لمختلف متصفحات الويب.', + 'تطبيق جوال لتجميع الشبكات الاجتماعية بتنسيق مجلة.', + 'تطبيق جوال لاكتشاف الأخبار.', + 'قارئ محتوى وخلاصة RSS على شبكة الإنترنت.', + 'قارئ RSS بسيط على شبكة الإنترنت.', + 'قارئ أخبار شخصي.', + 'تطبيق لإدارة قائمة قراءة المقالات.', + 'مجمع أخبار بصفحة أمامية منسقة.', + 'موزع للبيانات الصحفية.', + 'أرشيف مفتوح الوصول للمقالات العلمية.', + 'مجلة طبية عامة أسبوعية محكمة.', + 'آخر الأخبار من Google AI.', + 'الأخبار والمعلومات الرسمية من مايكروسوفت.', + 'مكتبة رقمية للمجلات الأكاديمية والكتب والمصادر.', + 'شركة تنشر البيانات الصحفية.', + 'مجلة علمية مفتوحة الوصول ومحكمة.', + 'البيانات الصحفية الرسمية من Apple.', + 'مجلة طبية أسبوعية.', + ], +}; + +const _urls = [ + 'https://techcrunch.com', 'https://www.bbc.com/news', + 'https://www.nytimes.com', 'https://www.theguardian.com', + 'https://edition.cnn.com', 'https://www.reuters.com', + 'https://www.aljazeera.com', 'http://www.xinhuanet.com/english/', + 'https://timesofindia.indiatimes.com/', 'https://www.folha.uol.com.br/', + 'https://www.sfchronicle.com', 'https://www.manchestereveningnews.co.uk', + 'https://www.smh.com.au', 'https://www.leparisien.fr', + 'https://www.thestar.com', 'https://www.morgenpost.de', + 'https://www.asahi.com/area/tokyo/', + 'https://www.hindustantimes.com/mumbai-news', + 'https://oglobo.globo.com/rio/', 'https://www.lavanguardia.com/local/barcelona', + 'https://www.usatoday.com', 'https://www.theglobeandmail.com', + 'https://www.theaustralian.com.au', 'https://www.lemonde.fr', + 'https://www.faz.net', 'https://www.yomiuri.co.jp', 'http://en.people.cn', + 'https://www.estadao.com.br', 'https://elpais.com', + 'https://www.corriere.it', 'https://edition.cnn.com', + 'https://www.bbc.com/news/world', 'https://www.economist.com', + 'https://www.france24.com/en/', 'https://www.dw.com/en/', + 'https://www.wsj.com', 'https://apnews.com', 'https://www.afp.com/en', + 'https://www.rt.com', 'https://www.cgtn.com', 'https://www.espn.com', + 'https://www.nature.com', 'https://www.hollywoodreporter.com', + 'https://www.vogue.com', 'https://www.nationalgeographic.com', + 'https://www.wired.com', 'https://www.bonappetit.com', + 'https://www.architecturaldigest.com', 'https://www.caranddriver.com', + 'https://www.pcgamer.com', 'https://stratechery.com', + 'https://daringfireball.net', 'https://waitbutwhy.com', + 'https://smittenkitchen.com', 'https://www.theverge.com', + 'https://gizmodo.com', 'https://kotaku.com', 'https://lifehacker.com', + 'https://mashable.com', 'https://www.engadget.com', + 'https://www.whitehouse.gov', 'https://www.gov.uk', + 'https://www.canada.ca/en.html', 'https://www.australia.gov.au', + 'https://www.bundesregierung.de/breg-de', 'https://www.gouvernement.fr', + 'https://japan.kantei.go.jp', 'https://www.india.gov.in', + 'https://www.gov.br/pt-br', 'http://english.gov.cn', + 'https://news.google.com', 'https://www.apple.com/apple-news/', + 'https://feedly.com', 'https://flipboard.com', 'https://www.smartnews.com', + 'https://www.inoreader.com', 'https://theoldreader.com', + 'https://newsblur.com', 'https://getpocket.com', 'https://digg.com', + 'https://www.prnewswire.com', 'https://arxiv.org', + 'https://www.thelancet.com', 'https://ai.googleblog.com', + 'https://news.microsoft.com', 'https://www.jstor.org', + 'https://www.businesswire.com', 'https://journals.plos.org/plosone/', + 'https://www.apple.com/newsroom/', 'https://www.nejm.org', +]; + +const _sourceTypes = [ + SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, + SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, + SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, + SourceType.newsAgency, SourceType.localNewsOutlet, + SourceType.localNewsOutlet, SourceType.localNewsOutlet, + SourceType.localNewsOutlet, SourceType.localNewsOutlet, + SourceType.localNewsOutlet, SourceType.localNewsOutlet, + SourceType.localNewsOutlet, SourceType.localNewsOutlet, + SourceType.localNewsOutlet, SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, SourceType.specializedPublisher, + SourceType.specializedPublisher, SourceType.specializedPublisher, + SourceType.specializedPublisher, SourceType.specializedPublisher, + SourceType.specializedPublisher, SourceType.specializedPublisher, + SourceType.specializedPublisher, SourceType.specializedPublisher, + SourceType.specializedPublisher, SourceType.blog, SourceType.blog, + SourceType.blog, SourceType.blog, SourceType.blog, SourceType.blog, + SourceType.blog, SourceType.blog, SourceType.blog, SourceType.blog, + SourceType.governmentSource, SourceType.governmentSource, + SourceType.governmentSource, SourceType.governmentSource, + SourceType.governmentSource, SourceType.governmentSource, + SourceType.governmentSource, SourceType.governmentSource, + SourceType.governmentSource, SourceType.governmentSource, + SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, + SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, + SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, + SourceType.aggregator, SourceType.other, SourceType.other, SourceType.other, + SourceType.other, SourceType.other, SourceType.other, SourceType.other, + SourceType.other, SourceType.other, SourceType.other, +]; + +const _langCodes = [ + 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'pt', 'en', 'en', + 'en', 'fr', 'en', 'de', 'ja', 'en', 'pt', 'es', 'en', 'en', 'en', 'fr', + 'de', 'ja', 'en', 'pt', 'es', 'it', 'en', 'en', 'en', 'en', 'de', 'en', + 'en', 'fr', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', + 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', + 'en', 'en', 'en', 'en', 'de', 'fr', 'en', 'en', 'pt', 'en', 'en', 'en', + 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', + 'en', 'en', 'en', 'en', 'en', 'en', +]; + +const _countryIndexes = [ + 0, 2, 0, 2, 0, 0, 0, 7, 8, 9, 0, 2, 3, 5, 1, 4, 6, 8, 9, 10, 0, 1, 3, 5, + 4, 6, 7, 9, 10, 11, 0, 2, 2, 5, 4, 0, 0, 5, 12, 7, 0, 2, 0, 0, 0, 0, 0, + 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 4, 5, 6, 8, 9, 7, 0, + 0, 0, 0, 6, 13, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, ]; From 96e96ff3c8ff61f31a6a1947ba42d5346120dfa0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:26:59 +0100 Subject: [PATCH 68/93] refactor: make topics fixtures multilingual --- lib/src/fixtures/topics.dart | 201 +++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 92 deletions(-) diff --git a/lib/src/fixtures/topics.dart b/lib/src/fixtures/topics.dart index 331b55d..82ea603 100644 --- a/lib/src/fixtures/topics.dart +++ b/lib/src/fixtures/topics.dart @@ -2,96 +2,113 @@ import 'package:core/src/enums/enums.dart'; import 'package:core/src/fixtures/fixture_ids.dart'; import 'package:core/src/models/entities/topic.dart'; -/// A list of predefined topics for fixture data. -final topicsFixturesData = [ - Topic( - id: kTopicId1, - name: 'Technology', - description: 'News and updates from the world of technology.', - iconUrl: 'https://example.com/icons/tech.png', - createdAt: DateTime.parse('2023-01-01T10:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-01T10:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId2, - name: 'Sports', - description: 'Latest scores, highlights, and news from sports.', - iconUrl: 'https://example.com/icons/sports.png', - createdAt: DateTime.parse('2023-01-02T11:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-02T11:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId3, - name: 'Politics', - description: 'Updates on political events and government policies.', - iconUrl: 'https://example.com/icons/politics.png', - createdAt: DateTime.parse('2023-01-03T12:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-03T12:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId4, - name: 'Science', - description: 'Discoveries and breakthroughs in scientific research.', - iconUrl: 'https://example.com/icons/science.png', - createdAt: DateTime.parse('2023-01-04T13:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-04T13:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId5, - name: 'Health', - description: 'Information and advice on health and wellness.', - iconUrl: 'https://example.com/icons/health.png', - createdAt: DateTime.parse('2023-01-05T14:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-05T14:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId6, - name: 'Entertainment', - description: 'News from movies, music, and pop culture.', - iconUrl: 'https://example.com/icons/entertainment.png', - createdAt: DateTime.parse('2023-01-06T15:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-06T15:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId7, - name: 'Business', - description: 'Financial markets, economy, and corporate news.', - iconUrl: 'https://example.com/icons/business.png', - createdAt: DateTime.parse('2023-01-07T16:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-07T16:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId8, - name: 'Travel', - description: 'Guides, tips, and news for travelers.', - iconUrl: 'https://example.com/icons/travel.png', - createdAt: DateTime.parse('2023-01-08T17:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-08T17:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId9, - name: 'Food', - description: 'Recipes, culinary trends, and food industry news.', - iconUrl: 'https://example.com/icons/food.png', - createdAt: DateTime.parse('2023-01-09T18:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-09T18:00:00.000Z'), - status: ContentStatus.active, - ), - Topic( - id: kTopicId10, - name: 'Education', - description: 'Developments in education and learning.', - iconUrl: 'https://example.com/icons/education.png', - createdAt: DateTime.parse('2023-01-10T19:00:00.000Z'), - updatedAt: DateTime.parse('2023-01-10T19:00:00.000Z'), - status: ContentStatus.active, - ), +/// Generates a list of predefined topics for fixture data. +/// +/// This function can be configured to generate topics in either English or +/// Arabic. +List getTopicsFixturesData({String languageCode = 'en'}) { + // Ensure only approved languages are used, default to 'en'. + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; + + final topics = []; + for (var i = 0; i < _topicIds.length; i++) { + topics.add( + Topic( + id: _topicIds[i], + name: _namesByLang[resolvedLanguageCode]![i], + description: _descriptionsByLang[resolvedLanguageCode]![i], + iconUrl: 'https://example.com/icons/${_iconNames[i]}.png', + createdAt: DateTime.parse( + '2023-01-01T10:00:00.000Z', + ).add(Duration(days: i)), + updatedAt: DateTime.parse( + '2023-01-01T10:00:00.000Z', + ).add(Duration(days: i)), + status: ContentStatus.active, + ), + ); + } + return topics; +} + +const _topicIds = [ + kTopicId1, + kTopicId2, + kTopicId3, + kTopicId4, + kTopicId5, + kTopicId6, + kTopicId7, + kTopicId8, + kTopicId9, + kTopicId10, +]; + +const _iconNames = [ + 'tech', + 'sports', + 'politics', + 'science', + 'health', + 'entertainment', + 'business', + 'travel', + 'food', + 'education', ]; + +final Map> _namesByLang = { + 'en': [ + 'Technology', + 'Sports', + 'Politics', + 'Science', + 'Health', + 'Entertainment', + 'Business', + 'Travel', + 'Food', + 'Education', + ], + 'ar': [ + 'التكنولوجيا', + 'الرياضة', + 'السياسة', + 'العلوم', + 'الصحة', + 'الترفيه', + 'الأعمال', + 'السفر', + 'الطعام', + 'التعليم', + ], +}; + +final Map> _descriptionsByLang = { + 'en': [ + 'News and updates from the world of technology.', + 'Latest scores, highlights, and news from sports.', + 'Updates on political events and government policies.', + 'Discoveries and breakthroughs in scientific research.', + 'Information and advice on health and wellness.', + 'News from movies, music, and pop culture.', + 'Financial markets, economy, and corporate news.', + 'Guides, tips, and news for travelers.', + 'Recipes, culinary trends, and food industry news.', + 'Developments in education and learning.', + ], + 'ar': [ + 'أخبار وتحديثات من عالم التكنولوجيا.', + 'آخر النتائج والأهداف والأخبار من عالم الرياضة.', + 'تحديثات حول الأحداث السياسية والسياسات الحكومية.', + 'اكتشافات وإنجازات في البحث العلمي.', + 'معلومات ونصائح حول الصحة والعافية.', + 'أخبار من الأفلام والموسيقى وثقافة البوب.', + 'الأسواق المالية والاقتصاد وأخبار الشركات.', + 'أدلة ونصائح وأخبار للمسافرين.', + 'وصفات واتجاهات الطهي وأخبار صناعة المواد الغذائية.', + 'التطورات في التعليم والتعلم.', + ], +}; From 93d00c6869fcf6b8dc318d16997ebd11e4be8a68 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:28:06 +0100 Subject: [PATCH 69/93] fix(core): update reports fixture to use proper getter functions - Replace direct imports with getter functions for comments and sources fixtures - Modify reportsFixturesData to use getHeadlinesFixturesData() and getSourcesFixturesData() - Update entity ID assignment to use getHeadlineCommentsFixturesData() for comments --- lib/src/fixtures/reports.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/fixtures/reports.dart b/lib/src/fixtures/reports.dart index 03e6f66..8e57598 100644 --- a/lib/src/fixtures/reports.dart +++ b/lib/src/fixtures/reports.dart @@ -1,5 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/fixtures/comments.dart'; /// A list of predefined reports for fixture data. /// This creates 1 report for each of the first 10 users, targeting a mix of @@ -7,7 +6,7 @@ import 'package:core/src/fixtures/comments.dart'; final List reportsFixturesData = () { final reports = []; final users = usersFixturesData.take(10).toList(); - final headlines = headlinesFixturesData.take(10).toList(); + final headlines = getHeadlinesFixturesData().take(10).toList(); final reportIds = [ kReportId1, kReportId2, @@ -56,7 +55,7 @@ final List reportsFixturesData = () { id: reportIds[i], reporterUserId: user.id, entityType: ReportableEntity.source, - entityId: sourcesFixturesData[i].id, + entityId: getSourcesFixturesData()[i].id, reason: sourceReasons[i % sourceReasons.length].name, additionalComments: 'This source has too many ads.', status: status, @@ -70,7 +69,7 @@ final List reportsFixturesData = () { id: reportIds[i], reporterUserId: user.id, entityType: ReportableEntity.comment, - entityId: commentsFixturesData[i].id, + entityId: getHeadlineCommentsFixturesData()[i].id, reason: commentReasons[i % commentReasons.length].name, additionalComments: 'This comment is spam.', status: status, From 865bbb707130620d975bbc392afa3c8e71130aea Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:28:39 +0100 Subject: [PATCH 70/93] feat(comment): add language field and update copyWith method - Add Language field to Comment class - Update copyWith method to include language parameter - Rename comment template to user_comment for clarity --- .../models/user_generated_content/comment.dart | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/src/models/user_generated_content/comment.dart b/lib/src/models/user_generated_content/comment.dart index ecc683a..ba4c12a 100644 --- a/lib/src/models/user_generated_content/comment.dart +++ b/lib/src/models/user_generated_content/comment.dart @@ -1,5 +1,6 @@ import 'package:core/src/enums/comment_status.dart'; import 'package:core/src/enums/engageable_type.dart'; +import 'package:core/src/models/entities/language.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -7,18 +8,19 @@ import 'package:meta/meta.dart'; part 'comment.g.dart'; -/// {@template comment} +/// {@template user_comment} /// Represents a user-submitted comment on a specific piece of content. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class Comment extends Equatable { - /// {@macro comment} + /// {@macro user_comment} const Comment({ required this.id, required this.userId, required this.entityId, required this.entityType, + required this.language, required this.content, required this.createdAt, required this.updatedAt, @@ -41,6 +43,9 @@ class Comment extends Equatable { /// The type of entity being commented on. final EngageableType entityType; + /// The language of the comment. + final Language language; + /// The text content of the comment. final String content; @@ -64,6 +69,7 @@ class Comment extends Equatable { userId, entityId, entityType, + language, content, status, createdAt, @@ -71,12 +77,17 @@ class Comment extends Equatable { ]; /// Creates a copy of this [Comment] with updated values. - Comment copyWith({String? content, CommentStatus? status}) { + Comment copyWith({ + String? content, + Language? language, + CommentStatus? status, + }) { return Comment( id: id, userId: userId, entityId: entityId, entityType: entityType, + language: language ?? this.language, content: content ?? this.content, status: status ?? this.status, createdAt: createdAt, From d2f1581cabbb102f0aa453d6a035ab7bea208b7a Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:32:34 +0100 Subject: [PATCH 71/93] chore: barrels --- lib/src/enums/enums.dart | 1 + lib/src/fixtures/fixture_ids.dart | 102 ++++++++++++++++++ lib/src/fixtures/fixtures.dart | 4 + lib/src/fixtures/headline_comments.dart | 7 +- .../user_generated_content.dart | 3 +- 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/lib/src/enums/enums.dart b/lib/src/enums/enums.dart index fe0d2e6..832ba93 100644 --- a/lib/src/enums/enums.dart +++ b/lib/src/enums/enums.dart @@ -12,6 +12,7 @@ export 'content_status.dart'; export 'content_type.dart'; export 'dashboard_user_role.dart'; export 'device_platform.dart'; +export 'engageable_type.dart'; export 'engagement_mode.dart'; export 'feed_decorator_category.dart'; export 'feed_decorator_type.dart'; diff --git a/lib/src/fixtures/fixture_ids.dart b/lib/src/fixtures/fixture_ids.dart index 979db06..dfc584e 100644 --- a/lib/src/fixtures/fixture_ids.dart +++ b/lib/src/fixtures/fixture_ids.dart @@ -1276,3 +1276,105 @@ const String kReportId7 = 'rep0000000000000000000007'; const String kReportId8 = 'rep0000000000000000000008'; const String kReportId9 = 'rep0000000000000000000009'; const String kReportId10 = 'rep0000000000000000000010'; + +/// Engagement Fixture IDs +const String kEngagementId1 = 'eng0000000000000000000001'; +const String kEngagementId2 = 'eng0000000000000000000002'; +const String kEngagementId3 = 'eng0000000000000000000003'; +const String kEngagementId4 = 'eng0000000000000000000004'; +const String kEngagementId5 = 'eng0000000000000000000005'; +const String kEngagementId6 = 'eng0000000000000000000006'; +const String kEngagementId7 = 'eng0000000000000000000007'; +const String kEngagementId8 = 'eng0000000000000000000008'; +const String kEngagementId9 = 'eng0000000000000000000009'; +const String kEngagementId10 = 'eng0000000000000000000010'; +const String kEngagementId11 = 'eng0000000000000000000011'; +const String kEngagementId12 = 'eng0000000000000000000012'; +const String kEngagementId13 = 'eng0000000000000000000013'; +const String kEngagementId14 = 'eng0000000000000000000014'; +const String kEngagementId15 = 'eng0000000000000000000015'; +const String kEngagementId16 = 'eng0000000000000000000016'; +const String kEngagementId17 = 'eng0000000000000000000017'; +const String kEngagementId18 = 'eng0000000000000000000018'; +const String kEngagementId19 = 'eng0000000000000000000019'; +const String kEngagementId20 = 'eng0000000000000000000020'; +const String kEngagementId21 = 'eng0000000000000000000021'; +const String kEngagementId22 = 'eng0000000000000000000022'; +const String kEngagementId23 = 'eng0000000000000000000023'; +const String kEngagementId24 = 'eng0000000000000000000024'; +const String kEngagementId25 = 'eng0000000000000000000025'; +const String kEngagementId26 = 'eng0000000000000000000026'; +const String kEngagementId27 = 'eng0000000000000000000027'; +const String kEngagementId28 = 'eng0000000000000000000028'; +const String kEngagementId29 = 'eng0000000000000000000029'; +const String kEngagementId30 = 'eng0000000000000000000030'; +const String kEngagementId31 = 'eng0000000000000000000031'; +const String kEngagementId32 = 'eng0000000000000000000032'; +const String kEngagementId33 = 'eng0000000000000000000033'; +const String kEngagementId34 = 'eng0000000000000000000034'; +const String kEngagementId35 = 'eng0000000000000000000035'; +const String kEngagementId36 = 'eng0000000000000000000036'; +const String kEngagementId37 = 'eng0000000000000000000037'; +const String kEngagementId38 = 'eng0000000000000000000038'; +const String kEngagementId39 = 'eng0000000000000000000039'; +const String kEngagementId40 = 'eng0000000000000000000040'; +const String kEngagementId41 = 'eng0000000000000000000041'; +const String kEngagementId42 = 'eng0000000000000000000042'; +const String kEngagementId43 = 'eng0000000000000000000043'; +const String kEngagementId44 = 'eng0000000000000000000044'; +const String kEngagementId45 = 'eng0000000000000000000045'; +const String kEngagementId46 = 'eng0000000000000000000046'; +const String kEngagementId47 = 'eng0000000000000000000047'; +const String kEngagementId48 = 'eng0000000000000000000048'; +const String kEngagementId49 = 'eng0000000000000000000049'; +const String kEngagementId50 = 'eng0000000000000000000050'; +const String kEngagementId51 = 'eng0000000000000000000051'; +const String kEngagementId52 = 'eng0000000000000000000052'; +const String kEngagementId53 = 'eng0000000000000000000053'; +const String kEngagementId54 = 'eng0000000000000000000054'; +const String kEngagementId55 = 'eng0000000000000000000055'; +const String kEngagementId56 = 'eng0000000000000000000056'; +const String kEngagementId57 = 'eng0000000000000000000057'; +const String kEngagementId58 = 'eng0000000000000000000058'; +const String kEngagementId59 = 'eng0000000000000000000059'; +const String kEngagementId60 = 'eng0000000000000000000060'; +const String kEngagementId61 = 'eng0000000000000000000061'; +const String kEngagementId62 = 'eng0000000000000000000062'; +const String kEngagementId63 = 'eng0000000000000000000063'; +const String kEngagementId64 = 'eng0000000000000000000064'; +const String kEngagementId65 = 'eng0000000000000000000065'; +const String kEngagementId66 = 'eng0000000000000000000066'; +const String kEngagementId67 = 'eng0000000000000000000067'; +const String kEngagementId68 = 'eng0000000000000000000068'; +const String kEngagementId69 = 'eng0000000000000000000069'; +const String kEngagementId70 = 'eng0000000000000000000070'; +const String kEngagementId71 = 'eng0000000000000000000071'; +const String kEngagementId72 = 'eng0000000000000000000072'; +const String kEngagementId73 = 'eng0000000000000000000073'; +const String kEngagementId74 = 'eng0000000000000000000074'; +const String kEngagementId75 = 'eng0000000000000000000075'; +const String kEngagementId76 = 'eng0000000000000000000076'; +const String kEngagementId77 = 'eng0000000000000000000077'; +const String kEngagementId78 = 'eng0000000000000000000078'; +const String kEngagementId79 = 'eng0000000000000000000079'; +const String kEngagementId80 = 'eng0000000000000000000080'; +const String kEngagementId81 = 'eng0000000000000000000081'; +const String kEngagementId82 = 'eng0000000000000000000082'; +const String kEngagementId83 = 'eng0000000000000000000083'; +const String kEngagementId84 = 'eng0000000000000000000084'; +const String kEngagementId85 = 'eng0000000000000000000085'; +const String kEngagementId86 = 'eng0000000000000000000086'; +const String kEngagementId87 = 'eng0000000000000000000087'; +const String kEngagementId88 = 'eng0000000000000000000088'; +const String kEngagementId89 = 'eng0000000000000000000089'; +const String kEngagementId90 = 'eng0000000000000000000090'; +const String kEngagementId91 = 'eng0000000000000000000091'; +const String kEngagementId92 = 'eng0000000000000000000092'; +const String kEngagementId93 = 'eng0000000000000000000093'; +const String kEngagementId94 = 'eng0000000000000000000094'; +const String kEngagementId95 = 'eng0000000000000000000095'; +const String kEngagementId96 = 'eng0000000000000000000096'; +const String kEngagementId97 = 'eng0000000000000000000097'; +const String kEngagementId98 = 'eng0000000000000000000098'; +const String kEngagementId99 = 'eng0000000000000000000099'; +const String kEngagementId100 = 'eng0000000000000000000100'; diff --git a/lib/src/fixtures/fixtures.dart b/lib/src/fixtures/fixtures.dart index 9902935..3ab9ce2 100644 --- a/lib/src/fixtures/fixtures.dart +++ b/lib/src/fixtures/fixtures.dart @@ -1,11 +1,15 @@ export 'app_settings.dart'; export 'countries.dart'; export 'dashboard_summary.dart'; +export 'engagements.dart'; export 'fixture_ids.dart'; +export 'headline_comments.dart'; +export 'headline_reactions.dart'; export 'headlines.dart'; export 'in_app_notifications.dart'; export 'languages.dart'; export 'remote_configs.dart'; +export 'reports.dart'; export 'saved_headline_filters.dart'; export 'saved_source_filters.dart'; export 'sources.dart'; diff --git a/lib/src/fixtures/headline_comments.dart b/lib/src/fixtures/headline_comments.dart index 52c979c..9daaf70 100644 --- a/lib/src/fixtures/headline_comments.dart +++ b/lib/src/fixtures/headline_comments.dart @@ -8,7 +8,7 @@ import 'package:core/core.dart'; List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { final comments = []; final users = usersFixturesData.take(10).toList(); - final headlines = headlinesFixturesData.take(100).toList(); + final headlines = getHeadlinesFixturesData().take(100).toList(); // Ensure only approved languages are used, default to 'en'. final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; @@ -181,7 +181,4 @@ List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { } return comments; -} - -/// A list of predefined comments for fixture data, defaulting to English. -final List headlineCommentsFixturesData = getHeadlineCommentsFixturesData(); +} \ No newline at end of file diff --git a/lib/src/models/user_generated_content/user_generated_content.dart b/lib/src/models/user_generated_content/user_generated_content.dart index b4e6231..9f354f4 100644 --- a/lib/src/models/user_generated_content/user_generated_content.dart +++ b/lib/src/models/user_generated_content/user_generated_content.dart @@ -1,3 +1,4 @@ export 'comment.dart'; -export 'headline_reaction.dart'; +export 'engagement.dart'; +export 'reaction.dart'; export 'report.dart'; From 025446958f6aa98c81a896f4b3cf57e7ba87c3db Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:38:59 +0100 Subject: [PATCH 72/93] fix(in-app-notifications): update headline fixtures data retrieval - Replace direct access to headlinesFixturesData with a function call - Use getHeadlinesFixturesData() to retrieve headline data - Ensure proper encapsulation and potential lazy loading of fixtures data --- lib/src/fixtures/in_app_notifications.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/fixtures/in_app_notifications.dart b/lib/src/fixtures/in_app_notifications.dart index 3cefa96..00686ac 100644 --- a/lib/src/fixtures/in_app_notifications.dart +++ b/lib/src/fixtures/in_app_notifications.dart @@ -17,10 +17,10 @@ List _generateAdminNotifications() { 21, (index) => 'in_app_notification_${index + 1}', ); - final headlineIds = headlinesFixturesData.map((e) => e.id).toList(); + final headlineIds = getHeadlinesFixturesData().map((e) => e.id).toList(); return List.generate(21, (index) { - final headline = headlinesFixturesData[index % headlineIds.length]; + final headline = getHeadlinesFixturesData()[index % headlineIds.length]; final notificationId = notificationIds[index]; final isRead = index > 3; From a4f53436090924e3125ef772e12693c7fea910ae Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:39:41 +0100 Subject: [PATCH 73/93] refactor: refactor in app notification ficture into language configurable --- .../fixtures/user_content_preferences.dart | 195 ++++++++++-------- 1 file changed, 104 insertions(+), 91 deletions(-) diff --git a/lib/src/fixtures/user_content_preferences.dart b/lib/src/fixtures/user_content_preferences.dart index 48f6c70..1031262 100644 --- a/lib/src/fixtures/user_content_preferences.dart +++ b/lib/src/fixtures/user_content_preferences.dart @@ -1,101 +1,114 @@ import 'package:core/core.dart'; -/// User Content Preferences Demo Data -final List userContentPreferencesFixturesData = [ - UserContentPreferences( - id: kAdminUserId, - followedCountries: const [], - followedSources: [ - sourcesFixturesData[0], // TechCrunch - sourcesFixturesData[1], // BBC News - sourcesFixturesData[10], // San Francisco Chronicle - sourcesFixturesData[40], // ESPN - ], - followedTopics: [ - topicsFixturesData[0], // Technology - topicsFixturesData[1], // Sports - topicsFixturesData[6], // Business - topicsFixturesData[7], // Travel - ], - savedHeadlines: [headlinesFixturesData[0], headlinesFixturesData[10]], - savedHeadlineFilters: savedHeadlineFiltersFixturesData - .map((e) => e.copyWith(userId: kAdminUserId)) - .toList(), - savedSourceFilters: savedSourceFiltersFixturesData - .map((e) => e.copyWith(userId: kAdminUserId)) - .toList(), - ), - UserContentPreferences( - id: kUser1Id, // Publisher (Premium) - followedCountries: const [], - followedSources: [ - sourcesFixturesData[0], // TechCrunch - sourcesFixturesData[1], // BBC News - ], - followedTopics: [ - topicsFixturesData[0], // Technology - topicsFixturesData[6], // Business - ], - savedHeadlines: [headlinesFixturesData[2], headlinesFixturesData[3]], - savedHeadlineFilters: savedHeadlineFiltersFixturesData - .map((e) => e.copyWith(userId: kUser1Id)) - .toList(), - savedSourceFilters: savedSourceFiltersFixturesData - .map((e) => e.copyWith(userId: kUser1Id)) - .toList(), - ), - UserContentPreferences( - id: kUser2Id, // Publisher (Standard) - followedCountries: const [], - followedSources: [ - sourcesFixturesData[3], // The Guardian - sourcesFixturesData[4], // CNN - ], - followedTopics: [ - topicsFixturesData[2], // Politics - topicsFixturesData[4], // Health - ], - savedHeadlines: [headlinesFixturesData[4], headlinesFixturesData[5]], - savedHeadlineFilters: savedHeadlineFiltersFixturesData - .map((e) => e.copyWith(userId: kUser2Id)) - .toList(), - savedSourceFilters: savedSourceFiltersFixturesData - .map((e) => e.copyWith(userId: kUser2Id)) - .toList(), - ), - // Add preferences for users 3-10 - ...List.generate(8, (index) { - final userId = [ - kUser3Id, - kUser4Id, - kUser5Id, - kUser6Id, - kUser7Id, - kUser8Id, - kUser9Id, - kUser10Id, - ][index]; - return UserContentPreferences( - id: userId, +/// Generates a list of predefined user content preferences for fixture data. +/// +/// This function can be configured to generate preferences in either English or +/// Arabic, which affects the nested fixture data like topics and sources. +List getUserContentPreferencesFixturesData({ + String languageCode = 'en', +}) { + // Ensure only approved languages are used, default to 'en'. + final resolvedLanguageCode = + ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + + // Get language-specific fixtures + final sources = getSourcesFixturesData(languageCode: resolvedLanguageCode); + final topics = getTopicsFixturesData(languageCode: resolvedLanguageCode); + final headlines = getHeadlinesFixturesData(languageCode: resolvedLanguageCode); + final savedHeadlineFilters = + getSavedHeadlineFiltersFixturesData(languageCode: resolvedLanguageCode); + final savedSourceFilters = + getSavedSourceFiltersFixturesData(languageCode: resolvedLanguageCode); + + return [ + UserContentPreferences( + id: kAdminUserId, followedCountries: const [], followedSources: [ - sourcesFixturesData[index % 10], - sourcesFixturesData[(index + 1) % 10], + sources[0], // TechCrunch + sources[1], // BBC News + sources[10], // San Francisco Chronicle + sources[40], // ESPN ], followedTopics: [ - topicsFixturesData[index % 5], - topicsFixturesData[(index + 1) % 5], + topics[0], // Technology + topics[1], // Sports + topics[6], // Business + topics[7], // Travel + ], + savedHeadlines: [headlines[0], headlines[10]], + savedHeadlineFilters: savedHeadlineFilters + .map((e) => e.copyWith(userId: kAdminUserId)) + .toList(), + savedSourceFilters: savedSourceFilters + .map((e) => e.copyWith(userId: kAdminUserId)) + .toList(), + ), + UserContentPreferences( + id: kUser1Id, // Publisher (Premium) + followedCountries: const [], + followedSources: [ + sources[0], // TechCrunch + sources[1], // BBC News ], - savedHeadlines: [ - headlinesFixturesData[index * 2], - headlinesFixturesData[index * 2 + 1], + followedTopics: [ + topics[0], // Technology + topics[6], // Business + ], + savedHeadlines: [headlines[2], headlines[3]], + savedHeadlineFilters: savedHeadlineFilters + .map((e) => e.copyWith(userId: kUser1Id)) + .toList(), + savedSourceFilters: savedSourceFilters + .map((e) => e.copyWith(userId: kUser1Id)) + .toList(), + ), + UserContentPreferences( + id: kUser2Id, // Publisher (Standard) + followedCountries: const [], + followedSources: [ + sources[3], // The Guardian + sources[4], // CNN + ], + followedTopics: [ + topics[2], // Politics + topics[4], // Health ], - savedHeadlineFilters: savedHeadlineFiltersFixturesData - .map((e) => e.copyWith(userId: userId)) + savedHeadlines: [headlines[4], headlines[5]], + savedHeadlineFilters: savedHeadlineFilters + .map((e) => e.copyWith(userId: kUser2Id)) .toList(), - savedSourceFilters: savedSourceFiltersFixturesData - .map((e) => e.copyWith(userId: userId)) + savedSourceFilters: savedSourceFilters + .map((e) => e.copyWith(userId: kUser2Id)) .toList(), - ); - }), -]; + ), + // Add preferences for users 3-10 + ...List.generate(8, (index) { + final userId = [ + kUser3Id, kUser4Id, kUser5Id, kUser6Id, + kUser7Id, kUser8Id, kUser9Id, kUser10Id, + ][index]; + return UserContentPreferences( + id: userId, + followedCountries: const [], + followedSources: [ + sources[index % 10], + sources[(index + 1) % 10], + ], + followedTopics: [ + topics[index % 5], + topics[(index + 1) % 5], + ], + savedHeadlines: [ + headlines[index * 2], + headlines[index * 2 + 1], + ], + savedHeadlineFilters: + savedHeadlineFilters.map((e) => e.copyWith(userId: userId)).toList(), + savedSourceFilters: + savedSourceFilters.map((e) => e.copyWith(userId: userId)).toList(), + ); + }), + ]; +} + \ No newline at end of file From 73d18f75538a6147cf030601092cddfd9a7d62e0 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:48:04 +0100 Subject: [PATCH 74/93] test(core): add EngageableType enum tests - Add unit tests for EngageableType enum values - Verify serialization and deserialization behavior - Test toString representation - Ensure proper error handling for invalid values --- test/src/enums/engageable_type_test.dart | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/src/enums/engageable_type_test.dart diff --git a/test/src/enums/engageable_type_test.dart b/test/src/enums/engageable_type_test.dart new file mode 100644 index 0000000..d463f6e --- /dev/null +++ b/test/src/enums/engageable_type_test.dart @@ -0,0 +1,39 @@ +import 'package:core/src/enums/engageable_type.dart'; +import 'package:test/test.dart'; + +void main() { + group('EngageableType', () { + test('has correct values', () { + expect(EngageableType.values, containsAll([EngageableType.headline])); + }); + + group('serialization', () { + test('uses correct string values for json serialization', () { + // This test verifies that the enum's string representation, + // which is used by json_serializable, matches the expected value. + expect(EngageableType.headline.name, 'headline'); + }); + + test('can be created from string value for json deserialization', () { + // This test verifies that the enum can be created from its + // string representation, mimicking json_serializable's behavior. + expect( + EngageableType.values.byName('headline'), + EngageableType.headline, + ); + }); + + test('throws ArgumentError for invalid string value', () { + // Verifies that an unknown string cannot be converted to an enum value. + expect( + () => EngageableType.values.byName('invalid_type'), + throwsA(isA()), + ); + }); + }); + + test('has correct toString representation', () { + expect(EngageableType.headline.toString(), 'EngageableType.headline'); + }); + }); +} From ddd5547c357b9edb4e68f25d5bcdc38c9cfd1e44 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:48:24 +0100 Subject: [PATCH 75/93] test(core): add Comment model tests - Add comprehensive tests for Comment constructor, fromJson/toJson, copyWith, and Equatable properties - Verify correct instance creation, round trip serialization, and equality checks - Ensure copyWith method correctly updates fields and generates new timestamp --- .../user_generated_content/comment_test.dart | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 test/src/models/user_generated_content/comment_test.dart diff --git a/test/src/models/user_generated_content/comment_test.dart b/test/src/models/user_generated_content/comment_test.dart new file mode 100644 index 0000000..5407955 --- /dev/null +++ b/test/src/models/user_generated_content/comment_test.dart @@ -0,0 +1,89 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Comment', () { + // Use the first item from the fixtures as the test subject. + final commentFixture = getHeadlineCommentsFixturesData().first; + + group('constructor', () { + test('returns correct instance', () { + expect(commentFixture, isA()); + }); + }); + + group('fromJson/toJson', () { + test('round trip with all fields populated', () { + final json = commentFixture.toJson(); + final result = Comment.fromJson(json); + expect(result, equals(commentFixture)); + }); + }); + + group('copyWith', () { + test('returns a new instance with updated fields', () { + const newContent = 'This is updated content.'; + const newStatus = CommentStatus.rejected; + + final updatedComment = commentFixture.copyWith( + content: newContent, + status: newStatus, + ); + + expect(updatedComment.id, commentFixture.id); + expect(updatedComment.content, newContent); + expect(updatedComment.status, newStatus); + // Verify other fields remain unchanged + expect(updatedComment.userId, commentFixture.userId); + expect(updatedComment.entityId, commentFixture.entityId); + // The updatedAt timestamp should be different + expect( + updatedComment.updatedAt, + isNot(equals(commentFixture.updatedAt)), + ); + }); + + test( + 'returns a new instance with a new timestamp if no updates provided', + () { + final copiedComment = commentFixture.copyWith(); + // Should be a new instance with a new `updatedAt` time + expect(copiedComment, isNot(equals(commentFixture))); + expect(copiedComment.id, commentFixture.id); + expect(copiedComment.content, commentFixture.content); + }, + ); + }); + + group('Equatable', () { + test('instances with the same properties are equal', () { + final comment1 = getHeadlineCommentsFixturesData().first; + final comment2 = getHeadlineCommentsFixturesData().first; + expect(comment1, equals(comment2)); + }); + + test('instances with different properties are not equal', () { + final comment1 = getHeadlineCommentsFixturesData().first; + final comment2 = getHeadlineCommentsFixturesData()[1]; + expect(comment1, isNot(equals(comment2))); + }); + }); + + test('props list should contain all relevant fields', () { + expect( + commentFixture.props, + equals([ + commentFixture.id, + commentFixture.userId, + commentFixture.entityId, + commentFixture.entityType, + commentFixture.language, + commentFixture.content, + commentFixture.status, + commentFixture.createdAt, + commentFixture.updatedAt, + ]), + ); + }); + }); +} From 0d1e7bd7eaa7cd986c0012d8fed011c2e2dc209c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:49:05 +0100 Subject: [PATCH 76/93] test(user_generated_content): add Engagement model tests - Add comprehensive tests for Engagement model constructor, fromJson/toJson, copyWith, and Equatable properties - Verify correct instance creation, JSON serialization, and equality checks - Include test cases for both populated and null comment scenarios --- .../engagement_test.dart | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 test/src/models/user_generated_content/engagement_test.dart diff --git a/test/src/models/user_generated_content/engagement_test.dart b/test/src/models/user_generated_content/engagement_test.dart new file mode 100644 index 0000000..50aaf2e --- /dev/null +++ b/test/src/models/user_generated_content/engagement_test.dart @@ -0,0 +1,84 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Engagement', () { + // Use the first item from the fixtures as the test subject. + final engagementFixture = getEngagementsFixturesData().first; + + group('constructor', () { + test('returns correct instance', () { + expect(engagementFixture, isA()); + }); + + test('returns correct instance with populated comment', () { + // The first fixture item should have a comment + expect(engagementFixture.comment, isNotNull); + }); + + test('returns correct instance with null comment', () { + // The second fixture item should have a null comment + final engagementWithoutComment = getEngagementsFixturesData()[1]; + expect(engagementWithoutComment.comment, isNull); + }); + }); + + group('fromJson/toJson', () { + test('round trip with all fields populated', () { + final json = engagementFixture.toJson(); + final result = Engagement.fromJson(json); + expect(result, equals(engagementFixture)); + }); + + test('round trip with null comment', () { + final engagementWithoutComment = getEngagementsFixturesData()[1]; + final json = engagementWithoutComment.toJson(); + final result = Engagement.fromJson(json); + expect(result, equals(engagementWithoutComment)); + }); + }); + + group('copyWith', () { + test('returns a new instance with updated fields', () { + final newReaction = reactionsFixturesData[2]; + + final updatedEngagement = engagementFixture.copyWith( + reaction: newReaction, + ); + + expect(updatedEngagement.reaction, newReaction); + // Verify other fields remain unchanged + expect(updatedEngagement.entityId, engagementFixture.entityId); + expect(updatedEngagement.comment, engagementFixture.comment); + }); + + test('returns an identical instance if no updates provided', () { + final copiedEngagement = engagementFixture.copyWith(); + expect(copiedEngagement, engagementFixture); + }); + }); + + group('Equatable', () { + test('instances with the same properties are equal', () { + final engagement1 = getEngagementsFixturesData().first.copyWith(); + final engagement2 = getEngagementsFixturesData().first.copyWith(); + expect(engagement1, equals(engagement2)); + }); + + test('instances with different properties are not equal', () { + final engagement1 = getEngagementsFixturesData()[0]; + final engagement2 = getEngagementsFixturesData()[1]; + expect(engagement1, isNot(equals(engagement2))); + }); + }); + + test('props list should contain all relevant fields', () { + expect(engagementFixture.props, [ + engagementFixture.entityId, + engagementFixture.entityType, + engagementFixture.reaction, + engagementFixture.comment, + ]); + }); + }); +} From 4bdd92ee156b15dd1a2d7de31b9b8321c39b877e Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:49:31 +0100 Subject: [PATCH 77/93] test(user_generated_content): add Reaction model tests - Add comprehensive tests for Reaction model constructor, fromJson/toJson, copyWith, and Equatable properties - Verify correct instance creation, round trip serialization, and equality checks - Ensure copyWith method correctly updates specified fields while leaving others unchanged - Validate that props list includes all relevant fields for equality comparison --- .../user_generated_content/reaction_test.dart | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/src/models/user_generated_content/reaction_test.dart diff --git a/test/src/models/user_generated_content/reaction_test.dart b/test/src/models/user_generated_content/reaction_test.dart new file mode 100644 index 0000000..05e43a9 --- /dev/null +++ b/test/src/models/user_generated_content/reaction_test.dart @@ -0,0 +1,73 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Reaction', () { + // Use the first item from the fixtures as the test subject. + final reactionFixture = reactionsFixturesData.first; + + group('constructor', () { + test('returns correct instance', () { + expect(reactionFixture, isA()); + }); + }); + + group('fromJson/toJson', () { + test('round trip with all fields populated', () { + final json = reactionFixture.toJson(); + final result = Reaction.fromJson(json); + expect(result, equals(reactionFixture)); + }); + }); + + group('copyWith', () { + test('returns a new instance with updated fields', () { + const newReactionType = ReactionType.insightful; + + final updatedReaction = reactionFixture.copyWith( + reactionType: newReactionType, + ); + + expect(updatedReaction.id, reactionFixture.id); + expect(updatedReaction.reactionType, newReactionType); + // Verify other fields remain unchanged + expect(updatedReaction.userId, reactionFixture.userId); + expect(updatedReaction.entityId, reactionFixture.entityId); + expect(updatedReaction.createdAt, reactionFixture.createdAt); + }); + + test('returns an identical instance if no updates provided', () { + final copiedReaction = reactionFixture.copyWith(); + expect(copiedReaction, reactionFixture); + }); + }); + + group('Equatable', () { + test('instances with the same properties are equal', () { + final reaction1 = reactionsFixturesData.first.copyWith(); + final reaction2 = reactionsFixturesData.first.copyWith(); + expect(reaction1, equals(reaction2)); + }); + + test('instances with different properties are not equal', () { + final reaction1 = reactionsFixturesData[0]; + final reaction2 = reactionsFixturesData[1]; + expect(reaction1, isNot(equals(reaction2))); + }); + }); + + test('props list should contain all relevant fields', () { + expect( + reactionFixture.props, + equals([ + reactionFixture.id, + reactionFixture.userId, + reactionFixture.entityId, + reactionFixture.entityType, + reactionFixture.reactionType, + reactionFixture.createdAt, + ]), + ); + }); + }); +} From b21b8e9ad9033742e0c5cdd2409a3a6270c32e7c Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:50:17 +0100 Subject: [PATCH 78/93] test(models): add report model tests - Create unit tests for Report model - Verify instantiation, equality, JSON serialization, and copyWith functionality --- .../user_generated_content/report_test.dart | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/src/models/user_generated_content/report_test.dart diff --git a/test/src/models/user_generated_content/report_test.dart b/test/src/models/user_generated_content/report_test.dart new file mode 100644 index 0000000..c0e0051 --- /dev/null +++ b/test/src/models/user_generated_content/report_test.dart @@ -0,0 +1,67 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('Report', () { + final now = DateTime.now(); + final reportFixture = Report( + id: 'report_1', + reporterUserId: 'user_1', + entityType: ReportableEntity.headline, + entityId: 'headline_1', + reason: HeadlineReportReason.clickbaitTitle.name, + status: ReportStatus.submitted, + createdAt: now, + additionalComments: 'The title is misleading.', + ); + + test('can be instantiated', () { + expect(reportFixture, isA()); + }); + + test('supports value equality', () { + final anotherReport = Report( + id: 'report_1', + reporterUserId: 'user_1', + entityType: ReportableEntity.headline, + entityId: 'headline_1', + reason: HeadlineReportReason.clickbaitTitle.name, + status: ReportStatus.submitted, + createdAt: now, + additionalComments: 'The title is misleading.', + ); + expect(reportFixture, equals(anotherReport)); + }); + + test('can be created from JSON', () { + final json = reportFixture.toJson(); + final fromJson = Report.fromJson(json); + expect(fromJson, equals(reportFixture)); + }); + + test('can be converted to JSON', () { + final json = reportFixture.toJson(); + final expectedJson = { + 'id': 'report_1', + 'reporterUserId': 'user_1', + 'entityType': 'headline', + 'entityId': 'headline_1', + 'reason': 'clickbaitTitle', + 'status': 'submitted', + 'additionalComments': 'The title is misleading.', + 'createdAt': now.toIso8601String(), + }; + expect(json, equals(expectedJson)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedReport = reportFixture.copyWith( + status: ReportStatus.resolved, + additionalComments: null, + ); + expect(updatedReport.status, ReportStatus.resolved); + expect(updatedReport.additionalComments, isNull); + expect(updatedReport, isNot(equals(reportFixture))); + }); + }); +} From 588c36f7542a18200b124c12a9f730e2d30c2fd7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 03:55:15 +0100 Subject: [PATCH 79/93] fix: sync tests with fixture namings update --- test/src/models/entities/headline_test.dart | 2 +- test/src/models/entities/source_test.dart | 2 +- test/src/models/entities/topic_test.dart | 2 +- test/src/models/feed/content_collection_item_test.dart | 4 ++-- test/src/models/feed/feed_item_test.dart | 10 +++++----- .../headline_filter_criteria_test.dart | 4 ++-- .../user_preferences/saved_headline_filter_test.dart | 4 ++-- .../user_preferences/saved_source_filter_test.dart | 4 ++-- .../user_preferences/source_filter_criteria_test.dart | 2 +- .../user_content_preferences_test.dart | 6 +++--- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/test/src/models/entities/headline_test.dart b/test/src/models/entities/headline_test.dart index b1d2af2..88a46c7 100644 --- a/test/src/models/entities/headline_test.dart +++ b/test/src/models/entities/headline_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('Headline Model', () { // Use the first headline from the fixtures as the base for testing. - final headlineFixture = headlinesFixturesData.first; + final headlineFixture = getHeadlinesFixturesData().first; final headlineJson = headlineFixture.toJson(); group('fromJson', () { diff --git a/test/src/models/entities/source_test.dart b/test/src/models/entities/source_test.dart index 8dac4a5..9355d41 100644 --- a/test/src/models/entities/source_test.dart +++ b/test/src/models/entities/source_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('Source Model', () { - final sourceFixture = sourcesFixturesData.first; + final sourceFixture = getSourcesFixturesData().first; final sourceJson = sourceFixture.toJson(); group('Constructor', () { diff --git a/test/src/models/entities/topic_test.dart b/test/src/models/entities/topic_test.dart index 01a4b86..fdef8c2 100644 --- a/test/src/models/entities/topic_test.dart +++ b/test/src/models/entities/topic_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('Topic', () { - final topicFixture = topicsFixturesData.first; + final topicFixture = getTopicsFixturesData().first; final topicJson = topicFixture.toJson(); test('supports value equality', () { diff --git a/test/src/models/feed/content_collection_item_test.dart b/test/src/models/feed/content_collection_item_test.dart index 3e098ba..3020f9f 100644 --- a/test/src/models/feed/content_collection_item_test.dart +++ b/test/src/models/feed/content_collection_item_test.dart @@ -3,8 +3,8 @@ import 'package:test/test.dart'; void main() { group('ContentCollectionItem', () { - final mockTopics = topicsFixturesData.take(3).toList(); - final mockSources = sourcesFixturesData.take(3).toList(); + final mockTopics = getTopicsFixturesData().take(3).toList(); + final mockSources = getSourcesFixturesData().take(3).toList(); final mockTopicCollection = ContentCollectionItem( id: 'cc-topic-1', diff --git a/test/src/models/feed/feed_item_test.dart b/test/src/models/feed/feed_item_test.dart index fd76ac5..d363184 100644 --- a/test/src/models/feed/feed_item_test.dart +++ b/test/src/models/feed/feed_item_test.dart @@ -3,9 +3,9 @@ import 'package:test/test.dart'; void main() { group('FeedItem', () { - final mockHeadline = headlinesFixturesData.first; - final mockTopic = topicsFixturesData.first; - final mockSource = sourcesFixturesData.first; + final mockHeadline = getHeadlinesFixturesData().first; + final mockTopic = getTopicsFixturesData().first; + final mockSource = getSourcesFixturesData().first; final mockCountry = countriesFixturesData.first; const mockCallToAction = CallToActionItem( @@ -20,14 +20,14 @@ void main() { final mockContentCollectionTopic = ContentCollectionItem( id: 'cc-topic-1', decoratorType: FeedDecoratorType.suggestedTopics, - items: topicsFixturesData.take(3).toList(), + items: getTopicsFixturesData().take(3).toList(), title: 'Suggested Topics', ); final mockContentCollectionSource = ContentCollectionItem( id: 'cc-source-1', decoratorType: FeedDecoratorType.suggestedSources, - items: sourcesFixturesData.take(3).toList(), + items: getSourcesFixturesData().take(3).toList(), title: 'Suggested Sources', ); diff --git a/test/src/models/user_preferences/headline_filter_criteria_test.dart b/test/src/models/user_preferences/headline_filter_criteria_test.dart index 5655871..5fe860e 100644 --- a/test/src/models/user_preferences/headline_filter_criteria_test.dart +++ b/test/src/models/user_preferences/headline_filter_criteria_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { group('HeadlineFilterCriteria', () { - final fullModel = savedHeadlineFiltersFixturesData[0].criteria; + final fullModel = getSavedHeadlineFiltersFixturesData()[0].criteria; final fullJson = fullModel.toJson(); const emptyModel = HeadlineFilterCriteria( @@ -46,7 +46,7 @@ void main() { }); test('copyWith should work correctly', () { - final topic2 = topicsFixturesData[1]; + final topic2 = getTopicsFixturesData()[1]; final copied = fullModel.copyWith(topics: [topic2]); expect(copied.topics, equals([topic2])); diff --git a/test/src/models/user_preferences/saved_headline_filter_test.dart b/test/src/models/user_preferences/saved_headline_filter_test.dart index ac731cc..e7c7567 100644 --- a/test/src/models/user_preferences/saved_headline_filter_test.dart +++ b/test/src/models/user_preferences/saved_headline_filter_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { group('SavedHeadlineFilter', () { - final fullModel = savedHeadlineFiltersFixturesData[0]; + final fullModel = getSavedHeadlineFiltersFixturesData()[0]; final fullJson = fullModel.toJson(); test('should be instantiable', () { @@ -58,7 +58,7 @@ void main() { }); test('copyWith with no arguments should return an identical instance', () { - final copied = savedHeadlineFiltersFixturesData[0].copyWith(); + final copied = getSavedHeadlineFiltersFixturesData()[0].copyWith(); expect(copied, equals(fullModel)); }); diff --git a/test/src/models/user_preferences/saved_source_filter_test.dart b/test/src/models/user_preferences/saved_source_filter_test.dart index 1f472db..3e31dc1 100644 --- a/test/src/models/user_preferences/saved_source_filter_test.dart +++ b/test/src/models/user_preferences/saved_source_filter_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { group('SavedSourceFilter', () { - final fullModel = savedSourceFiltersFixturesData[0]; + final fullModel = getSavedSourceFiltersFixturesData()[0]; final fullJson = fullModel.toJson(); test('should be instantiable', () { @@ -53,7 +53,7 @@ void main() { }); test('copyWith with no arguments should return an identical instance', () { - final copied = savedSourceFiltersFixturesData[0].copyWith(); + final copied = getSavedSourceFiltersFixturesData()[0].copyWith(); expect(copied, equals(fullModel)); }); diff --git a/test/src/models/user_preferences/source_filter_criteria_test.dart b/test/src/models/user_preferences/source_filter_criteria_test.dart index 05677f2..f376e2b 100644 --- a/test/src/models/user_preferences/source_filter_criteria_test.dart +++ b/test/src/models/user_preferences/source_filter_criteria_test.dart @@ -3,7 +3,7 @@ import 'package:test/test.dart'; void main() { group('SourceFilterCriteria', () { - final fullModel = savedSourceFiltersFixturesData[0].criteria; + final fullModel = getSavedSourceFiltersFixturesData()[0].criteria; final fullJson = fullModel.toJson(); const emptyModel = SourceFilterCriteria( 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 a740a6d..511d2ee 100644 --- a/test/src/models/user_preferences/user_content_preferences_test.dart +++ b/test/src/models/user_preferences/user_content_preferences_test.dart @@ -6,7 +6,7 @@ void main() { // Use the first item from the fixtures as the test subject. // This ensures tests are based on the canonical fixture data. final userContentPreferencesFixture = - userContentPreferencesFixturesData.first; + getUserContentPreferencesFixturesData().first; group('constructor', () { test('returns correct instance', () { @@ -15,7 +15,7 @@ void main() { test('returns correct instance with populated lists from fixture', () { // The base fixture should now have populated lists - final preferences = userContentPreferencesFixturesData.first; + final preferences = getUserContentPreferencesFixturesData().first; expect(preferences.followedCountries, isEmpty); expect(preferences.followedSources, isNotEmpty); expect(preferences.followedTopics, isNotEmpty); @@ -33,7 +33,7 @@ void main() { }); test('round trip with empty lists', () { - final emptyPreferences = userContentPreferencesFixturesData.first; + final emptyPreferences = getUserContentPreferencesFixturesData().first; final json = emptyPreferences.toJson(); final result = UserContentPreferences.fromJson(json); expect(result, equals(emptyPreferences)); From d98acec2b58b0b801d3d6ec5ee9f51f3b7c09a37 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:00:06 +0100 Subject: [PATCH 80/93] build(serialization): sync with file updates --- lib/src/models/config/app_config.g.dart | 32 ++++++++++++ lib/src/models/config/feed_config.g.dart | 51 ++++++++++++++++++ .../models/config/general_app_config.g.dart | 23 ++++++++ .../models/config/maintenance_config.g.dart | 21 ++++++++ .../config/navigation_ad_configuration.g.dart | 52 +++++++++++++++++++ .../navigation_ad_frequency_config.g.dart | 32 ++++++++++++ lib/src/models/config/reporting_config.g.dart | 33 ++++++++++++ lib/src/models/config/update_config.g.dart | 30 +++++++++++ .../user_generated_content/comment.g.dart | 26 +++++++--- .../user_generated_content/engagement.g.dart | 37 +++++++++++++ .../user_generated_content/reaction.g.dart | 49 +++++++++++++++++ 11 files changed, 380 insertions(+), 6 deletions(-) create mode 100644 lib/src/models/config/app_config.g.dart create mode 100644 lib/src/models/config/feed_config.g.dart create mode 100644 lib/src/models/config/general_app_config.g.dart create mode 100644 lib/src/models/config/maintenance_config.g.dart create mode 100644 lib/src/models/config/navigation_ad_configuration.g.dart create mode 100644 lib/src/models/config/navigation_ad_frequency_config.g.dart create mode 100644 lib/src/models/config/reporting_config.g.dart create mode 100644 lib/src/models/config/update_config.g.dart create mode 100644 lib/src/models/user_generated_content/engagement.g.dart create mode 100644 lib/src/models/user_generated_content/reaction.g.dart diff --git a/lib/src/models/config/app_config.g.dart b/lib/src/models/config/app_config.g.dart new file mode 100644 index 0000000..a49f838 --- /dev/null +++ b/lib/src/models/config/app_config.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppConfig _$AppConfigFromJson(Map json) => + $checkedCreate('AppConfig', json, ($checkedConvert) { + final val = AppConfig( + maintenance: $checkedConvert( + 'maintenance', + (v) => MaintenanceConfig.fromJson(v as Map), + ), + update: $checkedConvert( + 'update', + (v) => UpdateConfig.fromJson(v as Map), + ), + general: $checkedConvert( + 'general', + (v) => GeneralAppConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$AppConfigToJson(AppConfig instance) => { + 'maintenance': instance.maintenance.toJson(), + 'update': instance.update.toJson(), + 'general': instance.general.toJson(), +}; diff --git a/lib/src/models/config/feed_config.g.dart b/lib/src/models/config/feed_config.g.dart new file mode 100644 index 0000000..d83ac51 --- /dev/null +++ b/lib/src/models/config/feed_config.g.dart @@ -0,0 +1,51 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'feed_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeedConfig _$FeedConfigFromJson(Map json) => + $checkedCreate('FeedConfig', json, ($checkedConvert) { + final val = FeedConfig( + itemClickBehavior: $checkedConvert( + 'itemClickBehavior', + (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), + ), + decorators: $checkedConvert( + 'decorators', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$FeedDecoratorTypeEnumMap, k), + FeedDecoratorConfig.fromJson(e as Map), + ), + ), + ), + ); + return val; + }); + +Map _$FeedConfigToJson(FeedConfig instance) => + { + 'itemClickBehavior': + _$FeedItemClickBehaviorEnumMap[instance.itemClickBehavior]!, + 'decorators': instance.decorators.map( + (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), + ), + }; + +const _$FeedItemClickBehaviorEnumMap = { + FeedItemClickBehavior.defaultBehavior: 'default', + FeedItemClickBehavior.internalNavigation: 'internalNavigation', + FeedItemClickBehavior.externalNavigation: 'externalNavigation', +}; + +const _$FeedDecoratorTypeEnumMap = { + FeedDecoratorType.linkAccount: 'linkAccount', + FeedDecoratorType.upgrade: 'upgrade', + FeedDecoratorType.rateApp: 'rateApp', + FeedDecoratorType.enableNotifications: 'enableNotifications', + FeedDecoratorType.suggestedTopics: 'suggestedTopics', + FeedDecoratorType.suggestedSources: 'suggestedSources', +}; diff --git a/lib/src/models/config/general_app_config.g.dart b/lib/src/models/config/general_app_config.g.dart new file mode 100644 index 0000000..86d9ab8 --- /dev/null +++ b/lib/src/models/config/general_app_config.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'general_app_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GeneralAppConfig _$GeneralAppConfigFromJson( + Map json, +) => $checkedCreate('GeneralAppConfig', json, ($checkedConvert) { + final val = GeneralAppConfig( + termsOfServiceUrl: $checkedConvert('termsOfServiceUrl', (v) => v as String), + privacyPolicyUrl: $checkedConvert('privacyPolicyUrl', (v) => v as String), + ); + return val; +}); + +Map _$GeneralAppConfigToJson(GeneralAppConfig instance) => + { + 'termsOfServiceUrl': instance.termsOfServiceUrl, + 'privacyPolicyUrl': instance.privacyPolicyUrl, + }; diff --git a/lib/src/models/config/maintenance_config.g.dart b/lib/src/models/config/maintenance_config.g.dart new file mode 100644 index 0000000..fe64745 --- /dev/null +++ b/lib/src/models/config/maintenance_config.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'maintenance_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +MaintenanceConfig _$MaintenanceConfigFromJson(Map json) => + $checkedCreate('MaintenanceConfig', json, ($checkedConvert) { + final val = MaintenanceConfig( + isUnderMaintenance: $checkedConvert( + 'isUnderMaintenance', + (v) => v as bool, + ), + ); + return val; + }); + +Map _$MaintenanceConfigToJson(MaintenanceConfig instance) => + {'isUnderMaintenance': instance.isUnderMaintenance}; diff --git a/lib/src/models/config/navigation_ad_configuration.g.dart b/lib/src/models/config/navigation_ad_configuration.g.dart new file mode 100644 index 0000000..25ac008 --- /dev/null +++ b/lib/src/models/config/navigation_ad_configuration.g.dart @@ -0,0 +1,52 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'navigation_ad_configuration.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NavigationAdConfiguration _$NavigationAdConfigurationFromJson( + Map json, +) => $checkedCreate('NavigationAdConfiguration', json, ($checkedConvert) { + final val = NavigationAdConfiguration( + enabled: $checkedConvert('enabled', (v) => v as bool), + visibleTo: $checkedConvert( + 'visibleTo', + (v) => (v as Map).map( + (k, e) => MapEntry( + $enumDecode(_$AppUserRoleEnumMap, k), + NavigationAdFrequencyConfig.fromJson(e as Map), + ), + ), + ), + adType: $checkedConvert( + 'adType', + (v) => $enumDecodeNullable(_$AdTypeEnumMap, v) ?? AdType.interstitial, + ), + ); + return val; +}); + +Map _$NavigationAdConfigurationToJson( + NavigationAdConfiguration instance, +) => { + 'enabled': instance.enabled, + 'adType': _$AdTypeEnumMap[instance.adType]!, + 'visibleTo': instance.visibleTo.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), +}; + +const _$AppUserRoleEnumMap = { + AppUserRole.premiumUser: 'premiumUser', + AppUserRole.standardUser: 'standardUser', + AppUserRole.guestUser: 'guestUser', +}; + +const _$AdTypeEnumMap = { + AdType.banner: 'banner', + AdType.native: 'native', + AdType.video: 'video', + AdType.interstitial: 'interstitial', +}; diff --git a/lib/src/models/config/navigation_ad_frequency_config.g.dart b/lib/src/models/config/navigation_ad_frequency_config.g.dart new file mode 100644 index 0000000..956455c --- /dev/null +++ b/lib/src/models/config/navigation_ad_frequency_config.g.dart @@ -0,0 +1,32 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'navigation_ad_frequency_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +NavigationAdFrequencyConfig _$NavigationAdFrequencyConfigFromJson( + Map json, +) => $checkedCreate('NavigationAdFrequencyConfig', json, ($checkedConvert) { + final val = NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: $checkedConvert( + 'internalNavigationsBeforeShowingInterstitialAd', + (v) => (v as num).toInt(), + ), + externalNavigationsBeforeShowingInterstitialAd: $checkedConvert( + 'externalNavigationsBeforeShowingInterstitialAd', + (v) => (v as num).toInt(), + ), + ); + return val; +}); + +Map _$NavigationAdFrequencyConfigToJson( + NavigationAdFrequencyConfig instance, +) => { + 'internalNavigationsBeforeShowingInterstitialAd': + instance.internalNavigationsBeforeShowingInterstitialAd, + 'externalNavigationsBeforeShowingInterstitialAd': + instance.externalNavigationsBeforeShowingInterstitialAd, +}; diff --git a/lib/src/models/config/reporting_config.g.dart b/lib/src/models/config/reporting_config.g.dart new file mode 100644 index 0000000..7ae3eff --- /dev/null +++ b/lib/src/models/config/reporting_config.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reporting_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ReportingConfig _$ReportingConfigFromJson(Map json) => + $checkedCreate('ReportingConfig', json, ($checkedConvert) { + final val = ReportingConfig( + headlineReportingEnabled: $checkedConvert( + 'headlineReportingEnabled', + (v) => v as bool, + ), + sourceReportingEnabled: $checkedConvert( + 'sourceReportingEnabled', + (v) => v as bool, + ), + commentReportingEnabled: $checkedConvert( + 'commentReportingEnabled', + (v) => v as bool, + ), + ); + return val; + }); + +Map _$ReportingConfigToJson(ReportingConfig instance) => + { + 'headlineReportingEnabled': instance.headlineReportingEnabled, + 'sourceReportingEnabled': instance.sourceReportingEnabled, + 'commentReportingEnabled': instance.commentReportingEnabled, + }; diff --git a/lib/src/models/config/update_config.g.dart b/lib/src/models/config/update_config.g.dart new file mode 100644 index 0000000..66cee4a --- /dev/null +++ b/lib/src/models/config/update_config.g.dart @@ -0,0 +1,30 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'update_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UpdateConfig _$UpdateConfigFromJson( + Map json, +) => $checkedCreate('UpdateConfig', json, ($checkedConvert) { + final val = UpdateConfig( + latestAppVersion: $checkedConvert('latestAppVersion', (v) => v as String), + isLatestVersionOnly: $checkedConvert( + 'isLatestVersionOnly', + (v) => v as bool, + ), + iosUpdateUrl: $checkedConvert('iosUpdateUrl', (v) => v as String), + androidUpdateUrl: $checkedConvert('androidUpdateUrl', (v) => v as String), + ); + return val; +}); + +Map _$UpdateConfigToJson(UpdateConfig instance) => + { + 'latestAppVersion': instance.latestAppVersion, + 'isLatestVersionOnly': instance.isLatestVersionOnly, + 'iosUpdateUrl': instance.iosUpdateUrl, + 'androidUpdateUrl': instance.androidUpdateUrl, + }; diff --git a/lib/src/models/user_generated_content/comment.g.dart b/lib/src/models/user_generated_content/comment.g.dart index f680f6b..61daf4e 100644 --- a/lib/src/models/user_generated_content/comment.g.dart +++ b/lib/src/models/user_generated_content/comment.g.dart @@ -10,13 +10,17 @@ Comment _$CommentFromJson(Map json) => $checkedCreate('Comment', json, ($checkedConvert) { final val = Comment( id: $checkedConvert('id', (v) => v as String), - headlineId: $checkedConvert('headlineId', (v) => v as String), userId: $checkedConvert('userId', (v) => v as String), - content: $checkedConvert('content', (v) => v as String), - status: $checkedConvert( - 'status', - (v) => $enumDecode(_$CommentStatusEnumMap, v), + entityId: $checkedConvert('entityId', (v) => v as String), + entityType: $checkedConvert( + 'entityType', + (v) => $enumDecode(_$EngageableTypeEnumMap, v), ), + language: $checkedConvert( + 'language', + (v) => Language.fromJson(v as Map), + ), + content: $checkedConvert('content', (v) => v as String), createdAt: $checkedConvert( 'createdAt', (v) => dateTimeFromJson(v as String?), @@ -25,20 +29,30 @@ Comment _$CommentFromJson(Map json) => 'updatedAt', (v) => dateTimeFromJson(v as String?), ), + status: $checkedConvert( + 'status', + (v) => + $enumDecodeNullable(_$CommentStatusEnumMap, v) ?? + CommentStatus.pendingReview, + ), ); return val; }); Map _$CommentToJson(Comment instance) => { 'id': instance.id, - 'headlineId': instance.headlineId, 'userId': instance.userId, + 'entityId': instance.entityId, + 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, + 'language': instance.language.toJson(), 'content': instance.content, 'status': _$CommentStatusEnumMap[instance.status]!, 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), }; +const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; + const _$CommentStatusEnumMap = { CommentStatus.pendingReview: 'pendingReview', CommentStatus.approved: 'approved', diff --git a/lib/src/models/user_generated_content/engagement.g.dart b/lib/src/models/user_generated_content/engagement.g.dart new file mode 100644 index 0000000..4de41d4 --- /dev/null +++ b/lib/src/models/user_generated_content/engagement.g.dart @@ -0,0 +1,37 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'engagement.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Engagement _$EngagementFromJson(Map json) => + $checkedCreate('Engagement', json, ($checkedConvert) { + final val = Engagement( + entityId: $checkedConvert('entityId', (v) => v as String), + entityType: $checkedConvert( + 'entityType', + (v) => $enumDecode(_$EngageableTypeEnumMap, v), + ), + reaction: $checkedConvert( + 'reaction', + (v) => Reaction.fromJson(v as Map), + ), + comment: $checkedConvert( + 'comment', + (v) => v == null ? null : Comment.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$EngagementToJson(Engagement instance) => + { + 'entityId': instance.entityId, + 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, + 'reaction': instance.reaction.toJson(), + 'comment': instance.comment?.toJson(), + }; + +const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; diff --git a/lib/src/models/user_generated_content/reaction.g.dart b/lib/src/models/user_generated_content/reaction.g.dart new file mode 100644 index 0000000..8ca980d --- /dev/null +++ b/lib/src/models/user_generated_content/reaction.g.dart @@ -0,0 +1,49 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Reaction _$ReactionFromJson(Map json) => + $checkedCreate('Reaction', json, ($checkedConvert) { + final val = Reaction( + id: $checkedConvert('id', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), + entityId: $checkedConvert('entityId', (v) => v as String), + entityType: $checkedConvert( + 'entityType', + (v) => $enumDecode(_$EngageableTypeEnumMap, v), + ), + reactionType: $checkedConvert( + 'reactionType', + (v) => $enumDecode(_$ReactionTypeEnumMap, v), + ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + ); + return val; + }); + +Map _$ReactionToJson(Reaction instance) => { + 'id': instance.id, + 'userId': instance.userId, + 'entityId': instance.entityId, + 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, + 'reactionType': _$ReactionTypeEnumMap[instance.reactionType]!, + 'createdAt': dateTimeToJson(instance.createdAt), +}; + +const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; + +const _$ReactionTypeEnumMap = { + ReactionType.like: 'like', + ReactionType.insightful: 'insightful', + ReactionType.amusing: 'amusing', + ReactionType.sad: 'sad', + ReactionType.angry: 'angry', + ReactionType.skeptical: 'skeptical', +}; From 450337952e5954ac91d6e627b8827a8c7a536aa7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:05:48 +0100 Subject: [PATCH 81/93] style: format --- lib/src/fixtures/engagements.dart | 1 - lib/src/fixtures/headline_comments.dart | 4 +- lib/src/fixtures/remote_configs.dart | 1 - .../fixtures/user_content_preferences.dart | 55 ++++++++++--------- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/src/fixtures/engagements.dart b/lib/src/fixtures/engagements.dart index 589a68c..8eede4d 100644 --- a/lib/src/fixtures/engagements.dart +++ b/lib/src/fixtures/engagements.dart @@ -1,5 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/fixtures/headline_reactions.dart'; /// Generates a list of predefined engagements for fixture data. /// diff --git a/lib/src/fixtures/headline_comments.dart b/lib/src/fixtures/headline_comments.dart index 9daaf70..d66da0c 100644 --- a/lib/src/fixtures/headline_comments.dart +++ b/lib/src/fixtures/headline_comments.dart @@ -120,7 +120,7 @@ List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { kCommentId100, ]; - final Map> commentContentsByLang = { + final commentContentsByLang = >{ 'en': [ 'This is a really insightful article. It completely changed my perspective.', "I'm not sure I agree with the author's conclusion, but it's a well-argued piece.", @@ -181,4 +181,4 @@ List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { } return comments; -} \ No newline at end of file +} diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 46c2625..63d5975 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -218,7 +218,6 @@ final remoteConfigsFixturesData = [ engagement: EngagementConfig( enabled: true, engagementMode: EngagementMode.reactionsAndComments, - aiModerationEnabled: false, ), reporting: ReportingConfig( headlineReportingEnabled: true, diff --git a/lib/src/fixtures/user_content_preferences.dart b/lib/src/fixtures/user_content_preferences.dart index 1031262..787b3f3 100644 --- a/lib/src/fixtures/user_content_preferences.dart +++ b/lib/src/fixtures/user_content_preferences.dart @@ -8,17 +8,22 @@ List getUserContentPreferencesFixturesData({ String languageCode = 'en', }) { // Ensure only approved languages are used, default to 'en'. - final resolvedLanguageCode = - ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; // Get language-specific fixtures final sources = getSourcesFixturesData(languageCode: resolvedLanguageCode); final topics = getTopicsFixturesData(languageCode: resolvedLanguageCode); - final headlines = getHeadlinesFixturesData(languageCode: resolvedLanguageCode); - final savedHeadlineFilters = - getSavedHeadlineFiltersFixturesData(languageCode: resolvedLanguageCode); - final savedSourceFilters = - getSavedSourceFiltersFixturesData(languageCode: resolvedLanguageCode); + final headlines = getHeadlinesFixturesData( + languageCode: resolvedLanguageCode, + ); + final savedHeadlineFilters = getSavedHeadlineFiltersFixturesData( + languageCode: resolvedLanguageCode, + ); + final savedSourceFilters = getSavedSourceFiltersFixturesData( + languageCode: resolvedLanguageCode, + ); return [ UserContentPreferences( @@ -85,30 +90,28 @@ List getUserContentPreferencesFixturesData({ // Add preferences for users 3-10 ...List.generate(8, (index) { final userId = [ - kUser3Id, kUser4Id, kUser5Id, kUser6Id, - kUser7Id, kUser8Id, kUser9Id, kUser10Id, + kUser3Id, + kUser4Id, + kUser5Id, + kUser6Id, + kUser7Id, + kUser8Id, + kUser9Id, + kUser10Id, ][index]; return UserContentPreferences( id: userId, followedCountries: const [], - followedSources: [ - sources[index % 10], - sources[(index + 1) % 10], - ], - followedTopics: [ - topics[index % 5], - topics[(index + 1) % 5], - ], - savedHeadlines: [ - headlines[index * 2], - headlines[index * 2 + 1], - ], - savedHeadlineFilters: - savedHeadlineFilters.map((e) => e.copyWith(userId: userId)).toList(), - savedSourceFilters: - savedSourceFilters.map((e) => e.copyWith(userId: userId)).toList(), + followedSources: [sources[index % 10], sources[(index + 1) % 10]], + followedTopics: [topics[index % 5], topics[(index + 1) % 5]], + savedHeadlines: [headlines[index * 2], headlines[index * 2 + 1]], + savedHeadlineFilters: savedHeadlineFilters + .map((e) => e.copyWith(userId: userId)) + .toList(), + savedSourceFilters: savedSourceFilters + .map((e) => e.copyWith(userId: userId)) + .toList(), ); }), ]; } - \ No newline at end of file From 73d2cc0f8db2b7aa08f7fb396711d7c20c0be1c3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:06:03 +0100 Subject: [PATCH 82/93] refactor(engagement_config): remove unused AI moderation feature - Remove `aiModerationEnabled` property from EngagementConfig class - Update constructors, toJson, fromJson, and copyWith methods to exclude AI moderation - Adjust unit tests to reflect changes --- lib/src/models/config/engagement_config.dart | 23 ++++--------------- .../models/config/engagement_config.g.dart | 5 ---- .../models/config/engagement_config_test.dart | 6 +---- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/lib/src/models/config/engagement_config.dart b/lib/src/models/config/engagement_config.dart index c149b83..8154430 100644 --- a/lib/src/models/config/engagement_config.dart +++ b/lib/src/models/config/engagement_config.dart @@ -8,19 +8,14 @@ part 'engagement_config.g.dart'; /// {@template engagement_config} /// Defines the remote configuration for user engagement features. /// -/// This model allows administrators to remotely enable/disable the entire -/// engagement system, switch between `reactionsOnly` and `reactionsAndComments` -/// modes, and toggle a flag for future AI moderation. +/// This model allows administrators to remotely enable/disable the entire engagement +/// system and switch between `reactionsOnly` and `reactionsAndComments` modes. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class EngagementConfig extends Equatable { /// {@macro engagement_config} - const EngagementConfig({ - required this.enabled, - required this.engagementMode, - required this.aiModerationEnabled, - }); + const EngagementConfig({required this.enabled, required this.engagementMode}); /// Creates an [EngagementConfig] from JSON data. factory EngagementConfig.fromJson(Map json) => @@ -33,26 +28,18 @@ class EngagementConfig extends Equatable { /// reactions and comments). final EngagementMode engagementMode; - /// A flag to enable or disable AI-powered comment moderation. - final bool aiModerationEnabled; - /// Converts this [EngagementConfig] instance to JSON data. Map toJson() => _$EngagementConfigToJson(this); @override - List get props => [enabled, engagementMode, aiModerationEnabled]; + List get props => [enabled, engagementMode]; /// Creates a copy of this [EngagementConfig] but with the given fields /// replaced with the new values. - EngagementConfig copyWith({ - bool? enabled, - EngagementMode? engagementMode, - bool? aiModerationEnabled, - }) { + EngagementConfig copyWith({bool? enabled, EngagementMode? engagementMode}) { return EngagementConfig( enabled: enabled ?? this.enabled, engagementMode: engagementMode ?? this.engagementMode, - aiModerationEnabled: aiModerationEnabled ?? this.aiModerationEnabled, ); } } diff --git a/lib/src/models/config/engagement_config.g.dart b/lib/src/models/config/engagement_config.g.dart index f4face3..a49a8a8 100644 --- a/lib/src/models/config/engagement_config.g.dart +++ b/lib/src/models/config/engagement_config.g.dart @@ -14,10 +14,6 @@ EngagementConfig _$EngagementConfigFromJson(Map json) => 'engagementMode', (v) => $enumDecode(_$EngagementModeEnumMap, v), ), - aiModerationEnabled: $checkedConvert( - 'aiModerationEnabled', - (v) => v as bool, - ), ); return val; }); @@ -26,7 +22,6 @@ Map _$EngagementConfigToJson(EngagementConfig instance) => { 'enabled': instance.enabled, 'engagementMode': _$EngagementModeEnumMap[instance.engagementMode]!, - 'aiModerationEnabled': instance.aiModerationEnabled, }; const _$EngagementModeEnumMap = { diff --git a/test/src/models/config/engagement_config_test.dart b/test/src/models/config/engagement_config_test.dart index 64e2d63..6fdf3c9 100644 --- a/test/src/models/config/engagement_config_test.dart +++ b/test/src/models/config/engagement_config_test.dart @@ -28,13 +28,9 @@ void main() { }); test('copyWith creates a copy with updated values', () { - final updatedConfig = engagementConfigFixture.copyWith( - enabled: false, - engagementMode: EngagementMode.reactionsOnly, - ); + final updatedConfig = engagementConfigFixture.copyWith(enabled: false); expect(updatedConfig.enabled, isFalse); - expect(updatedConfig.engagementMode, EngagementMode.reactionsOnly); expect(updatedConfig, isNot(equals(engagementConfigFixture))); }); }); From cefd203bef5b273d79b38d46169cb171dd272b75 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:17:13 +0100 Subject: [PATCH 83/93] docs(CHANGELOG): update feature description for Community & Engagement System - Add mentions of foundational data models, fixtures, and tests for user reactions, comments, and reporting system - Improve clarity of the system's remote configurability via `CommunityConfig` model - Expand on the refactor of data models and configuration for the news aggregator pivot - Include information on the introduction of filter-based push notification system and updates to `UserPreferenceConfig` --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e64b982..0599882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Upcoming -- **feat**: Added a comprehensive **Community & Engagement System**. This major feature introduces the foundational data models for user reactions, comments, a multi-entity reporting system, and a smart app review funnel. The entire system is remotely configurable via a new unified `CommunityConfig` model and extends `UserLimitsConfig` to support role-based limits for comments and reports. +- **feat**: Added a comprehensive **Community & Engagement System**. This major feature introduces the foundational data models, fixtures and tests for user reactions, comments, a multi-entity reporting system, and a smart app review funnel. The entire system is remotely configurable via a new unified `CommunityConfig` model and extends `UserLimitsConfig` to support role-based limits for comments and reports. - **BREAKING** refactor!: Overhauled data models and configuration to align with the new identity pivot toward news aggregator. This major refactor introduces a more scalable remote configuration structure, standardizes enums and models for broader use (e.g., `FeedItem` settings), and simplifies ad, notification, and headline data structures for improved clarity and maintainability. - **feat**: Introduce data models to support a filter-based push notification system. This includes `SavedHeadlineFilter`, `SavedSourceFilter`, and related configuration models, providing the architectural foundation for clients to implement notification subscriptions. - **BREAKING** refactor!: Rework `UserPreferenceConfig` to support the new notification system with a more scalable, role-based map structure for all user limits. From a1c2b4fc118ab32dbed8a86962a3fc84b2f074b7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:43:14 +0100 Subject: [PATCH 84/93] chore: fixtures update --- lib/src/fixtures/engagements.dart | 10 ++++++++-- lib/src/fixtures/headline_comments.dart | 19 ++++++++++++------- lib/src/fixtures/headlines.dart | 25 ++++++++++++++++--------- lib/src/fixtures/reports.dart | 23 +++++++++++++++-------- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/lib/src/fixtures/engagements.dart b/lib/src/fixtures/engagements.dart index 8eede4d..90db6a3 100644 --- a/lib/src/fixtures/engagements.dart +++ b/lib/src/fixtures/engagements.dart @@ -5,10 +5,16 @@ import 'package:core/core.dart'; /// This function can be configured to generate data in either English or /// Arabic. It pairs reactions with comments to create realistic engagement /// scenarios. -List getEngagementsFixturesData({String languageCode = 'en'}) { +List getEngagementsFixturesData({ + String languageCode = 'en', + DateTime? now, +}) { final engagements = []; final reactions = reactionsFixturesData; - final comments = getHeadlineCommentsFixturesData(languageCode: languageCode); + final comments = getHeadlineCommentsFixturesData( + languageCode: languageCode, + now: now, + ); for (var i = 0; i < reactions.length; i++) { final reaction = reactions[i]; diff --git a/lib/src/fixtures/headline_comments.dart b/lib/src/fixtures/headline_comments.dart index d66da0c..f55f830 100644 --- a/lib/src/fixtures/headline_comments.dart +++ b/lib/src/fixtures/headline_comments.dart @@ -5,17 +5,22 @@ import 'package:core/core.dart'; /// This function can be configured to generate comments in either English or /// Arabic. It creates 10 comments for each of the first 10 users, with each /// comment targeting a unique headline. -List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { +List getHeadlineCommentsFixturesData({ + String languageCode = 'en', + DateTime? now, +}) { final comments = []; final users = usersFixturesData.take(10).toList(); final headlines = getHeadlinesFixturesData().take(100).toList(); - + final referenceTime = now ?? DateTime.now(); + // Ensure only approved languages are used, default to 'en'. - final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; final language = languagesFixturesData.firstWhere( (lang) => lang.code == resolvedLanguageCode, - orElse: () => - languagesFixturesData.firstWhere((lang) => lang.code == 'en'), + orElse: () => languagesFixturesData.firstWhere((lang) => lang.code == 'en'), ); final commentIds = [ kCommentId1, @@ -173,8 +178,8 @@ List getHeadlineCommentsFixturesData({String languageCode = 'en'}) { language: language, content: commentContents[j], status: status, - createdAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), - updatedAt: DateTime.now().subtract(Duration(days: i, hours: j * 2)), + createdAt: referenceTime.subtract(Duration(days: i, hours: j * 2)), + updatedAt: referenceTime.subtract(Duration(days: i, hours: j * 2)), ), ); } diff --git a/lib/src/fixtures/headlines.dart b/lib/src/fixtures/headlines.dart index d8b0c6b..a5e16df 100644 --- a/lib/src/fixtures/headlines.dart +++ b/lib/src/fixtures/headlines.dart @@ -9,15 +9,22 @@ import 'package:core/src/models/entities/headline.dart'; /// /// This function can be configured to generate headlines in either English or /// Arabic. -List getHeadlinesFixturesData({String languageCode = 'en'}) { +List getHeadlinesFixturesData({ + String languageCode = 'en', + DateTime? now, +}) { // Ensure only approved languages are used, default to 'en'. - final resolvedLanguageCode = - ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; + final referenceTime = now ?? DateTime.now(); - final sources = - source_fixtures.getSourcesFixturesData(languageCode: resolvedLanguageCode); - final topics = - topic_fixtures.getTopicsFixturesData(languageCode: resolvedLanguageCode); + final sources = source_fixtures.getSourcesFixturesData( + languageCode: resolvedLanguageCode, + ); + final topics = topic_fixtures.getTopicsFixturesData( + languageCode: resolvedLanguageCode, + ); final headlines = []; for (var i = 0; i < _headlineIds.length; i++) { @@ -38,8 +45,8 @@ List getHeadlinesFixturesData({String languageCode = 'en'}) { source: source, eventCountry: country, topic: topic, - createdAt: DateTime.now().subtract(Duration(minutes: i * 15)), - updatedAt: DateTime.now().subtract(Duration(minutes: i * 15)), + createdAt: referenceTime.subtract(Duration(minutes: i * 15)), + updatedAt: referenceTime.subtract(Duration(minutes: i * 15)), status: ContentStatus.active, ), ); diff --git a/lib/src/fixtures/reports.dart b/lib/src/fixtures/reports.dart index 8e57598..6ccaa1b 100644 --- a/lib/src/fixtures/reports.dart +++ b/lib/src/fixtures/reports.dart @@ -1,12 +1,19 @@ import 'package:core/core.dart'; -/// A list of predefined reports for fixture data. +/// Generates a list of predefined reports for fixture data. +/// /// This creates 1 report for each of the first 10 users, targeting a mix of /// headlines, sources, and comments. -final List reportsFixturesData = () { +/// +/// The optional [now] parameter allows for creating deterministic timestamps, +/// which is essential for testing. +List getReportsFixturesData({DateTime? now}) { final reports = []; + final referenceTime = now ?? DateTime.now(); final users = usersFixturesData.take(10).toList(); - final headlines = getHeadlinesFixturesData().take(10).toList(); + final headlines = getHeadlinesFixturesData( + now: referenceTime, + ).take(10).toList(); final reportIds = [ kReportId1, kReportId2, @@ -45,7 +52,7 @@ final List reportsFixturesData = () { reason: headlineReasons[i % headlineReasons.length].name, additionalComments: 'This headline seems misleading.', status: status, - createdAt: DateTime.now().subtract(Duration(days: i)), + createdAt: referenceTime.subtract(Duration(days: i)), ), ); } else if (i < 8) { @@ -59,7 +66,7 @@ final List reportsFixturesData = () { reason: sourceReasons[i % sourceReasons.length].name, additionalComments: 'This source has too many ads.', status: status, - createdAt: DateTime.now().subtract(Duration(days: i)), + createdAt: referenceTime.subtract(Duration(days: i)), ), ); } else { @@ -69,15 +76,15 @@ final List reportsFixturesData = () { id: reportIds[i], reporterUserId: user.id, entityType: ReportableEntity.comment, - entityId: getHeadlineCommentsFixturesData()[i].id, + entityId: getHeadlineCommentsFixturesData(now: referenceTime)[i].id, reason: commentReasons[i % commentReasons.length].name, additionalComments: 'This comment is spam.', status: status, - createdAt: DateTime.now().subtract(Duration(days: i)), + createdAt: referenceTime.subtract(Duration(days: i)), ), ); } } return reports; -}(); +} From 6cebaef862c4642e48f4dc6e3c7dc85c537ecc5d Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:49:54 +0100 Subject: [PATCH 85/93] fix(report): handle explicit null values for additional comments - Introduce ValueWrapper class to distinguish between unset and explicitly null fields - Modify Report.copyWith to use ValueWrapper for additionalComments parameter - Update Report.copyWith logic to handle explicit null values correctly --- .../models/user_generated_content/report.dart | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/src/models/user_generated_content/report.dart b/lib/src/models/user_generated_content/report.dart index 0cc6876..d504686 100644 --- a/lib/src/models/user_generated_content/report.dart +++ b/lib/src/models/user_generated_content/report.dart @@ -10,6 +10,17 @@ import 'package:meta/meta.dart'; part 'report.g.dart'; + +/// A wrapper class to distinguish between a field that is not provided and a +/// field that is explicitly set to null. +@immutable +class ValueWrapper { + const ValueWrapper(this.value); + /// The value being wrapped. + final T value; +} + + /// {@template report} /// A flexible data model for handling user reports across different entity /// types. @@ -87,7 +98,7 @@ class Report extends Equatable { String? entityId, String? reason, ReportStatus? status, - String? additionalComments, + ValueWrapper? additionalComments, DateTime? createdAt, }) { return Report( @@ -97,7 +108,9 @@ class Report extends Equatable { entityId: entityId ?? this.entityId, reason: reason ?? this.reason, status: status ?? this.status, - additionalComments: additionalComments ?? this.additionalComments, + additionalComments: additionalComments != null + ? additionalComments.value + : this.additionalComments, createdAt: createdAt ?? this.createdAt, ); } From 1854468cf6f773e7a8e0835365c24bd8553349b2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:50:03 +0100 Subject: [PATCH 86/93] test(comment): update tests to use dynamic date and language code - Modify comment fixtures to accept dynamic `now` and `languageCode` parameters - Update test cases to use a fixed date and language code for consistency - Adjust tests to ensure instances with different properties are not equal --- .../user_generated_content/comment_test.dart | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/src/models/user_generated_content/comment_test.dart b/test/src/models/user_generated_content/comment_test.dart index 5407955..08a4be2 100644 --- a/test/src/models/user_generated_content/comment_test.dart +++ b/test/src/models/user_generated_content/comment_test.dart @@ -3,8 +3,12 @@ import 'package:test/test.dart'; void main() { group('Comment', () { + final now = DateTime.now(); // Use the first item from the fixtures as the test subject. - final commentFixture = getHeadlineCommentsFixturesData().first; + final commentFixture = getHeadlineCommentsFixturesData( + now: now, + languageCode: 'en', + ).first; group('constructor', () { test('returns correct instance', () { @@ -57,14 +61,20 @@ void main() { group('Equatable', () { test('instances with the same properties are equal', () { - final comment1 = getHeadlineCommentsFixturesData().first; - final comment2 = getHeadlineCommentsFixturesData().first; + final comment1 = getHeadlineCommentsFixturesData( + now: now, + languageCode: 'en', + ).first; + final comment2 = getHeadlineCommentsFixturesData( + now: now, + languageCode: 'en', + ).first; expect(comment1, equals(comment2)); }); test('instances with different properties are not equal', () { - final comment1 = getHeadlineCommentsFixturesData().first; - final comment2 = getHeadlineCommentsFixturesData()[1]; + final comment1 = getHeadlineCommentsFixturesData(now: now).first; + final comment2 = getHeadlineCommentsFixturesData(now: now)[1]; expect(comment1, isNot(equals(comment2))); }); }); From 7c2f6b408285ed946f84b41d9ee741a279201557 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:50:20 +0100 Subject: [PATCH 87/93] test(user_generated_content): update engagement tests with configurable parameters - Modify engagement test cases to use a fixed `DateTime.now()` value - Add `languageCode` parameter to `getEngagementsFixturesData` function - Update test cases to handle null comment scenarios consistently - Ensure Equatable tests compare instances with the same properties --- .../engagement_test.dart | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/test/src/models/user_generated_content/engagement_test.dart b/test/src/models/user_generated_content/engagement_test.dart index 50aaf2e..4d26a96 100644 --- a/test/src/models/user_generated_content/engagement_test.dart +++ b/test/src/models/user_generated_content/engagement_test.dart @@ -3,8 +3,12 @@ import 'package:test/test.dart'; void main() { group('Engagement', () { + final now = DateTime.now(); // Use the first item from the fixtures as the test subject. - final engagementFixture = getEngagementsFixturesData().first; + final engagementFixture = getEngagementsFixturesData( + now: now, + languageCode: 'en', + ).first; group('constructor', () { test('returns correct instance', () { @@ -18,7 +22,9 @@ void main() { test('returns correct instance with null comment', () { // The second fixture item should have a null comment - final engagementWithoutComment = getEngagementsFixturesData()[1]; + final engagementWithoutComment = getEngagementsFixturesData( + now: now, + )[1]; expect(engagementWithoutComment.comment, isNull); }); }); @@ -31,7 +37,9 @@ void main() { }); test('round trip with null comment', () { - final engagementWithoutComment = getEngagementsFixturesData()[1]; + final engagementWithoutComment = getEngagementsFixturesData( + now: now, + )[1]; final json = engagementWithoutComment.toJson(); final result = Engagement.fromJson(json); expect(result, equals(engagementWithoutComment)); @@ -60,14 +68,20 @@ void main() { group('Equatable', () { test('instances with the same properties are equal', () { - final engagement1 = getEngagementsFixturesData().first.copyWith(); - final engagement2 = getEngagementsFixturesData().first.copyWith(); + final engagement1 = getEngagementsFixturesData( + now: now, + languageCode: 'en', + ).first; + final engagement2 = getEngagementsFixturesData( + now: now, + languageCode: 'en', + ).first; expect(engagement1, equals(engagement2)); }); test('instances with different properties are not equal', () { - final engagement1 = getEngagementsFixturesData()[0]; - final engagement2 = getEngagementsFixturesData()[1]; + final engagement1 = getEngagementsFixturesData(now: now)[0]; + final engagement2 = getEngagementsFixturesData(now: now)[1]; expect(engagement1, isNot(equals(engagement2))); }); }); From d07235165b163c0ded5485dd017803516b4a3909 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:50:36 +0100 Subject: [PATCH 88/93] test(models): update Report model tests to use fixtures and add test groups - Replace inline Report instantiation with fixture data - Add test groups for constructor, fromJson/toJson, copyWith, and Equatable - Enhance test coverage for Report model methods and properties --- .../user_generated_content/report_test.dart | 83 ++++++++----------- 1 file changed, 33 insertions(+), 50 deletions(-) diff --git a/test/src/models/user_generated_content/report_test.dart b/test/src/models/user_generated_content/report_test.dart index c0e0051..4141f1c 100644 --- a/test/src/models/user_generated_content/report_test.dart +++ b/test/src/models/user_generated_content/report_test.dart @@ -4,64 +4,47 @@ import 'package:test/test.dart'; void main() { group('Report', () { final now = DateTime.now(); - final reportFixture = Report( - id: 'report_1', - reporterUserId: 'user_1', - entityType: ReportableEntity.headline, - entityId: 'headline_1', - reason: HeadlineReportReason.clickbaitTitle.name, - status: ReportStatus.submitted, - createdAt: now, - additionalComments: 'The title is misleading.', - ); + // Use the first item from the fixtures as the test subject. + final reportFixture = getReportsFixturesData(now: now).first; - test('can be instantiated', () { - expect(reportFixture, isA()); + group('constructor', () { + test('returns correct instance', () { + expect(reportFixture, isA()); + }); }); - test('supports value equality', () { - final anotherReport = Report( - id: 'report_1', - reporterUserId: 'user_1', - entityType: ReportableEntity.headline, - entityId: 'headline_1', - reason: HeadlineReportReason.clickbaitTitle.name, - status: ReportStatus.submitted, - createdAt: now, - additionalComments: 'The title is misleading.', - ); - expect(reportFixture, equals(anotherReport)); + group('fromJson/toJson', () { + test('round trip with all fields populated', () { + final json = reportFixture.toJson(); + final fromJson = Report.fromJson(json); + expect(fromJson, equals(reportFixture)); + }); }); - test('can be created from JSON', () { - final json = reportFixture.toJson(); - final fromJson = Report.fromJson(json); - expect(fromJson, equals(reportFixture)); - }); + group('copyWith', () { + test('returns a new instance with updated values', () { + final updatedReport = reportFixture.copyWith( + status: ReportStatus.resolved, + ); + expect(updatedReport.status, ReportStatus.resolved); + expect(updatedReport, isNot(equals(reportFixture))); + }); - test('can be converted to JSON', () { - final json = reportFixture.toJson(); - final expectedJson = { - 'id': 'report_1', - 'reporterUserId': 'user_1', - 'entityType': 'headline', - 'entityId': 'headline_1', - 'reason': 'clickbaitTitle', - 'status': 'submitted', - 'additionalComments': 'The title is misleading.', - 'createdAt': now.toIso8601String(), - }; - expect(json, equals(expectedJson)); + test('copyWith allows setting a field to null', () { + final updatedReport = reportFixture.copyWith( + // Use ValueWrapper to explicitly pass null. + additionalComments: const ValueWrapper(null), + ); + expect(updatedReport.additionalComments, isNull); + }); }); - test('copyWith creates a copy with updated values', () { - final updatedReport = reportFixture.copyWith( - status: ReportStatus.resolved, - additionalComments: null, - ); - expect(updatedReport.status, ReportStatus.resolved); - expect(updatedReport.additionalComments, isNull); - expect(updatedReport, isNot(equals(reportFixture))); + group('Equatable', () { + test('instances with the same properties are equal', () { + final report1 = getReportsFixturesData(now: now).first; + final report2 = getReportsFixturesData(now: now).first; + expect(report1, equals(report2)); + }); }); }); } From c308fe949749317a99ed1486e36e77985236ab6b Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:51:39 +0100 Subject: [PATCH 89/93] style: format --- lib/src/fixtures/sources.dart | 797 ++++++++++++++---- lib/src/models/config/app_review_config.dart | 2 +- .../models/user_generated_content/report.dart | 3 +- .../models/config/reporting_config_test.dart | 5 +- 4 files changed, 641 insertions(+), 166 deletions(-) diff --git a/lib/src/fixtures/sources.dart b/lib/src/fixtures/sources.dart index 9b5708b..c360545 100644 --- a/lib/src/fixtures/sources.dart +++ b/lib/src/fixtures/sources.dart @@ -10,8 +10,9 @@ import 'package:core/src/models/entities/source.dart'; /// Arabic. List getSourcesFixturesData({String languageCode = 'en'}) { // Ensure only approved languages are used, default to 'en'. - final resolvedLanguageCode = - ['en', 'ar'].contains(languageCode) ? languageCode : 'en'; + final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) + ? languageCode + : 'en'; final sources = []; for (var i = 0; i < _sourceIds.length; i++) { @@ -21,8 +22,9 @@ List getSourcesFixturesData({String languageCode = 'en'}) { final url = _urls[i]; final logoUrl = 'https://api.companyenrich.com/logo/${url.split('/')[2]}'; final sourceType = _sourceTypes[i]; - final language = - languagesFixturesData.firstWhere((lang) => lang.code == _langCodes[i]); + final language = languagesFixturesData.firstWhere( + (lang) => lang.code == _langCodes[i], + ); final headquarters = countriesFixturesData[_countryIndexes[i]]; sources.add( @@ -35,10 +37,12 @@ List getSourcesFixturesData({String languageCode = 'en'}) { sourceType: sourceType, language: language, headquarters: headquarters, - createdAt: DateTime.parse('2023-01-01T10:00:00.000Z') - .add(Duration(days: i)), - updatedAt: DateTime.parse('2023-01-01T10:00:00.000Z') - .add(Duration(days: i)), + createdAt: DateTime.parse( + '2023-01-01T10:00:00.000Z', + ).add(Duration(days: i)), + updatedAt: DateTime.parse( + '2023-01-01T10:00:00.000Z', + ).add(Duration(days: i)), status: ContentStatus.active, ), ); @@ -47,74 +51,281 @@ List getSourcesFixturesData({String languageCode = 'en'}) { } const _sourceIds = [ - kSourceId1, kSourceId2, kSourceId3, kSourceId4, kSourceId5, kSourceId6, - kSourceId7, kSourceId8, kSourceId9, kSourceId10, kSourceId11, kSourceId12, - kSourceId13, kSourceId14, kSourceId15, kSourceId16, kSourceId17, kSourceId18, - kSourceId19, kSourceId20, kSourceId21, kSourceId22, kSourceId23, kSourceId24, - kSourceId25, kSourceId26, kSourceId27, kSourceId28, kSourceId29, kSourceId30, - kSourceId31, kSourceId32, kSourceId33, kSourceId34, kSourceId35, kSourceId36, - kSourceId37, kSourceId38, kSourceId39, kSourceId40, kSourceId41, kSourceId42, - kSourceId43, kSourceId44, kSourceId45, kSourceId46, kSourceId47, kSourceId48, - kSourceId49, kSourceId50, kSourceId51, kSourceId52, kSourceId53, kSourceId54, - kSourceId55, kSourceId56, kSourceId57, kSourceId58, kSourceId59, kSourceId60, - kSourceId61, kSourceId62, kSourceId63, kSourceId64, kSourceId65, kSourceId66, - kSourceId67, kSourceId68, kSourceId69, kSourceId70, kSourceId71, kSourceId72, - kSourceId73, kSourceId74, kSourceId75, kSourceId76, kSourceId77, kSourceId78, - kSourceId79, kSourceId80, kSourceId81, kSourceId82, kSourceId83, kSourceId84, - kSourceId85, kSourceId86, kSourceId87, kSourceId88, kSourceId89, kSourceId90, + kSourceId1, + kSourceId2, + kSourceId3, + kSourceId4, + kSourceId5, + kSourceId6, + kSourceId7, + kSourceId8, + kSourceId9, + kSourceId10, + kSourceId11, + kSourceId12, + kSourceId13, + kSourceId14, + kSourceId15, + kSourceId16, + kSourceId17, + kSourceId18, + kSourceId19, + kSourceId20, + kSourceId21, + kSourceId22, + kSourceId23, + kSourceId24, + kSourceId25, + kSourceId26, + kSourceId27, + kSourceId28, + kSourceId29, + kSourceId30, + kSourceId31, + kSourceId32, + kSourceId33, + kSourceId34, + kSourceId35, + kSourceId36, + kSourceId37, + kSourceId38, + kSourceId39, + kSourceId40, + kSourceId41, + kSourceId42, + kSourceId43, + kSourceId44, + kSourceId45, + kSourceId46, + kSourceId47, + kSourceId48, + kSourceId49, + kSourceId50, + kSourceId51, + kSourceId52, + kSourceId53, + kSourceId54, + kSourceId55, + kSourceId56, + kSourceId57, + kSourceId58, + kSourceId59, + kSourceId60, + kSourceId61, + kSourceId62, + kSourceId63, + kSourceId64, + kSourceId65, + kSourceId66, + kSourceId67, + kSourceId68, + kSourceId69, + kSourceId70, + kSourceId71, + kSourceId72, + kSourceId73, + kSourceId74, + kSourceId75, + kSourceId76, + kSourceId77, + kSourceId78, + kSourceId79, + kSourceId80, + kSourceId81, + kSourceId82, + kSourceId83, + kSourceId84, + kSourceId85, + kSourceId86, + kSourceId87, + kSourceId88, + kSourceId89, + kSourceId90, ]; final Map> _namesByLang = { 'en': [ - 'TechCrunch', 'BBC News', 'The New York Times', 'The Guardian', 'CNN', - 'Reuters', 'Al Jazeera English', 'Xinhua News Agency', 'The Times of India', - 'Folha de S.Paulo', 'San Francisco Chronicle', 'Manchester Evening News', - 'The Sydney Morning Herald', 'Le Parisien', 'The Toronto Star', - 'Berliner Morgenpost', 'The Asahi Shimbun (Tokyo)', - 'Hindustan Times (Mumbai)', 'O Globo (Rio de Janeiro)', - 'La Vanguardia (Barcelona)', 'USA Today', 'The Globe and Mail', - 'The Australian', 'Le Monde', 'Frankfurter Allgemeine Zeitung', - 'The Yomiuri Shimbun', "People's Daily", 'O Estado de S. Paulo', 'El País', - 'Corriere della Sera', 'CNN International', 'BBC World News', - 'The Economist', 'France 24', 'Deutsche Welle', 'The Wall Street Journal', - 'Associated Press (AP)', 'Agence France-Presse (AFP)', 'RT', 'CGTN', - 'ESPN', 'Nature', 'The Hollywood Reporter', 'Vogue', 'National Geographic', - 'Wired', 'Bon Appétit', 'Architectural Digest', 'Car and Driver', - 'PC Gamer', 'Stratechery by Ben Thompson', 'Daring Fireball', - 'Wait But Why', 'Smitten Kitchen', 'The Verge', 'Gizmodo', 'Kotaku', - 'Lifehacker', 'Mashable', 'Engadget', 'WhiteHouse.gov', 'GOV.UK', - 'Canada.ca', 'Australia.gov.au', 'Bundesregierung.de', 'Gouvernement.fr', - 'Kantei.go.jp', 'India.gov.in', 'Gov.br', 'English.gov.cn', 'Google News', - 'Apple News', 'Feedly', 'Flipboard', 'SmartNews', 'Inoreader', - 'The Old Reader', 'NewsBlur', 'Pocket', 'Digg', 'PR Newswire', 'arXiv', - 'The Lancet', 'Google AI Blog', 'Microsoft PressPass', 'JSTOR', - 'Business Wire', 'PLOS ONE', 'Apple Newsroom', + 'TechCrunch', + 'BBC News', + 'The New York Times', + 'The Guardian', + 'CNN', + 'Reuters', + 'Al Jazeera English', + 'Xinhua News Agency', + 'The Times of India', + 'Folha de S.Paulo', + 'San Francisco Chronicle', + 'Manchester Evening News', + 'The Sydney Morning Herald', + 'Le Parisien', + 'The Toronto Star', + 'Berliner Morgenpost', + 'The Asahi Shimbun (Tokyo)', + 'Hindustan Times (Mumbai)', + 'O Globo (Rio de Janeiro)', + 'La Vanguardia (Barcelona)', + 'USA Today', + 'The Globe and Mail', + 'The Australian', + 'Le Monde', + 'Frankfurter Allgemeine Zeitung', + 'The Yomiuri Shimbun', + "People's Daily", + 'O Estado de S. Paulo', + 'El País', + 'Corriere della Sera', + 'CNN International', + 'BBC World News', + 'The Economist', + 'France 24', + 'Deutsche Welle', + 'The Wall Street Journal', + 'Associated Press (AP)', + 'Agence France-Presse (AFP)', + 'RT', + 'CGTN', + 'ESPN', + 'Nature', + 'The Hollywood Reporter', + 'Vogue', + 'National Geographic', + 'Wired', + 'Bon Appétit', + 'Architectural Digest', + 'Car and Driver', + 'PC Gamer', + 'Stratechery by Ben Thompson', + 'Daring Fireball', + 'Wait But Why', + 'Smitten Kitchen', + 'The Verge', + 'Gizmodo', + 'Kotaku', + 'Lifehacker', + 'Mashable', + 'Engadget', + 'WhiteHouse.gov', + 'GOV.UK', + 'Canada.ca', + 'Australia.gov.au', + 'Bundesregierung.de', + 'Gouvernement.fr', + 'Kantei.go.jp', + 'India.gov.in', + 'Gov.br', + 'English.gov.cn', + 'Google News', + 'Apple News', + 'Feedly', + 'Flipboard', + 'SmartNews', + 'Inoreader', + 'The Old Reader', + 'NewsBlur', + 'Pocket', + 'Digg', + 'PR Newswire', + 'arXiv', + 'The Lancet', + 'Google AI Blog', + 'Microsoft PressPass', + 'JSTOR', + 'Business Wire', + 'PLOS ONE', + 'Apple Newsroom', 'The New England Journal of Medicine', ], 'ar': [ - 'تك كرانش', 'بي بي سي نيوز', 'نيويورك تايمز', 'الجارديان', 'سي إن إن', - 'رويترز', 'الجزيرة الإنجليزية', 'وكالة أنباء شينخوا', 'تايمز أوف إنديا', - 'فولها دي ساو باولو', 'سان فرانسيسكو كرونيكل', 'مانشستر إيفننغ نيوز', - 'سيدني مورنينغ هيرالد', 'لو باريزيان', 'تورنتو ستار', - 'برلينر مورغنبوست', 'أساهي شيمبون (طوكيو)', 'هندوستان تايمز (مومباي)', - 'أو جلوبو (ريو دي جانيرو)', 'لا فانجارديا (برشلونة)', 'يو إس إيه توداي', - 'ذا جلوب آند ميل', 'ذي أستراليان', 'لوموند', - 'فرانكفورتر ألجماينه تسايتונג', 'يomiuri Shimbun', 'صحيفة الشعب اليومية', - 'أو إستาดو دي ساو باولو', 'إل باييس', 'كورييري ديلا سيرا', - 'سي إن إن الدولية', 'بي بي سي وورلد نيوز', 'ذي إيكونوميست', 'فرانس 24', - 'دويتشه فيله', 'وول ستريت جورنال', 'أسوشيتد برس (AP)', - 'وكالة فرانس برس (AFP)', 'آر تي', 'سي جي تي إن', 'إي إس بي إن', 'نيتشر', - 'هوليوود ريبورتر', 'فوغ', 'ناشيونال جيوغرافيك', 'وايرد', 'بون أبيتيت', - 'أركيتكتشرال دايجست', 'كار آند درايفر', 'بي سي جيمر', - 'סטרטכרי بقلم بن طومسون', 'دارينج فايربول', 'ويت بات واي', - 'สมิตเทน คิทเช่น', 'ذا فيرج', 'جيزمودو', 'كوتاكو', 'لايف هاكر', 'ماشابل', - 'إنガジェット', 'WhiteHouse.gov', 'GOV.UK', 'Canada.ca', - 'Australia.gov.au', 'Bundesregierung.de', 'Gouvernement.fr', - 'Kantei.go.jp', 'India.gov.in', 'Gov.br', 'English.gov.cn', 'أخبار جوجل', - 'أخبار أبل', 'فيدلي', 'فليبورد', 'سمارت نيوز', 'إينوريدر', - 'ذا أولد ريدر', 'نيوز بلور', 'بوكيت', 'ديغ', 'بي آر نيوزواير', 'أرخايف', - 'ذا لانسيت', 'مدونة جوجل للذكاء الاصطناعي', 'مايكروسوفت بريس باس', - 'جيستور', 'بزنس واير', 'بلوس ون', 'غرفة أخبار أبل', + 'تك كرانش', + 'بي بي سي نيوز', + 'نيويورك تايمز', + 'الجارديان', + 'سي إن إن', + 'رويترز', + 'الجزيرة الإنجليزية', + 'وكالة أنباء شينخوا', + 'تايمز أوف إنديا', + 'فولها دي ساو باولو', + 'سان فرانسيسكو كرونيكل', + 'مانشستر إيفننغ نيوز', + 'سيدني مورنينغ هيرالد', + 'لو باريزيان', + 'تورنتو ستار', + 'برلينر مورغنبوست', + 'أساهي شيمبون (طوكيو)', + 'هندوستان تايمز (مومباي)', + 'أو جلوبو (ريو دي جانيرو)', + 'لا فانجارديا (برشلونة)', + 'يو إس إيه توداي', + 'ذا جلوب آند ميل', + 'ذي أستراليان', + 'لوموند', + 'فرانكفورتر ألجماينه تسايتונג', + 'يomiuri Shimbun', + 'صحيفة الشعب اليومية', + 'أو إستาดو دي ساو باولو', + 'إل باييس', + 'كورييري ديلا سيرا', + 'سي إن إن الدولية', + 'بي بي سي وورلد نيوز', + 'ذي إيكونوميست', + 'فرانس 24', + 'دويتشه فيله', + 'وول ستريت جورنال', + 'أسوشيتد برس (AP)', + 'وكالة فرانس برس (AFP)', + 'آر تي', + 'سي جي تي إن', + 'إي إس بي إن', + 'نيتشر', + 'هوليوود ريبورتر', + 'فوغ', + 'ناشيونال جيوغرافيك', + 'وايرد', + 'بون أبيتيت', + 'أركيتكتشرال دايجست', + 'كار آند درايفر', + 'بي سي جيمر', + 'סטרטכרי بقلم بن طومسون', + 'دارينج فايربول', + 'ويت بات واي', + 'สมิตเทน คิทเช่น', + 'ذا فيرج', + 'جيزمودو', + 'كوتاكو', + 'لايف هاكر', + 'ماشابل', + 'إنガジェット', + 'WhiteHouse.gov', + 'GOV.UK', + 'Canada.ca', + 'Australia.gov.au', + 'Bundesregierung.de', + 'Gouvernement.fr', + 'Kantei.go.jp', + 'India.gov.in', + 'Gov.br', + 'English.gov.cn', + 'أخبار جوجل', + 'أخبار أبل', + 'فيدلي', + 'فليبورد', + 'سمارت نيوز', + 'إينوريدر', + 'ذا أولد ريدر', + 'نيوز بلور', + 'بوكيت', + 'ديغ', + 'بي آر نيوزواير', + 'أرخايف', + 'ذا لانسيت', + 'مدونة جوجل للذكاء الاصطناعي', + 'مايكروسوفت بريس باس', + 'جيستور', + 'بزنس واير', + 'بلوس ون', + 'غرفة أخبار أبل', 'مجلة نيو إنجلاند الطبية', ], }; @@ -307,105 +518,373 @@ final Map> _descriptionsByLang = { }; const _urls = [ - 'https://techcrunch.com', 'https://www.bbc.com/news', - 'https://www.nytimes.com', 'https://www.theguardian.com', - 'https://edition.cnn.com', 'https://www.reuters.com', - 'https://www.aljazeera.com', 'http://www.xinhuanet.com/english/', - 'https://timesofindia.indiatimes.com/', 'https://www.folha.uol.com.br/', - 'https://www.sfchronicle.com', 'https://www.manchestereveningnews.co.uk', - 'https://www.smh.com.au', 'https://www.leparisien.fr', - 'https://www.thestar.com', 'https://www.morgenpost.de', + 'https://techcrunch.com', + 'https://www.bbc.com/news', + 'https://www.nytimes.com', + 'https://www.theguardian.com', + 'https://edition.cnn.com', + 'https://www.reuters.com', + 'https://www.aljazeera.com', + 'http://www.xinhuanet.com/english/', + 'https://timesofindia.indiatimes.com/', + 'https://www.folha.uol.com.br/', + 'https://www.sfchronicle.com', + 'https://www.manchestereveningnews.co.uk', + 'https://www.smh.com.au', + 'https://www.leparisien.fr', + 'https://www.thestar.com', + 'https://www.morgenpost.de', 'https://www.asahi.com/area/tokyo/', 'https://www.hindustantimes.com/mumbai-news', - 'https://oglobo.globo.com/rio/', 'https://www.lavanguardia.com/local/barcelona', - 'https://www.usatoday.com', 'https://www.theglobeandmail.com', - 'https://www.theaustralian.com.au', 'https://www.lemonde.fr', - 'https://www.faz.net', 'https://www.yomiuri.co.jp', 'http://en.people.cn', - 'https://www.estadao.com.br', 'https://elpais.com', - 'https://www.corriere.it', 'https://edition.cnn.com', - 'https://www.bbc.com/news/world', 'https://www.economist.com', - 'https://www.france24.com/en/', 'https://www.dw.com/en/', - 'https://www.wsj.com', 'https://apnews.com', 'https://www.afp.com/en', - 'https://www.rt.com', 'https://www.cgtn.com', 'https://www.espn.com', - 'https://www.nature.com', 'https://www.hollywoodreporter.com', - 'https://www.vogue.com', 'https://www.nationalgeographic.com', - 'https://www.wired.com', 'https://www.bonappetit.com', - 'https://www.architecturaldigest.com', 'https://www.caranddriver.com', - 'https://www.pcgamer.com', 'https://stratechery.com', - 'https://daringfireball.net', 'https://waitbutwhy.com', - 'https://smittenkitchen.com', 'https://www.theverge.com', - 'https://gizmodo.com', 'https://kotaku.com', 'https://lifehacker.com', - 'https://mashable.com', 'https://www.engadget.com', - 'https://www.whitehouse.gov', 'https://www.gov.uk', - 'https://www.canada.ca/en.html', 'https://www.australia.gov.au', - 'https://www.bundesregierung.de/breg-de', 'https://www.gouvernement.fr', - 'https://japan.kantei.go.jp', 'https://www.india.gov.in', - 'https://www.gov.br/pt-br', 'http://english.gov.cn', - 'https://news.google.com', 'https://www.apple.com/apple-news/', - 'https://feedly.com', 'https://flipboard.com', 'https://www.smartnews.com', - 'https://www.inoreader.com', 'https://theoldreader.com', - 'https://newsblur.com', 'https://getpocket.com', 'https://digg.com', - 'https://www.prnewswire.com', 'https://arxiv.org', - 'https://www.thelancet.com', 'https://ai.googleblog.com', - 'https://news.microsoft.com', 'https://www.jstor.org', - 'https://www.businesswire.com', 'https://journals.plos.org/plosone/', - 'https://www.apple.com/newsroom/', 'https://www.nejm.org', + 'https://oglobo.globo.com/rio/', + 'https://www.lavanguardia.com/local/barcelona', + 'https://www.usatoday.com', + 'https://www.theglobeandmail.com', + 'https://www.theaustralian.com.au', + 'https://www.lemonde.fr', + 'https://www.faz.net', + 'https://www.yomiuri.co.jp', + 'http://en.people.cn', + 'https://www.estadao.com.br', + 'https://elpais.com', + 'https://www.corriere.it', + 'https://edition.cnn.com', + 'https://www.bbc.com/news/world', + 'https://www.economist.com', + 'https://www.france24.com/en/', + 'https://www.dw.com/en/', + 'https://www.wsj.com', + 'https://apnews.com', + 'https://www.afp.com/en', + 'https://www.rt.com', + 'https://www.cgtn.com', + 'https://www.espn.com', + 'https://www.nature.com', + 'https://www.hollywoodreporter.com', + 'https://www.vogue.com', + 'https://www.nationalgeographic.com', + 'https://www.wired.com', + 'https://www.bonappetit.com', + 'https://www.architecturaldigest.com', + 'https://www.caranddriver.com', + 'https://www.pcgamer.com', + 'https://stratechery.com', + 'https://daringfireball.net', + 'https://waitbutwhy.com', + 'https://smittenkitchen.com', + 'https://www.theverge.com', + 'https://gizmodo.com', + 'https://kotaku.com', + 'https://lifehacker.com', + 'https://mashable.com', + 'https://www.engadget.com', + 'https://www.whitehouse.gov', + 'https://www.gov.uk', + 'https://www.canada.ca/en.html', + 'https://www.australia.gov.au', + 'https://www.bundesregierung.de/breg-de', + 'https://www.gouvernement.fr', + 'https://japan.kantei.go.jp', + 'https://www.india.gov.in', + 'https://www.gov.br/pt-br', + 'http://english.gov.cn', + 'https://news.google.com', + 'https://www.apple.com/apple-news/', + 'https://feedly.com', + 'https://flipboard.com', + 'https://www.smartnews.com', + 'https://www.inoreader.com', + 'https://theoldreader.com', + 'https://newsblur.com', + 'https://getpocket.com', + 'https://digg.com', + 'https://www.prnewswire.com', + 'https://arxiv.org', + 'https://www.thelancet.com', + 'https://ai.googleblog.com', + 'https://news.microsoft.com', + 'https://www.jstor.org', + 'https://www.businesswire.com', + 'https://journals.plos.org/plosone/', + 'https://www.apple.com/newsroom/', + 'https://www.nejm.org', ]; const _sourceTypes = [ - SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, - SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, - SourceType.newsAgency, SourceType.newsAgency, SourceType.newsAgency, - SourceType.newsAgency, SourceType.localNewsOutlet, - SourceType.localNewsOutlet, SourceType.localNewsOutlet, - SourceType.localNewsOutlet, SourceType.localNewsOutlet, - SourceType.localNewsOutlet, SourceType.localNewsOutlet, - SourceType.localNewsOutlet, SourceType.localNewsOutlet, - SourceType.localNewsOutlet, SourceType.nationalNewsOutlet, - SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, - SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, - SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, - SourceType.nationalNewsOutlet, SourceType.nationalNewsOutlet, - SourceType.nationalNewsOutlet, SourceType.internationalNewsOutlet, - SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, - SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, - SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, - SourceType.internationalNewsOutlet, SourceType.internationalNewsOutlet, - SourceType.internationalNewsOutlet, SourceType.specializedPublisher, - SourceType.specializedPublisher, SourceType.specializedPublisher, - SourceType.specializedPublisher, SourceType.specializedPublisher, - SourceType.specializedPublisher, SourceType.specializedPublisher, - SourceType.specializedPublisher, SourceType.specializedPublisher, - SourceType.specializedPublisher, SourceType.blog, SourceType.blog, - SourceType.blog, SourceType.blog, SourceType.blog, SourceType.blog, - SourceType.blog, SourceType.blog, SourceType.blog, SourceType.blog, - SourceType.governmentSource, SourceType.governmentSource, - SourceType.governmentSource, SourceType.governmentSource, - SourceType.governmentSource, SourceType.governmentSource, - SourceType.governmentSource, SourceType.governmentSource, - SourceType.governmentSource, SourceType.governmentSource, - SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, - SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, - SourceType.aggregator, SourceType.aggregator, SourceType.aggregator, - SourceType.aggregator, SourceType.other, SourceType.other, SourceType.other, - SourceType.other, SourceType.other, SourceType.other, SourceType.other, - SourceType.other, SourceType.other, SourceType.other, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.newsAgency, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.localNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.nationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.internationalNewsOutlet, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.specializedPublisher, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.blog, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.governmentSource, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.aggregator, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, + SourceType.other, ]; const _langCodes = [ - 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'pt', 'en', 'en', - 'en', 'fr', 'en', 'de', 'ja', 'en', 'pt', 'es', 'en', 'en', 'en', 'fr', - 'de', 'ja', 'en', 'pt', 'es', 'it', 'en', 'en', 'en', 'en', 'de', 'en', - 'en', 'fr', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', - 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', - 'en', 'en', 'en', 'en', 'de', 'fr', 'en', 'en', 'pt', 'en', 'en', 'en', - 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', 'en', - 'en', 'en', 'en', 'en', 'en', 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'pt', + 'en', + 'en', + 'en', + 'fr', + 'en', + 'de', + 'ja', + 'en', + 'pt', + 'es', + 'en', + 'en', + 'en', + 'fr', + 'de', + 'ja', + 'en', + 'pt', + 'es', + 'it', + 'en', + 'en', + 'en', + 'en', + 'de', + 'en', + 'en', + 'fr', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'de', + 'fr', + 'en', + 'en', + 'pt', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', + 'en', ]; const _countryIndexes = [ - 0, 2, 0, 2, 0, 0, 0, 7, 8, 9, 0, 2, 3, 5, 1, 4, 6, 8, 9, 10, 0, 1, 3, 5, - 4, 6, 7, 9, 10, 11, 0, 2, 2, 5, 4, 0, 0, 5, 12, 7, 0, 2, 0, 0, 0, 0, 0, - 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 4, 5, 6, 8, 9, 7, 0, - 0, 0, 0, 6, 13, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 2, + 0, + 2, + 0, + 2, + 0, + 0, + 0, + 7, + 8, + 9, + 0, + 2, + 3, + 5, + 1, + 4, + 6, + 8, + 9, + 10, + 0, + 1, + 3, + 5, + 4, + 6, + 7, + 9, + 10, + 11, + 0, + 2, + 2, + 5, + 4, + 0, + 0, + 5, + 12, + 7, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 1, + 3, + 4, + 5, + 6, + 8, + 9, + 7, + 0, + 0, + 0, + 0, + 6, + 13, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 2, ]; diff --git a/lib/src/models/config/app_review_config.dart b/lib/src/models/config/app_review_config.dart index b9bb401..c440c39 100644 --- a/lib/src/models/config/app_review_config.dart +++ b/lib/src/models/config/app_review_config.dart @@ -23,7 +23,7 @@ part 'app_review_config.g.dart'; /// 3. **Action**: /// - **On "Yes"**: The client sets `isCompleted` to `true` on the user's /// `UserFeedDecoratorStatus` for `rateApp` and immediately triggers the -/// native OS in-app review dialog if applicable ie the app is hosted in +/// native OS in-app review dialog if applicable ie the app is hosted in /// google play or apple store. The prompt will not be shown again. /// - **On "No"**: The client only updates the `lastShownAt` timestamp on /// the status object. The prompt will not be shown again until the diff --git a/lib/src/models/user_generated_content/report.dart b/lib/src/models/user_generated_content/report.dart index d504686..9bb70ba 100644 --- a/lib/src/models/user_generated_content/report.dart +++ b/lib/src/models/user_generated_content/report.dart @@ -10,17 +10,16 @@ import 'package:meta/meta.dart'; part 'report.g.dart'; - /// A wrapper class to distinguish between a field that is not provided and a /// field that is explicitly set to null. @immutable class ValueWrapper { const ValueWrapper(this.value); + /// The value being wrapped. final T value; } - /// {@template report} /// A flexible data model for handling user reports across different entity /// types. diff --git a/test/src/models/config/reporting_config_test.dart b/test/src/models/config/reporting_config_test.dart index 5df60a8..4286032 100644 --- a/test/src/models/config/reporting_config_test.dart +++ b/test/src/models/config/reporting_config_test.dart @@ -33,10 +33,7 @@ void main() { ); expect(updatedConfig.headlineReportingEnabled, isFalse); - expect( - updatedConfig, - isNot(equals(reportingConfigFixture)), - ); + expect(updatedConfig, isNot(equals(reportingConfigFixture))); }); }); } From 728fe1a765e204d4f9b1b50ed97f30c493a5d1f3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 04:55:30 +0100 Subject: [PATCH 90/93] refactor: remove predefined engagementsFixturesData list - Deleted the predefined list of engagementsFixturesData - This change allows for more flexibility in generating engagement fixtures - The getEngagementsFixturesData function can still be used to generate fixtures as needed --- lib/src/fixtures/engagements.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/fixtures/engagements.dart b/lib/src/fixtures/engagements.dart index 90db6a3..6363da9 100644 --- a/lib/src/fixtures/engagements.dart +++ b/lib/src/fixtures/engagements.dart @@ -34,9 +34,6 @@ List getEngagementsFixturesData({ return engagements; } -/// A list of predefined engagements for fixture data, defaulting to English. -final List engagementsFixturesData = getEngagementsFixturesData(); - const _engagementIds = [ kEngagementId1, kEngagementId2, From d716f050290f399eab9ac2e9518b62f93b9438b7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 05:10:25 +0100 Subject: [PATCH 91/93] refactor(fixture): update models and improve engagements fixture - Refactor Comment, Reaction, and Engagement models for better clarity - Enhance engagementsFixturesData to generate more realistic fixture data - Remove unnecessary properties and methods - Update related tests to reflect changes --- lib/src/fixtures/engagements.dart | 38 ++-- lib/src/fixtures/fixture_ids.dart | 202 +----------------- lib/src/fixtures/headline_comments.dart | 113 ---------- lib/src/fixtures/headline_reactions.dart | 132 +----------- .../user_generated_content/comment.dart | 48 +---- .../user_generated_content/engagement.dart | 42 +++- .../user_generated_content/reaction.dart | 47 +--- .../user_generated_content/comment_test.dart | 26 +-- .../engagement_test.dart | 24 ++- .../user_generated_content/reaction_test.dart | 22 +- 10 files changed, 98 insertions(+), 596 deletions(-) diff --git a/lib/src/fixtures/engagements.dart b/lib/src/fixtures/engagements.dart index 6363da9..55d1c4f 100644 --- a/lib/src/fixtures/engagements.dart +++ b/lib/src/fixtures/engagements.dart @@ -10,25 +10,39 @@ List getEngagementsFixturesData({ DateTime? now, }) { final engagements = []; + final users = usersFixturesData.take(10).toList(); + final headlines = getHeadlinesFixturesData( + languageCode: languageCode, + ).take(100).toList(); final reactions = reactionsFixturesData; final comments = getHeadlineCommentsFixturesData( languageCode: languageCode, now: now, ); + final referenceTime = now ?? DateTime.now(); - for (var i = 0; i < reactions.length; i++) { - final reaction = reactions[i]; - // Pair every other reaction with a comment for variety - final comment = i.isEven ? comments[i] : null; + for (var i = 0; i < 10; i++) { + for (var j = 0; j < 10; j++) { + final index = i * 10 + j; + final user = users[i]; + final headline = headlines[index]; + final reaction = reactions[index]; + // Pair every other reaction with a comment for variety + final comment = index.isEven ? comments[index] : null; - engagements.add( - Engagement( - entityId: reaction.entityId, - entityType: reaction.entityType, - reaction: reaction, - comment: comment, - ), - ); + engagements.add( + Engagement( + id: _engagementIds[index], + userId: user.id, + entityId: headline.id, + entityType: EngageableType.headline, + reaction: reaction, + comment: comment, + createdAt: referenceTime.subtract(Duration(days: i, hours: j)), + updatedAt: referenceTime.subtract(Duration(days: i, hours: j)), + ), + ); + } } return engagements; diff --git a/lib/src/fixtures/fixture_ids.dart b/lib/src/fixtures/fixture_ids.dart index dfc584e..c480101 100644 --- a/lib/src/fixtures/fixture_ids.dart +++ b/lib/src/fixtures/fixture_ids.dart @@ -1066,206 +1066,8 @@ const String kInAppNotificationId18 = 'in_app_notification_18'; const String kInAppNotificationId19 = 'in_app_notification_19'; const String kInAppNotificationId20 = 'in_app_notification_20'; const String kInAppNotificationId21 = 'in_app_notification_21'; -const String kCommentId1 = 'c00000000000000000000001'; -const String kCommentId2 = 'c00000000000000000000002'; -const String kCommentId3 = 'c00000000000000000000003'; -const String kCommentId4 = 'c00000000000000000000004'; -const String kCommentId5 = 'c00000000000000000000005'; -const String kCommentId6 = 'c00000000000000000000006'; -const String kCommentId7 = 'c00000000000000000000007'; -const String kCommentId8 = 'c00000000000000000000008'; -const String kCommentId9 = 'c00000000000000000000009'; -const String kCommentId10 = 'c00000000000000000000010'; -const String kCommentId11 = 'c00000000000000000000011'; -const String kCommentId12 = 'c00000000000000000000012'; -const String kCommentId13 = 'c00000000000000000000013'; -const String kCommentId14 = 'c00000000000000000000014'; -const String kCommentId15 = 'c00000000000000000000015'; -const String kCommentId16 = 'c00000000000000000000016'; -const String kCommentId17 = 'c00000000000000000000017'; -const String kCommentId18 = 'c00000000000000000000018'; -const String kCommentId19 = 'c00000000000000000000019'; -const String kCommentId20 = 'c00000000000000000000020'; -const String kCommentId21 = 'c00000000000000000000021'; -const String kCommentId22 = 'c00000000000000000000022'; -const String kCommentId23 = 'c00000000000000000000023'; -const String kCommentId24 = 'c00000000000000000000024'; -const String kCommentId25 = 'c00000000000000000000025'; -const String kCommentId26 = 'c00000000000000000000026'; -const String kCommentId27 = 'c00000000000000000000027'; -const String kCommentId28 = 'c00000000000000000000028'; -const String kCommentId29 = 'c00000000000000000000029'; -const String kCommentId30 = 'c00000000000000000000030'; -const String kCommentId31 = 'c00000000000000000000031'; -const String kCommentId32 = 'c00000000000000000000032'; -const String kCommentId33 = 'c00000000000000000000033'; -const String kCommentId34 = 'c00000000000000000000034'; -const String kCommentId35 = 'c00000000000000000000035'; -const String kCommentId36 = 'c00000000000000000000036'; -const String kCommentId37 = 'c00000000000000000000037'; -const String kCommentId38 = 'c00000000000000000000038'; -const String kCommentId39 = 'c00000000000000000000039'; -const String kCommentId40 = 'c00000000000000000000040'; -const String kCommentId41 = 'c00000000000000000000041'; -const String kCommentId42 = 'c00000000000000000000042'; -const String kCommentId43 = 'c00000000000000000000043'; -const String kCommentId44 = 'c00000000000000000000044'; -const String kCommentId45 = 'c00000000000000000000045'; -const String kCommentId46 = 'c00000000000000000000046'; -const String kCommentId47 = 'c00000000000000000000047'; -const String kCommentId48 = 'c00000000000000000000048'; -const String kCommentId49 = 'c00000000000000000000049'; -const String kCommentId50 = 'c00000000000000000000050'; -const String kCommentId51 = 'c00000000000000000000051'; -const String kCommentId52 = 'c00000000000000000000052'; -const String kCommentId53 = 'c00000000000000000000053'; -const String kCommentId54 = 'c00000000000000000000054'; -const String kCommentId55 = 'c00000000000000000000055'; -const String kCommentId56 = 'c00000000000000000000056'; -const String kCommentId57 = 'c00000000000000000000057'; -const String kCommentId58 = 'c00000000000000000000058'; -const String kCommentId59 = 'c00000000000000000000059'; -const String kCommentId60 = 'c00000000000000000000060'; -const String kCommentId61 = 'c00000000000000000000061'; -const String kCommentId62 = 'c00000000000000000000062'; -const String kCommentId63 = 'c00000000000000000000063'; -const String kCommentId64 = 'c00000000000000000000064'; -const String kCommentId65 = 'c00000000000000000000065'; -const String kCommentId66 = 'c00000000000000000000066'; -const String kCommentId67 = 'c00000000000000000000067'; -const String kCommentId68 = 'c00000000000000000000068'; -const String kCommentId69 = 'c00000000000000000000069'; -const String kCommentId70 = 'c00000000000000000000070'; -const String kCommentId71 = 'c00000000000000000000071'; -const String kCommentId72 = 'c00000000000000000000072'; -const String kCommentId73 = 'c00000000000000000000073'; -const String kCommentId74 = 'c00000000000000000000074'; -const String kCommentId75 = 'c00000000000000000000075'; -const String kCommentId76 = 'c00000000000000000000076'; -const String kCommentId77 = 'c00000000000000000000077'; -const String kCommentId78 = 'c00000000000000000000078'; -const String kCommentId79 = 'c00000000000000000000079'; -const String kCommentId80 = 'c00000000000000000000080'; -const String kCommentId81 = 'c00000000000000000000081'; -const String kCommentId82 = 'c00000000000000000000082'; -const String kCommentId83 = 'c00000000000000000000083'; -const String kCommentId84 = 'c00000000000000000000084'; -const String kCommentId85 = 'c00000000000000000000085'; -const String kCommentId86 = 'c00000000000000000000086'; -const String kCommentId87 = 'c00000000000000000000087'; -const String kCommentId88 = 'c00000000000000000000088'; -const String kCommentId89 = 'c00000000000000000000089'; -const String kCommentId90 = 'c00000000000000000000090'; -const String kCommentId91 = 'c00000000000000000000091'; -const String kCommentId92 = 'c00000000000000000000092'; -const String kCommentId93 = 'c00000000000000000000093'; -const String kCommentId94 = 'c00000000000000000000094'; -const String kCommentId95 = 'c00000000000000000000095'; -const String kCommentId96 = 'c00000000000000000000096'; -const String kCommentId97 = 'c00000000000000000000097'; -const String kCommentId98 = 'c00000000000000000000098'; -const String kCommentId99 = 'c00000000000000000000099'; -const String kCommentId100 = 'c00000000000000000000100'; -const String kReactionId1 = 'r00000000000000000000001'; -const String kReactionId2 = 'r00000000000000000000002'; -const String kReactionId3 = 'r00000000000000000000003'; -const String kReactionId4 = 'r00000000000000000000004'; -const String kReactionId5 = 'r00000000000000000000005'; -const String kReactionId6 = 'r00000000000000000000006'; -const String kReactionId7 = 'r00000000000000000000007'; -const String kReactionId8 = 'r00000000000000000000008'; -const String kReactionId9 = 'r00000000000000000000009'; -const String kReactionId10 = 'r00000000000000000000010'; -const String kReactionId11 = 'r00000000000000000000011'; -const String kReactionId12 = 'r00000000000000000000012'; -const String kReactionId13 = 'r00000000000000000000013'; -const String kReactionId14 = 'r00000000000000000000014'; -const String kReactionId15 = 'r00000000000000000000015'; -const String kReactionId16 = 'r00000000000000000000016'; -const String kReactionId17 = 'r00000000000000000000017'; -const String kReactionId18 = 'r00000000000000000000018'; -const String kReactionId19 = 'r00000000000000000000019'; -const String kReactionId20 = 'r00000000000000000000020'; -const String kReactionId21 = 'r00000000000000000000021'; -const String kReactionId22 = 'r00000000000000000000022'; -const String kReactionId23 = 'r00000000000000000000023'; -const String kReactionId24 = 'r00000000000000000000024'; -const String kReactionId25 = 'r00000000000000000000025'; -const String kReactionId26 = 'r00000000000000000000026'; -const String kReactionId27 = 'r00000000000000000000027'; -const String kReactionId28 = 'r00000000000000000000028'; -const String kReactionId29 = 'r00000000000000000000029'; -const String kReactionId30 = 'r00000000000000000000030'; -const String kReactionId31 = 'r00000000000000000000031'; -const String kReactionId32 = 'r00000000000000000000032'; -const String kReactionId33 = 'r00000000000000000000033'; -const String kReactionId34 = 'r00000000000000000000034'; -const String kReactionId35 = 'r00000000000000000000035'; -const String kReactionId36 = 'r00000000000000000000036'; -const String kReactionId37 = 'r00000000000000000000037'; -const String kReactionId38 = 'r00000000000000000000038'; -const String kReactionId39 = 'r00000000000000000000039'; -const String kReactionId40 = 'r00000000000000000000040'; -const String kReactionId41 = 'r00000000000000000000041'; -const String kReactionId42 = 'r00000000000000000000042'; -const String kReactionId43 = 'r00000000000000000000043'; -const String kReactionId44 = 'r00000000000000000000044'; -const String kReactionId45 = 'r00000000000000000000045'; -const String kReactionId46 = 'r00000000000000000000046'; -const String kReactionId47 = 'r00000000000000000000047'; -const String kReactionId48 = 'r00000000000000000000048'; -const String kReactionId49 = 'r00000000000000000000049'; -const String kReactionId50 = 'r00000000000000000000050'; -const String kReactionId51 = 'r00000000000000000000051'; -const String kReactionId52 = 'r00000000000000000000052'; -const String kReactionId53 = 'r00000000000000000000053'; -const String kReactionId54 = 'r00000000000000000000054'; -const String kReactionId55 = 'r00000000000000000000055'; -const String kReactionId56 = 'r00000000000000000000056'; -const String kReactionId57 = 'r00000000000000000000057'; -const String kReactionId58 = 'r00000000000000000000058'; -const String kReactionId59 = 'r00000000000000000000059'; -const String kReactionId60 = 'r00000000000000000000060'; -const String kReactionId61 = 'r00000000000000000000061'; -const String kReactionId62 = 'r00000000000000000000062'; -const String kReactionId63 = 'r00000000000000000000063'; -const String kReactionId64 = 'r00000000000000000000064'; -const String kReactionId65 = 'r00000000000000000000065'; -const String kReactionId66 = 'r00000000000000000000066'; -const String kReactionId67 = 'r00000000000000000000067'; -const String kReactionId68 = 'r00000000000000000000068'; -const String kReactionId69 = 'r00000000000000000000069'; -const String kReactionId70 = 'r00000000000000000000070'; -const String kReactionId71 = 'r00000000000000000000071'; -const String kReactionId72 = 'r00000000000000000000072'; -const String kReactionId73 = 'r00000000000000000000073'; -const String kReactionId74 = 'r00000000000000000000074'; -const String kReactionId75 = 'r00000000000000000000075'; -const String kReactionId76 = 'r00000000000000000000076'; -const String kReactionId77 = 'r00000000000000000000077'; -const String kReactionId78 = 'r00000000000000000000078'; -const String kReactionId79 = 'r00000000000000000000079'; -const String kReactionId80 = 'r00000000000000000000080'; -const String kReactionId81 = 'r00000000000000000000081'; -const String kReactionId82 = 'r00000000000000000000082'; -const String kReactionId83 = 'r00000000000000000000083'; -const String kReactionId84 = 'r00000000000000000000084'; -const String kReactionId85 = 'r00000000000000000000085'; -const String kReactionId86 = 'r00000000000000000000086'; -const String kReactionId87 = 'r00000000000000000000087'; -const String kReactionId88 = 'r00000000000000000000088'; -const String kReactionId89 = 'r00000000000000000000089'; -const String kReactionId90 = 'r00000000000000000000090'; -const String kReactionId91 = 'r00000000000000000000091'; -const String kReactionId92 = 'r00000000000000000000092'; -const String kReactionId93 = 'r00000000000000000000093'; -const String kReactionId94 = 'r00000000000000000000094'; -const String kReactionId95 = 'r00000000000000000000095'; -const String kReactionId96 = 'r00000000000000000000096'; -const String kReactionId97 = 'r00000000000000000000097'; -const String kReactionId98 = 'r00000000000000000000098'; -const String kReactionId99 = 'r00000000000000000000099'; -const String kReactionId100 = 'r00000000000000000000100'; + +/// Content Reports Fixture IDs. const String kReportId1 = 'rep0000000000000000000001'; const String kReportId2 = 'rep0000000000000000000002'; const String kReportId3 = 'rep0000000000000000000003'; diff --git a/lib/src/fixtures/headline_comments.dart b/lib/src/fixtures/headline_comments.dart index f55f830..939c1aa 100644 --- a/lib/src/fixtures/headline_comments.dart +++ b/lib/src/fixtures/headline_comments.dart @@ -11,8 +11,6 @@ List getHeadlineCommentsFixturesData({ }) { final comments = []; final users = usersFixturesData.take(10).toList(); - final headlines = getHeadlinesFixturesData().take(100).toList(); - final referenceTime = now ?? DateTime.now(); // Ensure only approved languages are used, default to 'en'. final resolvedLanguageCode = ['en', 'ar'].contains(languageCode) @@ -22,108 +20,6 @@ List getHeadlineCommentsFixturesData({ (lang) => lang.code == resolvedLanguageCode, orElse: () => languagesFixturesData.firstWhere((lang) => lang.code == 'en'), ); - final commentIds = [ - kCommentId1, - kCommentId2, - kCommentId3, - kCommentId4, - kCommentId5, - kCommentId6, - kCommentId7, - kCommentId8, - kCommentId9, - kCommentId10, - kCommentId11, - kCommentId12, - kCommentId13, - kCommentId14, - kCommentId15, - kCommentId16, - kCommentId17, - kCommentId18, - kCommentId19, - kCommentId20, - kCommentId21, - kCommentId22, - kCommentId23, - kCommentId24, - kCommentId25, - kCommentId26, - kCommentId27, - kCommentId28, - kCommentId29, - kCommentId30, - kCommentId31, - kCommentId32, - kCommentId33, - kCommentId34, - kCommentId35, - kCommentId36, - kCommentId37, - kCommentId38, - kCommentId39, - kCommentId40, - kCommentId41, - kCommentId42, - kCommentId43, - kCommentId44, - kCommentId45, - kCommentId46, - kCommentId47, - kCommentId48, - kCommentId49, - kCommentId50, - kCommentId51, - kCommentId52, - kCommentId53, - kCommentId54, - kCommentId55, - kCommentId56, - kCommentId57, - kCommentId58, - kCommentId59, - kCommentId60, - kCommentId61, - kCommentId62, - kCommentId63, - kCommentId64, - kCommentId65, - kCommentId66, - kCommentId67, - kCommentId68, - kCommentId69, - kCommentId70, - kCommentId71, - kCommentId72, - kCommentId73, - kCommentId74, - kCommentId75, - kCommentId76, - kCommentId77, - kCommentId78, - kCommentId79, - kCommentId80, - kCommentId81, - kCommentId82, - kCommentId83, - kCommentId84, - kCommentId85, - kCommentId86, - kCommentId87, - kCommentId88, - kCommentId89, - kCommentId90, - kCommentId91, - kCommentId92, - kCommentId93, - kCommentId94, - kCommentId95, - kCommentId96, - kCommentId97, - kCommentId98, - kCommentId99, - kCommentId100, - ]; final commentContentsByLang = >{ 'en': [ @@ -156,9 +52,6 @@ List getHeadlineCommentsFixturesData({ for (var i = 0; i < users.length; i++) { for (var j = 0; j < 10; j++) { - final user = users[i]; - final headlineIndex = i * 10 + j; - final headline = headlines[headlineIndex]; final commentIndex = i * 10 + j; // Vary the status for realism @@ -171,15 +64,9 @@ List getHeadlineCommentsFixturesData({ comments.add( Comment( - id: commentIds[commentIndex], - userId: user.id, - entityId: headline.id, - entityType: EngageableType.headline, language: language, content: commentContents[j], status: status, - createdAt: referenceTime.subtract(Duration(days: i, hours: j * 2)), - updatedAt: referenceTime.subtract(Duration(days: i, hours: j * 2)), ), ); } diff --git a/lib/src/fixtures/headline_reactions.dart b/lib/src/fixtures/headline_reactions.dart index fad3219..f58a3fe 100644 --- a/lib/src/fixtures/headline_reactions.dart +++ b/lib/src/fixtures/headline_reactions.dart @@ -1,136 +1,16 @@ import 'package:core/core.dart'; /// A list of predefined reactions for fixture data. -/// This creates 10 reactions for each of the first 10 users, with each -/// reaction targeting a unique headline. +/// This creates a list of reactions with varying types. final List reactionsFixturesData = () { final reactions = []; - final users = usersFixturesData.take(10).toList(); - final headlines = getHeadlinesFixturesData( - languageCode: 'en', - ).take(100).toList(); - final reactionIds = [ - kReactionId1, - kReactionId2, - kReactionId3, - kReactionId4, - kReactionId5, - kReactionId6, - kReactionId7, - kReactionId8, - kReactionId9, - kReactionId10, - kReactionId11, - kReactionId12, - kReactionId13, - kReactionId14, - kReactionId15, - kReactionId16, - kReactionId17, - kReactionId18, - kReactionId19, - kReactionId20, - kReactionId21, - kReactionId22, - kReactionId23, - kReactionId24, - kReactionId25, - kReactionId26, - kReactionId27, - kReactionId28, - kReactionId29, - kReactionId30, - kReactionId31, - kReactionId32, - kReactionId33, - kReactionId34, - kReactionId35, - kReactionId36, - kReactionId37, - kReactionId38, - kReactionId39, - kReactionId40, - kReactionId41, - kReactionId42, - kReactionId43, - kReactionId44, - kReactionId45, - kReactionId46, - kReactionId47, - kReactionId48, - kReactionId49, - kReactionId50, - kReactionId51, - kReactionId52, - kReactionId53, - kReactionId54, - kReactionId55, - kReactionId56, - kReactionId57, - kReactionId58, - kReactionId59, - kReactionId60, - kReactionId61, - kReactionId62, - kReactionId63, - kReactionId64, - kReactionId65, - kReactionId66, - kReactionId67, - kReactionId68, - kReactionId69, - kReactionId70, - kReactionId71, - kReactionId72, - kReactionId73, - kReactionId74, - kReactionId75, - kReactionId76, - kReactionId77, - kReactionId78, - kReactionId79, - kReactionId80, - kReactionId81, - kReactionId82, - kReactionId83, - kReactionId84, - kReactionId85, - kReactionId86, - kReactionId87, - kReactionId88, - kReactionId89, - kReactionId90, - kReactionId91, - kReactionId92, - kReactionId93, - kReactionId94, - kReactionId95, - kReactionId96, - kReactionId97, - kReactionId98, - kReactionId99, - kReactionId100, - ]; const reactionTypes = ReactionType.values; - for (var i = 0; i < users.length; i++) { - for (var j = 0; j < 10; j++) { - final user = users[i]; - final headlineIndex = i * 10 + j; - final headline = headlines[headlineIndex]; - final reactionIndex = i * 10 + j; - - reactions.add( - Reaction( - id: reactionIds[reactionIndex], - userId: user.id, - entityId: headline.id, - entityType: EngageableType.headline, - reactionType: reactionTypes[reactionIndex % reactionTypes.length], - createdAt: DateTime.now().subtract(Duration(days: i, hours: j)), - ), - ); - } + // Create 100 reactions, cycling through the available reaction types. + for (var i = 0; i < 100; i++) { + reactions.add( + Reaction(reactionType: reactionTypes[i % reactionTypes.length]), + ); } return reactions; diff --git a/lib/src/models/user_generated_content/comment.dart b/lib/src/models/user_generated_content/comment.dart index ba4c12a..f34d330 100644 --- a/lib/src/models/user_generated_content/comment.dart +++ b/lib/src/models/user_generated_content/comment.dart @@ -1,7 +1,5 @@ import 'package:core/src/enums/comment_status.dart'; -import 'package:core/src/enums/engageable_type.dart'; import 'package:core/src/models/entities/language.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'; @@ -9,21 +7,15 @@ import 'package:meta/meta.dart'; part 'comment.g.dart'; /// {@template user_comment} -/// Represents a user-submitted comment on a specific piece of content. +/// A value object representing the comment content within an [Engagement]. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class Comment extends Equatable { /// {@macro user_comment} const Comment({ - required this.id, - required this.userId, - required this.entityId, - required this.entityType, required this.language, required this.content, - required this.createdAt, - required this.updatedAt, this.status = CommentStatus.pendingReview, }); @@ -31,18 +23,6 @@ class Comment extends Equatable { factory Comment.fromJson(Map json) => _$CommentFromJson(json); - /// The unique identifier for the comment. - final String id; - - /// The ID of the user who authored the comment. - final String userId; - - /// The ID of the entity being commented on (e.g., a headline ID). - final String entityId; - - /// The type of entity being commented on. - final EngageableType entityType; - /// The language of the comment. final Language language; @@ -52,29 +32,11 @@ class Comment extends Equatable { /// The current moderation status of the comment. final CommentStatus status; - /// The timestamp when the comment was created. - @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) - final DateTime createdAt; - - /// The timestamp when the comment was last updated. - @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) - final DateTime updatedAt; - /// Converts this [Comment] instance to JSON data. Map toJson() => _$CommentToJson(this); @override - List get props => [ - id, - userId, - entityId, - entityType, - language, - content, - status, - createdAt, - updatedAt, - ]; + List get props => [language, content, status]; /// Creates a copy of this [Comment] with updated values. Comment copyWith({ @@ -83,15 +45,9 @@ class Comment extends Equatable { CommentStatus? status, }) { return Comment( - id: id, - userId: userId, - entityId: entityId, - entityType: entityType, language: language ?? this.language, content: content ?? this.content, status: status ?? this.status, - createdAt: createdAt, - updatedAt: DateTime.now(), ); } } diff --git a/lib/src/models/user_generated_content/engagement.dart b/lib/src/models/user_generated_content/engagement.dart index 75af0ca..273df4f 100644 --- a/lib/src/models/user_generated_content/engagement.dart +++ b/lib/src/models/user_generated_content/engagement.dart @@ -1,6 +1,7 @@ import 'package:core/src/enums/engageable_type.dart'; import 'package:core/src/models/user_generated_content/comment.dart'; import 'package:core/src/models/user_generated_content/reaction.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'; @@ -8,20 +9,22 @@ import 'package:meta/meta.dart'; part 'engagement.g.dart'; /// {@template engagement} -/// Represents a user's complete engagement action, which includes a mandatory -/// reaction and an optional comment, tied to a specific entity. -/// -/// This model is intended to be sent from the client to the backend in a -/// single request to record a user's interaction. +/// Represents a user's engagement with a specific piece of content. +/// An engagement consists of a mandatory reaction and an optional comment, +/// and is stored as a single document in the database. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class Engagement extends Equatable { /// {@macro engagement} const Engagement({ + required this.id, + required this.userId, required this.entityId, required this.entityType, required this.reaction, + required this.createdAt, + required this.updatedAt, this.comment, }); @@ -29,6 +32,12 @@ class Engagement extends Equatable { factory Engagement.fromJson(Map json) => _$EngagementFromJson(json); + /// The unique identifier for the engagement. + final String id; + + /// The ID of the user who made the engagement. + final String userId; + /// The ID of the entity being engaged with (e.g., a headline ID). final String entityId; @@ -41,19 +50,40 @@ class Engagement extends Equatable { /// The user's optional comment, provided along with the reaction. final Comment? comment; + /// The timestamp when the engagement was created. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime createdAt; + + /// The timestamp when the engagement was last updated. + @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) + final DateTime updatedAt; + /// Converts this [Engagement] instance to JSON data. Map toJson() => _$EngagementToJson(this); @override - List get props => [entityId, entityType, reaction, comment]; + List get props => [ + id, + userId, + entityId, + entityType, + reaction, + comment, + createdAt, + updatedAt, + ]; /// Creates a copy of this [Engagement] with updated values. Engagement copyWith({Reaction? reaction, Comment? comment}) { return Engagement( + id: id, + userId: userId, entityId: entityId, entityType: entityType, reaction: reaction ?? this.reaction, comment: comment ?? this.comment, + createdAt: createdAt, + updatedAt: DateTime.now(), ); } } diff --git a/lib/src/models/user_generated_content/reaction.dart b/lib/src/models/user_generated_content/reaction.dart index a67017c..dc27431 100644 --- a/lib/src/models/user_generated_content/reaction.dart +++ b/lib/src/models/user_generated_content/reaction.dart @@ -1,6 +1,4 @@ -import 'package:core/src/enums/engageable_type.dart'; import 'package:core/src/enums/reaction_type.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'; @@ -8,66 +6,29 @@ import 'package:meta/meta.dart'; part 'reaction.g.dart'; /// {@template reaction} -/// Represents a user's reaction to a specific piece of content. +/// A value object representing the type of reaction within an [Engagement]. /// {@endtemplate} @immutable @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class Reaction extends Equatable { /// {@macro reaction} - const Reaction({ - required this.id, - required this.userId, - required this.entityId, - required this.entityType, - required this.reactionType, - required this.createdAt, - }); + const Reaction({required this.reactionType}); /// Creates a [Reaction] from JSON data. factory Reaction.fromJson(Map json) => _$ReactionFromJson(json); - /// The unique identifier for the reaction. - final String id; - - /// The ID of the user who made the reaction. - final String userId; - - /// The ID of the entity being reacted to (e.g., a headline ID). - final String entityId; - - /// The type of entity being reacted to. - final EngageableType entityType; - /// The type of reaction (e.g., like, insightful). final ReactionType reactionType; - /// The timestamp when the reaction was created. - @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) - final DateTime createdAt; - /// Converts this [Reaction] instance to JSON data. Map toJson() => _$ReactionToJson(this); @override - List get props => [ - id, - userId, - entityId, - entityType, - reactionType, - createdAt, - ]; + List get props => [reactionType]; /// Creates a copy of this [Reaction] with updated values. Reaction copyWith({ReactionType? reactionType}) { - return Reaction( - id: id, - userId: userId, - entityId: entityId, - entityType: entityType, - reactionType: reactionType ?? this.reactionType, - createdAt: createdAt, - ); + return Reaction(reactionType: reactionType ?? this.reactionType); } } diff --git a/test/src/models/user_generated_content/comment_test.dart b/test/src/models/user_generated_content/comment_test.dart index 08a4be2..0ffd57b 100644 --- a/test/src/models/user_generated_content/comment_test.dart +++ b/test/src/models/user_generated_content/comment_test.dart @@ -34,29 +34,11 @@ void main() { status: newStatus, ); - expect(updatedComment.id, commentFixture.id); expect(updatedComment.content, newContent); expect(updatedComment.status, newStatus); // Verify other fields remain unchanged - expect(updatedComment.userId, commentFixture.userId); - expect(updatedComment.entityId, commentFixture.entityId); - // The updatedAt timestamp should be different - expect( - updatedComment.updatedAt, - isNot(equals(commentFixture.updatedAt)), - ); + expect(updatedComment.language, commentFixture.language); }); - - test( - 'returns a new instance with a new timestamp if no updates provided', - () { - final copiedComment = commentFixture.copyWith(); - // Should be a new instance with a new `updatedAt` time - expect(copiedComment, isNot(equals(commentFixture))); - expect(copiedComment.id, commentFixture.id); - expect(copiedComment.content, commentFixture.content); - }, - ); }); group('Equatable', () { @@ -83,15 +65,9 @@ void main() { expect( commentFixture.props, equals([ - commentFixture.id, - commentFixture.userId, - commentFixture.entityId, - commentFixture.entityType, commentFixture.language, commentFixture.content, commentFixture.status, - commentFixture.createdAt, - commentFixture.updatedAt, ]), ); }); diff --git a/test/src/models/user_generated_content/engagement_test.dart b/test/src/models/user_generated_content/engagement_test.dart index 4d26a96..f1bb6b3 100644 --- a/test/src/models/user_generated_content/engagement_test.dart +++ b/test/src/models/user_generated_content/engagement_test.dart @@ -56,14 +56,26 @@ void main() { expect(updatedEngagement.reaction, newReaction); // Verify other fields remain unchanged + expect(updatedEngagement.id, engagementFixture.id); + expect(updatedEngagement.userId, engagementFixture.userId); expect(updatedEngagement.entityId, engagementFixture.entityId); expect(updatedEngagement.comment, engagementFixture.comment); + expect(updatedEngagement.createdAt, engagementFixture.createdAt); + // The updatedAt timestamp should be different + expect( + updatedEngagement.updatedAt, + isNot(equals(engagementFixture.updatedAt)), + ); }); - test('returns an identical instance if no updates provided', () { - final copiedEngagement = engagementFixture.copyWith(); - expect(copiedEngagement, engagementFixture); - }); + test( + 'returns a new instance with a new timestamp if no updates provided', + () { + final copiedEngagement = engagementFixture.copyWith(); + expect(copiedEngagement, isNot(equals(engagementFixture))); + expect(copiedEngagement.id, engagementFixture.id); + }, + ); }); group('Equatable', () { @@ -88,10 +100,14 @@ void main() { test('props list should contain all relevant fields', () { expect(engagementFixture.props, [ + engagementFixture.id, + engagementFixture.userId, engagementFixture.entityId, engagementFixture.entityType, engagementFixture.reaction, engagementFixture.comment, + engagementFixture.createdAt, + engagementFixture.updatedAt, ]); }); }); diff --git a/test/src/models/user_generated_content/reaction_test.dart b/test/src/models/user_generated_content/reaction_test.dart index 05e43a9..44567db 100644 --- a/test/src/models/user_generated_content/reaction_test.dart +++ b/test/src/models/user_generated_content/reaction_test.dart @@ -28,17 +28,7 @@ void main() { reactionType: newReactionType, ); - expect(updatedReaction.id, reactionFixture.id); expect(updatedReaction.reactionType, newReactionType); - // Verify other fields remain unchanged - expect(updatedReaction.userId, reactionFixture.userId); - expect(updatedReaction.entityId, reactionFixture.entityId); - expect(updatedReaction.createdAt, reactionFixture.createdAt); - }); - - test('returns an identical instance if no updates provided', () { - final copiedReaction = reactionFixture.copyWith(); - expect(copiedReaction, reactionFixture); }); }); @@ -57,17 +47,7 @@ void main() { }); test('props list should contain all relevant fields', () { - expect( - reactionFixture.props, - equals([ - reactionFixture.id, - reactionFixture.userId, - reactionFixture.entityId, - reactionFixture.entityType, - reactionFixture.reactionType, - reactionFixture.createdAt, - ]), - ); + expect(reactionFixture.props, equals([reactionFixture.reactionType])); }); }); } From 8f42833bc85a00ae738c8f213a1d17c217bfe0a3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 05:16:44 +0100 Subject: [PATCH 92/93] refactor(report): rename `ReportableEntity.comment` to `ReportableEntity.engagement` - Update `ReportableEntity` enum: - Rename `comment` to `engagement` - Update JSON value to 'engagement' - Update related tests - Update fixtures to use `engagement` instead of `comment` - Update models to include missing fields and adjust serialization --- lib/src/enums/reportable_entity.dart | 6 ++--- lib/src/fixtures/reports.dart | 7 ++++-- .../user_generated_content/comment.g.dart | 23 ------------------- .../user_generated_content/engagement.g.dart | 14 +++++++++++ .../user_generated_content/reaction.g.dart | 18 --------------- .../user_generated_content/report.g.dart | 2 +- test/src/enums/reportable_entity_test.dart | 8 +++---- 7 files changed, 27 insertions(+), 51 deletions(-) diff --git a/lib/src/enums/reportable_entity.dart b/lib/src/enums/reportable_entity.dart index 403ea51..5417e58 100644 --- a/lib/src/enums/reportable_entity.dart +++ b/lib/src/enums/reportable_entity.dart @@ -16,7 +16,7 @@ enum ReportableEntity { @JsonValue('source') source, - /// The report is for a user-submitted comment. - @JsonValue('comment') - comment, + /// The report is for a user engagement (mainly for engagements with comments). + @JsonValue('engagement') + engagement, } diff --git a/lib/src/fixtures/reports.dart b/lib/src/fixtures/reports.dart index 6ccaa1b..656f321 100644 --- a/lib/src/fixtures/reports.dart +++ b/lib/src/fixtures/reports.dart @@ -14,6 +14,9 @@ List getReportsFixturesData({DateTime? now}) { final headlines = getHeadlinesFixturesData( now: referenceTime, ).take(10).toList(); + final engagementsWithComments = getEngagementsFixturesData( + now: referenceTime, + ).where((e) => e.comment != null).toList(); final reportIds = [ kReportId1, kReportId2, @@ -75,8 +78,8 @@ List getReportsFixturesData({DateTime? now}) { Report( id: reportIds[i], reporterUserId: user.id, - entityType: ReportableEntity.comment, - entityId: getHeadlineCommentsFixturesData(now: referenceTime)[i].id, + entityType: ReportableEntity.engagement, + entityId: engagementsWithComments[i].id, reason: commentReasons[i % commentReasons.length].name, additionalComments: 'This comment is spam.', status: status, diff --git a/lib/src/models/user_generated_content/comment.g.dart b/lib/src/models/user_generated_content/comment.g.dart index 61daf4e..c8492fe 100644 --- a/lib/src/models/user_generated_content/comment.g.dart +++ b/lib/src/models/user_generated_content/comment.g.dart @@ -9,26 +9,11 @@ part of 'comment.dart'; Comment _$CommentFromJson(Map json) => $checkedCreate('Comment', json, ($checkedConvert) { final val = Comment( - id: $checkedConvert('id', (v) => v as String), - userId: $checkedConvert('userId', (v) => v as String), - entityId: $checkedConvert('entityId', (v) => v as String), - entityType: $checkedConvert( - 'entityType', - (v) => $enumDecode(_$EngageableTypeEnumMap, v), - ), language: $checkedConvert( 'language', (v) => Language.fromJson(v as Map), ), content: $checkedConvert('content', (v) => v as String), - createdAt: $checkedConvert( - 'createdAt', - (v) => dateTimeFromJson(v as String?), - ), - updatedAt: $checkedConvert( - 'updatedAt', - (v) => dateTimeFromJson(v as String?), - ), status: $checkedConvert( 'status', (v) => @@ -40,19 +25,11 @@ Comment _$CommentFromJson(Map json) => }); Map _$CommentToJson(Comment instance) => { - 'id': instance.id, - 'userId': instance.userId, - 'entityId': instance.entityId, - 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, 'language': instance.language.toJson(), 'content': instance.content, 'status': _$CommentStatusEnumMap[instance.status]!, - 'createdAt': dateTimeToJson(instance.createdAt), - 'updatedAt': dateTimeToJson(instance.updatedAt), }; -const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; - const _$CommentStatusEnumMap = { CommentStatus.pendingReview: 'pendingReview', CommentStatus.approved: 'approved', diff --git a/lib/src/models/user_generated_content/engagement.g.dart b/lib/src/models/user_generated_content/engagement.g.dart index 4de41d4..2c5c775 100644 --- a/lib/src/models/user_generated_content/engagement.g.dart +++ b/lib/src/models/user_generated_content/engagement.g.dart @@ -9,6 +9,8 @@ part of 'engagement.dart'; Engagement _$EngagementFromJson(Map json) => $checkedCreate('Engagement', json, ($checkedConvert) { final val = Engagement( + id: $checkedConvert('id', (v) => v as String), + userId: $checkedConvert('userId', (v) => v as String), entityId: $checkedConvert('entityId', (v) => v as String), entityType: $checkedConvert( 'entityType', @@ -18,6 +20,14 @@ Engagement _$EngagementFromJson(Map json) => 'reaction', (v) => Reaction.fromJson(v as Map), ), + createdAt: $checkedConvert( + 'createdAt', + (v) => dateTimeFromJson(v as String?), + ), + updatedAt: $checkedConvert( + 'updatedAt', + (v) => dateTimeFromJson(v as String?), + ), comment: $checkedConvert( 'comment', (v) => v == null ? null : Comment.fromJson(v as Map), @@ -28,10 +38,14 @@ Engagement _$EngagementFromJson(Map json) => Map _$EngagementToJson(Engagement instance) => { + 'id': instance.id, + 'userId': instance.userId, 'entityId': instance.entityId, 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, 'reaction': instance.reaction.toJson(), 'comment': instance.comment?.toJson(), + 'createdAt': dateTimeToJson(instance.createdAt), + 'updatedAt': dateTimeToJson(instance.updatedAt), }; const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; diff --git a/lib/src/models/user_generated_content/reaction.g.dart b/lib/src/models/user_generated_content/reaction.g.dart index 8ca980d..209d603 100644 --- a/lib/src/models/user_generated_content/reaction.g.dart +++ b/lib/src/models/user_generated_content/reaction.g.dart @@ -9,36 +9,18 @@ part of 'reaction.dart'; Reaction _$ReactionFromJson(Map json) => $checkedCreate('Reaction', json, ($checkedConvert) { final val = Reaction( - id: $checkedConvert('id', (v) => v as String), - userId: $checkedConvert('userId', (v) => v as String), - entityId: $checkedConvert('entityId', (v) => v as String), - entityType: $checkedConvert( - 'entityType', - (v) => $enumDecode(_$EngageableTypeEnumMap, v), - ), reactionType: $checkedConvert( 'reactionType', (v) => $enumDecode(_$ReactionTypeEnumMap, v), ), - createdAt: $checkedConvert( - 'createdAt', - (v) => dateTimeFromJson(v as String?), - ), ); return val; }); Map _$ReactionToJson(Reaction instance) => { - 'id': instance.id, - 'userId': instance.userId, - 'entityId': instance.entityId, - 'entityType': _$EngageableTypeEnumMap[instance.entityType]!, 'reactionType': _$ReactionTypeEnumMap[instance.reactionType]!, - 'createdAt': dateTimeToJson(instance.createdAt), }; -const _$EngageableTypeEnumMap = {EngageableType.headline: 'headline'}; - const _$ReactionTypeEnumMap = { ReactionType.like: 'like', ReactionType.insightful: 'insightful', diff --git a/lib/src/models/user_generated_content/report.g.dart b/lib/src/models/user_generated_content/report.g.dart index 3016fda..84a93a5 100644 --- a/lib/src/models/user_generated_content/report.g.dart +++ b/lib/src/models/user_generated_content/report.g.dart @@ -47,7 +47,7 @@ Map _$ReportToJson(Report instance) => { const _$ReportableEntityEnumMap = { ReportableEntity.headline: 'headline', ReportableEntity.source: 'source', - ReportableEntity.comment: 'comment', + ReportableEntity.engagement: 'engagement', }; const _$ReportStatusEnumMap = { diff --git a/test/src/enums/reportable_entity_test.dart b/test/src/enums/reportable_entity_test.dart index ce9e261..1ab6dbd 100644 --- a/test/src/enums/reportable_entity_test.dart +++ b/test/src/enums/reportable_entity_test.dart @@ -9,7 +9,7 @@ void main() { containsAll([ ReportableEntity.headline, ReportableEntity.source, - ReportableEntity.comment, + ReportableEntity.engagement, ]), ); }); @@ -17,7 +17,7 @@ void main() { test('has correct string values', () { expect(ReportableEntity.headline.name, 'headline'); expect(ReportableEntity.source.name, 'source'); - expect(ReportableEntity.comment.name, 'comment'); + expect(ReportableEntity.engagement.name, 'engagement'); }); test('can be created from string values', () { @@ -27,8 +27,8 @@ void main() { ); expect(ReportableEntity.values.byName('source'), ReportableEntity.source); expect( - ReportableEntity.values.byName('comment'), - ReportableEntity.comment, + ReportableEntity.values.byName('engagement'), + ReportableEntity.engagement, ); }); }); From 754d1e5ade2886edceae4a5991f5e62dd9c625d6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Sun, 30 Nov 2025 05:17:23 +0100 Subject: [PATCH 93/93] style: format --- lib/src/models/user_generated_content/comment.dart | 6 ++++++ lib/src/models/user_generated_content/reaction.dart | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/src/models/user_generated_content/comment.dart b/lib/src/models/user_generated_content/comment.dart index f34d330..4775fa4 100644 --- a/lib/src/models/user_generated_content/comment.dart +++ b/lib/src/models/user_generated_content/comment.dart @@ -1,5 +1,11 @@ +import 'package:core/core.dart' show Engagement; import 'package:core/src/enums/comment_status.dart'; import 'package:core/src/models/entities/language.dart'; +import 'package:core/src/models/models.dart' show Engagement; +import 'package:core/src/models/user_generated_content/engagement.dart' + show Engagement; +import 'package:core/src/models/user_generated_content/user_generated_content.dart' + show Engagement; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; diff --git a/lib/src/models/user_generated_content/reaction.dart b/lib/src/models/user_generated_content/reaction.dart index dc27431..ddd7ebc 100644 --- a/lib/src/models/user_generated_content/reaction.dart +++ b/lib/src/models/user_generated_content/reaction.dart @@ -1,4 +1,10 @@ +import 'package:core/core.dart' show Engagement; import 'package:core/src/enums/reaction_type.dart'; +import 'package:core/src/models/models.dart' show Engagement; +import 'package:core/src/models/user_generated_content/engagement.dart' + show Engagement; +import 'package:core/src/models/user_generated_content/user_generated_content.dart' + show Engagement; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart';