From 90a91176883367fa7bf90119351338156f4c6222 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 09:28:34 +0100 Subject: [PATCH 01/75] chore: delete absolete files --- lib/src/enums/in_article_ad_slot_type.dart | 10 --- .../config/article_ad_configuration.dart | 58 ----------------- .../config/article_ad_configuration.g.dart | 65 ------------------- .../config/interstitial_ad_configuration.dart | 60 ----------------- .../interstitial_ad_configuration.g.dart | 52 --------------- .../interstitial_ad_frequency_config.dart | 44 ------------- .../interstitial_ad_frequency_config.g.dart | 26 -------- 7 files changed, 315 deletions(-) delete mode 100644 lib/src/enums/in_article_ad_slot_type.dart delete mode 100644 lib/src/models/config/article_ad_configuration.dart delete mode 100644 lib/src/models/config/article_ad_configuration.g.dart delete mode 100644 lib/src/models/config/interstitial_ad_configuration.dart delete mode 100644 lib/src/models/config/interstitial_ad_configuration.g.dart delete mode 100644 lib/src/models/config/interstitial_ad_frequency_config.dart delete mode 100644 lib/src/models/config/interstitial_ad_frequency_config.g.dart diff --git a/lib/src/enums/in_article_ad_slot_type.dart b/lib/src/enums/in_article_ad_slot_type.dart deleted file mode 100644 index d90c1d24..00000000 --- a/lib/src/enums/in_article_ad_slot_type.dart +++ /dev/null @@ -1,10 +0,0 @@ -/// {@template in_article_ad_slot_type} -/// Defines specific, standard locations for ads within an article's content. -/// {@endtemplate} -enum InArticleAdSlotType { - /// Ad placed just before a "Continue Reading" button. - aboveArticleContinueReadingButton, - - /// Ad placed just after a "Continue Reading" button. - belowArticleContinueReadingButton, -} diff --git a/lib/src/models/config/article_ad_configuration.dart b/lib/src/models/config/article_ad_configuration.dart deleted file mode 100644 index 8cb05f5f..00000000 --- a/lib/src/models/config/article_ad_configuration.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/enums/banner_ad_shape.dart'; -import 'package:core/src/enums/in_article_ad_slot_type.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'article_ad_configuration.g.dart'; - -/// {@template article_ad_configuration} -/// Master configuration for all ads on the article page (headline details), -/// excluding interstitial ads which are managed globally. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class ArticleAdConfiguration extends Equatable { - /// {@macro article_ad_configuration} - const ArticleAdConfiguration({ - required this.enabled, - required this.bannerAdShape, - required this.visibleTo, - }); - - /// Creates an [ArticleAdConfiguration] from JSON data. - factory ArticleAdConfiguration.fromJson(Map json) => - _$ArticleAdConfigurationFromJson(json); - - /// Converts this [ArticleAdConfiguration] instance to JSON data. - Map toJson() => _$ArticleAdConfigurationToJson(this); - - /// Master switch to enable or disable all in-article ads (excluding interstitial). - final bool enabled; - - /// The preferred shape for banner ads displayed in articles. - final BannerAdShape bannerAdShape; - - /// Explicitly defines which user roles can see in-article ad slots - /// and which specific slots are enabled for them. If a role is not - /// in this map, they will not see in-article ads. - final Map> visibleTo; - - @override - List get props => [enabled, bannerAdShape, visibleTo]; - - /// Creates a copy of this [ArticleAdConfiguration] but with the given fields - /// replaced with the new values. - ArticleAdConfiguration copyWith({ - bool? enabled, - Map>? visibleTo, - BannerAdShape? bannerAdShape, - }) { - return ArticleAdConfiguration( - enabled: enabled ?? this.enabled, - visibleTo: visibleTo ?? this.visibleTo, - bannerAdShape: bannerAdShape ?? this.bannerAdShape, - ); - } -} diff --git a/lib/src/models/config/article_ad_configuration.g.dart b/lib/src/models/config/article_ad_configuration.g.dart deleted file mode 100644 index 83c8a81b..00000000 --- a/lib/src/models/config/article_ad_configuration.g.dart +++ /dev/null @@ -1,65 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'article_ad_configuration.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ArticleAdConfiguration _$ArticleAdConfigurationFromJson( - Map json, -) => $checkedCreate('ArticleAdConfiguration', json, ($checkedConvert) { - final val = ArticleAdConfiguration( - enabled: $checkedConvert('enabled', (v) => v as bool), - bannerAdShape: $checkedConvert( - 'bannerAdShape', - (v) => $enumDecode(_$BannerAdShapeEnumMap, v), - ), - visibleTo: $checkedConvert( - 'visibleTo', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$AppUserRoleEnumMap, k), - (e as Map).map( - (k, e) => MapEntry( - $enumDecode(_$InArticleAdSlotTypeEnumMap, k), - e as bool, - ), - ), - ), - ), - ), - ); - return val; -}); - -Map _$ArticleAdConfigurationToJson( - ArticleAdConfiguration instance, -) => { - 'enabled': instance.enabled, - 'bannerAdShape': _$BannerAdShapeEnumMap[instance.bannerAdShape]!, - 'visibleTo': instance.visibleTo.map( - (k, e) => MapEntry( - _$AppUserRoleEnumMap[k]!, - e.map((k, e) => MapEntry(_$InArticleAdSlotTypeEnumMap[k]!, e)), - ), - ), -}; - -const _$BannerAdShapeEnumMap = { - BannerAdShape.square: 'square', - BannerAdShape.rectangle: 'rectangle', -}; - -const _$InArticleAdSlotTypeEnumMap = { - InArticleAdSlotType.aboveArticleContinueReadingButton: - 'aboveArticleContinueReadingButton', - InArticleAdSlotType.belowArticleContinueReadingButton: - 'belowArticleContinueReadingButton', -}; - -const _$AppUserRoleEnumMap = { - AppUserRole.premiumUser: 'premiumUser', - AppUserRole.standardUser: 'standardUser', - AppUserRole.guestUser: 'guestUser', -}; diff --git a/lib/src/models/config/interstitial_ad_configuration.dart b/lib/src/models/config/interstitial_ad_configuration.dart deleted file mode 100644 index abfcd731..00000000 --- a/lib/src/models/config/interstitial_ad_configuration.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:core/src/enums/ad_type.dart'; -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/models/config/interstitial_ad_frequency_config.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'interstitial_ad_configuration.g.dart'; - -/// {@template interstitial_ad_configuration} -/// Master configuration for all interstitial ads across the application. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class InterstitialAdConfiguration extends Equatable { - /// {@macro interstitial_ad_configuration} - const InterstitialAdConfiguration({ - required this.enabled, - required this.visibleTo, - this.adType = AdType.interstitial, - }) : assert( - adType == AdType.interstitial, - 'Interstitial ads must be of type interstitial.', - ); - - /// Creates an [InterstitialAdConfiguration] from JSON data. - factory InterstitialAdConfiguration.fromJson(Map json) => - _$InterstitialAdConfigurationFromJson(json); - - /// Converts this [InterstitialAdConfiguration] instance to JSON data. - Map toJson() => _$InterstitialAdConfigurationToJson(this); - - /// Master switch to enable or disable interstitial ads globally. - final bool enabled; - - /// The type of the ad, fixed to [AdType.interstitial]. - final AdType adType; - - /// Explicitly defines which user roles can see this interstitial ad - /// configuration and their specific frequency settings. If a role is not - /// in this map, they will not see interstitial ads. - final Map visibleTo; - - @override - List get props => [enabled, adType, visibleTo]; - - /// Creates a copy of this [InterstitialAdConfiguration] but with - /// the given fields replaced with the new values. - InterstitialAdConfiguration copyWith({ - bool? enabled, - AdType? adType, - Map? visibleTo, - }) { - return InterstitialAdConfiguration( - enabled: enabled ?? this.enabled, - adType: adType ?? this.adType, - visibleTo: visibleTo ?? this.visibleTo, - ); - } -} diff --git a/lib/src/models/config/interstitial_ad_configuration.g.dart b/lib/src/models/config/interstitial_ad_configuration.g.dart deleted file mode 100644 index a62d3c3b..00000000 --- a/lib/src/models/config/interstitial_ad_configuration.g.dart +++ /dev/null @@ -1,52 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'interstitial_ad_configuration.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -InterstitialAdConfiguration _$InterstitialAdConfigurationFromJson( - Map json, -) => $checkedCreate('InterstitialAdConfiguration', json, ($checkedConvert) { - final val = InterstitialAdConfiguration( - enabled: $checkedConvert('enabled', (v) => v as bool), - visibleTo: $checkedConvert( - 'visibleTo', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$AppUserRoleEnumMap, k), - InterstitialAdFrequencyConfig.fromJson(e as Map), - ), - ), - ), - adType: $checkedConvert( - 'adType', - (v) => $enumDecodeNullable(_$AdTypeEnumMap, v) ?? AdType.interstitial, - ), - ); - return val; -}); - -Map _$InterstitialAdConfigurationToJson( - InterstitialAdConfiguration 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/interstitial_ad_frequency_config.dart b/lib/src/models/config/interstitial_ad_frequency_config.dart deleted file mode 100644 index d03ed8eb..00000000 --- a/lib/src/models/config/interstitial_ad_frequency_config.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'interstitial_ad_frequency_config.g.dart'; - -/// {@template interstitial_ad_frequency_config} -/// Encapsulates ad frequency for interstitial ads shown during page transitions -/// for a specific user role. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class InterstitialAdFrequencyConfig extends Equatable { - /// {@macro interstitial_ad_frequency_config} - const InterstitialAdFrequencyConfig({ - required this.transitionsBeforeShowingInterstitialAds, - }); - - /// Creates a [InterstitialAdFrequencyConfig] from JSON data. - factory InterstitialAdFrequencyConfig.fromJson(Map json) => - _$InterstitialAdFrequencyConfigFromJson(json); - - /// Converts this [InterstitialAdFrequencyConfig] instance to JSON data. - Map toJson() => _$InterstitialAdFrequencyConfigToJson(this); - - /// The number of page transitions a user needs to make - /// before an interstitial ad is shown. - final int transitionsBeforeShowingInterstitialAds; - - @override - List get props => [transitionsBeforeShowingInterstitialAds]; - - /// Creates a copy of this [InterstitialAdFrequencyConfig] but with - /// the given fields replaced with the new values. - InterstitialAdFrequencyConfig copyWith({ - int? transitionsBeforeShowingInterstitialAds, - }) { - return InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: - transitionsBeforeShowingInterstitialAds ?? - this.transitionsBeforeShowingInterstitialAds, - ); - } -} diff --git a/lib/src/models/config/interstitial_ad_frequency_config.g.dart b/lib/src/models/config/interstitial_ad_frequency_config.g.dart deleted file mode 100644 index 2a22a80a..00000000 --- a/lib/src/models/config/interstitial_ad_frequency_config.g.dart +++ /dev/null @@ -1,26 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'interstitial_ad_frequency_config.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -InterstitialAdFrequencyConfig _$InterstitialAdFrequencyConfigFromJson( - Map json, -) => $checkedCreate('InterstitialAdFrequencyConfig', json, ($checkedConvert) { - final val = InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: $checkedConvert( - 'transitionsBeforeShowingInterstitialAds', - (v) => (v as num).toInt(), - ), - ); - return val; -}); - -Map _$InterstitialAdFrequencyConfigToJson( - InterstitialAdFrequencyConfig instance, -) => { - 'transitionsBeforeShowingInterstitialAds': - instance.transitionsBeforeShowingInterstitialAds, -}; From e1522ec3586a6f25ded95a116a9bc5f7a9f254d6 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 09:28:56 +0100 Subject: [PATCH 02/75] feat(enums): add HeadlineClickBehavior enum - Define a new enum for headline click behavior - Include options for default, in-app browser, and system browser - Add json_annotation for enum serialization --- lib/src/enums/headline_click_behavior.dart | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 lib/src/enums/headline_click_behavior.dart diff --git a/lib/src/enums/headline_click_behavior.dart b/lib/src/enums/headline_click_behavior.dart new file mode 100644 index 00000000..bacb60c3 --- /dev/null +++ b/lib/src/enums/headline_click_behavior.dart @@ -0,0 +1,19 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template headline_click_behavior} +/// Defines how a headline's external URL should be opened. +/// {@endtemplate} +@JsonEnum() +enum HeadlineClickBehavior { + /// Adhere to the behavior defined by the admin in the remote config. + @JsonValue('default') + defaultBehavior, + + /// Open the URL in a browser within the app. + @JsonValue('inAppBrowser') + inAppBrowser, + + /// Open the URL in the device's default system browser. + @JsonValue('systemBrowser') + systemBrowser, +} From 68e72055520ad0dfb3f3a339261984ae0e40feab Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:33:21 +0100 Subject: [PATCH 03/75] feat(enums): add FeedItemClickBehavior enum - Define a new enum to specify how feed item clicks should be handled - Include options for default behavior, internal navigation, and external navigation - Add JSON serialization annotations for each value --- lib/src/enums/feed_item_click_behavior.dart | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 lib/src/enums/feed_item_click_behavior.dart diff --git a/lib/src/enums/feed_item_click_behavior.dart b/lib/src/enums/feed_item_click_behavior.dart new file mode 100644 index 00000000..74354aba --- /dev/null +++ b/lib/src/enums/feed_item_click_behavior.dart @@ -0,0 +1,17 @@ +import 'package:json_annotation/json_annotation.dart'; + +/// {@template feed_item_click_behavior} +/// Defines how a feed item click should be handled. +/// {@endtemplate} +@JsonEnum() +enum FeedItemClickBehavior { + /// Adhere to the behavior defined by the admin in the remote config. + @JsonValue('default') + defaultBehavior, + + @JsonValue('internalNavigation') + internalNavigation, + + @JsonValue('externalNavigation') + externalNavigation, +} From ef87c7ef527e71b71dd65ef77b208276a208ec5b Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:33:51 +0100 Subject: [PATCH 04/75] chore: deleted files --- lib/src/enums/headline_click_behavior.dart | 19 ----- lib/src/enums/headline_density.dart | 11 --- lib/src/enums/headline_image_style.dart | 11 --- test/src/enums/headline_density_test.dart | 44 ------------ test/src/enums/headline_image_style_test.dart | 38 ---------- .../enums/in_article_ad_slot_type_test.dart | 39 ----------- .../config/article_ad_configuration_test.dart | 58 --------------- .../interstitial_ad_configuration_test.dart | 70 ------------------- ...interstitial_ad_frequency_config_test.dart | 63 ----------------- 9 files changed, 353 deletions(-) delete mode 100644 lib/src/enums/headline_click_behavior.dart delete mode 100644 lib/src/enums/headline_density.dart delete mode 100644 lib/src/enums/headline_image_style.dart delete mode 100644 test/src/enums/headline_density_test.dart delete mode 100644 test/src/enums/headline_image_style_test.dart delete mode 100644 test/src/enums/in_article_ad_slot_type_test.dart delete mode 100644 test/src/models/config/article_ad_configuration_test.dart delete mode 100644 test/src/models/config/interstitial_ad_configuration_test.dart delete mode 100644 test/src/models/config/interstitial_ad_frequency_config_test.dart diff --git a/lib/src/enums/headline_click_behavior.dart b/lib/src/enums/headline_click_behavior.dart deleted file mode 100644 index bacb60c3..00000000 --- a/lib/src/enums/headline_click_behavior.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -/// {@template headline_click_behavior} -/// Defines how a headline's external URL should be opened. -/// {@endtemplate} -@JsonEnum() -enum HeadlineClickBehavior { - /// Adhere to the behavior defined by the admin in the remote config. - @JsonValue('default') - defaultBehavior, - - /// Open the URL in a browser within the app. - @JsonValue('inAppBrowser') - inAppBrowser, - - /// Open the URL in the device's default system browser. - @JsonValue('systemBrowser') - systemBrowser, -} diff --git a/lib/src/enums/headline_density.dart b/lib/src/enums/headline_density.dart deleted file mode 100644 index 8538b2ae..00000000 --- a/lib/src/enums/headline_density.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Defines how densely headline information should be presented. -enum HeadlineDensity { - /// Minimal spacing, smaller title font. - compact, - - /// Balanced spacing and font sizes. - standard, - - /// More whitespace, potentially larger title font. - comfortable, -} diff --git a/lib/src/enums/headline_image_style.dart b/lib/src/enums/headline_image_style.dart deleted file mode 100644 index 155259a2..00000000 --- a/lib/src/enums/headline_image_style.dart +++ /dev/null @@ -1,11 +0,0 @@ -/// Defines how images should be displayed in the headline feed. -enum HeadlineImageStyle { - /// No image shown in the feed. - hidden, - - /// A small leading or trailing image thumbnail. - smallThumbnail, - - /// A more prominent image, perhaps above or below the text. - largeThumbnail, -} diff --git a/test/src/enums/headline_density_test.dart b/test/src/enums/headline_density_test.dart deleted file mode 100644 index d29b64a0..00000000 --- a/test/src/enums/headline_density_test.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:core/src/enums/headline_density.dart'; -import 'package:test/test.dart'; - -void main() { - group('HeadlineDensity', () { - test('has correct values', () { - expect( - HeadlineDensity.values, - containsAll([ - HeadlineDensity.compact, - HeadlineDensity.standard, - HeadlineDensity.comfortable, - ]), - ); - }); - - test('has correct string values', () { - expect(HeadlineDensity.compact.name, 'compact'); - expect(HeadlineDensity.standard.name, 'standard'); - expect(HeadlineDensity.comfortable.name, 'comfortable'); - }); - - test('can be created from string values', () { - expect(HeadlineDensity.values.byName('compact'), HeadlineDensity.compact); - expect( - HeadlineDensity.values.byName('standard'), - HeadlineDensity.standard, - ); - expect( - HeadlineDensity.values.byName('comfortable'), - HeadlineDensity.comfortable, - ); - }); - - test('has correct toString representation', () { - expect(HeadlineDensity.compact.toString(), 'HeadlineDensity.compact'); - expect(HeadlineDensity.standard.toString(), 'HeadlineDensity.standard'); - expect( - HeadlineDensity.comfortable.toString(), - 'HeadlineDensity.comfortable', - ); - }); - }); -} diff --git a/test/src/enums/headline_image_style_test.dart b/test/src/enums/headline_image_style_test.dart deleted file mode 100644 index dc25389f..00000000 --- a/test/src/enums/headline_image_style_test.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:core/src/enums/headline_image_style.dart'; -import 'package:test/test.dart'; - -void main() { - group('HeadlineImageStyle', () { - test('has correct values', () { - expect( - HeadlineImageStyle.values, - containsAll([ - HeadlineImageStyle.hidden, - HeadlineImageStyle.smallThumbnail, - HeadlineImageStyle.largeThumbnail, - ]), - ); - }); - - test('has correct string values', () { - expect(HeadlineImageStyle.hidden.name, 'hidden'); - expect(HeadlineImageStyle.smallThumbnail.name, 'smallThumbnail'); - expect(HeadlineImageStyle.largeThumbnail.name, 'largeThumbnail'); - }); - - test('can be created from string values', () { - expect( - HeadlineImageStyle.values.byName('hidden'), - HeadlineImageStyle.hidden, - ); - expect( - HeadlineImageStyle.values.byName('smallThumbnail'), - HeadlineImageStyle.smallThumbnail, - ); - expect( - HeadlineImageStyle.values.byName('largeThumbnail'), - HeadlineImageStyle.largeThumbnail, - ); - }); - }); -} diff --git a/test/src/enums/in_article_ad_slot_type_test.dart b/test/src/enums/in_article_ad_slot_type_test.dart deleted file mode 100644 index 4d7064a1..00000000 --- a/test/src/enums/in_article_ad_slot_type_test.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:core/src/enums/in_article_ad_slot_type.dart'; -import 'package:test/test.dart'; - -void main() { - group('InArticleAdSlotType', () { - test('has correct string values', () { - expect( - InArticleAdSlotType.aboveArticleContinueReadingButton.name, - 'aboveArticleContinueReadingButton', - ); - expect( - InArticleAdSlotType.belowArticleContinueReadingButton.name, - 'belowArticleContinueReadingButton', - ); - }); - - test('can be created from string values', () { - expect( - InArticleAdSlotType.values.byName('aboveArticleContinueReadingButton'), - InArticleAdSlotType.aboveArticleContinueReadingButton, - ); - expect( - InArticleAdSlotType.values.byName('belowArticleContinueReadingButton'), - InArticleAdSlotType.belowArticleContinueReadingButton, - ); - }); - - test('has correct toString representation', () { - expect( - InArticleAdSlotType.aboveArticleContinueReadingButton.toString(), - 'InArticleAdSlotType.aboveArticleContinueReadingButton', - ); - expect( - InArticleAdSlotType.belowArticleContinueReadingButton.toString(), - 'InArticleAdSlotType.belowArticleContinueReadingButton', - ); - }); - }); -} diff --git a/test/src/models/config/article_ad_configuration_test.dart b/test/src/models/config/article_ad_configuration_test.dart deleted file mode 100644 index fa84ad69..00000000 --- a/test/src/models/config/article_ad_configuration_test.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('ArticleAdConfiguration', () { - final articleAdConfigurationFixture = - remoteConfigsFixturesData.first.adConfig.articleAdConfiguration; - - test('can be instantiated', () { - expect(articleAdConfigurationFixture, isA()); - expect(articleAdConfigurationFixture.enabled, isTrue); - expect( - articleAdConfigurationFixture.bannerAdShape, - BannerAdShape.rectangle, - ); - expect( - articleAdConfigurationFixture.visibleTo, - isA>>(), - ); - }); - - test('supports value equality', () { - final config1 = articleAdConfigurationFixture.copyWith(); - final config2 = articleAdConfigurationFixture.copyWith(); - expect(config1, equals(config2)); - }); - - test('copyWith returns a new instance with updated values', () { - final updatedConfig = articleAdConfigurationFixture.copyWith( - enabled: false, - bannerAdShape: BannerAdShape.square, - visibleTo: { - AppUserRole.guestUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: false, - }, - }, - ); - - expect(updatedConfig.enabled, isFalse); - expect(updatedConfig.bannerAdShape, BannerAdShape.square); - expect(updatedConfig.visibleTo[AppUserRole.guestUser]!.length, 1); - expect(updatedConfig, isNot(equals(articleAdConfigurationFixture))); - }); - - test('copyWith returns same instance if no changes', () { - final updatedConfig = articleAdConfigurationFixture.copyWith(); - expect(updatedConfig, equals(articleAdConfigurationFixture)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = articleAdConfigurationFixture.toJson(); - final result = ArticleAdConfiguration.fromJson(json); - expect(result, equals(articleAdConfigurationFixture)); - }); - }); - }); -} diff --git a/test/src/models/config/interstitial_ad_configuration_test.dart b/test/src/models/config/interstitial_ad_configuration_test.dart deleted file mode 100644 index 89b3aa86..00000000 --- a/test/src/models/config/interstitial_ad_configuration_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('InterstitialAdConfiguration', () { - final interstitialAdConfigFixture = - remoteConfigsFixturesData.first.adConfig.interstitialAdConfiguration; - - group('constructor', () { - test('returns correct instance', () { - expect(interstitialAdConfigFixture, isA()); - expect(interstitialAdConfigFixture.enabled, isA()); - expect(interstitialAdConfigFixture.adType, AdType.interstitial); - expect( - interstitialAdConfigFixture.visibleTo, - isA>(), - ); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = interstitialAdConfigFixture.toJson(); - final result = InterstitialAdConfiguration.fromJson(json); - expect(result, interstitialAdConfigFixture); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated values', () { - final updatedConfig = interstitialAdConfigFixture.copyWith( - enabled: false, - visibleTo: { - AppUserRole.guestUser: const InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 10, - ), - }, - ); - - expect(updatedConfig.enabled, false); - expect( - updatedConfig - .visibleTo[AppUserRole.guestUser]! - .transitionsBeforeShowingInterstitialAds, - 10, - ); - expect(updatedConfig, isNot(equals(interstitialAdConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = interstitialAdConfigFixture.copyWith(); - expect(updatedConfig, equals(interstitialAdConfigFixture)); - }); - }); - - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = interstitialAdConfigFixture.copyWith(); - final config2 = interstitialAdConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = interstitialAdConfigFixture.copyWith(); - final config2 = interstitialAdConfigFixture.copyWith(enabled: false); - expect(config1, isNot(equals(config2))); - }); - }); - }); -} diff --git a/test/src/models/config/interstitial_ad_frequency_config_test.dart b/test/src/models/config/interstitial_ad_frequency_config_test.dart deleted file mode 100644 index 2fdc78fa..00000000 --- a/test/src/models/config/interstitial_ad_frequency_config_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('InterstitialAdFrequencyConfig', () { - // Access the InterstitialAdFrequencyConfig from the fixture's visibleTo map - final frequencyConfigFixture = remoteConfigsFixturesData - .first - .adConfig - .interstitialAdConfiguration - .visibleTo[AppUserRole.guestUser]!; - - group('constructor', () { - test('returns correct instance', () { - expect(frequencyConfigFixture, isA()); - expect( - frequencyConfigFixture.transitionsBeforeShowingInterstitialAds, - isA(), - ); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = frequencyConfigFixture.toJson(); - final result = InterstitialAdFrequencyConfig.fromJson(json); - expect(result, frequencyConfigFixture); - }); - }); - - group('copyWith', () { - test('returns a new instance with updated values', () { - final updatedConfig = frequencyConfigFixture.copyWith( - transitionsBeforeShowingInterstitialAds: 7, - ); - - expect(updatedConfig.transitionsBeforeShowingInterstitialAds, 7); - expect(updatedConfig, isNot(equals(frequencyConfigFixture))); - }); - - test('returns the same instance if no changes are made', () { - final updatedConfig = frequencyConfigFixture.copyWith(); - expect(updatedConfig, equals(frequencyConfigFixture)); - }); - }); - - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = frequencyConfigFixture.copyWith(); - final config2 = frequencyConfigFixture.copyWith(); - expect(config1, config2); - }); - - test('instances with different properties are not equal', () { - final config1 = frequencyConfigFixture.copyWith(); - final config2 = frequencyConfigFixture.copyWith( - transitionsBeforeShowingInterstitialAds: 100, - ); - expect(config1, isNot(equals(config2))); - }); - }); - }); -} From c2f55203e47e9cdd72a13d7855f7fcebb322e491 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:34:11 +0100 Subject: [PATCH 05/75] feat(lib): add FeedItemDensity enum - Defines how densely feed item information should be presented - Includes three options: compact, standard, and comfortable --- lib/src/enums/feed_item_density.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/src/enums/feed_item_density.dart diff --git a/lib/src/enums/feed_item_density.dart b/lib/src/enums/feed_item_density.dart new file mode 100644 index 00000000..18bc2c5c --- /dev/null +++ b/lib/src/enums/feed_item_density.dart @@ -0,0 +1,11 @@ +/// Defines how densely feed item information should be presented. +enum FeedItemDensity { + /// Minimal spacing, smaller title font. + compact, + + /// Balanced spacing and font sizes. + standard, + + /// More whitespace, potentially larger title font. + comfortable, +} From 7bf4b6184efac744c193436ada3c05bb0f77df74 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:34:36 +0100 Subject: [PATCH 06/75] feat(enums): add new enum for feed item image styles - Introduce FeedItemImageStyle enum to define how feed item images should be displayed - Include options for hidden images, small thumbnails, and large thumbnails --- lib/src/enums/feed_item_image_style.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/src/enums/feed_item_image_style.dart diff --git a/lib/src/enums/feed_item_image_style.dart b/lib/src/enums/feed_item_image_style.dart new file mode 100644 index 00000000..b923a081 --- /dev/null +++ b/lib/src/enums/feed_item_image_style.dart @@ -0,0 +1,11 @@ +/// Defines how a feed item image should be displayed. +enum FeedItemImageStyle { + /// No image shown in the feed. + hidden, + + /// A small leading or trailing image thumbnail. + smallThumbnail, + + /// A more prominent image, perhaps above or below the text. + largeThumbnail, +} From 095b9d01285c01ddc1c5f5736390406a2e1ba999 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:35:53 +0100 Subject: [PATCH 07/75] fix fixtures: update user app settings to use feedSettings with new preferences - Replace feedPreferences with feedSettings in userAppSettingsFixturesData - Update property names from headline* to feedItems* - Add feedItemsClickBehavior property with default value - Adjust feedItemsDensity and feedItemsImageStyle property values as needed --- lib/src/fixtures/user_app_settings.dart | 36 +++++++++++-------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/src/fixtures/user_app_settings.dart b/lib/src/fixtures/user_app_settings.dart index fc08c66d..000787ef 100644 --- a/lib/src/fixtures/user_app_settings.dart +++ b/lib/src/fixtures/user_app_settings.dart @@ -20,11 +20,10 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedDisplayPreferences( + feedItemsDensity: FeedItemDensity.standard, + feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), UserAppSettings( @@ -45,11 +44,10 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedDisplayPreferences( + feedItemsDensity: FeedItemDensity.standard, + feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), UserAppSettings( @@ -70,11 +68,10 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.compact, - headlineImageStyle: HeadlineImageStyle.largeThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedDisplayPreferences( + feedItemsDensity: FeedItemDensity.compact, + feedItemsImageStyle: FeedItemImageStyle.largeThumbnail, + feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), // Add settings for users 3-10, copying the admin's settings for simplicity @@ -107,11 +104,10 @@ final List userAppSettingsFixturesData = [ updatedAt: DateTime.now(), status: ContentStatus.active, ), - feedPreferences: const FeedDisplayPreferences( - headlineDensity: HeadlineDensity.standard, - headlineImageStyle: HeadlineImageStyle.smallThumbnail, - showSourceInHeadlineFeed: true, - showPublishDateInHeadlineFeed: true, + feedSettings: const FeedDisplayPreferences( + feedItemsDensity: FeedItemDensity.standard, + feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), ), From 4ce777b36fede485011435055710c7090e59d40b Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:37:01 +0100 Subject: [PATCH 08/75] fix(remote_configs): update demo platform ad identifiers and navigation ad config - Update AdPlatformIdentifiers fields for AdPlatformType.admob and AdPlatformType.demo - Replace articleAdConfiguration with navigationAdConfiguration - Update interstitialAdConfiguration to NavigationAdConfiguration - Add defaultHeadlineClickBehavior to RemoteConfig --- lib/src/fixtures/remote_configs.dart | 44 +++++++++------------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index c2a2d5b8..3e73801c 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -70,19 +70,14 @@ final List remoteConfigsFixturesData = [ primaryAdPlatform: AdPlatformType.demo, platformAdIdentifiers: { AdPlatformType.admob: AdPlatformIdentifiers( - feedNativeAdId: 'ca-app-pub-3940256099942544/2247696110', - feedBannerAdId: 'ca-app-pub-3940256099942544/6300978111', - feedToArticleInterstitialAdId: - 'ca-app-pub-3940256099942544/1033173712', - inArticleNativeAdId: 'ca-app-pub-3940256099942544/3986624511', - inArticleBannerAdId: 'ca-app-pub-3940256099942544/6300978111', + nativeAdId: 'ca-app-pub-3940256099942544/2247696110', + bannerAdId: 'ca-app-pub-3940256099942544/6300978111', + interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', ), AdPlatformType.demo: AdPlatformIdentifiers( - feedNativeAdId: '_', - feedBannerAdId: '_', - feedToArticleInterstitialAdId: '_', - inArticleNativeAdId: '_', - inArticleBannerAdId: '_', + nativeAdId: '_', + bannerAdId: '_', + interstitialAdId: '_', ), }, feedAdConfiguration: FeedAdConfiguration( @@ -99,28 +94,16 @@ final List remoteConfigsFixturesData = [ ), }, ), - articleAdConfiguration: ArticleAdConfiguration( + navigationAdConfiguration: NavigationAdConfiguration( enabled: true, - bannerAdShape: BannerAdShape.rectangle, visibleTo: { - AppUserRole.guestUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: true, - InArticleAdSlotType.belowArticleContinueReadingButton: true, - }, - AppUserRole.standardUser: { - InArticleAdSlotType.aboveArticleContinueReadingButton: true, - InArticleAdSlotType.belowArticleContinueReadingButton: true, - }, - }, - ), - interstitialAdConfiguration: InterstitialAdConfiguration( - enabled: true, - visibleTo: { - AppUserRole.guestUser: InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 5, + AppUserRole.guestUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 5, + externalNavigationsBeforeShowingInterstitialAd: 5, ), - AppUserRole.standardUser: InterstitialAdFrequencyConfig( - transitionsBeforeShowingInterstitialAds: 10, + AppUserRole.standardUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 8, + externalNavigationsBeforeShowingInterstitialAd: 8, ), }, ), @@ -158,5 +141,6 @@ final List remoteConfigsFixturesData = [ PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), + defaultHeadlineClickBehavior: FeedItemClickBehavior.internalNavigation, ), ]; From e31ecdb056099c265f54e8466be9ca839244f248 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:38:00 +0100 Subject: [PATCH 09/75] refactor(ad_config): replace ad configuration models with navigation model - Remove ArticleAdConfiguration and InterstitialAdConfiguration imports - Add NavigationAdConfiguration import - Replace FeedAdConfiguration with NavigationAdConfiguration in class properties --- lib/src/models/config/ad_config.dart | 29 ++++++++++------------------ 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/lib/src/models/config/ad_config.dart b/lib/src/models/config/ad_config.dart index 225c6da3..009d216d 100644 --- a/lib/src/models/config/ad_config.dart +++ b/lib/src/models/config/ad_config.dart @@ -1,8 +1,7 @@ import 'package:core/src/enums/ad_platform_type.dart'; import 'package:core/src/models/config/ad_platform_identifiers.dart'; -import 'package:core/src/models/config/article_ad_configuration.dart'; import 'package:core/src/models/config/feed_ad_configuration.dart'; -import 'package:core/src/models/config/interstitial_ad_configuration.dart'; +import 'package:core/src/models/config/navigation_ad_configuration.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -21,8 +20,7 @@ class AdConfig extends Equatable { required this.primaryAdPlatform, required this.platformAdIdentifiers, required this.feedAdConfiguration, - required this.articleAdConfiguration, - required this.interstitialAdConfiguration, + required this.navigationAdConfiguration, }); /// Creates an [AdConfig] from JSON data. @@ -35,20 +33,17 @@ class AdConfig extends Equatable { /// Global switch to enable or disable all ads in the application. final bool enabled; - /// Global choice: AdMob or Demo. + /// Global choice: AdMob, etc. final AdPlatformType primaryAdPlatform; /// Map to store identifiers for all platforms. final Map platformAdIdentifiers; - /// Configuration for main feed, search feed, similar headlines feed. + /// Configuration for main feed, search feed, etc. final FeedAdConfiguration feedAdConfiguration; - /// Configuration for article page ads (excluding interstitial). - final ArticleAdConfiguration articleAdConfiguration; - - /// Configuration for all interstitial ads. - final InterstitialAdConfiguration interstitialAdConfiguration; + /// Configuration for all navigation ads. + final NavigationAdConfiguration navigationAdConfiguration; @override List get props => [ @@ -56,8 +51,7 @@ class AdConfig extends Equatable { primaryAdPlatform, platformAdIdentifiers, feedAdConfiguration, - articleAdConfiguration, - interstitialAdConfiguration, + navigationAdConfiguration, ]; /// Creates a copy of this [AdConfig] but with the given fields replaced @@ -67,8 +61,7 @@ class AdConfig extends Equatable { AdPlatformType? primaryAdPlatform, Map? platformAdIdentifiers, FeedAdConfiguration? feedAdConfiguration, - ArticleAdConfiguration? articleAdConfiguration, - InterstitialAdConfiguration? interstitialAdConfiguration, + NavigationAdConfiguration? interstitialAdConfiguration, }) { return AdConfig( enabled: enabled ?? this.enabled, @@ -76,10 +69,8 @@ class AdConfig extends Equatable { platformAdIdentifiers: platformAdIdentifiers ?? this.platformAdIdentifiers, feedAdConfiguration: feedAdConfiguration ?? this.feedAdConfiguration, - articleAdConfiguration: - articleAdConfiguration ?? this.articleAdConfiguration, - interstitialAdConfiguration: - interstitialAdConfiguration ?? this.interstitialAdConfiguration, + navigationAdConfiguration: + interstitialAdConfiguration ?? this.navigationAdConfiguration, ); } } From c7f394f8ee951805355cf60da652a83212186d39 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:39:02 +0100 Subject: [PATCH 10/75] refactor(ad_platform_identifiers): simplify ad identifier structure - Remove feed-specific and in-article specific ad identifiers - Replace with generic native, banner, and interstitial ad identifiers - Update class documentation and property names accordingly --- .../config/ad_platform_identifiers.dart | 53 ++++++------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/lib/src/models/config/ad_platform_identifiers.dart b/lib/src/models/config/ad_platform_identifiers.dart index f0f204d2..06919cde 100644 --- a/lib/src/models/config/ad_platform_identifiers.dart +++ b/lib/src/models/config/ad_platform_identifiers.dart @@ -5,7 +5,7 @@ import 'package:meta/meta.dart'; part 'ad_platform_identifiers.g.dart'; /// {@template ad_platform_identifiers} -/// Holds all ad identifiers for a specific platform (AdMob, Demo etc ). +/// Holds all ad identifiers for a specific platform (AdMob, etc ). /// This object is generic and will be stored in a map in "AdConfig". /// {@endtemplate} @immutable @@ -13,11 +13,9 @@ part 'ad_platform_identifiers.g.dart'; class AdPlatformIdentifiers extends Equatable { /// {@macro ad_platform_identifiers} const AdPlatformIdentifiers({ - this.feedNativeAdId, - this.feedBannerAdId, - this.feedToArticleInterstitialAdId, - this.inArticleNativeAdId, - this.inArticleBannerAdId, + this.nativeAdId, + this.bannerAdId, + this.interstitialAdId, }); /// Creates an [AdPlatformIdentifiers] from JSON data. @@ -27,46 +25,29 @@ class AdPlatformIdentifiers extends Equatable { /// Converts this [AdPlatformIdentifiers] instance to JSON data. Map toJson() => _$AdPlatformIdentifiersToJson(this); - /// ID for native ads in feeds. - final String? feedNativeAdId; + /// ID for native ads. + final String? nativeAdId; - /// ID for banner ads in feeds. - final String? feedBannerAdId; + /// ID for banner ads. + final String? bannerAdId; - /// ID for interstitial ads during feed-to-article transitions. - final String? feedToArticleInterstitialAdId; - - /// ID for native in-article ads. - final String? inArticleNativeAdId; - - /// ID for banner in-article ads. - final String? inArticleBannerAdId; + /// ID for interstitial ads. + final String? interstitialAdId; @override - List get props => [ - feedNativeAdId, - feedBannerAdId, - feedToArticleInterstitialAdId, - inArticleNativeAdId, - inArticleBannerAdId, - ]; + List get props => [nativeAdId, bannerAdId, interstitialAdId]; /// Creates a copy of this [AdPlatformIdentifiers] but with the given fields /// replaced with the new values. AdPlatformIdentifiers copyWith({ - String? feedNativeAdId, - String? feedBannerAdId, - String? feedToArticleInterstitialAdId, - String? inArticleNativeAdId, - String? inArticleBannerAdId, + String? nativeAdId, + String? bannerAdId, + String? interstitialAdId, }) { return AdPlatformIdentifiers( - feedNativeAdId: feedNativeAdId ?? this.feedNativeAdId, - feedBannerAdId: feedBannerAdId ?? this.feedBannerAdId, - feedToArticleInterstitialAdId: - feedToArticleInterstitialAdId ?? this.feedToArticleInterstitialAdId, - inArticleNativeAdId: inArticleNativeAdId ?? this.inArticleNativeAdId, - inArticleBannerAdId: inArticleBannerAdId ?? this.inArticleBannerAdId, + nativeAdId: nativeAdId ?? this.nativeAdId, + bannerAdId: bannerAdId ?? this.bannerAdId, + interstitialAdId: interstitialAdId ?? this.interstitialAdId, ); } } From 56753294f06194f0410203ad3bed9071c5a65c3a Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:40:12 +0100 Subject: [PATCH 11/75] fix(models): correct variable name in AdConfig - Rename 'interstitialAdConfiguration' to 'navigationAdConfiguration' - This change ensures consistency with the class property name --- lib/src/models/config/ad_config.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/config/ad_config.dart b/lib/src/models/config/ad_config.dart index 009d216d..0836a59b 100644 --- a/lib/src/models/config/ad_config.dart +++ b/lib/src/models/config/ad_config.dart @@ -61,7 +61,7 @@ class AdConfig extends Equatable { AdPlatformType? primaryAdPlatform, Map? platformAdIdentifiers, FeedAdConfiguration? feedAdConfiguration, - NavigationAdConfiguration? interstitialAdConfiguration, + NavigationAdConfiguration? navigationAdConfiguration, }) { return AdConfig( enabled: enabled ?? this.enabled, @@ -70,7 +70,7 @@ class AdConfig extends Equatable { platformAdIdentifiers ?? this.platformAdIdentifiers, feedAdConfiguration: feedAdConfiguration ?? this.feedAdConfiguration, navigationAdConfiguration: - interstitialAdConfiguration ?? this.navigationAdConfiguration, + navigationAdConfiguration ?? this.navigationAdConfiguration, ); } } From d8fc7dda5e8ee88084c006f5e3375d90bf1904f7 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:43:52 +0100 Subject: [PATCH 12/75] chore: barrels --- lib/src/enums/enums.dart | 6 +++--- lib/src/models/config/config.dart | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/src/enums/enums.dart b/lib/src/enums/enums.dart index e0404450..f917dbae 100644 --- a/lib/src/enums/enums.dart +++ b/lib/src/enums/enums.dart @@ -12,9 +12,9 @@ export 'dashboard_user_role.dart'; export 'device_platform.dart'; export 'feed_decorator_category.dart'; export 'feed_decorator_type.dart'; -export 'headline_density.dart'; -export 'headline_image_style.dart'; -export 'in_article_ad_slot_type.dart'; +export 'feed_item_click_behavior.dart'; +export 'feed_item_density.dart'; +export 'feed_item_image_style.dart'; export 'push_notification_provider.dart'; export 'push_notification_subscription_delivery_type.dart'; export 'sort_order.dart'; diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index b05000e0..de548cfc 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,13 +1,12 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; export 'app_status.dart'; -export 'article_ad_configuration.dart'; export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; export 'feed_decorator_config.dart'; export 'feed_decorator_role_config.dart'; -export 'interstitial_ad_configuration.dart'; -export 'interstitial_ad_frequency_config.dart'; +export 'navigation_ad_configuration.dart'; +export 'navigation_ad_frequency_config.dart'; export 'push_notification_config.dart'; export 'remote_config.dart'; export 'saved_filter_limits.dart'; From 83fedbc43ed794a62b27f43d42936dee538e9cf2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:44:26 +0100 Subject: [PATCH 13/75] feat(lib): add navigation ad configuration model - Create new model for master configuration of navigation ads across the application - Include properties for enabled status, ad type, and visibility settings for different user roles - Implement JSON serialization and deserialization - Add copyWith method for easy instance manipulation --- .../config/navigation_ad_configuration.dart | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 lib/src/models/config/navigation_ad_configuration.dart diff --git a/lib/src/models/config/navigation_ad_configuration.dart b/lib/src/models/config/navigation_ad_configuration.dart new file mode 100644 index 00000000..85dedc28 --- /dev/null +++ b/lib/src/models/config/navigation_ad_configuration.dart @@ -0,0 +1,60 @@ +import 'package:core/src/enums/ad_type.dart'; +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/models/config/navigation_ad_frequency_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'navigation_ad_configuration.g.dart'; + +/// {@template navigation_ad_configuration} +/// Master configuration for all Navigation ads across the application. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NavigationAdConfiguration extends Equatable { + /// {@macro navigation_ad_configuration} + const NavigationAdConfiguration({ + required this.enabled, + required this.visibleTo, + this.adType = AdType.interstitial, + }) : assert( + adType == AdType.interstitial, + 'Navigation ads must be of type interstitial.', + ); + + /// Creates an [NavigationAdConfiguration] from JSON data. + factory NavigationAdConfiguration.fromJson(Map json) => + _$NavigationAdConfigurationFromJson(json); + + /// Converts this [NavigationAdConfiguration] instance to JSON data. + Map toJson() => _$NavigationAdConfigurationToJson(this); + + /// Master switch to enable or disable navigation ads globally. + final bool enabled; + + /// The type of the ad, fixed to [AdType.interstitial]. + final AdType adType; + + /// Explicitly defines which user roles can see this navigation ad + /// configuration and their specific frequency settings. If a role is not + /// in this map, they will not see navigation ads. + final Map visibleTo; + + @override + List get props => [enabled, adType, visibleTo]; + + /// Creates a copy of this [NavigationAdConfiguration] but with + /// the given fields replaced with the new values. + NavigationAdConfiguration copyWith({ + bool? enabled, + AdType? adType, + Map? visibleTo, + }) { + return NavigationAdConfiguration( + enabled: enabled ?? this.enabled, + adType: adType ?? this.adType, + visibleTo: visibleTo ?? this.visibleTo, + ); + } +} From a00a30fe912734881875aa2f8bb5be1dd25c40fa Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:57:03 +0100 Subject: [PATCH 14/75] feat(models): add NavigationAdFrequencyConfig model - Encapsulates ad frequency for navigation ads - Provides separate controls for internal and external navigations - Implements Equatable for value comparison - Supports JSON serialization and --- .../navigation_ad_frequency_config.dart | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 lib/src/models/config/navigation_ad_frequency_config.dart diff --git a/lib/src/models/config/navigation_ad_frequency_config.dart b/lib/src/models/config/navigation_ad_frequency_config.dart new file mode 100644 index 00000000..895e450d --- /dev/null +++ b/lib/src/models/config/navigation_ad_frequency_config.dart @@ -0,0 +1,57 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'navigation_ad_frequency_config.g.dart'; + +/// {@template navigation_ad_frequency_config} +/// Encapsulates ad frequency for navigation ads, providing separate controls +/// for ads shown during internal in-app navigation and those triggered by +/// navigating to an external URL. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class NavigationAdFrequencyConfig extends Equatable { + /// {@macro navigation_ad_frequency_config} + const NavigationAdFrequencyConfig({ + required this.internalNavigationsBeforeShowingInterstitialAd, + required this.externalNavigationsBeforeShowingInterstitialAd, + }); + + /// Creates a [NavigationAdFrequencyConfig] from JSON data. + factory NavigationAdFrequencyConfig.fromJson(Map json) => + _$NavigationAdFrequencyConfigFromJson(json); + + /// The number of internal page-to-page navigations a user needs to make + /// within the app before an interstitial ad is shown. + final int internalNavigationsBeforeShowingInterstitialAd; + + /// The number of external navigations a user needs to make + /// within the app before an interstitial ad is shown. + final int externalNavigationsBeforeShowingInterstitialAd; + + @override + List get props => [ + internalNavigationsBeforeShowingInterstitialAd, + externalNavigationsBeforeShowingInterstitialAd, + ]; + + /// Converts this [NavigationAdFrequencyConfig] instance to JSON data. + Map toJson() => _$NavigationAdFrequencyConfigToJson(this); + + /// Creates a copy of this [NavigationAdFrequencyConfig] but with + /// the given fields replaced with the new values. + NavigationAdFrequencyConfig copyWith({ + int? internalNavigationsBeforeShowingInterstitialAd, + int? externalNavigationsBeforeShowingInterstitialAd, + }) { + return NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: + internalNavigationsBeforeShowingInterstitialAd ?? + this.internalNavigationsBeforeShowingInterstitialAd, + externalNavigationsBeforeShowingInterstitialAd: + externalNavigationsBeforeShowingInterstitialAd ?? + this.externalNavigationsBeforeShowingInterstitialAd, + ); + } +} From 4e20f4d15d812ac1492b3329ddb048fe82dcc1f4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:57:22 +0100 Subject: [PATCH 15/75] refactor(headline): remove excerpt property from Headline class - Remove excerpt property from Headline class definition - Remove excerpt from props list - Remove excerpt from copyWith method - Adjust constructor and toMap/fromMap methods --- lib/src/models/entities/headline.dart | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/src/models/entities/headline.dart b/lib/src/models/entities/headline.dart index 83164a12..1b340c3c 100644 --- a/lib/src/models/entities/headline.dart +++ b/lib/src/models/entities/headline.dart @@ -21,7 +21,6 @@ class Headline extends FeedItem { const Headline({ required this.id, required this.title, - required this.excerpt, required this.url, required this.imageUrl, required this.source, @@ -43,9 +42,6 @@ class Headline extends FeedItem { /// Title of the headline. final String title; - /// Excerpt or snippet of the headline content. - final String excerpt; - /// URL to the full article or content. final String url; @@ -94,7 +90,6 @@ class Headline extends FeedItem { List get props => [ id, title, - excerpt, url, imageUrl, createdAt, @@ -115,7 +110,6 @@ class Headline extends FeedItem { Headline copyWith({ String? id, String? title, - String? excerpt, String? url, String? imageUrl, DateTime? createdAt, @@ -129,7 +123,6 @@ class Headline extends FeedItem { return Headline( id: id ?? this.id, title: title ?? this.title, - excerpt: excerpt ?? this.excerpt, url: url ?? this.url, imageUrl: imageUrl ?? this.imageUrl, createdAt: createdAt ?? this.createdAt, From 890e41b2b3b9abfd6d032202e825dec7ffde7ffc Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:58:28 +0100 Subject: [PATCH 16/75] refactor(models): update FeedDisplayPreferences with new feed item related properties - Replace headlineDensity with feedItemDensity - Replace headlineImageStyle with feedItemImageStyle - Add new property feedItemClickBehavior - Remove showSourceInHeadlineFeed and showPublishDateInHeadlineFeed - Update class comments and method names accordingly --- .../feed_display_preferences.dart | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/lib/src/models/user_settings/feed_display_preferences.dart b/lib/src/models/user_settings/feed_display_preferences.dart index 981447fe..cd22c3db 100644 --- a/lib/src/models/user_settings/feed_display_preferences.dart +++ b/lib/src/models/user_settings/feed_display_preferences.dart @@ -13,27 +13,23 @@ part 'feed_display_preferences.g.dart'; class FeedDisplayPreferences extends Equatable { /// {@macro feed_display_preferences} const FeedDisplayPreferences({ - required this.headlineDensity, - required this.headlineImageStyle, - required this.showSourceInHeadlineFeed, - required this.showPublishDateInHeadlineFeed, + required this.feedItemDensity, + required this.feedItemImageStyle, + required this.feedItemClickBehavior, }); /// Creates a [FeedDisplayPreferences] instance from a JSON map. factory FeedDisplayPreferences.fromJson(Map json) => _$FeedDisplayPreferencesFromJson(json); - /// How densely headline information should be presented. - final HeadlineDensity headlineDensity; + /// How densely feed information should be presented. + final FeedItemDensity feedItemDensity; - /// How images should be displayed in the headline feed. - final HeadlineImageStyle headlineImageStyle; + /// How images should be displayed in the feed. + final FeedItemImageStyle feedItemImageStyle; - /// Whether to show the source name directly in the headline feed item. - final bool showSourceInHeadlineFeed; - - /// Whether to show the publish date in the headline feed item. - final bool showPublishDateInHeadlineFeed; + /// How feed items links should be opened. + final FeedItemClickBehavior feedItemClickBehavior; /// Converts this [FeedDisplayPreferences] instance to a JSON map. Map toJson() => _$FeedDisplayPreferencesToJson(this); @@ -41,26 +37,22 @@ class FeedDisplayPreferences extends Equatable { /// Creates a copy of this [FeedDisplayPreferences] but with the given fields /// replaced with the new values. FeedDisplayPreferences copyWith({ - HeadlineDensity? headlineDensity, - HeadlineImageStyle? headlineImageStyle, - bool? showSourceInHeadlineFeed, - bool? showPublishDateInHeadlineFeed, + FeedItemDensity? feedItemDensity, + FeedItemImageStyle? feedItemImageStyle, + FeedItemClickBehavior? feedItemClickBehavior, }) { return FeedDisplayPreferences( - headlineDensity: headlineDensity ?? this.headlineDensity, - headlineImageStyle: headlineImageStyle ?? this.headlineImageStyle, - showSourceInHeadlineFeed: - showSourceInHeadlineFeed ?? this.showSourceInHeadlineFeed, - showPublishDateInHeadlineFeed: - showPublishDateInHeadlineFeed ?? this.showPublishDateInHeadlineFeed, + feedItemDensity: feedItemDensity ?? this.feedItemDensity, + feedItemImageStyle: feedItemImageStyle ?? this.feedItemImageStyle, + feedItemClickBehavior: + feedItemClickBehavior ?? this.feedItemClickBehavior, ); } @override List get props => [ - headlineDensity, - headlineImageStyle, - showSourceInHeadlineFeed, - showPublishDateInHeadlineFeed, + feedItemDensity, + feedItemImageStyle, + feedItemClickBehavior, ]; } From 046a0aa22783eda74fd8170d356e02fadc106d8e Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 10:59:14 +0100 Subject: [PATCH 17/75] fix(user_app_settings): correct field names in fixtures - Renamed `feedItemsDensity` to `feedItemDensity` - Renamed `feedItemsImageStyle` to `feedItemImageStyle` - Renamed `feedItemsClickBehavior` to `feedItemClickBehavior` --- lib/src/fixtures/user_app_settings.dart | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/fixtures/user_app_settings.dart b/lib/src/fixtures/user_app_settings.dart index 000787ef..d687dc4d 100644 --- a/lib/src/fixtures/user_app_settings.dart +++ b/lib/src/fixtures/user_app_settings.dart @@ -21,9 +21,9 @@ final List userAppSettingsFixturesData = [ status: ContentStatus.active, ), feedSettings: const FeedDisplayPreferences( - feedItemsDensity: FeedItemDensity.standard, - feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), UserAppSettings( @@ -45,9 +45,9 @@ final List userAppSettingsFixturesData = [ status: ContentStatus.active, ), feedSettings: const FeedDisplayPreferences( - feedItemsDensity: FeedItemDensity.standard, - feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), UserAppSettings( @@ -69,9 +69,9 @@ final List userAppSettingsFixturesData = [ status: ContentStatus.active, ), feedSettings: const FeedDisplayPreferences( - feedItemsDensity: FeedItemDensity.compact, - feedItemsImageStyle: FeedItemImageStyle.largeThumbnail, - feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, + feedItemDensity: FeedItemDensity.compact, + feedItemImageStyle: FeedItemImageStyle.largeThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), // Add settings for users 3-10, copying the admin's settings for simplicity @@ -105,9 +105,9 @@ final List userAppSettingsFixturesData = [ status: ContentStatus.active, ), feedSettings: const FeedDisplayPreferences( - feedItemsDensity: FeedItemDensity.standard, - feedItemsImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemsClickBehavior: FeedItemClickBehavior.defaultBehavior, + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, ), ), ), From d43be9b0e106fda536f3c75fa9fc56f19be4580b Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:00:30 +0100 Subject: [PATCH 18/75] feat(RemoteConfig): add feedItemClickBehavior configuration - Add new required field 'feedItemClickBehavior' of type FeedItemClickBehavior - Update constructor to include new parameter - Add documentation for the new field - Update copyWith method to handle new parameter - Update equality check and hashCode to include new field --- lib/src/fixtures/remote_configs.dart | 2 +- lib/src/models/config/remote_config.dart | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 3e73801c..c1df4cfb 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -141,6 +141,6 @@ final List remoteConfigsFixturesData = [ PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), - defaultHeadlineClickBehavior: FeedItemClickBehavior.internalNavigation, + feedItemClickBehavior: FeedItemClickBehavior.internalNavigation, ), ]; diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 85df21ff..152bdaa3 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -23,6 +23,7 @@ class RemoteConfig extends Equatable { required this.feedDecoratorConfig, required this.userPreferenceConfig, required this.pushNotificationConfig, + required this.feedItemClickBehavior, required this.createdAt, required this.updatedAt, }); @@ -50,6 +51,9 @@ class RemoteConfig extends Equatable { /// Defines the global configuration for the push notification system. final PushNotificationConfig pushNotificationConfig; + /// The default behavior when clicking feed items. + final FeedItemClickBehavior feedItemClickBehavior; + /// The creation timestamp of the remote config. @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) final DateTime createdAt; @@ -69,6 +73,7 @@ class RemoteConfig extends Equatable { Map? feedDecoratorConfig, AppStatus? appStatus, PushNotificationConfig? pushNotificationConfig, + FeedItemClickBehavior? feedItemClickBehavior, DateTime? createdAt, DateTime? updatedAt, }) { @@ -80,6 +85,8 @@ class RemoteConfig extends Equatable { appStatus: appStatus ?? this.appStatus, pushNotificationConfig: pushNotificationConfig ?? this.pushNotificationConfig, + feedItemClickBehavior: + feedItemClickBehavior ?? this.feedItemClickBehavior, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, ); @@ -93,6 +100,7 @@ class RemoteConfig extends Equatable { feedDecoratorConfig, appStatus, pushNotificationConfig, + feedItemClickBehavior, createdAt, updatedAt, ]; From bfc8d91315dd961ad17156b0850083e3a7d9d443 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:09:32 +0100 Subject: [PATCH 19/75] chore: delete absolete files --- lib/src/fixtures/user_app_settings.dart | 114 ------------------ .../feed_display_preferences.dart | 58 --------- .../feed_display_preferences.g.dart | 53 -------- .../user_settings/user_app_settings.dart | 70 ----------- .../user_settings/user_app_settings.g.dart | 35 ------ .../feed_display_preferences_test.dart | 63 ---------- .../user_settings/user_app_settings_test.dart | 72 ----------- 7 files changed, 465 deletions(-) delete mode 100644 lib/src/fixtures/user_app_settings.dart delete mode 100644 lib/src/models/user_settings/feed_display_preferences.dart delete mode 100644 lib/src/models/user_settings/feed_display_preferences.g.dart delete mode 100644 lib/src/models/user_settings/user_app_settings.dart delete mode 100644 lib/src/models/user_settings/user_app_settings.g.dart delete mode 100644 test/src/models/user_settings/feed_display_preferences_test.dart delete mode 100644 test/src/models/user_settings/user_app_settings_test.dart diff --git a/lib/src/fixtures/user_app_settings.dart b/lib/src/fixtures/user_app_settings.dart deleted file mode 100644 index d687dc4d..00000000 --- a/lib/src/fixtures/user_app_settings.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:core/core.dart'; - -/// User App Settings Demo Data -final List userAppSettingsFixturesData = [ - UserAppSettings( - id: kAdminUserId, - displaySettings: const DisplaySettings( - baseTheme: AppBaseTheme.system, - accentTheme: AppAccentTheme.defaultBlue, - fontFamily: 'SystemDefault', - textScaleFactor: AppTextScaleFactor.medium, - fontWeight: AppFontWeight.regular, - ), - language: Language( - id: 'lang-en', - code: 'en', - name: 'English', - nativeName: 'English', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - status: ContentStatus.active, - ), - feedSettings: const FeedDisplayPreferences( - feedItemDensity: FeedItemDensity.standard, - feedItemImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, - ), - ), - UserAppSettings( - id: kUser1Id, - displaySettings: const DisplaySettings( - baseTheme: AppBaseTheme.system, - accentTheme: AppAccentTheme.defaultBlue, - fontFamily: 'SystemDefault', - textScaleFactor: AppTextScaleFactor.medium, - fontWeight: AppFontWeight.regular, - ), - language: Language( - id: 'lang-en', - code: 'en', - name: 'English', - nativeName: 'English', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - status: ContentStatus.active, - ), - feedSettings: const FeedDisplayPreferences( - feedItemDensity: FeedItemDensity.standard, - feedItemImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, - ), - ), - UserAppSettings( - id: kUser2Id, - displaySettings: const DisplaySettings( - baseTheme: AppBaseTheme.dark, - accentTheme: AppAccentTheme.newsRed, - fontFamily: 'SystemDefault', - textScaleFactor: AppTextScaleFactor.medium, - fontWeight: AppFontWeight.regular, - ), - language: Language( - id: 'lang-en', - code: 'en', - name: 'English', - nativeName: 'English', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - status: ContentStatus.active, - ), - feedSettings: const FeedDisplayPreferences( - feedItemDensity: FeedItemDensity.compact, - feedItemImageStyle: FeedItemImageStyle.largeThumbnail, - feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, - ), - ), - // Add settings for users 3-10, copying the admin's settings for simplicity - ...List.generate( - 8, - (index) => UserAppSettings( - id: [ - kUser3Id, - kUser4Id, - kUser5Id, - kUser6Id, - kUser7Id, - kUser8Id, - kUser9Id, - kUser10Id, - ][index], - displaySettings: const DisplaySettings( - baseTheme: AppBaseTheme.system, - accentTheme: AppAccentTheme.defaultBlue, - fontFamily: 'SystemDefault', - textScaleFactor: AppTextScaleFactor.medium, - fontWeight: AppFontWeight.regular, - ), - language: Language( - id: 'lang-en', - code: 'en', - name: 'English', - nativeName: 'English', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - status: ContentStatus.active, - ), - feedSettings: const FeedDisplayPreferences( - feedItemDensity: FeedItemDensity.standard, - feedItemImageStyle: FeedItemImageStyle.smallThumbnail, - feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, - ), - ), - ), -]; diff --git a/lib/src/models/user_settings/feed_display_preferences.dart b/lib/src/models/user_settings/feed_display_preferences.dart deleted file mode 100644 index cd22c3db..00000000 --- a/lib/src/models/user_settings/feed_display_preferences.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:core/core.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'feed_display_preferences.g.dart'; - -/// {@template feed_display_preferences} -/// User preferences for how feeds are displayed. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class FeedDisplayPreferences extends Equatable { - /// {@macro feed_display_preferences} - const FeedDisplayPreferences({ - required this.feedItemDensity, - required this.feedItemImageStyle, - required this.feedItemClickBehavior, - }); - - /// Creates a [FeedDisplayPreferences] instance from a JSON map. - factory FeedDisplayPreferences.fromJson(Map json) => - _$FeedDisplayPreferencesFromJson(json); - - /// How densely feed information should be presented. - final FeedItemDensity feedItemDensity; - - /// How images should be displayed in the feed. - final FeedItemImageStyle feedItemImageStyle; - - /// How feed items links should be opened. - final FeedItemClickBehavior feedItemClickBehavior; - - /// Converts this [FeedDisplayPreferences] instance to a JSON map. - Map toJson() => _$FeedDisplayPreferencesToJson(this); - - /// Creates a copy of this [FeedDisplayPreferences] but with the given fields - /// replaced with the new values. - FeedDisplayPreferences copyWith({ - FeedItemDensity? feedItemDensity, - FeedItemImageStyle? feedItemImageStyle, - FeedItemClickBehavior? feedItemClickBehavior, - }) { - return FeedDisplayPreferences( - feedItemDensity: feedItemDensity ?? this.feedItemDensity, - feedItemImageStyle: feedItemImageStyle ?? this.feedItemImageStyle, - feedItemClickBehavior: - feedItemClickBehavior ?? this.feedItemClickBehavior, - ); - } - - @override - List get props => [ - feedItemDensity, - feedItemImageStyle, - feedItemClickBehavior, - ]; -} diff --git a/lib/src/models/user_settings/feed_display_preferences.g.dart b/lib/src/models/user_settings/feed_display_preferences.g.dart deleted file mode 100644 index 9f3819c4..00000000 --- a/lib/src/models/user_settings/feed_display_preferences.g.dart +++ /dev/null @@ -1,53 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'feed_display_preferences.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -FeedDisplayPreferences _$FeedDisplayPreferencesFromJson( - Map json, -) => $checkedCreate('FeedDisplayPreferences', json, ($checkedConvert) { - final val = FeedDisplayPreferences( - headlineDensity: $checkedConvert( - 'headlineDensity', - (v) => $enumDecode(_$HeadlineDensityEnumMap, v), - ), - headlineImageStyle: $checkedConvert( - 'headlineImageStyle', - (v) => $enumDecode(_$HeadlineImageStyleEnumMap, v), - ), - showSourceInHeadlineFeed: $checkedConvert( - 'showSourceInHeadlineFeed', - (v) => v as bool, - ), - showPublishDateInHeadlineFeed: $checkedConvert( - 'showPublishDateInHeadlineFeed', - (v) => v as bool, - ), - ); - return val; -}); - -Map _$FeedDisplayPreferencesToJson( - FeedDisplayPreferences instance, -) => { - 'headlineDensity': _$HeadlineDensityEnumMap[instance.headlineDensity]!, - 'headlineImageStyle': - _$HeadlineImageStyleEnumMap[instance.headlineImageStyle]!, - 'showSourceInHeadlineFeed': instance.showSourceInHeadlineFeed, - 'showPublishDateInHeadlineFeed': instance.showPublishDateInHeadlineFeed, -}; - -const _$HeadlineDensityEnumMap = { - HeadlineDensity.compact: 'compact', - HeadlineDensity.standard: 'standard', - HeadlineDensity.comfortable: 'comfortable', -}; - -const _$HeadlineImageStyleEnumMap = { - HeadlineImageStyle.hidden: 'hidden', - HeadlineImageStyle.smallThumbnail: 'smallThumbnail', - HeadlineImageStyle.largeThumbnail: 'largeThumbnail', -}; diff --git a/lib/src/models/user_settings/user_app_settings.dart b/lib/src/models/user_settings/user_app_settings.dart deleted file mode 100644 index 77f0f387..00000000 --- a/lib/src/models/user_settings/user_app_settings.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:core/src/models/entities/language.dart'; -import 'package:core/src/models/user_settings/display_settings.dart'; -import 'package:core/src/models/user_settings/feed_display_preferences.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'user_app_settings.g.dart'; - -/// {@template user_app_settings} -/// Represents a collection of user-specific application settings, -/// including display preferences, language selection, and feed display options. -/// -/// This model unifies settings that are tied to a specific user, -/// making it suitable for management via a generic data client. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class UserAppSettings extends Equatable { - /// {@macro user_app_settings} - /// - /// Creates a new instance of [UserAppSettings]. - /// - /// An [id] is required, typically the user's unique identifier. - /// Provides sensible defaults for nested settings if not specified. - const UserAppSettings({ - required this.id, - required this.displaySettings, - required this.language, - required this.feedPreferences, - }); - - /// Factory method to create a [UserAppSettings] instance from a JSON map. - factory UserAppSettings.fromJson(Map json) => - _$UserAppSettingsFromJson(json); - - /// The unique identifier for the user settings, typically the user's ID. - final String id; - - /// User-configurable settings related to the application's visual appearance. - final DisplaySettings displaySettings; - - /// The selected application language. - final Language language; - - /// User-configurable settings for how content feeds are displayed. - final FeedDisplayPreferences feedPreferences; - - /// Converts this [UserAppSettings] instance to a JSON map. - Map toJson() => _$UserAppSettingsToJson(this); - - @override - List get props => [id, displaySettings, language, feedPreferences]; - - /// Creates a copy of this [UserAppSettings] but with the given fields - /// replaced with the new values. - UserAppSettings copyWith({ - String? id, - DisplaySettings? displaySettings, - Language? language, - FeedDisplayPreferences? feedPreferences, - }) { - return UserAppSettings( - id: id ?? this.id, - displaySettings: displaySettings ?? this.displaySettings, - language: language ?? this.language, - feedPreferences: feedPreferences ?? this.feedPreferences, - ); - } -} diff --git a/lib/src/models/user_settings/user_app_settings.g.dart b/lib/src/models/user_settings/user_app_settings.g.dart deleted file mode 100644 index b39cddce..00000000 --- a/lib/src/models/user_settings/user_app_settings.g.dart +++ /dev/null @@ -1,35 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_app_settings.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserAppSettings _$UserAppSettingsFromJson(Map json) => - $checkedCreate('UserAppSettings', json, ($checkedConvert) { - final val = UserAppSettings( - id: $checkedConvert('id', (v) => v as String), - displaySettings: $checkedConvert( - 'displaySettings', - (v) => DisplaySettings.fromJson(v as Map), - ), - language: $checkedConvert( - 'language', - (v) => Language.fromJson(v as Map), - ), - feedPreferences: $checkedConvert( - 'feedPreferences', - (v) => FeedDisplayPreferences.fromJson(v as Map), - ), - ); - return val; - }); - -Map _$UserAppSettingsToJson(UserAppSettings instance) => - { - 'id': instance.id, - 'displaySettings': instance.displaySettings.toJson(), - 'language': instance.language.toJson(), - 'feedPreferences': instance.feedPreferences.toJson(), - }; diff --git a/test/src/models/user_settings/feed_display_preferences_test.dart b/test/src/models/user_settings/feed_display_preferences_test.dart deleted file mode 100644 index cd8951da..00000000 --- a/test/src/models/user_settings/feed_display_preferences_test.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:core/core.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:test/test.dart'; - -void main() { - group('FeedDisplayPreferences', () { - // Derive the test subject from the main app settings fixture. - final feedPreferencesFixture = - userAppSettingsFixturesData.first.feedPreferences; - - test('supports value equality', () { - final preferences1 = feedPreferencesFixture.copyWith(); - final preferences2 = feedPreferencesFixture.copyWith(); - expect(preferences1, equals(preferences2)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final original = feedPreferencesFixture.copyWith( - headlineDensity: HeadlineDensity.compact, - headlineImageStyle: HeadlineImageStyle.largeThumbnail, - showSourceInHeadlineFeed: false, - showPublishDateInHeadlineFeed: false, - ); - final json = original.toJson(); - final reconstructed = FeedDisplayPreferences.fromJson(json); - expect(reconstructed, equals(original)); - }); - - test( - 'throws CheckedFromJsonException when required fields are missing', - () { - final json = {}; - expect( - () => FeedDisplayPreferences.fromJson(json), - throwsA(isA()), - ); - }, - ); - }); - - group('copyWith', () { - test('returns a new object with updated headlineDensity', () { - final updated = feedPreferencesFixture.copyWith( - headlineDensity: HeadlineDensity.compact, - ); - expect(updated.headlineDensity, HeadlineDensity.compact); - expect( - updated.headlineImageStyle, - feedPreferencesFixture.headlineImageStyle, - ); - }); - - test('returns a new object with updated showSourceInHeadlineFeed', () { - final updated = feedPreferencesFixture.copyWith( - showSourceInHeadlineFeed: false, - ); - expect(updated.showSourceInHeadlineFeed, isFalse); - expect(updated.showPublishDateInHeadlineFeed, isTrue); - }); - }); - }); -} diff --git a/test/src/models/user_settings/user_app_settings_test.dart b/test/src/models/user_settings/user_app_settings_test.dart deleted file mode 100644 index e00979e8..00000000 --- a/test/src/models/user_settings/user_app_settings_test.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:core/core.dart'; -import 'package:test/test.dart'; - -void main() { - group('UserAppSettings', () { - final userAppSettingsFixture = userAppSettingsFixturesData.first; - - test('supports value equality', () { - final settings1 = userAppSettingsFixture.copyWith(); - final settings2 = userAppSettingsFixture.copyWith(); - expect(settings1, equals(settings2)); - }); - - test('props are correct', () { - expect( - userAppSettingsFixture.props, - equals([ - userAppSettingsFixture.id, - userAppSettingsFixture.displaySettings, - userAppSettingsFixture.language, - userAppSettingsFixture.feedPreferences, - ]), - ); - }); - - group('copyWith', () { - test('returns the same object if no arguments are provided', () { - final original = userAppSettingsFixture; - expect(original.copyWith(), equals(original)); - }); - - test('replaces non-null values', () { - final original = userAppSettingsFixture; - final newDisplaySettings = original.displaySettings.copyWith( - accentTheme: AppAccentTheme.newsRed, - ); - final newLanguage = Language( - id: 'lang-es', - code: 'es', - name: 'Spanish', - nativeName: 'Español', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - status: ContentStatus.active, - ); - final newFeedPreferences = original.feedPreferences.copyWith( - headlineDensity: HeadlineDensity.compact, - ); - - final copied = original.copyWith( - displaySettings: newDisplaySettings, - language: newLanguage, - feedPreferences: newFeedPreferences, - ); - - expect(copied.id, original.id); - expect(copied.displaySettings, newDisplaySettings); - expect(copied.language, newLanguage); - expect(copied.feedPreferences, newFeedPreferences); - }); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final original = userAppSettingsFixture; - final json = original.toJson(); - final reconstructed = UserAppSettings.fromJson(json); - expect(reconstructed, equals(original)); - }); - }); - }); -} From 70b1c1cc6131bc45456bd836457951667cdc6ecb Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:09:48 +0100 Subject: [PATCH 20/75] fix(fixtures): add app settings demo data - Create new file for app settings fixtures - Add demo data for admin, user1, user2, and users 3-10 - Include various display settings, languages, and feed settings configurations --- lib/src/fixtures/app_settings.dart | 114 +++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 lib/src/fixtures/app_settings.dart diff --git a/lib/src/fixtures/app_settings.dart b/lib/src/fixtures/app_settings.dart new file mode 100644 index 00000000..785bcfb2 --- /dev/null +++ b/lib/src/fixtures/app_settings.dart @@ -0,0 +1,114 @@ +import 'package:core/core.dart'; + +/// App Settings Demo Data +final List appSettingsFixturesData = [ + AppSettings( + id: kAdminUserId, + displaySettings: const DisplaySettings( + baseTheme: AppBaseTheme.system, + accentTheme: AppAccentTheme.defaultBlue, + fontFamily: 'SystemDefault', + textScaleFactor: AppTextScaleFactor.medium, + fontWeight: AppFontWeight.regular, + ), + language: Language( + id: 'lang-en', + code: 'en', + name: 'English', + nativeName: 'English', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + status: ContentStatus.active, + ), + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, + ), + ), + AppSettings( + id: kUser1Id, + displaySettings: const DisplaySettings( + baseTheme: AppBaseTheme.system, + accentTheme: AppAccentTheme.defaultBlue, + fontFamily: 'SystemDefault', + textScaleFactor: AppTextScaleFactor.medium, + fontWeight: AppFontWeight.regular, + ), + language: Language( + id: 'lang-en', + code: 'en', + name: 'English', + nativeName: 'English', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + status: ContentStatus.active, + ), + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, + ), + ), + AppSettings( + id: kUser2Id, + displaySettings: const DisplaySettings( + baseTheme: AppBaseTheme.dark, + accentTheme: AppAccentTheme.newsRed, + fontFamily: 'SystemDefault', + textScaleFactor: AppTextScaleFactor.medium, + fontWeight: AppFontWeight.regular, + ), + language: Language( + id: 'lang-en', + code: 'en', + name: 'English', + nativeName: 'English', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + status: ContentStatus.active, + ), + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.compact, + feedItemImageStyle: FeedItemImageStyle.largeThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, + ), + ), + // Add settings for users 3-10, copying the admin's settings for simplicity + ...List.generate( + 8, + (index) => AppSettings( + id: [ + kUser3Id, + kUser4Id, + kUser5Id, + kUser6Id, + kUser7Id, + kUser8Id, + kUser9Id, + kUser10Id, + ][index], + displaySettings: const DisplaySettings( + baseTheme: AppBaseTheme.system, + accentTheme: AppAccentTheme.defaultBlue, + fontFamily: 'SystemDefault', + textScaleFactor: AppTextScaleFactor.medium, + fontWeight: AppFontWeight.regular, + ), + language: Language( + id: 'lang-en', + code: 'en', + name: 'English', + nativeName: 'English', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + status: ContentStatus.active, + ), + feedSettings: const FeedSettings( + feedItemDensity: FeedItemDensity.standard, + feedItemImageStyle: FeedItemImageStyle.smallThumbnail, + feedItemClickBehavior: FeedItemClickBehavior.defaultBehavior, + ), + ), + ), +]; From 99b8ecc82775530121368c5ddb5b1695037cf64c Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:10:01 +0100 Subject: [PATCH 21/75] refactor(fixtures): update exports in fixtures.dart - Add export for app_settings.dart - Remove export for user_app_settings.dart - Reorder exports for better organization --- lib/src/fixtures/fixtures.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/fixtures/fixtures.dart b/lib/src/fixtures/fixtures.dart index b9b0500b..99029356 100644 --- a/lib/src/fixtures/fixtures.dart +++ b/lib/src/fixtures/fixtures.dart @@ -1,3 +1,4 @@ +export 'app_settings.dart'; export 'countries.dart'; export 'dashboard_summary.dart'; export 'fixture_ids.dart'; @@ -9,6 +10,5 @@ export 'saved_headline_filters.dart'; export 'saved_source_filters.dart'; export 'sources.dart'; export 'topics.dart'; -export 'user_app_settings.dart'; export 'user_content_preferences.dart'; export 'users.dart'; From 81f4289460a9281a972f1e8bff19ccc897f303df Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:11:00 +0100 Subject: [PATCH 22/75] feat(core): add app settings model - Create AppSettings class to encapsulate user-specific application settings - Include nested settings for display preferences, language, and feed display options - Implement JSON serialization and deserialization - Add immutable marker and equatable for comparison --- .../models/user_settings/app_settings.dart | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 lib/src/models/user_settings/app_settings.dart diff --git a/lib/src/models/user_settings/app_settings.dart b/lib/src/models/user_settings/app_settings.dart new file mode 100644 index 00000000..6c9c2905 --- /dev/null +++ b/lib/src/models/user_settings/app_settings.dart @@ -0,0 +1,70 @@ +import 'package:core/src/models/entities/language.dart'; +import 'package:core/src/models/user_settings/display_settings.dart'; +import 'package:core/src/models/user_settings/feed_settings.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'app_settings.g.dart'; + +/// {@template app_settings} +/// Represents a collection of user-specific application settings, +/// including display preferences, language selection, and feed display options. +/// +/// This model unifies settings that are tied to a specific user, +/// making it suitable for management via a generic data client. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class AppSettings extends Equatable { + /// {@macro app_settings} + /// + /// Creates a new instance of [AppSettings]. + /// + /// An [id] is required, typically the user's unique identifier. + /// Provides sensible defaults for nested settings if not specified. + const AppSettings({ + required this.id, + required this.language, + required this.displaySettings, + required this.feedSettings, + }); + + /// Factory method to create a [AppSettings] instance from a JSON map. + factory AppSettings.fromJson(Map json) => + _$AppSettingsFromJson(json); + + /// The unique identifier for the user settings, typically the user's ID. + final String id; + + /// The selected application language. + final Language language; + + /// User-configurable settings related to the application's visual appearance. + final DisplaySettings displaySettings; + + /// User-configurable settings for how content feeds are displayed. + final FeedSettings feedSettings; + + /// Converts this [AppSettings] instance to a JSON map. + Map toJson() => _$AppSettingsToJson(this); + + @override + List get props => [id, displaySettings, language, feedSettings]; + + /// Creates a copy of this [AppSettings] but with the given fields + /// replaced with the new values. + AppSettings copyWith({ + String? id, + Language? language, + DisplaySettings? displaySettings, + FeedSettings? feedSettings, + }) { + return AppSettings( + id: id ?? this.id, + language: language ?? this.language, + displaySettings: displaySettings ?? this.displaySettings, + feedSettings: feedSettings ?? this.feedSettings, + ); + } +} From d396d8e08d03484be891675551c460cab49f2af4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:11:48 +0100 Subject: [PATCH 23/75] feat(models): add feed settings model - Create FeedSettings class to store user preferences for feed display - Include properties for feed item density, image style, and click behavior - Implement Equatable for value comparison - Add JSON serialization support with json_annotation --- .../models/user_settings/feed_settings.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/src/models/user_settings/feed_settings.dart diff --git a/lib/src/models/user_settings/feed_settings.dart b/lib/src/models/user_settings/feed_settings.dart new file mode 100644 index 00000000..7ed33fa4 --- /dev/null +++ b/lib/src/models/user_settings/feed_settings.dart @@ -0,0 +1,58 @@ +import 'package:core/core.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'feed_settings.g.dart'; + +/// {@template feed_settings} +/// User preferences for how feeds are displayed. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeedSettings extends Equatable { + /// {@macro feed_settings} + const FeedSettings({ + required this.feedItemDensity, + required this.feedItemImageStyle, + required this.feedItemClickBehavior, + }); + + /// Creates a [FeedSettings] instance from a JSON map. + factory FeedSettings.fromJson(Map json) => + _$FeedSettingsFromJson(json); + + /// How densely feed information should be presented. + final FeedItemDensity feedItemDensity; + + /// How images should be displayed in the feed. + final FeedItemImageStyle feedItemImageStyle; + + /// How feed items links should be opened. + final FeedItemClickBehavior feedItemClickBehavior; + + /// Converts this [FeedSettings] instance to a JSON map. + Map toJson() => _$FeedSettingsToJson(this); + + /// Creates a copy of this [FeedSettings] but with the given fields + /// replaced with the new values. + FeedSettings copyWith({ + FeedItemDensity? feedItemDensity, + FeedItemImageStyle? feedItemImageStyle, + FeedItemClickBehavior? feedItemClickBehavior, + }) { + return FeedSettings( + feedItemDensity: feedItemDensity ?? this.feedItemDensity, + feedItemImageStyle: feedItemImageStyle ?? this.feedItemImageStyle, + feedItemClickBehavior: + feedItemClickBehavior ?? this.feedItemClickBehavior, + ); + } + + @override + List get props => [ + feedItemDensity, + feedItemImageStyle, + feedItemClickBehavior, + ]; +} From d244fa3bafa7b6e1157b2f4eb37766383aa67c0b Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:12:32 +0100 Subject: [PATCH 24/75] refactor(user_settings): update exports and rename modules - Add export for app_settings.dart - Remove exports for feed_display_preferences.dart and user_app_settings.dart - Rename feed_settings.dart to replace feed_display_preferences.dart --- lib/src/models/user_settings/user_settings.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/user_settings/user_settings.dart b/lib/src/models/user_settings/user_settings.dart index 0c1e3ae6..d2711a75 100644 --- a/lib/src/models/user_settings/user_settings.dart +++ b/lib/src/models/user_settings/user_settings.dart @@ -1,3 +1,3 @@ +export 'app_settings.dart'; export 'display_settings.dart'; -export 'feed_display_preferences.dart'; -export 'user_app_settings.dart'; +export 'feed_settings.dart'; From 09047dac1fa96647e0562a411995bd2ec09fae4a Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:12:52 +0100 Subject: [PATCH 25/75] fix: tests --- test/src/enums/feed_item_density_test.dart | 44 ++++++++++++ .../src/enums/feed_item_image_style_test.dart | 38 ++++++++++ test/src/models/config/ad_config_test.dart | 8 +-- .../config/ad_platform_identifiers_test.dart | 12 ++-- .../user_settings/app_settings_test.dart | 72 +++++++++++++++++++ .../user_settings/display_settings_test.dart | 2 +- .../user_settings/feed_settings_test.dart | 62 ++++++++++++++++ 7 files changed, 227 insertions(+), 11 deletions(-) create mode 100644 test/src/enums/feed_item_density_test.dart create mode 100644 test/src/enums/feed_item_image_style_test.dart create mode 100644 test/src/models/user_settings/app_settings_test.dart create mode 100644 test/src/models/user_settings/feed_settings_test.dart diff --git a/test/src/enums/feed_item_density_test.dart b/test/src/enums/feed_item_density_test.dart new file mode 100644 index 00000000..38becb59 --- /dev/null +++ b/test/src/enums/feed_item_density_test.dart @@ -0,0 +1,44 @@ +import 'package:core/src/enums/feed_item_density.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedItemDensity', () { + test('has correct values', () { + expect( + FeedItemDensity.values, + containsAll([ + FeedItemDensity.compact, + FeedItemDensity.standard, + FeedItemDensity.comfortable, + ]), + ); + }); + + test('has correct string values', () { + expect(FeedItemDensity.compact.name, 'compact'); + expect(FeedItemDensity.standard.name, 'standard'); + expect(FeedItemDensity.comfortable.name, 'comfortable'); + }); + + test('can be created from string values', () { + expect(FeedItemDensity.values.byName('compact'), FeedItemDensity.compact); + expect( + FeedItemDensity.values.byName('standard'), + FeedItemDensity.standard, + ); + expect( + FeedItemDensity.values.byName('comfortable'), + FeedItemDensity.comfortable, + ); + }); + + test('has correct toString representation', () { + expect(FeedItemDensity.compact.toString(), 'FeedItemDensity.compact'); + expect(FeedItemDensity.standard.toString(), 'FeedItemDensity.standard'); + expect( + FeedItemDensity.comfortable.toString(), + 'FeedItemDensity.comfortable', + ); + }); + }); +} diff --git a/test/src/enums/feed_item_image_style_test.dart b/test/src/enums/feed_item_image_style_test.dart new file mode 100644 index 00000000..e5601137 --- /dev/null +++ b/test/src/enums/feed_item_image_style_test.dart @@ -0,0 +1,38 @@ +import 'package:core/src/enums/feed_item_image_style.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedItemImageStyle', () { + test('has correct values', () { + expect( + FeedItemImageStyle.values, + containsAll([ + FeedItemImageStyle.hidden, + FeedItemImageStyle.smallThumbnail, + FeedItemImageStyle.largeThumbnail, + ]), + ); + }); + + test('has correct string values', () { + expect(FeedItemImageStyle.hidden.name, 'hidden'); + expect(FeedItemImageStyle.smallThumbnail.name, 'smallThumbnail'); + expect(FeedItemImageStyle.largeThumbnail.name, 'largeThumbnail'); + }); + + test('can be created from string values', () { + expect( + FeedItemImageStyle.values.byName('hidden'), + FeedItemImageStyle.hidden, + ); + expect( + FeedItemImageStyle.values.byName('smallThumbnail'), + FeedItemImageStyle.smallThumbnail, + ); + expect( + FeedItemImageStyle.values.byName('largeThumbnail'), + FeedItemImageStyle.largeThumbnail, + ); + }); + }); +} diff --git a/test/src/models/config/ad_config_test.dart b/test/src/models/config/ad_config_test.dart index 9ffe0e1a..1549354a 100644 --- a/test/src/models/config/ad_config_test.dart +++ b/test/src/models/config/ad_config_test.dart @@ -18,8 +18,8 @@ void main() { isA(), ); expect( - adConfigFixture.interstitialAdConfiguration, - isA(), + adConfigFixture.navigationAdConfiguration, + isA(), ); }); @@ -35,13 +35,13 @@ void main() { feedAdConfiguration: adConfigFixture.feedAdConfiguration.copyWith( enabled: false, ), - interstitialAdConfiguration: adConfigFixture.interstitialAdConfiguration + navigationAdConfiguration: adConfigFixture.navigationAdConfiguration .copyWith(enabled: false), ); expect(updatedConfig.primaryAdPlatform, AdPlatformType.admob); expect(updatedConfig.feedAdConfiguration.enabled, isFalse); - expect(updatedConfig.interstitialAdConfiguration.enabled, isFalse); + expect(updatedConfig.navigationAdConfiguration.enabled, isFalse); expect(updatedConfig, isNot(equals(adConfigFixture))); }); diff --git a/test/src/models/config/ad_platform_identifiers_test.dart b/test/src/models/config/ad_platform_identifiers_test.dart index a15448fc..0fc60c93 100644 --- a/test/src/models/config/ad_platform_identifiers_test.dart +++ b/test/src/models/config/ad_platform_identifiers_test.dart @@ -16,19 +16,19 @@ void main() { test('can be instantiated (AdMob)', () { expect(admobIdentifiersFixture, isA()); expect( - admobIdentifiersFixture.feedNativeAdId, + admobIdentifiersFixture.nativeAdId, 'ca-app-pub-3940256099942544/2247696110', ); expect( - admobIdentifiersFixture.feedToArticleInterstitialAdId, + admobIdentifiersFixture.interstitialAdId, 'ca-app-pub-3940256099942544/1033173712', ); }); test('can be instantiated (Demo)', () { expect(demoIdentifiersFixture, isA()); - expect(demoIdentifiersFixture.feedNativeAdId, '_'); - expect(demoIdentifiersFixture.feedToArticleInterstitialAdId, '_'); + expect(demoIdentifiersFixture.nativeAdId, '_'); + expect(demoIdentifiersFixture.interstitialAdId, '_'); }); test('supports value equality', () { @@ -39,11 +39,11 @@ void main() { test('copyWith returns a new instance with updated values', () { final updatedIdentifiers = admobIdentifiersFixture.copyWith( - feedNativeAdId: 'new_native_id', + nativeAdId: 'new_native_id', inArticleBannerAdId: 'new_banner_id', ); - expect(updatedIdentifiers.feedNativeAdId, 'new_native_id'); + expect(updatedIdentifiers.nativeAdId, 'new_native_id'); expect(updatedIdentifiers.inArticleBannerAdId, 'new_banner_id'); expect(updatedIdentifiers, isNot(equals(admobIdentifiersFixture))); }); diff --git a/test/src/models/user_settings/app_settings_test.dart b/test/src/models/user_settings/app_settings_test.dart new file mode 100644 index 00000000..2233a662 --- /dev/null +++ b/test/src/models/user_settings/app_settings_test.dart @@ -0,0 +1,72 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('AppSettings', () { + final appSettingsFixture = appSettingsFixturesData.first; + + test('supports value equality', () { + final settings1 = appSettingsFixture.copyWith(); + final settings2 = appSettingsFixture.copyWith(); + expect(settings1, equals(settings2)); + }); + + test('props are correct', () { + expect( + appSettingsFixture.props, + equals([ + appSettingsFixture.id, + appSettingsFixture.displaySettings, + appSettingsFixture.language, + appSettingsFixture.feedSettings, + ]), + ); + }); + + group('copyWith', () { + test('returns the same object if no arguments are provided', () { + final original = appSettingsFixture; + expect(original.copyWith(), equals(original)); + }); + + test('replaces non-null values', () { + final original = appSettingsFixture; + final newDisplaySettings = original.displaySettings.copyWith( + accentTheme: AppAccentTheme.newsRed, + ); + final newLanguage = Language( + id: 'lang-es', + code: 'es', + name: 'Spanish', + nativeName: 'Español', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + status: ContentStatus.active, + ); + final newFeedPreferences = original.feedSettings.copyWith( + feedItemDensity: FeedItemDensity.compact, + ); + + final copied = original.copyWith( + displaySettings: newDisplaySettings, + language: newLanguage, + feedSettings: newFeedPreferences, + ); + + expect(copied.id, original.id); + expect(copied.displaySettings, newDisplaySettings); + expect(copied.language, newLanguage); + expect(copied.feedSettings, newFeedPreferences); + }); + }); + + group('fromJson/toJson', () { + test('round trip', () { + final original = appSettingsFixture; + final json = original.toJson(); + final reconstructed = AppSettings.fromJson(json); + expect(reconstructed, equals(original)); + }); + }); + }); +} diff --git a/test/src/models/user_settings/display_settings_test.dart b/test/src/models/user_settings/display_settings_test.dart index 9178fb49..7fdb63b5 100644 --- a/test/src/models/user_settings/display_settings_test.dart +++ b/test/src/models/user_settings/display_settings_test.dart @@ -5,7 +5,7 @@ void main() { group('DisplaySettings', () { // Derive the test subject from the main app settings fixture. final displaySettingsFixture = - userAppSettingsFixturesData.first.displaySettings; + appSettingsFixturesData.first.displaySettings; test('supports value equality', () { final settings1 = displaySettingsFixture.copyWith(); diff --git a/test/src/models/user_settings/feed_settings_test.dart b/test/src/models/user_settings/feed_settings_test.dart new file mode 100644 index 00000000..7475ecaa --- /dev/null +++ b/test/src/models/user_settings/feed_settings_test.dart @@ -0,0 +1,62 @@ +import 'package:core/core.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedSettings', () { + // Derive the test subject from the main app settings fixture. + final feedSettingsFixture = appSettingsFixturesData.first.feedSettings; + + test('supports value equality', () { + final preferences1 = feedSettingsFixture.copyWith(); + final preferences2 = feedSettingsFixture.copyWith(); + expect(preferences1, equals(preferences2)); + }); + + group('fromJson/toJson', () { + test('round trip', () { + final original = feedSettingsFixture.copyWith( + feedItemDensity: FeedItemDensity.compact, + feedItemImageStyle: FeedItemImageStyle.largeThumbnail, + ); + final json = original.toJson(); + final reconstructed = FeedSettings.fromJson(json); + expect(reconstructed, equals(original)); + }); + + test( + 'throws CheckedFromJsonException when required fields are missing', + () { + final json = {}; + expect( + () => FeedSettings.fromJson(json), + throwsA(isA()), + ); + }, + ); + }); + + group('copyWith', () { + test('returns a new object with updated headlineDensity', () { + final updated = feedSettingsFixture.copyWith( + feedItemDensity: FeedItemDensity.compact, + ); + expect(updated.feedItemDensity, FeedItemDensity.compact); + expect( + updated.feedItemImageStyle, + feedSettingsFixture.feedItemImageStyle, + ); + }); + + test('returns a new object with updated showSourceInHeadlineFeed', () { + final updated = feedSettingsFixture.copyWith( + feedItemClickBehavior: FeedItemClickBehavior.externalNavigation, + ); + expect( + updated.feedItemClickBehavior, + FeedItemClickBehavior.externalNavigation, + ); + }); + }); + }); +} From 2d793dfc3eedb371679a26ff6bf5b124aa30aab4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:15:30 +0100 Subject: [PATCH 26/75] build(serialization); generate --- lib/src/models/config/ad_config.g.dart | 14 ++--- .../config/ad_platform_identifiers.g.dart | 25 +++------ .../config/navigation_ad_configuration.g.dart | 52 ++++++++++++++++++ .../navigation_ad_frequency_config.g.dart | 32 +++++++++++ lib/src/models/config/remote_config.g.dart | 12 +++++ lib/src/models/entities/headline.g.dart | 2 - .../models/user_settings/app_settings.g.dart | 35 ++++++++++++ .../models/user_settings/feed_settings.g.dart | 53 +++++++++++++++++++ 8 files changed, 194 insertions(+), 31 deletions(-) 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/user_settings/app_settings.g.dart create mode 100644 lib/src/models/user_settings/feed_settings.g.dart diff --git a/lib/src/models/config/ad_config.g.dart b/lib/src/models/config/ad_config.g.dart index 3c64a10b..415da410 100644 --- a/lib/src/models/config/ad_config.g.dart +++ b/lib/src/models/config/ad_config.g.dart @@ -27,14 +27,9 @@ AdConfig _$AdConfigFromJson(Map json) => 'feedAdConfiguration', (v) => FeedAdConfiguration.fromJson(v as Map), ), - articleAdConfiguration: $checkedConvert( - 'articleAdConfiguration', - (v) => ArticleAdConfiguration.fromJson(v as Map), - ), - interstitialAdConfiguration: $checkedConvert( - 'interstitialAdConfiguration', - (v) => - InterstitialAdConfiguration.fromJson(v as Map), + navigationAdConfiguration: $checkedConvert( + 'navigationAdConfiguration', + (v) => NavigationAdConfiguration.fromJson(v as Map), ), ); return val; @@ -47,8 +42,7 @@ Map _$AdConfigToJson(AdConfig instance) => { (k, e) => MapEntry(_$AdPlatformTypeEnumMap[k]!, e.toJson()), ), 'feedAdConfiguration': instance.feedAdConfiguration.toJson(), - 'articleAdConfiguration': instance.articleAdConfiguration.toJson(), - 'interstitialAdConfiguration': instance.interstitialAdConfiguration.toJson(), + 'navigationAdConfiguration': instance.navigationAdConfiguration.toJson(), }; const _$AdPlatformTypeEnumMap = { diff --git a/lib/src/models/config/ad_platform_identifiers.g.dart b/lib/src/models/config/ad_platform_identifiers.g.dart index f26d9969..433f3ad2 100644 --- a/lib/src/models/config/ad_platform_identifiers.g.dart +++ b/lib/src/models/config/ad_platform_identifiers.g.dart @@ -10,20 +10,9 @@ AdPlatformIdentifiers _$AdPlatformIdentifiersFromJson( Map json, ) => $checkedCreate('AdPlatformIdentifiers', json, ($checkedConvert) { final val = AdPlatformIdentifiers( - feedNativeAdId: $checkedConvert('feedNativeAdId', (v) => v as String?), - feedBannerAdId: $checkedConvert('feedBannerAdId', (v) => v as String?), - feedToArticleInterstitialAdId: $checkedConvert( - 'feedToArticleInterstitialAdId', - (v) => v as String?, - ), - inArticleNativeAdId: $checkedConvert( - 'inArticleNativeAdId', - (v) => v as String?, - ), - inArticleBannerAdId: $checkedConvert( - 'inArticleBannerAdId', - (v) => v as String?, - ), + nativeAdId: $checkedConvert('nativeAdId', (v) => v as String?), + bannerAdId: $checkedConvert('bannerAdId', (v) => v as String?), + interstitialAdId: $checkedConvert('interstitialAdId', (v) => v as String?), ); return val; }); @@ -31,9 +20,7 @@ AdPlatformIdentifiers _$AdPlatformIdentifiersFromJson( Map _$AdPlatformIdentifiersToJson( AdPlatformIdentifiers instance, ) => { - 'feedNativeAdId': instance.feedNativeAdId, - 'feedBannerAdId': instance.feedBannerAdId, - 'feedToArticleInterstitialAdId': instance.feedToArticleInterstitialAdId, - 'inArticleNativeAdId': instance.inArticleNativeAdId, - 'inArticleBannerAdId': instance.inArticleBannerAdId, + 'nativeAdId': instance.nativeAdId, + 'bannerAdId': instance.bannerAdId, + 'interstitialAdId': instance.interstitialAdId, }; 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 00000000..25ac008a --- /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 00000000..956455c5 --- /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/remote_config.g.dart b/lib/src/models/config/remote_config.g.dart index 35643788..fa91c367 100644 --- a/lib/src/models/config/remote_config.g.dart +++ b/lib/src/models/config/remote_config.g.dart @@ -35,6 +35,10 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => 'pushNotificationConfig', (v) => PushNotificationConfig.fromJson(v as Map), ), + feedItemClickBehavior: $checkedConvert( + 'feedItemClickBehavior', + (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), + ), createdAt: $checkedConvert( 'createdAt', (v) => dateTimeFromJson(v as String?), @@ -57,6 +61,8 @@ Map _$RemoteConfigToJson(RemoteConfig instance) => ), 'appStatus': instance.appStatus.toJson(), 'pushNotificationConfig': instance.pushNotificationConfig.toJson(), + 'feedItemClickBehavior': + _$FeedItemClickBehaviorEnumMap[instance.feedItemClickBehavior]!, 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), }; @@ -69,3 +75,9 @@ const _$FeedDecoratorTypeEnumMap = { FeedDecoratorType.suggestedTopics: 'suggestedTopics', FeedDecoratorType.suggestedSources: 'suggestedSources', }; + +const _$FeedItemClickBehaviorEnumMap = { + FeedItemClickBehavior.defaultBehavior: 'default', + FeedItemClickBehavior.internalNavigation: 'internalNavigation', + FeedItemClickBehavior.externalNavigation: 'externalNavigation', +}; diff --git a/lib/src/models/entities/headline.g.dart b/lib/src/models/entities/headline.g.dart index f45c9ff6..2a5cdb86 100644 --- a/lib/src/models/entities/headline.g.dart +++ b/lib/src/models/entities/headline.g.dart @@ -11,7 +11,6 @@ Headline _$HeadlineFromJson(Map json) => final val = Headline( id: $checkedConvert('id', (v) => v as String), title: $checkedConvert('title', (v) => v as String), - excerpt: $checkedConvert('excerpt', (v) => v as String), url: $checkedConvert('url', (v) => v as String), imageUrl: $checkedConvert('imageUrl', (v) => v as String), source: $checkedConvert( @@ -46,7 +45,6 @@ Headline _$HeadlineFromJson(Map json) => Map _$HeadlineToJson(Headline instance) => { 'id': instance.id, 'title': instance.title, - 'excerpt': instance.excerpt, 'url': instance.url, 'imageUrl': instance.imageUrl, 'source': instance.source.toJson(), diff --git a/lib/src/models/user_settings/app_settings.g.dart b/lib/src/models/user_settings/app_settings.g.dart new file mode 100644 index 00000000..0b11fcd4 --- /dev/null +++ b/lib/src/models/user_settings/app_settings.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'app_settings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +AppSettings _$AppSettingsFromJson(Map json) => + $checkedCreate('AppSettings', json, ($checkedConvert) { + final val = AppSettings( + id: $checkedConvert('id', (v) => v as String), + language: $checkedConvert( + 'language', + (v) => Language.fromJson(v as Map), + ), + displaySettings: $checkedConvert( + 'displaySettings', + (v) => DisplaySettings.fromJson(v as Map), + ), + feedSettings: $checkedConvert( + 'feedSettings', + (v) => FeedSettings.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$AppSettingsToJson(AppSettings instance) => + { + 'id': instance.id, + 'language': instance.language.toJson(), + 'displaySettings': instance.displaySettings.toJson(), + 'feedSettings': instance.feedSettings.toJson(), + }; diff --git a/lib/src/models/user_settings/feed_settings.g.dart b/lib/src/models/user_settings/feed_settings.g.dart new file mode 100644 index 00000000..e08831c4 --- /dev/null +++ b/lib/src/models/user_settings/feed_settings.g.dart @@ -0,0 +1,53 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'feed_settings.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeedSettings _$FeedSettingsFromJson(Map json) => + $checkedCreate('FeedSettings', json, ($checkedConvert) { + final val = FeedSettings( + feedItemDensity: $checkedConvert( + 'feedItemDensity', + (v) => $enumDecode(_$FeedItemDensityEnumMap, v), + ), + feedItemImageStyle: $checkedConvert( + 'feedItemImageStyle', + (v) => $enumDecode(_$FeedItemImageStyleEnumMap, v), + ), + feedItemClickBehavior: $checkedConvert( + 'feedItemClickBehavior', + (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), + ), + ); + return val; + }); + +Map _$FeedSettingsToJson(FeedSettings instance) => + { + 'feedItemDensity': _$FeedItemDensityEnumMap[instance.feedItemDensity]!, + 'feedItemImageStyle': + _$FeedItemImageStyleEnumMap[instance.feedItemImageStyle]!, + 'feedItemClickBehavior': + _$FeedItemClickBehaviorEnumMap[instance.feedItemClickBehavior]!, + }; + +const _$FeedItemDensityEnumMap = { + FeedItemDensity.compact: 'compact', + FeedItemDensity.standard: 'standard', + FeedItemDensity.comfortable: 'comfortable', +}; + +const _$FeedItemImageStyleEnumMap = { + FeedItemImageStyle.hidden: 'hidden', + FeedItemImageStyle.smallThumbnail: 'smallThumbnail', + FeedItemImageStyle.largeThumbnail: 'largeThumbnail', +}; + +const _$FeedItemClickBehaviorEnumMap = { + FeedItemClickBehavior.defaultBehavior: 'default', + FeedItemClickBehavior.internalNavigation: 'internalNavigation', + FeedItemClickBehavior.externalNavigation: 'externalNavigation', +}; From 0881c4609354242083209fb86a96e64d4c87a69a Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:16:32 +0100 Subject: [PATCH 27/75] fix: sync headlines fixture --- lib/src/fixtures/headlines.dart | 218 -------------------------------- 1 file changed, 218 deletions(-) diff --git a/lib/src/fixtures/headlines.dart b/lib/src/fixtures/headlines.dart index 5df59934..721179eb 100644 --- a/lib/src/fixtures/headlines.dart +++ b/lib/src/fixtures/headlines.dart @@ -11,8 +11,6 @@ final headlinesFixturesData = [ id: kHeadlineId1, isBreaking: false, title: 'AI Breakthrough: New Model Achieves Human-Level Performance', - excerpt: - 'Researchers announce a significant leap in artificial intelligence, with a new model demonstrating unprecedented capabilities.', url: 'https://example.com/news/ai-breakthrough-1', imageUrl: 'https://picsum.photos/seed/kHeadlineId1/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -26,8 +24,6 @@ final headlinesFixturesData = [ id: kHeadlineId2, isBreaking: false, title: 'Local Team Wins Championship in Thrilling Final', - excerpt: - 'The city celebrates as the underdog team clinches the national championship in a nail-biting finish.', url: 'https://example.com/news/sports-championship-2', imageUrl: 'https://picsum.photos/seed/kHeadlineId2/800/600', source: sourcesFixturesData[1], // BBC News @@ -41,8 +37,6 @@ final headlinesFixturesData = [ id: kHeadlineId3, isBreaking: false, title: 'Global Leaders Meet to Discuss Climate Change Policies', - excerpt: - 'A summit of world leaders convenes to address urgent climate change issues and propose new international agreements.', url: 'https://example.com/news/politics-climate-3', imageUrl: 'https://picsum.photos/seed/kHeadlineId3/800/600', source: sourcesFixturesData[2], // The New York Times @@ -56,8 +50,6 @@ final headlinesFixturesData = [ id: kHeadlineId4, isBreaking: true, title: 'New Planet Discovered in Distant Galaxy', - excerpt: - 'Astronomers confirm the existence of a new exoplanet, sparking excitement in the scientific community.', url: 'https://example.com/news/science-planet-4', imageUrl: 'https://picsum.photos/seed/kHeadlineId4/800/600', source: sourcesFixturesData[3], // The Guardian @@ -71,8 +63,6 @@ final headlinesFixturesData = [ id: kHeadlineId5, isBreaking: false, title: 'Breakthrough in Cancer Research Offers New Hope', - excerpt: - 'A new study reveals a promising treatment approach for a common type of cancer, moving closer to a cure.', url: 'https://example.com/news/health-cancer-5', imageUrl: 'https://picsum.photos/seed/kHeadlineId5/800/600', source: sourcesFixturesData[4], // CNN @@ -86,8 +76,6 @@ final headlinesFixturesData = [ id: kHeadlineId6, isBreaking: false, title: 'Blockbuster Movie Breaks Box Office Records', - excerpt: - 'The highly anticipated film shatters previous box office records in its opening weekend, delighting fans worldwide.', url: 'https://example.com/news/entertainment-movie-6', imageUrl: 'https://picsum.photos/seed/kHeadlineId6/800/600', source: sourcesFixturesData[5], // Reuters @@ -101,8 +89,6 @@ final headlinesFixturesData = [ id: kHeadlineId7, isBreaking: false, title: 'Stock Market Reaches All-Time High Amid Economic Boom', - excerpt: - 'Major indices surge as strong economic data and corporate earnings drive investor confidence.', url: 'https://example.com/news/business-market-7', imageUrl: 'https://picsum.photos/seed/kHeadlineId7/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -116,8 +102,6 @@ final headlinesFixturesData = [ id: kHeadlineId8, isBreaking: false, title: 'New Travel Restrictions Lifted for Popular Destinations', - excerpt: - 'Governments ease travel advisories, opening up new opportunities for international tourism.', url: 'https://example.com/news/travel-restrictions-8', imageUrl: 'https://picsum.photos/seed/kHeadlineId8/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -131,8 +115,6 @@ final headlinesFixturesData = [ id: kHeadlineId9, isBreaking: false, title: 'Michelin Star Chef Opens New Restaurant in City Center', - excerpt: - 'A world-renowned chef brings their culinary expertise to the city with a highly anticipated new dining establishment.', url: 'https://example.com/news/food-restaurant-9', imageUrl: 'https://picsum.photos/seed/kHeadlineId9/800/600', source: sourcesFixturesData[8], // The Times of India @@ -146,8 +128,6 @@ final headlinesFixturesData = [ id: kHeadlineId10, isBreaking: false, title: 'Innovative Teaching Methods Boost Student Engagement', - excerpt: - 'Schools adopting new pedagogical approaches report significant improvements in student participation and learning outcomes.', url: 'https://example.com/news/education-methods-10', imageUrl: 'https://picsum.photos/seed/kHeadlineId10/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -161,8 +141,6 @@ final headlinesFixturesData = [ id: kHeadlineId11, isBreaking: false, title: 'Cybersecurity Firms Warn of New Global Threat', - excerpt: - 'Experts advise immediate updates as a sophisticated new malware strain targets critical infrastructure worldwide.', url: 'https://example.com/news/cybersecurity-threat-11', imageUrl: 'https://picsum.photos/seed/kHeadlineId11/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -176,8 +154,6 @@ final headlinesFixturesData = [ id: kHeadlineId12, isBreaking: false, title: 'Olympics Committee Announces Host City for 2032 Games', - excerpt: - 'The highly anticipated decision for the next Summer Olympics host city has been revealed, promising a spectacular event.', url: 'https://example.com/news/sports-olympics-12', imageUrl: 'https://picsum.photos/seed/kHeadlineId12/800/600', source: sourcesFixturesData[1], // BBC News @@ -191,8 +167,6 @@ final headlinesFixturesData = [ id: kHeadlineId13, isBreaking: false, title: 'New Bill Aims to Reform Healthcare System', - excerpt: - 'Legislators introduce a comprehensive bill designed to address rising healthcare costs and expand access to services.', url: 'https://example.com/news/politics-healthcare-13', imageUrl: 'https://picsum.photos/seed/kHeadlineId13/800/600', source: sourcesFixturesData[2], // The New York Times @@ -206,8 +180,6 @@ final headlinesFixturesData = [ id: kHeadlineId14, isBreaking: false, title: 'Archaeologists Uncover Ancient City Ruins', - excerpt: - 'A team of archaeologists makes a groundbreaking discovery, revealing a previously unknown ancient civilization.', url: 'https://example.com/news/science-archaeology-14', imageUrl: 'https://picsum.photos/seed/kHeadlineId14/800/600', source: sourcesFixturesData[3], // The Guardian @@ -221,8 +193,6 @@ final headlinesFixturesData = [ id: kHeadlineId15, isBreaking: false, title: 'Dietary Guidelines Updated for Public Health', - excerpt: - 'New recommendations from health organizations aim to improve public nutrition and combat chronic diseases.', url: 'https://example.com/news/health-diet-15', imageUrl: 'https://picsum.photos/seed/kHeadlineId15/800/600', source: sourcesFixturesData[4], // CNN @@ -236,8 +206,6 @@ final headlinesFixturesData = [ id: kHeadlineId16, isBreaking: false, title: 'Music Festival Announces Star-Studded Lineup', - excerpt: - 'Fans eagerly await the annual music festival as organizers unveil a lineup featuring top artists from various genres.', url: 'https://example.com/news/entertainment-music-16', imageUrl: 'https://picsum.photos/seed/kHeadlineId16/800/600', source: sourcesFixturesData[5], // Reuters @@ -251,8 +219,6 @@ final headlinesFixturesData = [ id: kHeadlineId17, isBreaking: false, title: 'Tech Giant Acquires Startup in Multi-Billion Dollar Deal', - excerpt: - 'A major technology company expands its portfolio with the acquisition of a promising startup, signaling market consolidation.', url: 'https://example.com/news/business-acquisition-17', imageUrl: 'https://picsum.photos/seed/kHeadlineId17/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -266,8 +232,6 @@ final headlinesFixturesData = [ id: kHeadlineId18, isBreaking: false, title: 'Space Tourism Takes Off: First Commercial Flights Announced', - excerpt: - 'The era of space tourism begins as companies unveil plans for regular commercial flights to orbit.', url: 'https://example.com/news/travel-space-18', imageUrl: 'https://picsum.photos/seed/kHeadlineId18/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -281,8 +245,6 @@ final headlinesFixturesData = [ id: kHeadlineId19, isBreaking: false, title: 'Future of Food: Lab-Grown Meat Gains Popularity', - excerpt: - 'As sustainability concerns grow, lab-grown meat alternatives are becoming a staple in modern diets.', url: 'https://example.com/news/food-lab-meat-19', imageUrl: 'https://picsum.photos/seed/kHeadlineId19/800/600', source: sourcesFixturesData[8], // The Times of India @@ -296,8 +258,6 @@ final headlinesFixturesData = [ id: kHeadlineId20, isBreaking: false, title: 'Online Learning Platforms See Surge in Enrollment', - excerpt: - 'The shift to digital education continues as more students opt for flexible online courses and certifications.', url: 'https://example.com/news/education-online-20', imageUrl: 'https://picsum.photos/seed/kHeadlineId20/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -311,8 +271,6 @@ final headlinesFixturesData = [ id: kHeadlineId21, isBreaking: false, title: 'Quantum Computing Achieves New Milestone', - excerpt: - 'Scientists report a significant advancement in quantum computing, bringing the technology closer to practical applications.', url: 'https://example.com/news/tech-quantum-21', imageUrl: 'https://picsum.photos/seed/kHeadlineId21/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -326,8 +284,6 @@ final headlinesFixturesData = [ id: kHeadlineId22, isBreaking: false, title: 'World Cup Qualifiers: Unexpected Upsets Shake Rankings', - excerpt: - 'Several top-ranked teams suffer surprising defeats in the latest World Cup qualifiers, reshuffling the global football landscape.', url: 'https://example.com/news/sports-worldcup-22', imageUrl: 'https://picsum.photos/seed/kHeadlineId22/800/600', source: sourcesFixturesData[1], // BBC News @@ -341,8 +297,6 @@ final headlinesFixturesData = [ id: kHeadlineId23, isBreaking: false, title: 'Election Results: New Government Takes Power', - excerpt: - 'Following a closely contested election, a new political party forms the government, promising significant policy changes.', url: 'https://example.com/news/politics-election-23', imageUrl: 'https://picsum.photos/seed/kHeadlineId23/800/600', source: sourcesFixturesData[2], // The New York Times @@ -356,8 +310,6 @@ final headlinesFixturesData = [ id: kHeadlineId24, isBreaking: false, title: 'Breakthrough in Fusion Energy Research Announced', - excerpt: - 'Scientists achieve a major milestone in fusion energy, bringing clean, limitless power closer to reality.', url: 'https://example.com/news/science-fusion-24', imageUrl: 'https://picsum.photos/seed/kHeadlineId24/800/600', source: sourcesFixturesData[3], // The Guardian @@ -371,8 +323,6 @@ final headlinesFixturesData = [ id: kHeadlineId25, isBreaking: false, title: 'Mental Health Awareness Campaign Launched Globally', - excerpt: - 'A new international initiative aims to destigmatize mental health issues and provide greater support resources.', url: 'https://example.com/news/health-mental-25', imageUrl: 'https://picsum.photos/seed/kHeadlineId25/800/600', source: sourcesFixturesData[4], // CNN @@ -386,8 +336,6 @@ final headlinesFixturesData = [ id: kHeadlineId26, isBreaking: false, title: 'Gaming Industry Sees Record Growth in Virtual Reality', - excerpt: - 'The virtual reality sector of the gaming industry experiences unprecedented expansion, driven by new hardware and immersive titles.', url: 'https://example.com/news/entertainment-vr-26', imageUrl: 'https://picsum.photos/seed/kHeadlineId26/800/600', source: sourcesFixturesData[5], // Reuters @@ -401,8 +349,6 @@ final headlinesFixturesData = [ id: kHeadlineId27, isBreaking: false, title: 'Global Supply Chain Disruptions Impacting Consumer Goods', - excerpt: - 'Ongoing challenges in global logistics are leading to shortages and price increases for a wide range of consumer products.', url: 'https://example.com/news/business-supplychain-27', imageUrl: 'https://picsum.photos/seed/kHeadlineId27/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -416,8 +362,6 @@ final headlinesFixturesData = [ id: kHeadlineId28, isBreaking: false, title: 'Arctic Expedition Discovers New Marine Species', - excerpt: - 'Scientists on an Arctic research mission identify several previously unknown species of marine life, highlighting biodiversity.', url: 'https://example.com/news/travel-arctic-28', imageUrl: 'https://picsum.photos/seed/kHeadlineId28/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -431,8 +375,6 @@ final headlinesFixturesData = [ id: kHeadlineId29, isBreaking: false, title: 'Rise of Plant-Based Cuisine: New Restaurants Open', - excerpt: - 'The culinary scene is embracing plant-based diets with an increasing number of restaurants specializing in vegan and vegetarian dishes.', url: 'https://example.com/news/food-plantbased-29', imageUrl: 'https://picsum.photos/seed/kHeadlineId29/800/600', source: sourcesFixturesData[8], // The Times of India @@ -446,8 +388,6 @@ final headlinesFixturesData = [ id: kHeadlineId30, isBreaking: false, title: 'Education Technology Transforms Classrooms', - excerpt: - 'New digital tools and platforms are revolutionizing traditional classroom settings, enhancing interactive learning experiences.', url: 'https://example.com/news/education-edtech-30', imageUrl: 'https://picsum.photos/seed/kHeadlineId30/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -461,8 +401,6 @@ final headlinesFixturesData = [ id: kHeadlineId31, isBreaking: false, title: 'SpaceX Launches New Satellite Constellation', - excerpt: - "Elon Musk's SpaceX successfully deploys a new batch of Starlink satellites, expanding global internet coverage.", url: 'https://example.com/news/tech-spacex-31', imageUrl: 'https://picsum.photos/seed/kHeadlineId31/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -476,8 +414,6 @@ final headlinesFixturesData = [ id: kHeadlineId32, isBreaking: false, title: 'Football Legend Announces Retirement', - excerpt: - 'A celebrated football player declares their retirement, marking the end of an illustrious career.', url: 'https://example.com/news/sports-retirement-32', imageUrl: 'https://picsum.photos/seed/kHeadlineId32/800/600', source: sourcesFixturesData[1], // BBC News @@ -491,8 +427,6 @@ final headlinesFixturesData = [ id: kHeadlineId33, isBreaking: false, title: 'G7 Summit Concludes with Joint Statement on Global Economy', - excerpt: - 'Leaders from the G7 nations issue a unified statement addressing economic challenges and future cooperation.', url: 'https://example.com/news/politics-g7-33', imageUrl: 'https://picsum.photos/seed/kHeadlineId33/800/600', source: sourcesFixturesData[2], // The New York Times @@ -506,8 +440,6 @@ final headlinesFixturesData = [ id: kHeadlineId34, isBreaking: false, title: "Breakthrough in Alzheimer's Research Offers New Treatment Path", - excerpt: - "Scientists identify a novel therapeutic target for Alzheimer's disease, paving the way for more effective treatments.", url: 'https://example.com/news/science-alzheimers-34', imageUrl: 'https://picsum.photos/seed/kHeadlineId34/800/600', source: sourcesFixturesData[3], // The Guardian @@ -521,8 +453,6 @@ final headlinesFixturesData = [ id: kHeadlineId35, isBreaking: false, title: 'Global Vaccination Campaign Reaches Billions', - excerpt: - 'International efforts to vaccinate the world population against a new virus achieve unprecedented reach.', url: 'https://example.com/news/health-vaccine-35', imageUrl: 'https://picsum.photos/seed/kHeadlineId35/800/600', source: sourcesFixturesData[4], // CNN @@ -536,8 +466,6 @@ final headlinesFixturesData = [ id: kHeadlineId36, isBreaking: false, title: 'Streaming Wars Intensify with New Platform Launches', - excerpt: - 'The competition in the streaming market heats up as several new services enter the fray, offering diverse content.', url: 'https://example.com/news/entertainment-streaming-36', imageUrl: 'https://picsum.photos/seed/kHeadlineId36/800/600', source: sourcesFixturesData[5], // Reuters @@ -551,8 +479,6 @@ final headlinesFixturesData = [ id: kHeadlineId37, isBreaking: false, title: 'Cryptocurrency Market Experiences Major Volatility', - excerpt: - 'Digital currency values fluctuate wildly, prompting investors to reassess their strategies.', url: 'https://example.com/news/business-crypto-37', imageUrl: 'https://picsum.photos/seed/kHeadlineId37/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -566,8 +492,6 @@ final headlinesFixturesData = [ id: kHeadlineId38, isBreaking: false, title: 'Sustainable Tourism Initiatives Gain Momentum', - excerpt: - 'Travel industry shifts towards eco-friendly practices, offering responsible options for environmentally conscious travelers.', url: 'https://example.com/news/travel-sustainable-38', imageUrl: 'https://picsum.photos/seed/kHeadlineId38/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -581,8 +505,6 @@ final headlinesFixturesData = [ id: kHeadlineId39, isBreaking: false, title: 'Food Security Summit Addresses Global Hunger', - excerpt: - 'International conference focuses on strategies to combat food insecurity and ensure equitable access to nutrition worldwide.', url: 'https://example.com/news/food-security-39', imageUrl: 'https://picsum.photos/seed/kHeadlineId39/800/600', source: sourcesFixturesData[8], // The Times of India @@ -596,8 +518,6 @@ final headlinesFixturesData = [ id: kHeadlineId40, isBreaking: false, title: 'Robotics in Education: New Tools for Learning', - excerpt: - 'Schools integrate advanced robotics into their curriculum, providing hands-on learning experiences for students.', url: 'https://example.com/news/education-robotics-40', imageUrl: 'https://picsum.photos/seed/kHeadlineId40/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -611,8 +531,6 @@ final headlinesFixturesData = [ id: kHeadlineId41, isBreaking: false, title: 'AI Ethics Debate Intensifies Among Tech Leaders', - excerpt: - 'Discussions around the ethical implications of artificial intelligence gain traction, with calls for stricter regulations.', url: 'https://example.com/news/tech-ethics-41', imageUrl: 'https://picsum.photos/seed/kHeadlineId41/800/600', source: sourcesFixturesData[0], // TechCrunch @@ -626,8 +544,6 @@ final headlinesFixturesData = [ id: kHeadlineId42, isBreaking: false, title: 'Esports Industry Sees Massive Investment Boom', - excerpt: - 'The competitive gaming sector attracts record investments, solidifying its position as a major entertainment industry.', url: 'https://example.com/news/sports-esports-42', imageUrl: 'https://picsum.photos/seed/kHeadlineId42/800/600', source: sourcesFixturesData[1], // BBC News @@ -641,8 +557,6 @@ final headlinesFixturesData = [ id: kHeadlineId43, isBreaking: false, title: 'International Sanctions Imposed on Rogue State', - excerpt: - "Global powers unite to impose new economic sanctions in response to a nation's controversial actions.", url: 'https://example.com/news/politics-sanctions-43', imageUrl: 'https://picsum.photos/seed/kHeadlineId43/800/600', source: sourcesFixturesData[2], // The New York Times @@ -656,8 +570,6 @@ final headlinesFixturesData = [ id: kHeadlineId44, isBreaking: false, title: 'New Species of Deep-Sea Creature Discovered', - excerpt: - 'Oceanographers exploring the deepest parts of the ocean encounter a never-before-seen marine organism.', url: 'https://example.com/news/science-deepsea-44', imageUrl: 'https://picsum.photos/seed/kHeadlineId44/800/600', source: sourcesFixturesData[3], // The Guardian @@ -671,8 +583,6 @@ final headlinesFixturesData = [ id: kHeadlineId45, isBreaking: false, title: 'Global Health Crisis: New Pandemic Preparedness Plan', - excerpt: - 'International health organizations unveil a comprehensive strategy to prevent and respond to future pandemics.', url: 'https://example.com/news/health-pandemic-45', imageUrl: 'https://picsum.photos/seed/kHeadlineId45/800/600', source: sourcesFixturesData[4], // CNN @@ -686,8 +596,6 @@ final headlinesFixturesData = [ id: kHeadlineId46, isBreaking: false, title: 'Hollywood Strikes Continue: Impact on Film Production', - excerpt: - 'Ongoing labor disputes in Hollywood lead to widespread production halts, affecting upcoming movie and TV releases.', url: 'https://example.com/news/entertainment-strikes-46', imageUrl: 'https://picsum.photos/seed/kHeadlineId46/800/600', source: sourcesFixturesData[5], // Reuters @@ -701,8 +609,6 @@ final headlinesFixturesData = [ id: kHeadlineId47, isBreaking: false, title: 'Emerging Markets Show Strong Economic Resilience', - excerpt: - 'Despite global uncertainties, several emerging economies demonstrate robust growth and attract foreign investment.', url: 'https://example.com/news/business-emerging-47', imageUrl: 'https://picsum.photos/seed/kHeadlineId47/800/600', source: sourcesFixturesData[6], // Al Jazeera English @@ -716,8 +622,6 @@ final headlinesFixturesData = [ id: kHeadlineId48, isBreaking: false, title: 'Adventure Tourism Booms in Remote Regions', - excerpt: - 'Travelers seek unique experiences in off-the-beaten-path destinations, boosting local economies in remote areas.', url: 'https://example.com/news/travel-adventure-48', imageUrl: 'https://picsum.photos/seed/kHeadlineId48/800/600', source: sourcesFixturesData[7], // Xinhua News Agency @@ -731,8 +635,6 @@ final headlinesFixturesData = [ id: kHeadlineId49, isBreaking: false, title: 'The Rise of Sustainable Food Packaging', - excerpt: - 'Innovations in eco-friendly packaging solutions are transforming the food industry, reducing environmental impact.', url: 'https://example.com/news/food-packaging-49', imageUrl: 'https://picsum.photos/seed/kHeadlineId49/800/600', source: sourcesFixturesData[8], // The Times of India @@ -746,8 +648,6 @@ final headlinesFixturesData = [ id: kHeadlineId50, isBreaking: false, title: 'Personalized Learning: Tailoring Education to Individual Needs', - excerpt: - "New educational models focus on customized learning paths, adapting to each student's pace and preferences.", url: 'https://example.com/news/education-personalized-50', imageUrl: 'https://picsum.photos/seed/kHeadlineId50/800/600', source: sourcesFixturesData[9], // Folha de S.Paulo @@ -765,8 +665,6 @@ final headlinesFixturesData = [ id: kHeadlineId51, isBreaking: false, title: 'City Council Approves New Downtown Development Plan', - excerpt: - 'The San Francisco City Council has given the green light to a major redevelopment project aimed at revitalizing the downtown core.', url: 'https://example.com/news/sf-downtown-plan', imageUrl: 'https://picsum.photos/seed/kHeadlineId51/800/600', // San Francisco Chronicle @@ -781,8 +679,6 @@ final headlinesFixturesData = [ id: kHeadlineId52, isBreaking: false, title: 'Tech Startups Flourish in the Bay Area', - excerpt: - 'A new report shows a significant increase in venture capital funding for tech startups in San Francisco.', url: 'https://example.com/news/sf-tech-boom', imageUrl: 'https://picsum.photos/seed/kHeadlineId52/800/600', // San Francisco Chronicle @@ -797,8 +693,6 @@ final headlinesFixturesData = [ id: kHeadlineId53, isBreaking: false, title: 'Golden Gate Bridge Retrofit Project Begins', - excerpt: - 'A multi-year seismic retrofit project for the Golden Gate Bridge has officially commenced.', url: 'https://example.com/news/ggb-retrofit', imageUrl: 'https://picsum.photos/seed/kHeadlineId53/800/600', // San Francisco Chronicle @@ -813,8 +707,6 @@ final headlinesFixturesData = [ id: kHeadlineId54, isBreaking: false, title: 'Local Chef Wins Prestigious Culinary Award', - excerpt: - 'A San Francisco-based chef has been awarded the coveted "Golden Spoon" for culinary innovation.', url: 'https://example.com/news/sf-chef-award', imageUrl: 'https://picsum.photos/seed/kHeadlineId54/800/600', // San Francisco Chronicle @@ -829,8 +721,6 @@ final headlinesFixturesData = [ id: kHeadlineId55, isBreaking: false, title: 'Warriors Secure Victory in Season Opener', - excerpt: - 'The Golden State Warriors started their season with a decisive win at the Chase Center.', url: 'https://example.com/news/warriors-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId55/800/600', // San Francisco Chronicle @@ -845,8 +735,6 @@ final headlinesFixturesData = [ id: kHeadlineId56, isBreaking: false, title: 'Manchester United Announces New Stadium Expansion Plans', - excerpt: - 'The club has revealed ambitious plans to increase the capacity of Old Trafford.', url: 'https://example.com/news/mu-stadium-expansion', imageUrl: 'https://picsum.photos/seed/kHeadlineId56/800/600', // Manchester Evening News @@ -861,8 +749,6 @@ final headlinesFixturesData = [ id: kHeadlineId57, isBreaking: false, title: 'New Tram Line Opens in Greater Manchester', - excerpt: - 'The new Metrolink line is set to improve public transport links across the region.', url: 'https://example.com/news/manchester-tram-line', imageUrl: 'https://picsum.photos/seed/kHeadlineId57/800/600', // Manchester Evening News @@ -877,8 +763,6 @@ final headlinesFixturesData = [ id: kHeadlineId58, isBreaking: false, title: 'Manchester Tech Hub Attracts Global Talent', - excerpt: - 'A report highlights Manchester as a growing hub for technology and innovation in Europe.', url: 'https://example.com/news/manchester-tech-hub', imageUrl: 'https://picsum.photos/seed/kHeadlineId58/800/600', // Manchester Evening News @@ -893,8 +777,6 @@ final headlinesFixturesData = [ id: kHeadlineId59, isBreaking: false, title: 'Coronation Street Filming Causes Local Buzz', - excerpt: - 'Fans gather as the popular soap opera films on location in central Manchester.', url: 'https://example.com/news/corrie-filming', imageUrl: 'https://picsum.photos/seed/kHeadlineId59/800/600', // Manchester Evening News @@ -909,8 +791,6 @@ final headlinesFixturesData = [ id: kHeadlineId60, isBreaking: false, title: 'Council Debates Clean Air Zone Implementation', - excerpt: - 'Greater Manchester leaders are in talks over the future of the controversial Clean Air Zone.', url: 'https://example.com/news/manc-caz-debate', imageUrl: 'https://picsum.photos/seed/kHeadlineId60/800/600', // Manchester Evening News @@ -925,8 +805,6 @@ final headlinesFixturesData = [ id: kHeadlineId61, isBreaking: false, title: 'Sydney Opera House Announces New Season Lineup', - excerpt: - 'A star-studded lineup of performances has been announced for the upcoming season at the iconic venue.', url: 'https://example.com/news/sydney-opera-season', imageUrl: 'https://picsum.photos/seed/kHeadlineId61/800/600', // The Sydney Morning Herald @@ -941,8 +819,6 @@ final headlinesFixturesData = [ id: kHeadlineId62, isBreaking: false, title: 'Housing Prices in Sydney Continue to Climb', - excerpt: - 'The latest real estate data shows a persistent upward trend in property values across the Sydney metropolitan area.', url: 'https://example.com/news/sydney-housing-prices', imageUrl: 'https://picsum.photos/seed/kHeadlineId62/800/600', // The Sydney Morning Herald @@ -957,8 +833,6 @@ final headlinesFixturesData = [ id: kHeadlineId63, isBreaking: false, title: 'NSW Government Unveils New Infrastructure Projects', - excerpt: - 'The New South Wales government has committed billions to new transport and public works projects.', url: 'https://example.com/news/nsw-infrastructure', imageUrl: 'https://picsum.photos/seed/kHeadlineId63/800/600', // The Sydney Morning Herald @@ -973,8 +847,6 @@ final headlinesFixturesData = [ id: kHeadlineId64, isBreaking: false, title: 'Swans Triumph in AFL Derby Match', - excerpt: - 'The Sydney Swans secured a memorable victory over their local rivals in a heated AFL match.', url: 'https://example.com/news/swans-afl-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId64/800/600', // The Sydney Morning Herald @@ -989,8 +861,6 @@ final headlinesFixturesData = [ id: kHeadlineId65, isBreaking: false, title: 'Bondi Beach Erosion Concerns Prompt Action', - excerpt: - 'Local authorities are exploring new measures to combat coastal erosion at the world-famous Bondi Beach.', url: 'https://example.com/news/bondi-erosion', imageUrl: 'https://picsum.photos/seed/kHeadlineId65/800/600', // The Sydney Morning Herald @@ -1005,8 +875,6 @@ final headlinesFixturesData = [ id: kHeadlineId66, isBreaking: false, title: 'Paris Metro Expansion: New Stations Opened', - excerpt: - 'The Grand Paris Express project reaches a new milestone with the opening of several new metro stations.', url: 'https://example.com/news/paris-metro-expansion', imageUrl: 'https://picsum.photos/seed/kHeadlineId66/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1020,8 +888,6 @@ final headlinesFixturesData = [ id: kHeadlineId67, isBreaking: false, title: 'Louvre Museum Unveils New Egyptian Antiquities Wing', - excerpt: - 'A new wing dedicated to ancient Egyptian artifacts has been opened to the public at the Louvre.', url: 'https://example.com/news/louvre-egyptian-wing', imageUrl: 'https://picsum.photos/seed/kHeadlineId67/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1035,8 +901,6 @@ final headlinesFixturesData = [ id: kHeadlineId68, isBreaking: false, title: 'Paris Saint-Germain Secures Ligue 1 Title', - excerpt: - 'PSG has been crowned champions of France after a dominant season.', url: 'https://example.com/news/psg-ligue1-title', imageUrl: 'https://picsum.photos/seed/kHeadlineId68/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1050,8 +914,6 @@ final headlinesFixturesData = [ id: kHeadlineId69, isBreaking: false, title: 'Mayor of Paris Announces New Green Initiatives', - excerpt: - 'The mayor has outlined a plan to increase green spaces and reduce pollution in the city.', url: 'https://example.com/news/paris-green-initiatives', imageUrl: 'https://picsum.photos/seed/kHeadlineId69/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1065,8 +927,6 @@ final headlinesFixturesData = [ id: kHeadlineId70, isBreaking: false, title: 'Paris Fashion Week Highlights New Trends', - excerpt: - "The world's top designers showcased their latest collections during the celebrated Paris Fashion Week.", url: 'https://example.com/news/paris-fashion-week', imageUrl: 'https://picsum.photos/seed/kHeadlineId70/800/600', // Le Parisien source: sourcesFixturesData[13], // Le Parisien @@ -1080,8 +940,6 @@ final headlinesFixturesData = [ id: kHeadlineId71, isBreaking: false, title: 'Toronto Raptors Make Key Trade Ahead of Deadline', - excerpt: - 'The Raptors have made a significant move to bolster their roster for the playoff push.', url: 'https://example.com/news/raptors-trade', imageUrl: 'https://picsum.photos/seed/kHeadlineId71/800/600', // The Toronto Star @@ -1096,8 +954,6 @@ final headlinesFixturesData = [ id: kHeadlineId72, isBreaking: false, title: 'TTC Announces Service Changes for Summer', - excerpt: - 'The Toronto Transit Commission has released its updated schedule and service adjustments for the summer season.', url: 'https://example.com/news/ttc-summer-changes', imageUrl: 'https://picsum.photos/seed/kHeadlineId72/800/600', // The Toronto Star @@ -1112,8 +968,6 @@ final headlinesFixturesData = [ id: kHeadlineId73, isBreaking: false, title: 'Toronto International Film Festival (TIFF) Lineup Revealed', - excerpt: - "Organizers of TIFF have announced a highly anticipated lineup of films for this year's festival.", url: 'https://example.com/news/tiff-lineup', imageUrl: 'https://picsum.photos/seed/kHeadlineId73/800/600', // The Toronto Star @@ -1128,8 +982,6 @@ final headlinesFixturesData = [ id: kHeadlineId74, isBreaking: false, title: 'City of Toronto Grapples with Housing Affordability', - excerpt: - 'City council is debating new policies to address the ongoing housing affordability crisis in Toronto.', url: 'https://example.com/news/toronto-housing-crisis', imageUrl: 'https://picsum.photos/seed/kHeadlineId74/800/600', // The Toronto Star @@ -1144,8 +996,6 @@ final headlinesFixturesData = [ id: kHeadlineId75, isBreaking: false, title: 'New Waterfront Development Project Approved', - excerpt: - "A major new development on Toronto's waterfront has received final approval from the city.", url: 'https://example.com/news/toronto-waterfront-project', imageUrl: 'https://picsum.photos/seed/kHeadlineId75/800/600', // The Toronto Star @@ -1160,8 +1010,6 @@ final headlinesFixturesData = [ id: kHeadlineId76, isBreaking: false, title: 'Berlin Philharmonic Announces New Conductor', - excerpt: - 'The world-renowned orchestra has named a new chief conductor, marking a new era.', url: 'https://example.com/news/berlin-phil-conductor', imageUrl: 'https://picsum.photos/seed/kHeadlineId76/800/600', // Berliner Morgenpost @@ -1176,8 +1024,6 @@ final headlinesFixturesData = [ id: kHeadlineId77, isBreaking: false, title: 'Remnants of Berlin Wall Unearthed During Construction', - excerpt: - 'A previously unknown section of the Berlin Wall has been discovered at a construction site in the city center.', url: 'https://example.com/news/berlin-wall-discovery', imageUrl: 'https://picsum.photos/seed/kHeadlineId77/800/600', // Berliner Morgenpost @@ -1192,8 +1038,6 @@ final headlinesFixturesData = [ id: kHeadlineId78, isBreaking: false, title: 'Hertha BSC Faces Relegation Battle', - excerpt: - 'The Berlin-based football club is in a tough fight to avoid relegation from the Bundesliga.', url: 'https://example.com/news/hertha-bsc-relegation', imageUrl: 'https://picsum.photos/seed/kHeadlineId78/800/600', // Berliner Morgenpost @@ -1208,8 +1052,6 @@ final headlinesFixturesData = [ id: kHeadlineId79, isBreaking: false, title: 'Berlin Senate Approves Rent Control Measures', - excerpt: - 'New measures aimed at controlling rent prices in the German capital have been approved by the Berlin Senate.', url: 'https://example.com/news/berlin-rent-control', imageUrl: 'https://picsum.photos/seed/kHeadlineId79/800/600', // Berliner Morgenpost @@ -1224,8 +1066,6 @@ final headlinesFixturesData = [ id: kHeadlineId80, isBreaking: false, title: 'Brandenburg Airport Reports Record Passenger Numbers', - excerpt: - "Berlin's new airport has reported its busiest month on record, signaling a recovery in air travel.", url: 'https://example.com/news/ber-airport-record', imageUrl: 'https://picsum.photos/seed/kHeadlineId80/800/600', // Berliner Morgenpost @@ -1240,8 +1080,6 @@ final headlinesFixturesData = [ id: kHeadlineId81, isBreaking: false, title: 'Tokyo Government Tackles Aging Population Issues', - excerpt: - 'The Tokyo Metropolitan Government has announced new policies to support its rapidly aging population.', url: 'https://example.com/news/tokyo-aging-population', imageUrl: 'https://picsum.photos/seed/kHeadlineId81/800/600', // The Asahi Shimbun (Tokyo) @@ -1256,8 +1094,6 @@ final headlinesFixturesData = [ id: kHeadlineId82, isBreaking: false, title: 'New Shinkansen Line to Connect Tokyo and Tsuruga', - excerpt: - 'The Hokuriku Shinkansen line has been extended, reducing travel time between Tokyo and the Hokuriku region.', url: 'https://example.com/news/shinkansen-extension', imageUrl: 'https://picsum.photos/seed/kHeadlineId82/800/600', // The Asahi Shimbun (Tokyo) @@ -1272,8 +1108,6 @@ final headlinesFixturesData = [ id: kHeadlineId83, isBreaking: false, title: 'Yomiuri Giants Clinch Central League Pennant', - excerpt: - 'The Tokyo-based Yomiuri Giants have won the Central League pennant in Japanese professional baseball.', url: 'https://example.com/news/giants-win-pennant', imageUrl: 'https://picsum.photos/seed/kHeadlineId83/800/600', // The Asahi Shimbun (Tokyo) @@ -1288,8 +1122,6 @@ final headlinesFixturesData = [ id: kHeadlineId84, isBreaking: false, title: 'Studio Ghibli Announces New Film Project', - excerpt: - 'The celebrated animation studio has announced its first new feature film in several years, exciting fans worldwide.', url: 'https://example.com/news/ghibli-new-film', imageUrl: 'https://picsum.photos/seed/kHeadlineId84/800/600', // The Asahi Shimbun (Tokyo) @@ -1304,8 +1136,6 @@ final headlinesFixturesData = [ id: kHeadlineId85, isBreaking: false, title: "Tokyo's Tsukiji Outer Market Thrives After Relocation", - excerpt: - 'Years after the inner market moved, the Tsukiji Outer Market continues to be a vibrant destination for food lovers.', url: 'https://example.com/news/tsukiji-market-thrives', imageUrl: 'https://picsum.photos/seed/kHeadlineId85/800/600', // The Asahi Shimbun (Tokyo) @@ -1320,8 +1150,6 @@ final headlinesFixturesData = [ id: kHeadlineId86, isBreaking: false, title: 'Mumbai Metro Expands with New Aqua Line', - excerpt: - 'The new Aqua Line of the Mumbai Metro is now operational, aiming to ease traffic congestion in the city.', url: 'https://example.com/news/mumbai-metro-aqua-line', imageUrl: 'https://picsum.photos/seed/kHeadlineId86/800/600', // Hindustan Times (Mumbai) @@ -1336,8 +1164,6 @@ final headlinesFixturesData = [ id: kHeadlineId87, isBreaking: false, title: 'Bollywood Film Shoots Bring Stars to Mumbai Streets', - excerpt: - 'Major Bollywood productions are currently filming across Mumbai, drawing crowds of onlookers.', url: 'https://example.com/news/bollywood-mumbai-shoots', imageUrl: 'https://picsum.photos/seed/kHeadlineId87/800/600', // Hindustan Times (Mumbai) @@ -1352,8 +1178,6 @@ final headlinesFixturesData = [ id: kHeadlineId88, isBreaking: false, title: 'Mumbai Indians Gear Up for IPL Season', - excerpt: - 'The local cricket franchise, Mumbai Indians, has begun its training camp ahead of the new IPL season.', url: 'https://example.com/news/mumbai-indians-ipl', imageUrl: 'https://picsum.photos/seed/kHeadlineId88/800/600', // Hindustan Times (Mumbai) @@ -1368,8 +1192,6 @@ final headlinesFixturesData = [ id: kHeadlineId89, isBreaking: false, title: 'BMC Tackles Monsoon Preparedness in Mumbai', - excerpt: - 'The Brihanmumbai Municipal Corporation (BMC) has outlined its plan for monsoon preparedness to prevent flooding.', url: 'https://example.com/news/bmc-monsoon-prep', imageUrl: 'https://picsum.photos/seed/kHeadlineId89/800/600', // Hindustan Times (Mumbai) @@ -1384,8 +1206,6 @@ final headlinesFixturesData = [ id: kHeadlineId90, isBreaking: false, title: "Mumbai's Financial District Sees New Investments", - excerpt: - 'The Bandra Kurla Complex (BKC) continues to attract major national and international business investments.', url: 'https://example.com/news/mumbai-bkc-investments', imageUrl: 'https://picsum.photos/seed/kHeadlineId90/800/600', // Hindustan Times (Mumbai) @@ -1400,8 +1220,6 @@ final headlinesFixturesData = [ id: kHeadlineId91, isBreaking: false, title: 'Rio Carnival Preparations in Full Swing', - excerpt: - 'Samba schools across Rio de Janeiro are finalizing their preparations for the world-famous Carnival parade.', url: 'https://example.com/news/rio-carnival-prep', imageUrl: 'https://picsum.photos/seed/kHeadlineId91/800/600', // O Globo (Rio de Janeiro) @@ -1416,8 +1234,6 @@ final headlinesFixturesData = [ id: kHeadlineId92, isBreaking: false, title: 'Flamengo Wins Key Match at Maracanã Stadium', - excerpt: - "Rio's beloved football club, Flamengo, celebrated a crucial victory in front of a packed Maracanã stadium.", url: 'https://example.com/news/flamengo-maracana-win', imageUrl: 'https://picsum.photos/seed/kHeadlineId92/800/600', // O Globo (Rio de Janeiro) @@ -1432,8 +1248,6 @@ final headlinesFixturesData = [ id: kHeadlineId93, isBreaking: false, title: 'Security Boosted in Rio Ahead of Major Summit', - excerpt: - 'Security measures are being increased across Rio de Janeiro as the city prepares to host an international summit.', url: 'https://example.com/news/rio-security-boost', imageUrl: 'https://picsum.photos/seed/kHeadlineId93/800/600', // O Globo (Rio de Janeiro) @@ -1448,8 +1262,6 @@ final headlinesFixturesData = [ id: kHeadlineId94, isBreaking: false, title: 'Sugarloaf Mountain Cable Car Undergoes Modernization', - excerpt: - 'The iconic cable car system for Sugarloaf Mountain is being updated with new technology and cabins.', url: 'https://example.com/news/sugarloaf-cable-car-update', imageUrl: 'https://picsum.photos/seed/kHeadlineId94/800/600', // O Globo (Rio de Janeiro) @@ -1464,8 +1276,6 @@ final headlinesFixturesData = [ id: kHeadlineId95, isBreaking: false, title: "Bossa Nova Festival Celebrates Rio's Musical Heritage", - excerpt: - 'A music festival in Ipanema is celebrating the rich history of Bossa Nova, born in the neighborhoods of Rio.', url: 'https://example.com/news/bossa-nova-festival', imageUrl: 'https://picsum.photos/seed/kHeadlineId95/800/600', // O Globo (Rio de Janeiro) @@ -1480,8 +1290,6 @@ final headlinesFixturesData = [ id: kHeadlineId96, isBreaking: false, title: 'Sagrada Família Nears Completion After 140 Years', - excerpt: - "Barcelona's iconic basilica, designed by Gaudí, is entering its final phase of construction.", url: 'https://example.com/news/sagrada-familia-completion', imageUrl: 'https://picsum.photos/seed/kHeadlineId96/800/600', // La Vanguardia (Barcelona) @@ -1496,8 +1304,6 @@ final headlinesFixturesData = [ id: kHeadlineId97, isBreaking: false, title: 'FC Barcelona Presents New Kit at Camp Nou', - excerpt: - 'The football club has unveiled its new home kit for the upcoming La Liga season.', url: 'https://example.com/news/fcb-new-kit', imageUrl: 'https://picsum.photos/seed/kHeadlineId97/800/600', // La Vanguardia (Barcelona) @@ -1512,8 +1318,6 @@ final headlinesFixturesData = [ id: kHeadlineId98, isBreaking: false, title: 'Catalan Government Discusses Tourism Strategy', - excerpt: - 'Leaders in Catalonia are debating a new long-term strategy to manage tourism in Barcelona and the wider region.', url: 'https://example.com/news/catalan-tourism-strategy', imageUrl: 'https://picsum.photos/seed/kHeadlineId98/800/600', // La Vanguardia (Barcelona) @@ -1528,8 +1332,6 @@ final headlinesFixturesData = [ id: kHeadlineId99, isBreaking: false, title: "Barcelona's Tech Scene Booms with New Hub", - excerpt: - 'The 22@ innovation district in Barcelona continues to expand, attracting tech companies from around the globe.', url: 'https://example.com/news/barcelona-tech-hub', imageUrl: 'https://picsum.photos/seed/kHeadlineId99/800/600', // La Vanguardia (Barcelona) @@ -1544,8 +1346,6 @@ final headlinesFixturesData = [ id: kHeadlineId100, isBreaking: false, title: 'La Boqueria Market: A Taste of Barcelona', - excerpt: - 'A feature on the historic La Boqueria market, exploring its culinary delights and cultural significance.', url: 'https://example.com/news/la-boqueria-feature', imageUrl: 'https://picsum.photos/seed/kHeadlineId100/800/600', // La Vanguardia (Barcelona) @@ -1566,8 +1366,6 @@ final headlinesFixturesData = [ id: kHeadlineId101, isBreaking: false, title: 'National Parks See Record Visitor Numbers', - excerpt: - 'A new report from the National Park Service shows a surge in visitors to parks across the USA.', url: 'https://example.com/news/national-parks-visitors', imageUrl: 'https://picsum.photos/seed/kHeadlineId101/800/600', source: sourcesFixturesData[20], // USA Today @@ -1584,8 +1382,6 @@ final headlinesFixturesData = [ id: kHeadlineId106, isBreaking: false, title: 'Canadian Government Announces New Federal Budget', - excerpt: - 'The federal budget includes new spending on healthcare and climate initiatives across Canada.', url: 'https://example.com/news/canada-federal-budget', imageUrl: 'https://picsum.photos/seed/kHeadlineId106/800/600', source: sourcesFixturesData[21], // The Globe and Mail @@ -1604,8 +1400,6 @@ final headlinesFixturesData = [ id: kHeadlineId151, isBreaking: false, title: 'Global Supply Chain Issues Persist', - excerpt: - 'Experts warn that global supply chain disruptions are likely to continue affecting international trade.', url: 'https://example.com/news/global-supply-chain', imageUrl: 'https://picsum.photos/seed/kHeadlineId151/800/600', source: sourcesFixturesData[30], // CNN International @@ -1623,8 +1417,6 @@ final headlinesFixturesData = [ id: kHeadlineId201, isBreaking: false, title: 'World Cup Finals: An Unforgettable Match', - excerpt: - 'The World Cup final delivered a thrilling conclusion to the tournament with a dramatic penalty shootout.', url: 'https://example.com/news/world-cup-final', imageUrl: 'https://picsum.photos/seed/kHeadlineId201/800/600', source: sourcesFixturesData[40], // ESPN @@ -1642,8 +1434,6 @@ final headlinesFixturesData = [ id: kHeadlineId251, isBreaking: false, title: 'The Future of Content and Aggregation', - excerpt: - 'A deep dive into how AI is changing the landscape of content creation and aggregation platforms.', url: 'https://example.com/news/stratechery-content-ai', imageUrl: 'https://picsum.photos/seed/kHeadlineId251/800/600', source: sourcesFixturesData[50], // Stratechery by Ben Thompson @@ -1661,8 +1451,6 @@ final headlinesFixturesData = [ id: kHeadlineId301, isBreaking: false, title: 'President Signs Executive Order on Cybersecurity', - excerpt: - "A new executive order has been signed to strengthen the nation's cybersecurity infrastructure.", url: 'https://example.com/news/wh-cyber-order', imageUrl: 'https://picsum.photos/seed/kHeadlineId301/800/600', source: sourcesFixturesData[60], // WhiteHouse.gov @@ -1680,8 +1468,6 @@ final headlinesFixturesData = [ id: kHeadlineId351, isBreaking: false, title: 'This Week in Tech: A Google News Roundup', - excerpt: - 'Google News aggregates the top technology stories of the week, from AI breakthroughs to new gadget releases.', url: 'https://example.com/news/gnews-tech-roundup', imageUrl: 'https://picsum.photos/seed/kHeadlineId351/800/600', source: sourcesFixturesData[70], // Google News @@ -1699,8 +1485,6 @@ final headlinesFixturesData = [ id: kHeadlineId401, isBreaking: false, title: 'Global Tech Corp Announces Record Quarterly Earnings', - excerpt: - 'Global Tech Corp today announced financial results for its fiscal third quarter, reporting record revenue and profit.', url: 'https://example.com/news/prn-earnings', imageUrl: 'https://picsum.photos/seed/kHeadlineId401/800/600', source: sourcesFixturesData[80], // PR Newswire @@ -1715,8 +1499,6 @@ final headlinesFixturesData = [ id: kHeadlineId411, isBreaking: false, title: 'Phase 3 Trial Results for New Diabetes Drug Published', - excerpt: - 'A new study in The Lancet details the successful phase 3 clinical trial results for a novel type 2 diabetes treatment.', url: 'https://example.com/news/lancet-diabetes-drug', imageUrl: 'https://picsum.photos/seed/kHeadlineId411/800/600', source: sourcesFixturesData[82], // The Lancet From c140f8fe703bc2f781538d0c7fd88ca45b1c5bde Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:18:36 +0100 Subject: [PATCH 28/75] refactor(notifications): remove notification body field - Remove 'body' field from PushNotificationPayload model - Update in-app notification generation to exclude excerpt as body text - Adjust documentation to reflect changes in notification payload structure --- lib/src/fixtures/in_app_notifications.dart | 1 - .../push_notifications/push_notification_payload.dart | 10 ++-------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/src/fixtures/in_app_notifications.dart b/lib/src/fixtures/in_app_notifications.dart index 38e0e063..c682a07b 100644 --- a/lib/src/fixtures/in_app_notifications.dart +++ b/lib/src/fixtures/in_app_notifications.dart @@ -29,7 +29,6 @@ List _generateAdminNotifications() { userId: kAdminUserId, payload: PushNotificationPayload( title: headline.title, - body: headline.excerpt, imageUrl: headline.imageUrl, data: { 'notificationId': notificationId, diff --git a/lib/src/models/push_notifications/push_notification_payload.dart b/lib/src/models/push_notifications/push_notification_payload.dart index f5b201a5..06748244 100644 --- a/lib/src/models/push_notifications/push_notification_payload.dart +++ b/lib/src/models/push_notifications/push_notification_payload.dart @@ -8,7 +8,7 @@ part 'push_notification_payload.g.dart'; /// Represents the generic structure of a push notification message. /// /// This model defines the content of a notification, such as its title and -/// body, and includes a flexible `data` map for custom payloads, typically +/// image, and includes a flexible `data` map for custom payloads, typically /// used for deep-linking or passing additional information to the client app. /// {@endtemplate} @immutable @@ -17,7 +17,6 @@ class PushNotificationPayload extends Equatable { /// {@macro push_notification_payload} const PushNotificationPayload({ required this.title, - required this.body, required this.data, this.imageUrl, }); @@ -29,9 +28,6 @@ class PushNotificationPayload extends Equatable { /// The title of the notification. final String title; - /// The main body text of the notification. - final String body; - /// An optional URL for an image to be displayed in the notification. final String? imageUrl; @@ -46,19 +42,17 @@ class PushNotificationPayload extends Equatable { Map toJson() => _$PushNotificationPayloadToJson(this); @override - List get props => [title, body, imageUrl, data]; + List get props => [title, imageUrl, data]; /// Creates a copy of this [PushNotificationPayload] but with the given fields /// replaced with the new values. PushNotificationPayload copyWith({ String? title, - String? body, String? imageUrl, Map? data, }) { return PushNotificationPayload( title: title ?? this.title, - body: body ?? this.body, imageUrl: imageUrl ?? this.imageUrl, data: data ?? this.data, ); From 5c122fc2ebcc24319d116e9b114ef0f976b3e2a5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:18:52 +0100 Subject: [PATCH 29/75] build(serialization): generate --- .../models/push_notifications/push_notification_payload.g.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/models/push_notifications/push_notification_payload.g.dart b/lib/src/models/push_notifications/push_notification_payload.g.dart index 2356b9b1..660d90fd 100644 --- a/lib/src/models/push_notifications/push_notification_payload.g.dart +++ b/lib/src/models/push_notifications/push_notification_payload.g.dart @@ -11,7 +11,6 @@ PushNotificationPayload _$PushNotificationPayloadFromJson( ) => $checkedCreate('PushNotificationPayload', json, ($checkedConvert) { final val = PushNotificationPayload( title: $checkedConvert('title', (v) => v as String), - body: $checkedConvert('body', (v) => v as String), data: $checkedConvert('data', (v) => v as Map), imageUrl: $checkedConvert('imageUrl', (v) => v as String?), ); @@ -22,7 +21,6 @@ Map _$PushNotificationPayloadToJson( PushNotificationPayload instance, ) => { 'title': instance.title, - 'body': instance.body, 'imageUrl': instance.imageUrl, 'data': instance.data, }; From 079cc758d430b1119cefbf91ca403c514719cac3 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 11:21:27 +0100 Subject: [PATCH 30/75] fix: synced tests --- test/src/models/config/ad_config_test.dart | 4 ---- .../models/config/ad_platform_identifiers_test.dart | 4 ++-- test/src/models/entities/headline_test.dart | 2 -- .../push_notification_payload_test.dart | 11 ++--------- 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/test/src/models/config/ad_config_test.dart b/test/src/models/config/ad_config_test.dart index 1549354a..386e4987 100644 --- a/test/src/models/config/ad_config_test.dart +++ b/test/src/models/config/ad_config_test.dart @@ -13,10 +13,6 @@ void main() { isA>(), ); expect(adConfigFixture.feedAdConfiguration, isA()); - expect( - adConfigFixture.articleAdConfiguration, - isA(), - ); expect( adConfigFixture.navigationAdConfiguration, isA(), diff --git a/test/src/models/config/ad_platform_identifiers_test.dart b/test/src/models/config/ad_platform_identifiers_test.dart index 0fc60c93..29d4b786 100644 --- a/test/src/models/config/ad_platform_identifiers_test.dart +++ b/test/src/models/config/ad_platform_identifiers_test.dart @@ -40,11 +40,11 @@ void main() { test('copyWith returns a new instance with updated values', () { final updatedIdentifiers = admobIdentifiersFixture.copyWith( nativeAdId: 'new_native_id', - inArticleBannerAdId: 'new_banner_id', + interstitialAdId: 'new_banner_id', ); expect(updatedIdentifiers.nativeAdId, 'new_native_id'); - expect(updatedIdentifiers.inArticleBannerAdId, 'new_banner_id'); + expect(updatedIdentifiers.interstitialAdId, 'new_interstitial_id'); expect(updatedIdentifiers, isNot(equals(admobIdentifiersFixture))); }); diff --git a/test/src/models/entities/headline_test.dart b/test/src/models/entities/headline_test.dart index be181d0a..2ada4ff5 100644 --- a/test/src/models/entities/headline_test.dart +++ b/test/src/models/entities/headline_test.dart @@ -32,7 +32,6 @@ void main() { expect(copiedHeadline.id, headlineFixture.id); expect(copiedHeadline.title, updatedTitle); - expect(copiedHeadline.excerpt, headlineFixture.excerpt); expect(copiedHeadline.url, updatedUrl); expect(copiedHeadline.imageUrl, headlineFixture.imageUrl); expect(copiedHeadline.source, headlineFixture.source); @@ -67,7 +66,6 @@ void main() { expect(headlineFixture.props, [ headlineFixture.id, headlineFixture.title, - headlineFixture.excerpt, headlineFixture.url, headlineFixture.imageUrl, headlineFixture.createdAt, diff --git a/test/src/models/push_notifications/push_notification_payload_test.dart b/test/src/models/push_notifications/push_notification_payload_test.dart index 0fbdc489..cb6a65a9 100644 --- a/test/src/models/push_notifications/push_notification_payload_test.dart +++ b/test/src/models/push_notifications/push_notification_payload_test.dart @@ -4,7 +4,6 @@ import 'package:test/test.dart'; void main() { group('PushNotificationPayload', () { const title = 'Breaking News'; - const body = 'This is a test breaking news notification.'; const imageUrl = 'https://example.com/image.jpg'; const data = { 'contentType': 'headline', @@ -13,14 +12,12 @@ void main() { const payload = PushNotificationPayload( title: title, - body: body, imageUrl: imageUrl, data: data, ); final json = { 'title': title, - 'body': body, 'imageUrl': imageUrl, 'data': data, }; @@ -29,7 +26,6 @@ void main() { // Arrange: Create another instance with the same values. const anotherPayload = PushNotificationPayload( title: title, - body: body, imageUrl: imageUrl, data: data, ); @@ -40,7 +36,7 @@ void main() { test('props are correct', () { // Assert: The props list should contain all the fields. - expect(payload.props, equals([title, body, imageUrl, data])); + expect(payload.props, equals([title, imageUrl, data])); }); test('can be created from JSON', () { @@ -62,20 +58,17 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define the updated values. const newTitle = 'Updated News'; - const newBody = 'Updated body content.'; // Act: Create a copy with the updated values. - final copiedPayload = payload.copyWith(title: newTitle, body: newBody); + final copiedPayload = payload.copyWith(title: newTitle); // Assert: The new instance should have the updated values. expect(copiedPayload.title, equals(newTitle)); - expect(copiedPayload.body, equals(newBody)); expect(copiedPayload.imageUrl, equals(imageUrl)); expect(copiedPayload.data, equals(data)); // Assert: The original instance should remain unchanged. expect(payload.title, equals(title)); - expect(payload.body, equals(body)); }); test( From c00d79675f420112cc5e180fa726d1b69fcb7b59 Mon Sep 17 00:00:00 2001 From: fulleni Date: Mon, 24 Nov 2025 12:07:04 +0100 Subject: [PATCH 31/75] test(remote-config): re-add internal nav FeedItemClickBehavior to fixture Add FeedItemClickBehavior.internalNavigation to remote config fixture to test internal navigation functionality in the newsfeed. This change ensures that the fixture data matches the behavior we want to test in the app's newsfeed component. --- lib/src/fixtures/remote_configs.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index c1df4cfb..723dce26 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -132,6 +132,7 @@ final List remoteConfigsFixturesData = [ }, ), }, + feedItemClickBehavior: FeedItemClickBehavior.internalNavigation, pushNotificationConfig: const PushNotificationConfig( enabled: true, primaryProvider: PushNotificationProvider.firebase, @@ -141,6 +142,5 @@ final List remoteConfigsFixturesData = [ PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), - feedItemClickBehavior: FeedItemClickBehavior.internalNavigation, ), ]; From 516b37ba6d49837e8083cb0c23dac79be81fc54b Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:01:39 +0100 Subject: [PATCH 32/75] feat(core): add AppConfig model - Create a new AppConfig class to hold application-level configurations - Include MaintenanceConfig, UpdateConfig, and GeneralAppConfig - Implement JSON serialization and deserialization - Use Equatable for value comparison --- lib/src/models/config/app_config.dart | 41 +++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/src/models/config/app_config.dart diff --git a/lib/src/models/config/app_config.dart b/lib/src/models/config/app_config.dart new file mode 100644 index 00000000..b0402bd6 --- /dev/null +++ b/lib/src/models/config/app_config.dart @@ -0,0 +1,41 @@ +import 'package:core/src/models/config/general_app_config.dart'; +import 'package:core/src/models/config/maintenance_config.dart'; +import 'package:core/src/models/config/update_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'app_config.g.dart'; + +/// {@template app_config} +/// A container for all application-level configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class AppConfig extends Equatable { + /// {@macro app_config} + const AppConfig({ + required this.maintenance, + required this.update, + required this.general, + }); + + /// Creates an [AppConfig] from JSON data. + factory AppConfig.fromJson(Map json) => + _$AppConfigFromJson(json); + + /// Configuration for maintenance mode. + final MaintenanceConfig maintenance; + + /// Configuration for application updates. + final UpdateConfig update; + + /// General application settings. + final GeneralAppConfig general; + + /// Converts this [AppConfig] instance to JSON data. + Map toJson() => _$AppConfigToJson(this); + + @override + List get props => [maintenance, update, general]; +} From 718571013e7ea7feb62820bcacce1e32fadac521 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:01:54 +0100 Subject: [PATCH 33/75] feat(core): add features_config model - Create a new FeaturesConfig class to container all user-facing feature configurations - Include AdConfig, PushNotificationConfig, and FeedConfig - Add JSON serialization and deserialization support --- lib/src/models/config/features_config.dart | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 lib/src/models/config/features_config.dart diff --git a/lib/src/models/config/features_config.dart b/lib/src/models/config/features_config.dart new file mode 100644 index 00000000..29240bb6 --- /dev/null +++ b/lib/src/models/config/features_config.dart @@ -0,0 +1,41 @@ +import 'package:core/src/models/config/ad_config.dart'; +import 'package:core/src/models/config/feed_config.dart'; +import 'package:core/src/models/config/push_notification_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'features_config.g.dart'; + +/// {@template features_config} +/// A container for all user-facing feature configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeaturesConfig extends Equatable { + /// {@macro features_config} + const FeaturesConfig({ + required this.ads, + required this.pushNotifications, + required this.feed, + }); + + /// Creates a [FeaturesConfig] from JSON data. + factory FeaturesConfig.fromJson(Map json) => + _$FeaturesConfigFromJson(json); + + /// Configuration for all ad-related features. + final AdConfig ads; + + /// Configuration for the push notification system. + final PushNotificationConfig pushNotifications; + + /// Configuration for all feed-related features. + final FeedConfig feed; + + /// Converts this [FeaturesConfig] instance to JSON data. + Map toJson() => _$FeaturesConfigToJson(this); + + @override + List get props => [ads, pushNotifications, feed]; +} From e84b31ec747d19a669d4f8fc53ee8a892e9c774f Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:02:14 +0100 Subject: [PATCH 34/75] feat(models): add FeedConfig class - Create a new FeedConfig class to hold all feed-related configurations - Include itemClickBehavior and decorators properties - Implement JSON serialization and deserialization - Extend Equatable for value comparison --- lib/src/models/config/feed_config.dart | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/src/models/config/feed_config.dart diff --git a/lib/src/models/config/feed_config.dart b/lib/src/models/config/feed_config.dart new file mode 100644 index 00000000..758e951c --- /dev/null +++ b/lib/src/models/config/feed_config.dart @@ -0,0 +1,34 @@ +import 'package:core/src/enums/feed_decorator_type.dart'; +import 'package:core/src/enums/feed_item_click_behavior.dart'; +import 'package:core/src/models/config/feed_decorator_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'feed_config.g.dart'; + +/// {@template feed_config} +/// A container for all feed-related configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class FeedConfig extends Equatable { + /// {@macro feed_config} + const FeedConfig({required this.itemClickBehavior, required this.decorators}); + + /// Creates a [FeedConfig] from JSON data. + factory FeedConfig.fromJson(Map json) => + _$FeedConfigFromJson(json); + + /// The default behavior when clicking feed items. + final FeedItemClickBehavior itemClickBehavior; + + /// Defines configuration settings for all feed decorators. + final Map decorators; + + /// Converts this [FeedConfig] instance to JSON data. + Map toJson() => _$FeedConfigToJson(this); + + @override + List get props => [itemClickBehavior, decorators]; +} From 980b0e7dcee0639a7278d938da830c18430d9aa9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:02:40 +0100 Subject: [PATCH 35/75] feat(config): add GeneralAppConfig model - Create a new configuration model for general application settings - Implement Equatable and JsonSerializable for efficient comparison and JSON conversion - Add basic structure with placeholders for future configuration options --- lib/src/models/config/general_app_config.dart | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 lib/src/models/config/general_app_config.dart diff --git a/lib/src/models/config/general_app_config.dart b/lib/src/models/config/general_app_config.dart new file mode 100644 index 00000000..4c3f4f79 --- /dev/null +++ b/lib/src/models/config/general_app_config.dart @@ -0,0 +1,25 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'general_app_config.g.dart'; + +/// {@template general_app_config} +/// Defines general application-level settings not covered by other configs. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class GeneralAppConfig extends Equatable { + /// {@macro general_app_config} + const GeneralAppConfig(); + + /// Creates a [GeneralAppConfig] from JSON data. + factory GeneralAppConfig.fromJson(Map json) => + _$GeneralAppConfigFromJson(json); + + /// Converts this [GeneralAppConfig] instance to JSON data. + Map toJson() => _$GeneralAppConfigToJson(this); + + @override + List get props => []; +} From b5a9759aef02d373fdb3ba4d8decdfcb5a755c1c Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:03:23 +0100 Subject: [PATCH 36/75] feat(config): add MaintenanceConfig model - Defines configuration settings related to application maintenance status - Includes isUnderMaintenance property to indicate if the app is currently under maintenance - Implements JSON serialization and deserialization - Extends Equatable for easy value comparison --- lib/src/models/config/maintenance_config.dart | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 lib/src/models/config/maintenance_config.dart diff --git a/lib/src/models/config/maintenance_config.dart b/lib/src/models/config/maintenance_config.dart new file mode 100644 index 00000000..efaa1f1a --- /dev/null +++ b/lib/src/models/config/maintenance_config.dart @@ -0,0 +1,28 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'maintenance_config.g.dart'; + +/// {@template maintenance_config} +/// Defines configuration settings related to application maintenance status. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class MaintenanceConfig extends Equatable { + /// {@macro maintenance_config} + const MaintenanceConfig({required this.isUnderMaintenance}); + + /// Creates a [MaintenanceConfig] from JSON data. + factory MaintenanceConfig.fromJson(Map json) => + _$MaintenanceConfigFromJson(json); + + /// Indicates if the app is currently under maintenance. + final bool isUnderMaintenance; + + /// Converts this [MaintenanceConfig] instance to JSON data. + Map toJson() => _$MaintenanceConfigToJson(this); + + @override + List get props => [isUnderMaintenance]; +} From 3e086cb4712422b7c39ce667392a1c4ca7942d19 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:03:37 +0100 Subject: [PATCH 37/75] refactor(core): migrate RemoteConfig to use AppConfig, FeaturesConfig, UserConfig - Remove deprecated configuration fields from RemoteConfig - Add new AppConfig, FeaturesConfig, and UserConfig fields - Update imports and class properties accordingly - Adjust copyWith --- lib/src/models/config/remote_config.dart | 72 +++++++----------------- 1 file changed, 21 insertions(+), 51 deletions(-) diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 152bdaa3..235029f7 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,4 +1,7 @@ import 'package:core/core.dart'; +import 'package:core/src/models/config/app_config.dart'; +import 'package:core/src/models/config/features_config.dart'; +import 'package:core/src/models/config/user_config.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -7,10 +10,8 @@ part 'remote_config.g.dart'; /// Represents the overall application configuration. /// -/// This model serves as a central container for various configuration -/// settings that can be fetched from a remote source. It includes settings -/// for user preference limits, ad display, and feed decorators. -/// +/// This model serves as a central container for various configuration settings +/// that can be fetched from a remote source. /// There should typically be only one instance of this configuration, /// identified by a fixed ID (e.g., 'app_config'). @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) @@ -18,14 +19,11 @@ class RemoteConfig extends Equatable { /// Creates a new [RemoteConfig] instance. const RemoteConfig({ required this.id, - required this.appStatus, - required this.adConfig, - required this.feedDecoratorConfig, - required this.userPreferenceConfig, - required this.pushNotificationConfig, - required this.feedItemClickBehavior, required this.createdAt, required this.updatedAt, + required this.app, + required this.features, + required this.user, }); /// Factory method to create an [RemoteConfig] instance from a JSON map. @@ -35,24 +33,14 @@ class RemoteConfig extends Equatable { /// The unique identifier for this configuration. final String id; - /// Defines configuration settings related to user preference limits. - final UserPreferenceConfig userPreferenceConfig; - - /// Defines configuration settings related to ad display. - final AdConfig adConfig; - - /// Defines configuration settings for all feed decorators. - final Map feedDecoratorConfig; - - /// Defines configuration settings related to the overall application status - /// (maintenance, updates). - final AppStatus appStatus; + /// Configuration for application-level settings (maintenance, updates, etc.). + final AppConfig app; - /// Defines the global configuration for the push notification system. - final PushNotificationConfig pushNotificationConfig; + /// Configuration for all user-facing features (ads, feed, notifications). + final FeaturesConfig features; - /// The default behavior when clicking feed items. - final FeedItemClickBehavior feedItemClickBehavior; + /// Configuration for user-specific settings (role-based limits, etc.). + final UserConfig user; /// The creation timestamp of the remote config. @JsonKey(fromJson: dateTimeFromJson, toJson: dateTimeToJson) @@ -68,42 +56,24 @@ class RemoteConfig extends Equatable { /// Creates a new [RemoteConfig] instance with specified changes. RemoteConfig copyWith({ String? id, - UserPreferenceConfig? userPreferenceConfig, - AdConfig? adConfig, - Map? feedDecoratorConfig, - AppStatus? appStatus, - PushNotificationConfig? pushNotificationConfig, - FeedItemClickBehavior? feedItemClickBehavior, DateTime? createdAt, DateTime? updatedAt, + AppConfig? app, + FeaturesConfig? features, + UserConfig? user, }) { return RemoteConfig( id: id ?? this.id, - userPreferenceConfig: userPreferenceConfig ?? this.userPreferenceConfig, - adConfig: adConfig ?? this.adConfig, - feedDecoratorConfig: feedDecoratorConfig ?? this.feedDecoratorConfig, - appStatus: appStatus ?? this.appStatus, - pushNotificationConfig: - pushNotificationConfig ?? this.pushNotificationConfig, - feedItemClickBehavior: - feedItemClickBehavior ?? this.feedItemClickBehavior, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt, + app: app ?? this.app, + features: features ?? this.features, + user: user ?? this.user, ); } @override - List get props => [ - id, - userPreferenceConfig, - adConfig, - feedDecoratorConfig, - appStatus, - pushNotificationConfig, - feedItemClickBehavior, - createdAt, - updatedAt, - ]; + List get props => [id, createdAt, updatedAt, app, features, user]; @override bool get stringify => true; From 51edb66930e00ce04324abdf7572fce5ad0faaef Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:05:51 +0100 Subject: [PATCH 38/75] feat(core): add UserConfig model - Create a new UserConfig class to hold user-related configurations - Include UserLimitsConfig as a property for role-based quantitative limits - Implement JSON serialization and deserialization - Extend Equatable for easy value comparison --- lib/src/models/config/user_config.dart | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 lib/src/models/config/user_config.dart diff --git a/lib/src/models/config/user_config.dart b/lib/src/models/config/user_config.dart new file mode 100644 index 00000000..2cce6e23 --- /dev/null +++ b/lib/src/models/config/user_config.dart @@ -0,0 +1,29 @@ +import 'package:core/src/models/config/user_limits_config.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'user_config.g.dart'; + +/// {@template user_config} +/// A container for all user-related configurations. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class UserConfig extends Equatable { + /// {@macro user_config} + const UserConfig({required this.limits}); + + /// Creates a [UserConfig] from JSON data. + factory UserConfig.fromJson(Map json) => + _$UserConfigFromJson(json); + + /// Role-based quantitative limits for user actions. + final UserLimitsConfig limits; + + /// Converts this [UserConfig] instance to JSON data. + Map toJson() => _$UserConfigToJson(this); + + @override + List get props => [limits]; +} From 6a821ce9cb56f82501db4408ba3c1cc1a458c833 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:06:01 +0100 Subject: [PATCH 39/75] feat(models): add UpdateConfig model - Defines configuration settings related to application updates - Includes properties for latest app version, update URLs, and version enforcement - Implements JSON serialization and equality checks --- lib/src/models/config/update_config.dart | 47 ++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 lib/src/models/config/update_config.dart diff --git a/lib/src/models/config/update_config.dart b/lib/src/models/config/update_config.dart new file mode 100644 index 00000000..38d40f81 --- /dev/null +++ b/lib/src/models/config/update_config.dart @@ -0,0 +1,47 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'update_config.g.dart'; + +/// {@template update_config} +/// Defines configuration settings related to application updates. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class UpdateConfig extends Equatable { + /// {@macro update_config} + const UpdateConfig({ + required this.latestAppVersion, + required this.isLatestVersionOnly, + required this.iosUpdateUrl, + required this.androidUpdateUrl, + }); + + /// Creates an [UpdateConfig] from JSON data. + factory UpdateConfig.fromJson(Map json) => + _$UpdateConfigFromJson(json); + + /// The latest available app version. + final String latestAppVersion; + + /// Indicates if only the latest version of the app is allowed to run. + final bool isLatestVersionOnly; + + /// URL for iOS app updates. + final String iosUpdateUrl; + + /// URL for Android app updates. + final String androidUpdateUrl; + + /// Converts this [UpdateConfig] instance to JSON data. + Map toJson() => _$UpdateConfigToJson(this); + + @override + List get props => [ + latestAppVersion, + isLatestVersionOnly, + iosUpdateUrl, + androidUpdateUrl, + ]; +} From 9fd32f6a9147d80720bca0e4844bb4bc2615f20f Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:06:54 +0100 Subject: [PATCH 40/75] feat(core): add UserLimitsConfig model - Define a new model for role-based user action and preference limits - Include limits for followed items, saved headlines, and filters - Use a map-based structure for scalable and maintainable configuration - Implement JSON serialization and deserialization --- lib/src/models/config/user_limits_config.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/src/models/config/user_limits_config.dart diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart new file mode 100644 index 00000000..85c97764 --- /dev/null +++ b/lib/src/models/config/user_limits_config.dart @@ -0,0 +1,58 @@ +import 'package:core/src/enums/app_user_role.dart'; +import 'package:core/src/models/config/saved_filter_limits.dart'; +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'user_limits_config.g.dart'; + +/// {@template user_limits_config} +/// Defines role-based quantitative limits for user actions and preferences. +/// +/// This model uses a map-based structure where the key is the [AppUserRole] +/// and the value is the specific limit for that role, ensuring a scalable +/// and maintainable configuration for user-related constraints. +/// {@endtemplate} +@immutable +@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) +class UserLimitsConfig extends Equatable { + /// {@macro user_limits_config} + const UserLimitsConfig({ + required this.followedItems, + required this.savedHeadlines, + required this.savedHeadlineFilters, + required this.savedSourceFilters, + }); + + /// Creates a [UserLimitsConfig] from JSON data. + factory UserLimitsConfig.fromJson(Map json) => + _$UserLimitsConfigFromJson(json); + + /// Role-based limits for the number of followed items (topics, sources, + /// countries). The limit applies to each category individually. + final Map followedItems; + + /// Role-based limits for the number of saved headlines. + final Map savedHeadlines; + + /// Role-based limits for saved headline filters, using the + /// [SavedFilterLimits] model to define total, pinned, and notification + /// subscription counts. This map defines the limits per user role. + final Map savedHeadlineFilters; + + /// Role-based limits for saved source filters, using the + /// [SavedFilterLimits] model to define total and pinned counts. This map + /// defines the limits per user role. + final Map savedSourceFilters; + + /// Converts this [UserLimitsConfig] instance to JSON data. + Map toJson() => _$UserLimitsConfigToJson(this); + + @override + List get props => [ + followedItems, + savedHeadlines, + savedHeadlineFilters, + savedSourceFilters, + ]; +} From 2757af6a6d42f5d4a2c7d4c8dcedbebce57c486a Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:08:30 +0100 Subject: [PATCH 41/75] refactor(remote_configs): update fixture to match new RemoteConfig structure - Replace appStatus with AppConfig containing maintenance and update info - Combine userPreferenceConfig into UserConfig with limits - Move adConfig into FeaturesConfig along with feed and push notification settings - Remove feedDecoratorConfig, as it's now part of FeedConfig - Simplify pushNotificationConfig to PushNotificationConfig --- lib/src/fixtures/remote_configs.dart | 250 ++++++++++++++------------- 1 file changed, 129 insertions(+), 121 deletions(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 723dce26..bcd49819 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -1,146 +1,154 @@ import 'package:core/core.dart'; -/// A list of initial remote config data to be loaded into the in-memory -/// remote config repository. -final List remoteConfigsFixturesData = [ +const kRemoteConfigId = 'app_config'; + +final remoteConfigsFixturesData = [ RemoteConfig( id: kRemoteConfigId, createdAt: DateTime.now(), updatedAt: DateTime.now(), - appStatus: const AppStatus( - isUnderMaintenance: false, - latestAppVersion: '1.1.0', - isLatestVersionOnly: false, - iosUpdateUrl: 'https://apps.apple.com/app/example/id1234567890', - androidUpdateUrl: - 'https://play.google.com/store/apps/details?id=com.example.app', - ), - userPreferenceConfig: const UserPreferenceConfig( - // Role-based limits for followed items (topics, sources, countries). - followedItemsLimit: { - AppUserRole.guestUser: 5, - AppUserRole.standardUser: 15, - AppUserRole.premiumUser: 30, - }, - // Role-based limits for the number of saved headlines. - savedHeadlinesLimit: { - AppUserRole.guestUser: 10, - AppUserRole.standardUser: 30, - AppUserRole.premiumUser: 100, - }, - // Role-based limits for saved headline filters. - savedHeadlineFiltersLimit: { - AppUserRole.guestUser: SavedFilterLimits( - total: 3, - pinned: 3, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 1, - PushNotificationSubscriptionDeliveryType.dailyDigest: 0, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 0, - }, - ), - AppUserRole.standardUser: SavedFilterLimits( - total: 10, - pinned: 5, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 3, - PushNotificationSubscriptionDeliveryType.dailyDigest: 2, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 2, - }, - ), - AppUserRole.premiumUser: SavedFilterLimits( - total: 25, - pinned: 10, - notificationSubscriptions: { - PushNotificationSubscriptionDeliveryType.breakingOnly: 10, - PushNotificationSubscriptionDeliveryType.dailyDigest: 10, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: 10, - }, - ), - }, - // Role-based limits for saved source filters. - savedSourceFiltersLimit: { - AppUserRole.guestUser: SavedFilterLimits(total: 3, pinned: 3), - AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), - AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), - }, + app: const AppConfig( + maintenance: MaintenanceConfig(isUnderMaintenance: false), + update: UpdateConfig( + latestAppVersion: '1.1.0', + isLatestVersionOnly: false, + iosUpdateUrl: 'https://apps.apple.com/app/example/id1234567890', + androidUpdateUrl: + 'https://play.google.com/store/apps/details?id=com.example.app', + ), + general: GeneralAppConfig(), ), - adConfig: const AdConfig( - enabled: true, - primaryAdPlatform: AdPlatformType.demo, - platformAdIdentifiers: { - AdPlatformType.admob: AdPlatformIdentifiers( - nativeAdId: 'ca-app-pub-3940256099942544/2247696110', - bannerAdId: 'ca-app-pub-3940256099942544/6300978111', - interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', - ), - AdPlatformType.demo: AdPlatformIdentifiers( - nativeAdId: '_', - bannerAdId: '_', - interstitialAdId: '_', - ), - }, - feedAdConfiguration: FeedAdConfiguration( - enabled: true, - adType: AdType.native, - visibleTo: { - AppUserRole.guestUser: FeedAdFrequencyConfig( - adFrequency: 5, - adPlacementInterval: 3, + user: const UserConfig( + limits: UserLimitsConfig( + followedItems: { + AppUserRole.guestUser: 5, + AppUserRole.standardUser: 15, + AppUserRole.premiumUser: 30, + }, + savedHeadlines: { + AppUserRole.guestUser: 10, + AppUserRole.standardUser: 30, + AppUserRole.premiumUser: 100, + }, + savedHeadlineFilters: { + AppUserRole.guestUser: SavedFilterLimits( + total: 3, + pinned: 3, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 1, + PushNotificationSubscriptionDeliveryType.dailyDigest: 0, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 0, + }, + ), + AppUserRole.standardUser: SavedFilterLimits( + total: 10, + pinned: 5, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 3, + PushNotificationSubscriptionDeliveryType.dailyDigest: 2, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 2, + }, ), - AppUserRole.standardUser: FeedAdFrequencyConfig( - adFrequency: 10, - adPlacementInterval: 5, + AppUserRole.premiumUser: SavedFilterLimits( + total: 25, + pinned: 10, + notificationSubscriptions: { + PushNotificationSubscriptionDeliveryType.breakingOnly: 10, + PushNotificationSubscriptionDeliveryType.dailyDigest: 10, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 10, + }, ), }, + savedSourceFilters: { + AppUserRole.guestUser: SavedFilterLimits(total: 3, pinned: 3), + AppUserRole.standardUser: SavedFilterLimits(total: 10, pinned: 5), + AppUserRole.premiumUser: SavedFilterLimits(total: 25, pinned: 10), + }, ), - navigationAdConfiguration: NavigationAdConfiguration( + ), + features: const FeaturesConfig( + ads: AdConfig( enabled: true, - visibleTo: { - AppUserRole.guestUser: NavigationAdFrequencyConfig( - internalNavigationsBeforeShowingInterstitialAd: 5, - externalNavigationsBeforeShowingInterstitialAd: 5, + primaryAdPlatform: AdPlatformType.demo, + platformAdIdentifiers: { + AdPlatformType.admob: AdPlatformIdentifiers( + nativeAdId: 'ca-app-pub-3940256099942544/2247696110', + bannerAdId: 'ca-app-pub-3940256099942544/6300978111', + interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', ), - AppUserRole.standardUser: NavigationAdFrequencyConfig( - internalNavigationsBeforeShowingInterstitialAd: 8, - externalNavigationsBeforeShowingInterstitialAd: 8, + AdPlatformType.demo: AdPlatformIdentifiers( + nativeAdId: '_', + bannerAdId: '_', + interstitialAdId: '_', ), }, + feedAdConfiguration: FeedAdConfiguration( + enabled: true, + adType: AdType.native, + visibleTo: { + AppUserRole.guestUser: FeedAdFrequencyConfig( + adFrequency: 5, + adPlacementInterval: 3, + ), + AppUserRole.standardUser: FeedAdFrequencyConfig( + adFrequency: 10, + adPlacementInterval: 5, + ), + }, + ), + navigationAdConfiguration: NavigationAdConfiguration( + enabled: true, + visibleTo: { + AppUserRole.guestUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 5, + externalNavigationsBeforeShowingInterstitialAd: 5, + ), + AppUserRole.standardUser: NavigationAdFrequencyConfig( + internalNavigationsBeforeShowingInterstitialAd: 8, + externalNavigationsBeforeShowingInterstitialAd: 8, + ), + }, + ), ), - ), - feedDecoratorConfig: const { - FeedDecoratorType.rateApp: FeedDecoratorConfig( - category: FeedDecoratorCategory.callToAction, - enabled: true, - visibleTo: { - AppUserRole.guestUser: FeedDecoratorRoleConfig(daysBetweenViews: 14), - AppUserRole.standardUser: FeedDecoratorRoleConfig( - daysBetweenViews: 30, + feed: FeedConfig( + itemClickBehavior: FeedItemClickBehavior.internalNavigation, + decorators: { + FeedDecoratorType.rateApp: FeedDecoratorConfig( + category: FeedDecoratorCategory.callToAction, + enabled: true, + visibleTo: { + AppUserRole.guestUser: + FeedDecoratorRoleConfig(daysBetweenViews: 14), + AppUserRole.standardUser: FeedDecoratorRoleConfig( + daysBetweenViews: 30, + ), + AppUserRole.premiumUser: + FeedDecoratorRoleConfig(daysBetweenViews: 0), + }, + ), + FeedDecoratorType.suggestedTopics: FeedDecoratorConfig( + category: FeedDecoratorCategory.contentCollection, + enabled: true, + itemsToDisplay: 5, + visibleTo: { + AppUserRole.guestUser: + FeedDecoratorRoleConfig(daysBetweenViews: 7), + AppUserRole.standardUser: FeedDecoratorRoleConfig( + daysBetweenViews: 14, + ), + }, ), - AppUserRole.premiumUser: FeedDecoratorRoleConfig(daysBetweenViews: 0), }, ), - FeedDecoratorType.suggestedTopics: FeedDecoratorConfig( - category: FeedDecoratorCategory.contentCollection, + pushNotifications: PushNotificationConfig( enabled: true, - itemsToDisplay: 5, - visibleTo: { - AppUserRole.guestUser: FeedDecoratorRoleConfig(daysBetweenViews: 7), - AppUserRole.standardUser: FeedDecoratorRoleConfig( - daysBetweenViews: 14, - ), + primaryProvider: PushNotificationProvider.firebase, + deliveryConfigs: { + PushNotificationSubscriptionDeliveryType.breakingOnly: true, + PushNotificationSubscriptionDeliveryType.dailyDigest: true, + PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, }, ), - }, - feedItemClickBehavior: FeedItemClickBehavior.internalNavigation, - pushNotificationConfig: const PushNotificationConfig( - enabled: true, - primaryProvider: PushNotificationProvider.firebase, - deliveryConfigs: { - PushNotificationSubscriptionDeliveryType.breakingOnly: true, - PushNotificationSubscriptionDeliveryType.dailyDigest: true, - PushNotificationSubscriptionDeliveryType.weeklyRoundup: true, - }, ), ), ]; From a2093fe398018a965099376b65b47536d1624564 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:08:42 +0100 Subject: [PATCH 42/75] build(serialziation): generate --- lib/src/models/config/app_config.g.dart | 32 ++++++++ lib/src/models/config/config.dart | 9 ++- lib/src/models/config/features_config.g.dart | 33 ++++++++ lib/src/models/config/feed_config.g.dart | 51 ++++++++++++ .../models/config/general_app_config.g.dart | 16 ++++ .../models/config/maintenance_config.g.dart | 21 +++++ lib/src/models/config/remote_config.g.dart | 68 ++++------------ lib/src/models/config/update_config.g.dart | 30 +++++++ lib/src/models/config/user_config.g.dart | 21 +++++ ...onfig.g.dart => user_limits_config.g.dart} | 55 +++++++------ .../models/config/user_preference_config.dart | 81 ------------------- ...test.dart => user_limits_config_test.dart} | 42 +++++----- 12 files changed, 275 insertions(+), 184 deletions(-) create mode 100644 lib/src/models/config/app_config.g.dart create mode 100644 lib/src/models/config/features_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/update_config.g.dart create mode 100644 lib/src/models/config/user_config.g.dart rename lib/src/models/config/{user_preference_config.g.dart => user_limits_config.g.dart} (51%) delete mode 100644 lib/src/models/config/user_preference_config.dart rename test/src/models/config/{user_preference_config_test.dart => user_limits_config_test.dart} (56%) 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 00000000..a49f838f --- /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/config.dart b/lib/src/models/config/config.dart index de548cfc..67291014 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,13 +1,20 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; +export 'app_config.dart'; export 'app_status.dart'; +export 'features_config.dart'; export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; +export 'feed_config.dart'; export 'feed_decorator_config.dart'; export 'feed_decorator_role_config.dart'; +export 'general_app_config.dart'; +export 'maintenance_config.dart'; export 'navigation_ad_configuration.dart'; export 'navigation_ad_frequency_config.dart'; export 'push_notification_config.dart'; export 'remote_config.dart'; export 'saved_filter_limits.dart'; -export 'user_preference_config.dart'; +export 'update_config.dart'; +export 'user_config.dart'; +export 'user_limits_config.dart'; diff --git a/lib/src/models/config/features_config.g.dart b/lib/src/models/config/features_config.g.dart new file mode 100644 index 00000000..d6b1a61f --- /dev/null +++ b/lib/src/models/config/features_config.g.dart @@ -0,0 +1,33 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'features_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +FeaturesConfig _$FeaturesConfigFromJson(Map json) => + $checkedCreate('FeaturesConfig', json, ($checkedConvert) { + final val = FeaturesConfig( + ads: $checkedConvert( + 'ads', + (v) => AdConfig.fromJson(v as Map), + ), + pushNotifications: $checkedConvert( + 'pushNotifications', + (v) => PushNotificationConfig.fromJson(v as Map), + ), + feed: $checkedConvert( + 'feed', + (v) => FeedConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$FeaturesConfigToJson(FeaturesConfig instance) => + { + 'ads': instance.ads.toJson(), + 'pushNotifications': instance.pushNotifications.toJson(), + 'feed': instance.feed.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 00000000..d83ac514 --- /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 00000000..1c248342 --- /dev/null +++ b/lib/src/models/config/general_app_config.g.dart @@ -0,0 +1,16 @@ +// 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(); + return val; + }); + +Map _$GeneralAppConfigToJson(GeneralAppConfig instance) => + {}; 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 00000000..fe647457 --- /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/remote_config.g.dart b/lib/src/models/config/remote_config.g.dart index fa91c367..d48a92fe 100644 --- a/lib/src/models/config/remote_config.g.dart +++ b/lib/src/models/config/remote_config.g.dart @@ -10,35 +10,6 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => $checkedCreate('RemoteConfig', json, ($checkedConvert) { final val = RemoteConfig( id: $checkedConvert('id', (v) => v as String), - appStatus: $checkedConvert( - 'appStatus', - (v) => AppStatus.fromJson(v as Map), - ), - adConfig: $checkedConvert( - 'adConfig', - (v) => AdConfig.fromJson(v as Map), - ), - feedDecoratorConfig: $checkedConvert( - 'feedDecoratorConfig', - (v) => (v as Map).map( - (k, e) => MapEntry( - $enumDecode(_$FeedDecoratorTypeEnumMap, k), - FeedDecoratorConfig.fromJson(e as Map), - ), - ), - ), - userPreferenceConfig: $checkedConvert( - 'userPreferenceConfig', - (v) => UserPreferenceConfig.fromJson(v as Map), - ), - pushNotificationConfig: $checkedConvert( - 'pushNotificationConfig', - (v) => PushNotificationConfig.fromJson(v as Map), - ), - feedItemClickBehavior: $checkedConvert( - 'feedItemClickBehavior', - (v) => $enumDecode(_$FeedItemClickBehaviorEnumMap, v), - ), createdAt: $checkedConvert( 'createdAt', (v) => dateTimeFromJson(v as String?), @@ -47,6 +18,18 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => 'updatedAt', (v) => dateTimeFromJson(v as String?), ), + app: $checkedConvert( + 'app', + (v) => AppConfig.fromJson(v as Map), + ), + features: $checkedConvert( + 'features', + (v) => FeaturesConfig.fromJson(v as Map), + ), + user: $checkedConvert( + 'user', + (v) => UserConfig.fromJson(v as Map), + ), ); return val; }); @@ -54,30 +37,9 @@ RemoteConfig _$RemoteConfigFromJson(Map json) => Map _$RemoteConfigToJson(RemoteConfig instance) => { 'id': instance.id, - 'userPreferenceConfig': instance.userPreferenceConfig.toJson(), - 'adConfig': instance.adConfig.toJson(), - 'feedDecoratorConfig': instance.feedDecoratorConfig.map( - (k, e) => MapEntry(_$FeedDecoratorTypeEnumMap[k]!, e.toJson()), - ), - 'appStatus': instance.appStatus.toJson(), - 'pushNotificationConfig': instance.pushNotificationConfig.toJson(), - 'feedItemClickBehavior': - _$FeedItemClickBehaviorEnumMap[instance.feedItemClickBehavior]!, + 'app': instance.app.toJson(), + 'features': instance.features.toJson(), + 'user': instance.user.toJson(), 'createdAt': dateTimeToJson(instance.createdAt), 'updatedAt': dateTimeToJson(instance.updatedAt), }; - -const _$FeedDecoratorTypeEnumMap = { - FeedDecoratorType.linkAccount: 'linkAccount', - FeedDecoratorType.upgrade: 'upgrade', - FeedDecoratorType.rateApp: 'rateApp', - FeedDecoratorType.enableNotifications: 'enableNotifications', - FeedDecoratorType.suggestedTopics: 'suggestedTopics', - FeedDecoratorType.suggestedSources: 'suggestedSources', -}; - -const _$FeedItemClickBehaviorEnumMap = { - FeedItemClickBehavior.defaultBehavior: 'default', - FeedItemClickBehavior.internalNavigation: 'internalNavigation', - FeedItemClickBehavior.externalNavigation: 'externalNavigation', -}; 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 00000000..66cee4a8 --- /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/config/user_config.g.dart b/lib/src/models/config/user_config.g.dart new file mode 100644 index 00000000..9ce87dec --- /dev/null +++ b/lib/src/models/config/user_config.g.dart @@ -0,0 +1,21 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_config.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +UserConfig _$UserConfigFromJson(Map json) => + $checkedCreate('UserConfig', json, ($checkedConvert) { + final val = UserConfig( + limits: $checkedConvert( + 'limits', + (v) => UserLimitsConfig.fromJson(v as Map), + ), + ); + return val; + }); + +Map _$UserConfigToJson(UserConfig instance) => + {'limits': instance.limits.toJson()}; diff --git a/lib/src/models/config/user_preference_config.g.dart b/lib/src/models/config/user_limits_config.g.dart similarity index 51% rename from lib/src/models/config/user_preference_config.g.dart rename to lib/src/models/config/user_limits_config.g.dart index c0ebcd4e..4f398182 100644 --- a/lib/src/models/config/user_preference_config.g.dart +++ b/lib/src/models/config/user_limits_config.g.dart @@ -1,31 +1,31 @@ // GENERATED CODE - DO NOT MODIFY BY HAND -part of 'user_preference_config.dart'; +part of 'user_limits_config.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** -UserPreferenceConfig _$UserPreferenceConfigFromJson( +UserLimitsConfig _$UserLimitsConfigFromJson( Map json, -) => $checkedCreate('UserPreferenceConfig', json, ($checkedConvert) { - final val = UserPreferenceConfig( - followedItemsLimit: $checkedConvert( - 'followedItemsLimit', +) => $checkedCreate('UserLimitsConfig', json, ($checkedConvert) { + final val = UserLimitsConfig( + followedItems: $checkedConvert( + 'followedItems', (v) => (v as Map).map( (k, e) => MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), ), ), - savedHeadlinesLimit: $checkedConvert( - 'savedHeadlinesLimit', + savedHeadlines: $checkedConvert( + 'savedHeadlines', (v) => (v as Map).map( (k, e) => MapEntry($enumDecode(_$AppUserRoleEnumMap, k), (e as num).toInt()), ), ), - savedHeadlineFiltersLimit: $checkedConvert( - 'savedHeadlineFiltersLimit', + savedHeadlineFilters: $checkedConvert( + 'savedHeadlineFilters', (v) => (v as Map).map( (k, e) => MapEntry( $enumDecode(_$AppUserRoleEnumMap, k), @@ -33,8 +33,8 @@ UserPreferenceConfig _$UserPreferenceConfigFromJson( ), ), ), - savedSourceFiltersLimit: $checkedConvert( - 'savedSourceFiltersLimit', + savedSourceFilters: $checkedConvert( + 'savedSourceFilters', (v) => (v as Map).map( (k, e) => MapEntry( $enumDecode(_$AppUserRoleEnumMap, k), @@ -46,22 +46,21 @@ UserPreferenceConfig _$UserPreferenceConfigFromJson( return val; }); -Map _$UserPreferenceConfigToJson( - UserPreferenceConfig instance, -) => { - 'followedItemsLimit': instance.followedItemsLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), - ), - 'savedHeadlinesLimit': instance.savedHeadlinesLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), - ), - 'savedHeadlineFiltersLimit': instance.savedHeadlineFiltersLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), - ), - 'savedSourceFiltersLimit': instance.savedSourceFiltersLimit.map( - (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), - ), -}; +Map _$UserLimitsConfigToJson(UserLimitsConfig instance) => + { + 'followedItems': instance.followedItems.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), + 'savedHeadlines': instance.savedHeadlines.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e), + ), + 'savedHeadlineFilters': instance.savedHeadlineFilters.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), + 'savedSourceFilters': instance.savedSourceFilters.map( + (k, e) => MapEntry(_$AppUserRoleEnumMap[k]!, e.toJson()), + ), + }; const _$AppUserRoleEnumMap = { AppUserRole.premiumUser: 'premiumUser', diff --git a/lib/src/models/config/user_preference_config.dart b/lib/src/models/config/user_preference_config.dart deleted file mode 100644 index 73d35dcf..00000000 --- a/lib/src/models/config/user_preference_config.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:core/src/enums/app_user_role.dart'; -import 'package:core/src/models/config/remote_config.dart'; -import 'package:core/src/models/config/saved_filter_limits.dart'; -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:meta/meta.dart'; - -part 'user_preference_config.g.dart'; - -/// {@template user_preference_config} -/// Defines configuration for all user preferences, including role-based -/// limits for followed items, saved headlines, and saved filters. -/// -/// This model is part of the overall [RemoteConfig]. It uses a map-based -/// structure where the key is the [AppUserRole] and the value is the specific -/// limit for that role, ensuring a scalable and maintainable configuration. -/// {@endtemplate} -@immutable -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class UserPreferenceConfig extends Equatable { - /// {@macro user_preference_config} - const UserPreferenceConfig({ - required this.followedItemsLimit, - required this.savedHeadlinesLimit, - required this.savedHeadlineFiltersLimit, - required this.savedSourceFiltersLimit, - }); - - /// Factory method to create a [UserPreferenceConfig] instance from a JSON map. - factory UserPreferenceConfig.fromJson(Map json) => - _$UserPreferenceConfigFromJson(json); - - /// Role-based limits for the number of followed items (topics, sources, - /// countries). The limit applies to each category individually. - final Map followedItemsLimit; - - /// Role-based limits for the number of saved headlines. - final Map savedHeadlinesLimit; - - /// Role-based limits for saved headline filters, using the - /// [SavedFilterLimits] model to define total, pinned, and notification - /// subscription counts. This map defines the limits per user role. - final Map savedHeadlineFiltersLimit; - - /// Role-based limits for saved source filters, using the - /// [SavedFilterLimits] model to define total and pinned counts. This map - /// defines the limits per user role. - final Map savedSourceFiltersLimit; - - /// Converts this [UserPreferenceConfig] instance to a JSON map. - Map toJson() => _$UserPreferenceConfigToJson(this); - - /// Creates a copy of this [UserPreferenceConfig] but with the given fields - /// replaced with the new values. - UserPreferenceConfig copyWith({ - Map? followedItemsLimit, - Map? savedHeadlinesLimit, - Map? savedHeadlineFiltersLimit, - Map? savedSourceFiltersLimit, - }) { - return UserPreferenceConfig( - followedItemsLimit: followedItemsLimit ?? this.followedItemsLimit, - savedHeadlinesLimit: savedHeadlinesLimit ?? this.savedHeadlinesLimit, - savedHeadlineFiltersLimit: - savedHeadlineFiltersLimit ?? this.savedHeadlineFiltersLimit, - savedSourceFiltersLimit: - savedSourceFiltersLimit ?? this.savedSourceFiltersLimit, - ); - } - - @override - List get props => [ - followedItemsLimit, - savedHeadlinesLimit, - savedHeadlineFiltersLimit, - savedSourceFiltersLimit, - ]; - - @override - bool get stringify => true; -} diff --git a/test/src/models/config/user_preference_config_test.dart b/test/src/models/config/user_limits_config_test.dart similarity index 56% rename from test/src/models/config/user_preference_config_test.dart rename to test/src/models/config/user_limits_config_test.dart index 88c51f9e..86f00963 100644 --- a/test/src/models/config/user_preference_config_test.dart +++ b/test/src/models/config/user_limits_config_test.dart @@ -2,20 +2,20 @@ import 'package:core/core.dart'; import 'package:test/test.dart'; void main() { - group('UserPreferenceConfig', () { + group('UserLimitsConfig', () { // Derive the test subject from the main remote config fixture. - final userPreferenceConfigFixture = - remoteConfigsFixturesData.first.userPreferenceConfig; + final UserLimitsConfigFixture = + remoteConfigsFixturesData.first.UserLimitsConfig; group('constructor', () { test('returns correct instance', () { - expect(userPreferenceConfigFixture, isA()); + expect(UserLimitsConfigFixture, isA()); expect( - userPreferenceConfigFixture.followedItemsLimit[AppUserRole.guestUser], + UserLimitsConfigFixture.followedItemsLimit[AppUserRole.guestUser], isA(), ); expect( - userPreferenceConfigFixture.savedHeadlinesLimit[AppUserRole + UserLimitsConfigFixture.savedHeadlinesLimit[AppUserRole .premiumUser], isA(), ); @@ -24,25 +24,25 @@ void main() { group('fromJson/toJson', () { test('round trip', () { - final json = userPreferenceConfigFixture.toJson(); - final result = UserPreferenceConfig.fromJson(json); - expect(result, userPreferenceConfigFixture); + final json = UserLimitsConfigFixture.toJson(); + final result = UserLimitsConfig.fromJson(json); + expect(result, UserLimitsConfigFixture); }); }); group('copyWith', () { test('returns a new instance with updated values', () { final newFollowedItemsLimit = Map.of( - userPreferenceConfigFixture.followedItemsLimit, + UserLimitsConfigFixture.followedItemsLimit, ); newFollowedItemsLimit[AppUserRole.guestUser] = 6; final newSavedHeadlinesLimit = Map.of( - userPreferenceConfigFixture.savedHeadlinesLimit, + UserLimitsConfigFixture.savedHeadlinesLimit, ); newSavedHeadlinesLimit[AppUserRole.premiumUser] = 101; - final updatedConfig = userPreferenceConfigFixture.copyWith( + final updatedConfig = UserLimitsConfigFixture.copyWith( followedItemsLimit: newFollowedItemsLimit, savedHeadlinesLimit: newSavedHeadlinesLimit, ); @@ -50,33 +50,33 @@ void main() { expect(updatedConfig.followedItemsLimit[AppUserRole.guestUser], 6); expect( updatedConfig.savedHeadlinesLimit[AppUserRole.guestUser], - userPreferenceConfigFixture.savedHeadlinesLimit[AppUserRole + UserLimitsConfigFixture.savedHeadlinesLimit[AppUserRole .guestUser], ); expect(updatedConfig.savedHeadlinesLimit[AppUserRole.premiumUser], 101); - expect(updatedConfig, isNot(equals(userPreferenceConfigFixture))); + expect(updatedConfig, isNot(equals(UserLimitsConfigFixture))); }); test('returns the same instance if no changes are made', () { - final updatedConfig = userPreferenceConfigFixture.copyWith(); - expect(updatedConfig, equals(userPreferenceConfigFixture)); + final updatedConfig = UserLimitsConfigFixture.copyWith(); + expect(updatedConfig, equals(UserLimitsConfigFixture)); }); }); group('Equatable', () { test('instances with the same properties are equal', () { - final config1 = userPreferenceConfigFixture.copyWith(); - final config2 = userPreferenceConfigFixture.copyWith(); + final config1 = UserLimitsConfigFixture.copyWith(); + final config2 = UserLimitsConfigFixture.copyWith(); expect(config1, config2); }); test('instances with different properties are not equal', () { - final config1 = userPreferenceConfigFixture.copyWith(); + final config1 = UserLimitsConfigFixture.copyWith(); final newFollowedItemsLimit = Map.of( - userPreferenceConfigFixture.followedItemsLimit, + UserLimitsConfigFixture.followedItemsLimit, ); newFollowedItemsLimit[AppUserRole.guestUser] = 99; - final config2 = userPreferenceConfigFixture.copyWith( + final config2 = UserLimitsConfigFixture.copyWith( followedItemsLimit: newFollowedItemsLimit, ); expect(config1, isNot(equals(config2))); From 6f50b2efe59ce4601e017a8497d17bc6a83f4ba8 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:20:08 +0100 Subject: [PATCH 43/75] feat(config): update GeneralAppConfig and remote configs fixtures - Update GeneralAppConfig to include termsOfServiceUrl and privacyPolicyUrl - Add corresponding data to remote configs fixtures - Refactor FeedDecoratorRoleConfig instances in remote configs --- lib/src/fixtures/remote_configs.dart | 22 +++++++++++-------- lib/src/models/config/general_app_config.dart | 13 +++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index bcd49819..70bf4d69 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -1,7 +1,5 @@ import 'package:core/core.dart'; -const kRemoteConfigId = 'app_config'; - final remoteConfigsFixturesData = [ RemoteConfig( id: kRemoteConfigId, @@ -16,7 +14,10 @@ final remoteConfigsFixturesData = [ androidUpdateUrl: 'https://play.google.com/store/apps/details?id=com.example.app', ), - general: GeneralAppConfig(), + general: GeneralAppConfig( + termsOfServiceUrl: 'https://example.com/terms', + privacyPolicyUrl: 'https://example.com/privacy', + ), ), user: const UserConfig( limits: UserLimitsConfig( @@ -117,13 +118,15 @@ final remoteConfigsFixturesData = [ category: FeedDecoratorCategory.callToAction, enabled: true, visibleTo: { - AppUserRole.guestUser: - FeedDecoratorRoleConfig(daysBetweenViews: 14), + AppUserRole.guestUser: FeedDecoratorRoleConfig( + daysBetweenViews: 14, + ), AppUserRole.standardUser: FeedDecoratorRoleConfig( daysBetweenViews: 30, ), - AppUserRole.premiumUser: - FeedDecoratorRoleConfig(daysBetweenViews: 0), + AppUserRole.premiumUser: FeedDecoratorRoleConfig( + daysBetweenViews: 0, + ), }, ), FeedDecoratorType.suggestedTopics: FeedDecoratorConfig( @@ -131,8 +134,9 @@ final remoteConfigsFixturesData = [ enabled: true, itemsToDisplay: 5, visibleTo: { - AppUserRole.guestUser: - FeedDecoratorRoleConfig(daysBetweenViews: 7), + AppUserRole.guestUser: FeedDecoratorRoleConfig( + daysBetweenViews: 7, + ), AppUserRole.standardUser: FeedDecoratorRoleConfig( daysBetweenViews: 14, ), diff --git a/lib/src/models/config/general_app_config.dart b/lib/src/models/config/general_app_config.dart index 4c3f4f79..6547856a 100644 --- a/lib/src/models/config/general_app_config.dart +++ b/lib/src/models/config/general_app_config.dart @@ -11,15 +11,24 @@ part 'general_app_config.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) class GeneralAppConfig extends Equatable { /// {@macro general_app_config} - const GeneralAppConfig(); + const GeneralAppConfig({ + required this.termsOfServiceUrl, + required this.privacyPolicyUrl, + }); /// Creates a [GeneralAppConfig] from JSON data. factory GeneralAppConfig.fromJson(Map json) => _$GeneralAppConfigFromJson(json); + /// The URL for the application's Terms of Service page. + final String termsOfServiceUrl; + + /// The URL for the application's Privacy Policy page. + final String privacyPolicyUrl; + /// Converts this [GeneralAppConfig] instance to JSON data. Map toJson() => _$GeneralAppConfigToJson(this); @override - List get props => []; + List get props => [termsOfServiceUrl, privacyPolicyUrl]; } From 9c0cba00aa4d3ccc8f668b582308eaed7c966538 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:25:45 +0100 Subject: [PATCH 44/75] build(serialization): generate --- .../models/config/general_app_config.g.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/src/models/config/general_app_config.g.dart b/lib/src/models/config/general_app_config.g.dart index 1c248342..86d9ab82 100644 --- a/lib/src/models/config/general_app_config.g.dart +++ b/lib/src/models/config/general_app_config.g.dart @@ -6,11 +6,18 @@ part of 'general_app_config.dart'; // JsonSerializableGenerator // ************************************************************************** -GeneralAppConfig _$GeneralAppConfigFromJson(Map json) => - $checkedCreate('GeneralAppConfig', json, ($checkedConvert) { - final val = GeneralAppConfig(); - return val; - }); +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, + }; From c4e974b9e6c3e2c623cb362825833c23d3ccdf95 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:26:24 +0100 Subject: [PATCH 45/75] style: misc --- lib/src/models/config/remote_config.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/models/config/remote_config.dart b/lib/src/models/config/remote_config.dart index 235029f7..e9a6e1d8 100644 --- a/lib/src/models/config/remote_config.dart +++ b/lib/src/models/config/remote_config.dart @@ -1,7 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/src/models/config/app_config.dart'; -import 'package:core/src/models/config/features_config.dart'; -import 'package:core/src/models/config/user_config.dart'; import 'package:core/src/utils/json_helpers.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; From 4c0ed61285e6707814e26958d77d3219347ffa91 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:38:25 +0100 Subject: [PATCH 46/75] refactor(push_notifications): replace data map with typed fields - Remove data map in PushNotificationPayload - Add typed fields: notificationId, notificationType, contentType, contentId - Update payload creation and serialization - Adjust imports to include necessary enum classes --- lib/src/fixtures/in_app_notifications.dart | 11 ++--- .../push_notification_payload.dart | 45 ++++++++++++++----- .../push_notification_payload.g.dart | 31 ++++++++++++- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/lib/src/fixtures/in_app_notifications.dart b/lib/src/fixtures/in_app_notifications.dart index c682a07b..3cefa960 100644 --- a/lib/src/fixtures/in_app_notifications.dart +++ b/lib/src/fixtures/in_app_notifications.dart @@ -30,13 +30,10 @@ List _generateAdminNotifications() { payload: PushNotificationPayload( title: headline.title, imageUrl: headline.imageUrl, - data: { - 'notificationId': notificationId, - 'notificationType': - PushNotificationSubscriptionDeliveryType.breakingOnly.name, - 'contentType': 'headline', - 'headlineId': headline.id, - }, + notificationId: notificationId, + notificationType: PushNotificationSubscriptionDeliveryType.breakingOnly, + contentType: ContentType.headline, + contentId: headline.id, ), createdAt: DateTime.now().subtract(Duration(days: index * 2)), readAt: isRead ? DateTime.now().subtract(Duration(hours: index)) : null, diff --git a/lib/src/models/push_notifications/push_notification_payload.dart b/lib/src/models/push_notifications/push_notification_payload.dart index 06748244..f45812d3 100644 --- a/lib/src/models/push_notifications/push_notification_payload.dart +++ b/lib/src/models/push_notifications/push_notification_payload.dart @@ -1,3 +1,5 @@ +import 'package:core/src/enums/content_type.dart'; +import 'package:core/src/enums/push_notification_subscription_delivery_type.dart'; import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:meta/meta.dart'; @@ -17,7 +19,10 @@ class PushNotificationPayload extends Equatable { /// {@macro push_notification_payload} const PushNotificationPayload({ required this.title, - required this.data, + required this.notificationId, + required this.notificationType, + required this.contentType, + required this.contentId, this.imageUrl, }); @@ -28,33 +33,51 @@ class PushNotificationPayload extends Equatable { /// The title of the notification. final String title; + /// The unique identifier of the notification. + final String notificationId; + + /// The type of notification, which determines its delivery characteristics. + final PushNotificationSubscriptionDeliveryType notificationType; + + /// The type of content the notification is related to (e.g., headline, topic). + final ContentType contentType; + + /// The unique identifier of the content associated with the notification. + final String contentId; + /// An optional URL for an image to be displayed in the notification. final String? imageUrl; - /// A map of custom key-value data to be sent with the notification. - /// - /// This is commonly used for deep-linking, allowing the app to navigate to - /// a specific screen or content when the notification is tapped. - /// For example: `{'contentType': 'headline', 'id': 'headline-123'}`. - final Map data; - /// Converts this [PushNotificationPayload] instance to JSON data. Map toJson() => _$PushNotificationPayloadToJson(this); @override - List get props => [title, imageUrl, data]; + List get props => [ + title, + notificationId, + notificationType, + contentType, + contentId, + imageUrl, + ]; /// Creates a copy of this [PushNotificationPayload] but with the given fields /// replaced with the new values. PushNotificationPayload copyWith({ String? title, + String? notificationId, + PushNotificationSubscriptionDeliveryType? notificationType, + ContentType? contentType, + String? contentId, String? imageUrl, - Map? data, }) { return PushNotificationPayload( title: title ?? this.title, + notificationId: notificationId ?? this.notificationId, + notificationType: notificationType ?? this.notificationType, + contentType: contentType ?? this.contentType, + contentId: contentId ?? this.contentId, imageUrl: imageUrl ?? this.imageUrl, - data: data ?? this.data, ); } } diff --git a/lib/src/models/push_notifications/push_notification_payload.g.dart b/lib/src/models/push_notifications/push_notification_payload.g.dart index 660d90fd..3b394242 100644 --- a/lib/src/models/push_notifications/push_notification_payload.g.dart +++ b/lib/src/models/push_notifications/push_notification_payload.g.dart @@ -11,7 +11,16 @@ PushNotificationPayload _$PushNotificationPayloadFromJson( ) => $checkedCreate('PushNotificationPayload', json, ($checkedConvert) { final val = PushNotificationPayload( title: $checkedConvert('title', (v) => v as String), - data: $checkedConvert('data', (v) => v as Map), + notificationId: $checkedConvert('notificationId', (v) => v as String), + notificationType: $checkedConvert( + 'notificationType', + (v) => $enumDecode(_$PushNotificationSubscriptionDeliveryTypeEnumMap, v), + ), + contentType: $checkedConvert( + 'contentType', + (v) => $enumDecode(_$ContentTypeEnumMap, v), + ), + contentId: $checkedConvert('contentId', (v) => v as String), imageUrl: $checkedConvert('imageUrl', (v) => v as String?), ); return val; @@ -21,6 +30,24 @@ Map _$PushNotificationPayloadToJson( PushNotificationPayload instance, ) => { 'title': instance.title, + 'notificationId': instance.notificationId, + 'notificationType': + _$PushNotificationSubscriptionDeliveryTypeEnumMap[instance + .notificationType]!, + 'contentType': _$ContentTypeEnumMap[instance.contentType]!, + 'contentId': instance.contentId, 'imageUrl': instance.imageUrl, - 'data': instance.data, +}; + +const _$PushNotificationSubscriptionDeliveryTypeEnumMap = { + PushNotificationSubscriptionDeliveryType.breakingOnly: 'breakingOnly', + PushNotificationSubscriptionDeliveryType.dailyDigest: 'dailyDigest', + PushNotificationSubscriptionDeliveryType.weeklyRoundup: 'weeklyRoundup', +}; + +const _$ContentTypeEnumMap = { + ContentType.headline: 'headline', + ContentType.topic: 'topic', + ContentType.source: 'source', + ContentType.country: 'country', }; From 776d0712af2d9a0b99171f4e12b28ac714808a5a Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:39:40 +0100 Subject: [PATCH 47/75] feat(utils): add export for nullable_date_time_converter - Add 'nullable_date_time_converter.dart' to the list of exported utilities --- lib/src/utils/utils.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/utils/utils.dart b/lib/src/utils/utils.dart index b4d5e63a..1c0414e4 100644 --- a/lib/src/utils/utils.dart +++ b/lib/src/utils/utils.dart @@ -1 +1,2 @@ export 'json_helpers.dart'; +export 'nullable_date_time_converter.dart'; From f1a67aa22f702c4ab7042abfe8b5e45a50d441cb Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:42:17 +0100 Subject: [PATCH 48/75] fix(remote_configs): update ad platform configuration - Replace AdPlatformType.demo with AdPlatformType.admob as the primary ad platform - Remove AdPlatformType.demo from platformAdIdentifiers map - Update AdPlatformType.admob identifiers with valid test ad IDs --- lib/src/fixtures/remote_configs.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 70bf4d69..82335b5a 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -70,18 +70,13 @@ final remoteConfigsFixturesData = [ features: const FeaturesConfig( ads: AdConfig( enabled: true, - primaryAdPlatform: AdPlatformType.demo, + primaryAdPlatform: AdPlatformType.admob, platformAdIdentifiers: { AdPlatformType.admob: AdPlatformIdentifiers( nativeAdId: 'ca-app-pub-3940256099942544/2247696110', bannerAdId: 'ca-app-pub-3940256099942544/6300978111', interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', ), - AdPlatformType.demo: AdPlatformIdentifiers( - nativeAdId: '_', - bannerAdId: '_', - interstitialAdId: '_', - ), }, feedAdConfiguration: FeedAdConfiguration( enabled: true, From bfece2acec5d61adf79861a742d1ac2d9c98f2d4 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:46:48 +0100 Subject: [PATCH 49/75] test/config: update ad config test to reflect model changes - Update test to use new ad config data path - Change expected primary ad platform from demo to admob --- test/src/models/config/ad_config_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/models/config/ad_config_test.dart b/test/src/models/config/ad_config_test.dart index 386e4987..9e53b360 100644 --- a/test/src/models/config/ad_config_test.dart +++ b/test/src/models/config/ad_config_test.dart @@ -3,11 +3,11 @@ import 'package:test/test.dart'; void main() { group('AdConfig', () { - final adConfigFixture = remoteConfigsFixturesData.first.adConfig; + final adConfigFixture = remoteConfigsFixturesData.first.features.ads; test('can be instantiated', () { expect(adConfigFixture, isA()); - expect(adConfigFixture.primaryAdPlatform, AdPlatformType.demo); + expect(adConfigFixture.primaryAdPlatform, AdPlatformType.admob); expect( adConfigFixture.platformAdIdentifiers, isA>(), From 8d067430fc002f0a7023e86c3a39d93b5d0b195c Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:49:55 +0100 Subject: [PATCH 50/75] test(ad_platform_identifiers): remove demo platform and update tests - Remove Demo platform references from AdPlatformIdentifiers tests - Update interstitialAdId value in copyWith test - Remove round trip test for Demo platform - Adjust imports and data access to reflect changes in remote config structure --- .../config/ad_platform_identifiers_test.dart | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/test/src/models/config/ad_platform_identifiers_test.dart b/test/src/models/config/ad_platform_identifiers_test.dart index 29d4b786..e63a47d8 100644 --- a/test/src/models/config/ad_platform_identifiers_test.dart +++ b/test/src/models/config/ad_platform_identifiers_test.dart @@ -5,14 +5,10 @@ void main() { group('AdPlatformIdentifiers', () { final admobIdentifiersFixture = remoteConfigsFixturesData .first - .adConfig + .features + .ads .platformAdIdentifiers[AdPlatformType.admob]!; - final demoIdentifiersFixture = remoteConfigsFixturesData - .first - .adConfig - .platformAdIdentifiers[AdPlatformType.demo]!; - test('can be instantiated (AdMob)', () { expect(admobIdentifiersFixture, isA()); expect( @@ -25,12 +21,6 @@ void main() { ); }); - test('can be instantiated (Demo)', () { - expect(demoIdentifiersFixture, isA()); - expect(demoIdentifiersFixture.nativeAdId, '_'); - expect(demoIdentifiersFixture.interstitialAdId, '_'); - }); - test('supports value equality', () { final identifiers1 = admobIdentifiersFixture.copyWith(); final identifiers2 = admobIdentifiersFixture.copyWith(); @@ -40,7 +30,7 @@ void main() { test('copyWith returns a new instance with updated values', () { final updatedIdentifiers = admobIdentifiersFixture.copyWith( nativeAdId: 'new_native_id', - interstitialAdId: 'new_banner_id', + interstitialAdId: 'new_interstitial_id', ); expect(updatedIdentifiers.nativeAdId, 'new_native_id'); @@ -60,12 +50,6 @@ void main() { final result = AdPlatformIdentifiers.fromJson(json); expect(result, equals(admobIdentifiersFixture)); }); - - test('round trip (Demo)', () { - final json = demoIdentifiersFixture.toJson(); - final result = AdPlatformIdentifiers.fromJson(json); - expect(result, equals(demoIdentifiersFixture)); - }); }); }); } From 3d2c9a6d913c276f95876e49bf70c80b812b61b5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 03:50:48 +0100 Subject: [PATCH 51/75] chore: delete absolete files --- lib/src/models/config/app_status.dart | 67 --------------------- lib/src/models/config/app_status.g.dart | 31 ---------- lib/src/models/config/config.dart | 1 - test/src/models/config/app_status_test.dart | 64 -------------------- 4 files changed, 163 deletions(-) delete mode 100644 lib/src/models/config/app_status.dart delete mode 100644 lib/src/models/config/app_status.g.dart delete mode 100644 test/src/models/config/app_status_test.dart diff --git a/lib/src/models/config/app_status.dart b/lib/src/models/config/app_status.dart deleted file mode 100644 index cd3ed524..00000000 --- a/lib/src/models/config/app_status.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:json_annotation/json_annotation.dart'; - -part 'app_status.g.dart'; - -/// Represents the application's remote status, including maintenance and update information. -@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true) -class AppStatus extends Equatable { - const AppStatus({ - required this.isUnderMaintenance, - required this.latestAppVersion, - required this.isLatestVersionOnly, - required this.iosUpdateUrl, - required this.androidUpdateUrl, - }); - - /// Factory method to create a [AppStatus] instance from a JSON map. - factory AppStatus.fromJson(Map json) => - _$AppStatusFromJson(json); - - /// Indicates if the app is currently under maintenance. - final bool isUnderMaintenance; - - /// The latest available app version. - final String latestAppVersion; - - /// Indicates if only the latest version of the app is allowed to run (force update). - final bool isLatestVersionOnly; - - /// URL for iOS app updates. - final String iosUpdateUrl; - - /// URL for Android app updates. - final String androidUpdateUrl; - - /// Converts this [AppStatus] instance to a JSON map. - Map toJson() => _$AppStatusToJson(this); - - /// Creates a new [AppStatus] instance with specified changes. - AppStatus copyWith({ - bool? isUnderMaintenance, - String? latestAppVersion, - bool? isLatestVersionOnly, - String? iosUpdateUrl, - String? androidUpdateUrl, - }) { - return AppStatus( - isUnderMaintenance: isUnderMaintenance ?? this.isUnderMaintenance, - latestAppVersion: latestAppVersion ?? this.latestAppVersion, - isLatestVersionOnly: isLatestVersionOnly ?? this.isLatestVersionOnly, - iosUpdateUrl: iosUpdateUrl ?? this.iosUpdateUrl, - androidUpdateUrl: androidUpdateUrl ?? this.androidUpdateUrl, - ); - } - - @override - List get props => [ - isUnderMaintenance, - latestAppVersion, - isLatestVersionOnly, - iosUpdateUrl, - androidUpdateUrl, - ]; - - @override - bool get stringify => true; -} diff --git a/lib/src/models/config/app_status.g.dart b/lib/src/models/config/app_status.g.dart deleted file mode 100644 index 1fd7bcbe..00000000 --- a/lib/src/models/config/app_status.g.dart +++ /dev/null @@ -1,31 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'app_status.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AppStatus _$AppStatusFromJson( - Map json, -) => $checkedCreate('AppStatus', json, ($checkedConvert) { - final val = AppStatus( - isUnderMaintenance: $checkedConvert('isUnderMaintenance', (v) => v as bool), - 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 _$AppStatusToJson(AppStatus instance) => { - 'isUnderMaintenance': instance.isUnderMaintenance, - 'latestAppVersion': instance.latestAppVersion, - 'isLatestVersionOnly': instance.isLatestVersionOnly, - 'iosUpdateUrl': instance.iosUpdateUrl, - 'androidUpdateUrl': instance.androidUpdateUrl, -}; diff --git a/lib/src/models/config/config.dart b/lib/src/models/config/config.dart index 67291014..a1aa0e82 100644 --- a/lib/src/models/config/config.dart +++ b/lib/src/models/config/config.dart @@ -1,7 +1,6 @@ export 'ad_config.dart'; export 'ad_platform_identifiers.dart'; export 'app_config.dart'; -export 'app_status.dart'; export 'features_config.dart'; export 'feed_ad_configuration.dart'; export 'feed_ad_frequency_config.dart'; diff --git a/test/src/models/config/app_status_test.dart b/test/src/models/config/app_status_test.dart deleted file mode 100644 index 1f73bf71..00000000 --- a/test/src/models/config/app_status_test.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:core/core.dart'; -import 'package:json_annotation/json_annotation.dart'; -import 'package:test/test.dart'; - -void main() { - group('AppStatus', () { - // Derive the test subject from the main remote config fixture. - final appStatusFixture = remoteConfigsFixturesData.first.appStatus; - - test('can be instantiated', () { - expect(appStatusFixture, isNotNull); - }); - - test('supports value equality', () { - final status1 = appStatusFixture.copyWith(); - final status2 = appStatusFixture.copyWith(); - expect(status1, equals(status2)); - }); - - test('copyWith returns a new instance with updated values', () { - final updatedAppStatus = appStatusFixture.copyWith( - isUnderMaintenance: true, - latestAppVersion: '1.0.1', - ); - - expect(updatedAppStatus.isUnderMaintenance, isTrue); - expect(updatedAppStatus.latestAppVersion, '1.0.1'); - expect( - updatedAppStatus.isLatestVersionOnly, - appStatusFixture.isLatestVersionOnly, - ); - expect(updatedAppStatus.iosUpdateUrl, appStatusFixture.iosUpdateUrl); - expect( - updatedAppStatus.androidUpdateUrl, - appStatusFixture.androidUpdateUrl, - ); - expect(updatedAppStatus, isNot(equals(appStatusFixture))); - }); - - test('copyWith returns same instance if no changes', () { - final updatedAppStatus = appStatusFixture.copyWith(); - expect(updatedAppStatus, equals(appStatusFixture)); - }); - - group('fromJson/toJson', () { - test('round trip', () { - final json = appStatusFixture.toJson(); - final result = AppStatus.fromJson(json); - expect(result, equals(appStatusFixture)); - }); - - test( - 'throws FormatException on invalid JSON (missing required field)', - () { - final json = appStatusFixture.toJson()..remove('isLatestVersionOnly'); - expect( - () => AppStatus.fromJson(json), - throwsA(isA()), - ); - }, - ); - }); - }); -} From e82909224460a866d12e6ce7a5ca7649d9b601a9 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:04:04 +0100 Subject: [PATCH 52/75] test(remote_config): update tests to reflect model changes - Adjust equality check to include new properties and exclude deprecated ones - Update copyWith test to use new nested models for app and features - Remove assertions for deprecated properties --- .../src/models/config/remote_config_test.dart | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/test/src/models/config/remote_config_test.dart b/test/src/models/config/remote_config_test.dart index c22a115e..a0f90efc 100644 --- a/test/src/models/config/remote_config_test.dart +++ b/test/src/models/config/remote_config_test.dart @@ -20,13 +20,11 @@ void main() { remoteConfigFixture.props, equals([ remoteConfigFixture.id, - remoteConfigFixture.userPreferenceConfig, - remoteConfigFixture.adConfig, - remoteConfigFixture.feedDecoratorConfig, - remoteConfigFixture.appStatus, - remoteConfigFixture.pushNotificationConfig, remoteConfigFixture.createdAt, remoteConfigFixture.updatedAt, + remoteConfigFixture.app, + remoteConfigFixture.features, + remoteConfigFixture.user, ]), ); }); @@ -50,35 +48,32 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define new values for various properties. const newId = 'new_app_config'; - final newAppStatus = remoteConfigFixture.appStatus.copyWith( - isUnderMaintenance: true, + final newApp = remoteConfigFixture.app.copyWith( + maintenance: const MaintenanceConfig(isUnderMaintenance: true), ); - final newPushConfig = remoteConfigFixture.pushNotificationConfig.copyWith( - primaryProvider: PushNotificationProvider.oneSignal, + final newFeatures = remoteConfigFixture.features.copyWith( + pushNotifications: remoteConfigFixture.features.pushNotifications + .copyWith(primaryProvider: PushNotificationProvider.oneSignal), ); // Act: Create a copy with the updated values. final copiedConfig = remoteConfigFixture.copyWith( id: newId, - appStatus: newAppStatus, - pushNotificationConfig: newPushConfig, + app: newApp, + features: newFeatures, ); // Assert: The new instance should have the updated values. expect(copiedConfig.id, equals(newId)); - expect(copiedConfig.appStatus, equals(newAppStatus)); - expect(copiedConfig.pushNotificationConfig, equals(newPushConfig)); + expect(copiedConfig.app, equals(newApp)); + expect(copiedConfig.features, equals(newFeatures)); // Assert: Unchanged properties remain the same. - expect( - copiedConfig.userPreferenceConfig, - equals(remoteConfigFixture.userPreferenceConfig), - ); - expect(copiedConfig.adConfig, equals(remoteConfigFixture.adConfig)); + expect(copiedConfig.user, equals(remoteConfigFixture.user)); // Assert: The original instance remains unchanged. expect(remoteConfigFixture.id, isNot(equals(newId))); - expect(remoteConfigFixture.appStatus, isNot(equals(newAppStatus))); + expect(remoteConfigFixture.app, isNot(equals(newApp))); }); test( From 0eed0c414e9a8d90f9be9212888fb59ae975a376 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:04:58 +0100 Subject: [PATCH 53/75] fix(models): update feed ad configuration path in test - Change the path to access feed ad configuration in test fixture - This modification ensures the test reflects the correct structure of the remote configs data --- test/src/models/config/feed_ad_configuration_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/models/config/feed_ad_configuration_test.dart b/test/src/models/config/feed_ad_configuration_test.dart index 7dd62d73..34e640e8 100644 --- a/test/src/models/config/feed_ad_configuration_test.dart +++ b/test/src/models/config/feed_ad_configuration_test.dart @@ -4,7 +4,7 @@ import 'package:test/test.dart'; void main() { group('FeedAdConfiguration', () { final feedAdConfigurationFixture = - remoteConfigsFixturesData.first.adConfig.feedAdConfiguration; + remoteConfigsFixturesData.first.features.ads.feedAdConfiguration; test('can be instantiated', () { expect(feedAdConfigurationFixture, isA()); From fc0e1a953e5546ac154eef6897f2870300839bfd Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:05:09 +0100 Subject: [PATCH 54/75] test: update FeedDecoratorConfig tests to reflect new remote config structure - Update test cases to use the new remote config structure - Change feedDecoratorConfig access to features.feed.decorators - This change improves the test to match the recent modifications in the remote config model --- test/src/models/config/feed_decorator_config_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/models/config/feed_decorator_config_test.dart b/test/src/models/config/feed_decorator_config_test.dart index 07df1fe5..df509f1c 100644 --- a/test/src/models/config/feed_decorator_config_test.dart +++ b/test/src/models/config/feed_decorator_config_test.dart @@ -5,9 +5,9 @@ void main() { group('FeedDecoratorConfig', () { final remoteConfig = remoteConfigsFixturesData.first; final rateAppDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.rateApp]!; + remoteConfig.features.feed.decorators[FeedDecoratorType.rateApp]!; final suggestedTopicsDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.suggestedTopics]!; + remoteConfig.features.feed.decorators[FeedDecoratorType.suggestedTopics]!; test('can be instantiated', () { expect(rateAppDecorator, isA()); From 6d9ed14a86d7682321f93a82c55873c398a09c4f Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:05:29 +0100 Subject: [PATCH 55/75] test(config): update FeedDecoratorRoleConfig test to use correct property path - Change remoteConfig.feedDecoratorConfig to remoteConfig.features.feed.decorators - This modification ensures the test accesses the correct property path in the remote config object --- test/src/models/config/feed_decorator_role_config_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/models/config/feed_decorator_role_config_test.dart b/test/src/models/config/feed_decorator_role_config_test.dart index e52ee10d..e4fb261d 100644 --- a/test/src/models/config/feed_decorator_role_config_test.dart +++ b/test/src/models/config/feed_decorator_role_config_test.dart @@ -5,7 +5,7 @@ void main() { group('FeedDecoratorRoleConfig', () { final remoteConfig = remoteConfigsFixturesData.first; final rateAppDecorator = - remoteConfig.feedDecoratorConfig[FeedDecoratorType.rateApp]!; + remoteConfig.features.feed.decorators[FeedDecoratorType.rateApp]!; final guestRoleConfig = rateAppDecorator.visibleTo[AppUserRole.guestUser]!; test('can be instantiated', () { From 4f4db0f7c50958e9b8cd45a72bf71d698ef32a80 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:05:39 +0100 Subject: [PATCH 56/75] test(config): update feed ad frequency config test to match new structure - Adjust test to reflect changes in the config structure - Access 'features' and 'ads' before retrieving 'feedAdConfiguration' - Ensure the test still correctly accesses the configuration for guest users --- test/src/models/config/feed_ad_frequency_config_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/src/models/config/feed_ad_frequency_config_test.dart b/test/src/models/config/feed_ad_frequency_config_test.dart index da650854..d1a47d90 100644 --- a/test/src/models/config/feed_ad_frequency_config_test.dart +++ b/test/src/models/config/feed_ad_frequency_config_test.dart @@ -6,7 +6,8 @@ void main() { // Access the FeedAdFrequencyConfig from the fixture's visibleTo map final feedAdFrequencyConfigFixture = remoteConfigsFixturesData .first - .adConfig + .features + .ads .feedAdConfiguration .visibleTo[AppUserRole.guestUser]!; From 154c9111e5ffce02d44c5383fa9cb4d9a060bcc2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:05:53 +0100 Subject: [PATCH 57/75] test: update push notification config reference in tests - Change pushNotificationConfig reference to remoteConfigsFixturesData.first.features.pushNotifications - Update both instances where the old reference was used --- test/src/models/config/push_notification_config_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/src/models/config/push_notification_config_test.dart b/test/src/models/config/push_notification_config_test.dart index e74d711c..01b8579d 100644 --- a/test/src/models/config/push_notification_config_test.dart +++ b/test/src/models/config/push_notification_config_test.dart @@ -6,7 +6,7 @@ void main() { // Retrieve the PushNotificationConfig from the remoteConfigsFixturesData. // This ensures consistency with predefined application configurations. final pushNotificationConfig = - remoteConfigsFixturesData.first.pushNotificationConfig; + remoteConfigsFixturesData.first.features.pushNotifications; // Corresponding JSON representation final json = pushNotificationConfig.toJson(); @@ -14,7 +14,7 @@ void main() { test('supports value equality', () { // Arrange: Create another instance with the same values. final anotherConfig = - remoteConfigsFixturesData.first.pushNotificationConfig; + remoteConfigsFixturesData.first.features.pushNotifications; // Assert: The two instances should be equal. expect(pushNotificationConfig, equals(anotherConfig)); From eabbd4355ce84559467aa6668ac8f13047f9dad5 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:06:24 +0100 Subject: [PATCH 58/75] feat(models): add copyWith method to config models - Add copyWith method to AppConfig, FeaturesConfig, FeedConfig, GeneralAppConfig, and MaintenanceConfig classes - This allows creating new instances with modified values while keeping other values unchanged --- lib/src/models/config/app_config.dart | 14 ++++++++++++++ lib/src/models/config/features_config.dart | 14 ++++++++++++++ lib/src/models/config/feed_config.dart | 12 ++++++++++++ lib/src/models/config/general_app_config.dart | 12 ++++++++++++ lib/src/models/config/maintenance_config.dart | 8 ++++++++ 5 files changed, 60 insertions(+) diff --git a/lib/src/models/config/app_config.dart b/lib/src/models/config/app_config.dart index b0402bd6..01bde770 100644 --- a/lib/src/models/config/app_config.dart +++ b/lib/src/models/config/app_config.dart @@ -38,4 +38,18 @@ class AppConfig extends Equatable { @override List get props => [maintenance, update, general]; + + /// Creates a copy of this [AppConfig] but with the given fields + /// replaced with the new values. + AppConfig copyWith({ + MaintenanceConfig? maintenance, + UpdateConfig? update, + GeneralAppConfig? general, + }) { + return AppConfig( + maintenance: maintenance ?? this.maintenance, + update: update ?? this.update, + general: general ?? this.general, + ); + } } diff --git a/lib/src/models/config/features_config.dart b/lib/src/models/config/features_config.dart index 29240bb6..5aa1c9fb 100644 --- a/lib/src/models/config/features_config.dart +++ b/lib/src/models/config/features_config.dart @@ -38,4 +38,18 @@ class FeaturesConfig extends Equatable { @override List get props => [ads, pushNotifications, feed]; + + /// Creates a copy of this [FeaturesConfig] but with the given fields + /// replaced with the new values. + FeaturesConfig copyWith({ + AdConfig? ads, + PushNotificationConfig? pushNotifications, + FeedConfig? feed, + }) { + return FeaturesConfig( + ads: ads ?? this.ads, + pushNotifications: pushNotifications ?? this.pushNotifications, + feed: feed ?? this.feed, + ); + } } diff --git a/lib/src/models/config/feed_config.dart b/lib/src/models/config/feed_config.dart index 758e951c..cd02dbaf 100644 --- a/lib/src/models/config/feed_config.dart +++ b/lib/src/models/config/feed_config.dart @@ -31,4 +31,16 @@ class FeedConfig extends Equatable { @override List get props => [itemClickBehavior, decorators]; + + /// Creates a copy of this [FeedConfig] but with the given fields + /// replaced with the new values. + FeedConfig copyWith({ + FeedItemClickBehavior? itemClickBehavior, + Map? decorators, + }) { + return FeedConfig( + itemClickBehavior: itemClickBehavior ?? this.itemClickBehavior, + decorators: decorators ?? this.decorators, + ); + } } diff --git a/lib/src/models/config/general_app_config.dart b/lib/src/models/config/general_app_config.dart index 6547856a..ad40ab26 100644 --- a/lib/src/models/config/general_app_config.dart +++ b/lib/src/models/config/general_app_config.dart @@ -32,3 +32,15 @@ class GeneralAppConfig extends Equatable { @override List get props => [termsOfServiceUrl, privacyPolicyUrl]; } + +/// Creates a copy of this [GeneralAppConfig] but with the given fields +/// replaced with the new values. +GeneralAppConfig copyWith({ + String? termsOfServiceUrl, + String? privacyPolicyUrl, +}) { + return GeneralAppConfig( + termsOfServiceUrl: termsOfServiceUrl ?? this.termsOfServiceUrl, + privacyPolicyUrl: privacyPolicyUrl ?? this.privacyPolicyUrl, + ); +} diff --git a/lib/src/models/config/maintenance_config.dart b/lib/src/models/config/maintenance_config.dart index efaa1f1a..bb17be53 100644 --- a/lib/src/models/config/maintenance_config.dart +++ b/lib/src/models/config/maintenance_config.dart @@ -25,4 +25,12 @@ class MaintenanceConfig extends Equatable { @override List get props => [isUnderMaintenance]; + + /// Creates a copy of this [MaintenanceConfig] but with the given fields + /// replaced with the new values. + MaintenanceConfig copyWith({bool? isUnderMaintenance}) { + return MaintenanceConfig( + isUnderMaintenance: isUnderMaintenance ?? this.isUnderMaintenance, + ); + } } From 34d32fc04c98a9d66b620dbef378e1130864f0d2 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:08:34 +0100 Subject: [PATCH 59/75] style: indent copyWith method in GeneralAppConfig - Move copyWith method inside the GeneralAppConfig class - Adjust indentation to match Dart best practices --- lib/src/models/config/general_app_config.dart | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/src/models/config/general_app_config.dart b/lib/src/models/config/general_app_config.dart index ad40ab26..ff1b6baf 100644 --- a/lib/src/models/config/general_app_config.dart +++ b/lib/src/models/config/general_app_config.dart @@ -31,16 +31,16 @@ class GeneralAppConfig extends Equatable { @override List get props => [termsOfServiceUrl, privacyPolicyUrl]; -} -/// Creates a copy of this [GeneralAppConfig] but with the given fields -/// replaced with the new values. -GeneralAppConfig copyWith({ - String? termsOfServiceUrl, - String? privacyPolicyUrl, -}) { - return GeneralAppConfig( - termsOfServiceUrl: termsOfServiceUrl ?? this.termsOfServiceUrl, - privacyPolicyUrl: privacyPolicyUrl ?? this.privacyPolicyUrl, - ); + /// Creates a copy of this [GeneralAppConfig] but with the given fields + /// replaced with the new values. + GeneralAppConfig copyWith({ + String? termsOfServiceUrl, + String? privacyPolicyUrl, + }) { + return GeneralAppConfig( + termsOfServiceUrl: termsOfServiceUrl ?? this.termsOfServiceUrl, + privacyPolicyUrl: privacyPolicyUrl ?? this.privacyPolicyUrl, + ); + } } From fa0ecc493c38c32b8d7a6d20983e80813a2d6b9b Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:13:33 +0100 Subject: [PATCH 60/75] test(core): add AppConfig unit tests - Create unit tests for AppConfig model - Verify instantiation, equality, props, JSON conversion, and copyWith functionality - Ensure comprehensive coverage of AppConfig features and behavior --- test/src/models/config/app_config_test.dart | 57 +++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 test/src/models/config/app_config_test.dart diff --git a/test/src/models/config/app_config_test.dart b/test/src/models/config/app_config_test.dart new file mode 100644 index 00000000..11ffa79c --- /dev/null +++ b/test/src/models/config/app_config_test.dart @@ -0,0 +1,57 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('AppConfig', () { + final appConfigFixture = remoteConfigsFixturesData.first.app; + final json = appConfigFixture.toJson(); + + test('can be instantiated', () { + expect(appConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app; + expect(appConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + appConfigFixture.props, + equals([ + appConfigFixture.maintenance, + appConfigFixture.update, + appConfigFixture.general, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = AppConfig.fromJson(json); + expect(fromJson, equals(appConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = appConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = appConfigFixture.copyWith( + maintenance: const MaintenanceConfig(isUnderMaintenance: true), + ); + + expect(updatedConfig.maintenance.isUnderMaintenance, isTrue); + expect(updatedConfig.update, equals(appConfigFixture.update)); + expect(updatedConfig, isNot(equals(appConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = appConfigFixture.copyWith(); + expect(copiedConfig, equals(appConfigFixture)); + }, + ); + }); +} From 52c0d9d713b0f195ab8ab693e5543861502fe644 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:14:01 +0100 Subject: [PATCH 61/75] test(core): add FeaturesConfig model tests - Add unit tests for FeaturesConfig model - Verify instantiation, equality, props, JSON serialization/deserialization - Test copyWith functionality with and without updated arguments --- .../models/config/features_config_test.dart | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/src/models/config/features_config_test.dart diff --git a/test/src/models/config/features_config_test.dart b/test/src/models/config/features_config_test.dart new file mode 100644 index 00000000..74329eb8 --- /dev/null +++ b/test/src/models/config/features_config_test.dart @@ -0,0 +1,60 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeaturesConfig', () { + final featuresConfigFixture = remoteConfigsFixturesData.first.features; + final json = featuresConfigFixture.toJson(); + + test('can be instantiated', () { + expect(featuresConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.features; + expect(featuresConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + featuresConfigFixture.props, + equals([ + featuresConfigFixture.ads, + featuresConfigFixture.pushNotifications, + featuresConfigFixture.feed, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = FeaturesConfig.fromJson(json); + expect(fromJson, equals(featuresConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = featuresConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = featuresConfigFixture.copyWith( + ads: featuresConfigFixture.ads.copyWith(enabled: false), + ); + + expect(updatedConfig.ads.enabled, isFalse); + expect( + updatedConfig.pushNotifications, + equals(featuresConfigFixture.pushNotifications), + ); + expect(updatedConfig, isNot(equals(featuresConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = featuresConfigFixture.copyWith(); + expect(copiedConfig, equals(featuresConfigFixture)); + }, + ); + }); +} From 821568462201738d92517c37df96713771aeba28 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:14:26 +0100 Subject: [PATCH 62/75] test(core): add FeedConfig model tests - Add unit tests for FeedConfig class - Verify instantiation, equality, props, JSON serialization/deserialization - Test copyWith functionality with and without updated values --- test/src/models/config/feed_config_test.dart | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/src/models/config/feed_config_test.dart diff --git a/test/src/models/config/feed_config_test.dart b/test/src/models/config/feed_config_test.dart new file mode 100644 index 00000000..3b0a6781 --- /dev/null +++ b/test/src/models/config/feed_config_test.dart @@ -0,0 +1,59 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('FeedConfig', () { + final feedConfigFixture = remoteConfigsFixturesData.first.features.feed; + final json = feedConfigFixture.toJson(); + + test('can be instantiated', () { + expect(feedConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.features.feed; + expect(feedConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + feedConfigFixture.props, + equals([ + feedConfigFixture.itemClickBehavior, + feedConfigFixture.decorators, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = FeedConfig.fromJson(json); + expect(fromJson, equals(feedConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = feedConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = feedConfigFixture.copyWith( + itemClickBehavior: FeedItemClickBehavior.externalNavigation, + ); + + expect( + updatedConfig.itemClickBehavior, + FeedItemClickBehavior.externalNavigation, + ); + expect(updatedConfig.decorators, equals(feedConfigFixture.decorators)); + expect(updatedConfig, isNot(equals(feedConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = feedConfigFixture.copyWith(); + expect(copiedConfig, equals(feedConfigFixture)); + }, + ); + }); +} From 1827eda63e76c46ada3a07b6dd974ce1460e1dcc Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:15:03 +0100 Subject: [PATCH 63/75] test(core): add GeneralAppConfig model tests - Add unit tests for GeneralAppConfig model - Verify instantiation, equality, props, JSON serialization/deserialization - Test copyWith functionality with and without updated values --- .../config/general_app_config_test.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 test/src/models/config/general_app_config_test.dart diff --git a/test/src/models/config/general_app_config_test.dart b/test/src/models/config/general_app_config_test.dart new file mode 100644 index 00000000..a906e76c --- /dev/null +++ b/test/src/models/config/general_app_config_test.dart @@ -0,0 +1,59 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('GeneralAppConfig', () { + final generalAppConfigFixture = remoteConfigsFixturesData.first.app.general; + final json = generalAppConfigFixture.toJson(); + + test('can be instantiated', () { + expect(generalAppConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.general; + expect(generalAppConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + generalAppConfigFixture.props, + equals([ + generalAppConfigFixture.termsOfServiceUrl, + generalAppConfigFixture.privacyPolicyUrl, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = GeneralAppConfig.fromJson(json); + expect(fromJson, equals(generalAppConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = generalAppConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = generalAppConfigFixture.copyWith( + termsOfServiceUrl: 'https://example.com/new-terms', + ); + + expect(updatedConfig.termsOfServiceUrl, 'https://example.com/new-terms'); + expect( + updatedConfig.privacyPolicyUrl, + equals(generalAppConfigFixture.privacyPolicyUrl), + ); + expect(updatedConfig, isNot(equals(generalAppConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = generalAppConfigFixture.copyWith(); + expect(copiedConfig, equals(generalAppConfigFixture)); + }, + ); + }); +} From f082a244e68cc9d24bc2462728cd9b55b1c6e26f Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:15:22 +0100 Subject: [PATCH 64/75] test(core): add MaintenanceConfig model tests - Create unit tests for MaintenanceConfig model - Verify instantiation, equality, props, JSON serialization/deserialization - Test copyWith functionality with and without updated values --- .../config/maintenance_config_test.dart | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/src/models/config/maintenance_config_test.dart diff --git a/test/src/models/config/maintenance_config_test.dart b/test/src/models/config/maintenance_config_test.dart new file mode 100644 index 00000000..39c4cc91 --- /dev/null +++ b/test/src/models/config/maintenance_config_test.dart @@ -0,0 +1,53 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('MaintenanceConfig', () { + final maintenanceConfigFixture = + remoteConfigsFixturesData.first.app.maintenance; + final json = maintenanceConfigFixture.toJson(); + + test('can be instantiated', () { + expect(maintenanceConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.maintenance; + expect(maintenanceConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + maintenanceConfigFixture.props, + equals([maintenanceConfigFixture.isUnderMaintenance]), + ); + }); + + test('can be created from JSON', () { + final fromJson = MaintenanceConfig.fromJson(json); + expect(fromJson, equals(maintenanceConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = maintenanceConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = maintenanceConfigFixture.copyWith( + isUnderMaintenance: true, + ); + + expect(updatedConfig.isUnderMaintenance, isTrue); + expect(updatedConfig, isNot(equals(maintenanceConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = maintenanceConfigFixture.copyWith(); + expect(copiedConfig, equals(maintenanceConfigFixture)); + }, + ); + }); +} From 32ae5be3c71a6071b0c06ce3ecd7c1d82b168122 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:15:39 +0100 Subject: [PATCH 65/75] test(core): add UpdateConfig model tests - Add unit tests for UpdateConfig model - Verify instantiation, equality, props, JSON serialization/deserialization - Test copyWith functionality with and without updated values --- .../src/models/config/update_config_test.dart | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/src/models/config/update_config_test.dart diff --git a/test/src/models/config/update_config_test.dart b/test/src/models/config/update_config_test.dart new file mode 100644 index 00000000..bdb4a0f9 --- /dev/null +++ b/test/src/models/config/update_config_test.dart @@ -0,0 +1,63 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('UpdateConfig', () { + final updateConfigFixture = remoteConfigsFixturesData.first.app.update; + final json = updateConfigFixture.toJson(); + + test('can be instantiated', () { + expect(updateConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.app.update; + expect(updateConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect( + updateConfigFixture.props, + equals([ + updateConfigFixture.latestAppVersion, + updateConfigFixture.isLatestVersionOnly, + updateConfigFixture.iosUpdateUrl, + updateConfigFixture.androidUpdateUrl, + ]), + ); + }); + + test('can be created from JSON', () { + final fromJson = UpdateConfig.fromJson(json); + expect(fromJson, equals(updateConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = updateConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final updatedConfig = updateConfigFixture.copyWith( + latestAppVersion: '2.0.0', + isLatestVersionOnly: true, + ); + + expect(updatedConfig.latestAppVersion, '2.0.0'); + expect(updatedConfig.isLatestVersionOnly, isTrue); + expect( + updatedConfig.iosUpdateUrl, + equals(updateConfigFixture.iosUpdateUrl), + ); + expect(updatedConfig, isNot(equals(updateConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = updateConfigFixture.copyWith(); + expect(copiedConfig, equals(updateConfigFixture)); + }, + ); + }); +} From 06241b5bb7012ec8aaae2e72e64c898598f2712d Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:16:42 +0100 Subject: [PATCH 66/75] test(user_limits_config): rewrite tests for UserLimitsConfig - Refactor test structure to improve readability and maintainability - Add tests for JSON serialization and deserialization - Include tests for value equality and prop correctness - Implement tests for copyWith functionality --- test/src/models/config/user_config_test.dart | 50 ++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/src/models/config/user_config_test.dart diff --git a/test/src/models/config/user_config_test.dart b/test/src/models/config/user_config_test.dart new file mode 100644 index 00000000..e27fcdc1 --- /dev/null +++ b/test/src/models/config/user_config_test.dart @@ -0,0 +1,50 @@ +import 'package:core/core.dart'; +import 'package:test/test.dart'; + +void main() { + group('UserConfig', () { + final userConfigFixture = remoteConfigsFixturesData.first.user; + final json = userConfigFixture.toJson(); + + test('can be instantiated', () { + expect(userConfigFixture, isA()); + }); + + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.user; + expect(userConfigFixture, equals(anotherConfig)); + }); + + test('props are correct', () { + expect(userConfigFixture.props, equals([userConfigFixture.limits])); + }); + + test('can be created from JSON', () { + final fromJson = UserConfig.fromJson(json); + expect(fromJson, equals(userConfigFixture)); + }); + + test('can be converted to JSON', () { + final toJson = userConfigFixture.toJson(); + expect(toJson, equals(json)); + }); + + test('copyWith creates a copy with updated values', () { + final newLimits = userConfigFixture.limits.copyWith( + savedHeadlines: {AppUserRole.guestUser: 999}, + ); + final updatedConfig = userConfigFixture.copyWith(limits: newLimits); + + expect(updatedConfig.limits, equals(newLimits)); + expect(updatedConfig, isNot(equals(userConfigFixture))); + }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = userConfigFixture.copyWith(); + expect(copiedConfig, equals(userConfigFixture)); + }, + ); + }); +} From 76edafac8a6b886e16a566ee8aa1f604529e33ca Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:17:09 +0100 Subject: [PATCH 67/75] test(user_limits_config): rewrite tests for UserLimitsConfig - Refactor test structure to improve readability and maintainability - Add tests for JSON serialization and deserialization - Include tests for value equality and prop correctness - Implement tests for copyWith functionality --- .../config/user_limits_config_test.dart | 113 +++++++----------- 1 file changed, 44 insertions(+), 69 deletions(-) diff --git a/test/src/models/config/user_limits_config_test.dart b/test/src/models/config/user_limits_config_test.dart index 86f00963..761e3ea6 100644 --- a/test/src/models/config/user_limits_config_test.dart +++ b/test/src/models/config/user_limits_config_test.dart @@ -3,84 +3,59 @@ import 'package:test/test.dart'; void main() { group('UserLimitsConfig', () { - // Derive the test subject from the main remote config fixture. - final UserLimitsConfigFixture = - remoteConfigsFixturesData.first.UserLimitsConfig; + final userLimitsConfigFixture = remoteConfigsFixturesData.first.user.limits; + final json = userLimitsConfigFixture.toJson(); - group('constructor', () { - test('returns correct instance', () { - expect(UserLimitsConfigFixture, isA()); - expect( - UserLimitsConfigFixture.followedItemsLimit[AppUserRole.guestUser], - isA(), - ); - expect( - UserLimitsConfigFixture.savedHeadlinesLimit[AppUserRole - .premiumUser], - isA(), - ); - }); + test('can be instantiated', () { + expect(userLimitsConfigFixture, isA()); }); - group('fromJson/toJson', () { - test('round trip', () { - final json = UserLimitsConfigFixture.toJson(); - final result = UserLimitsConfig.fromJson(json); - expect(result, UserLimitsConfigFixture); - }); + test('supports value equality', () { + final anotherConfig = remoteConfigsFixturesData.first.user.limits; + expect(userLimitsConfigFixture, equals(anotherConfig)); }); - group('copyWith', () { - test('returns a new instance with updated values', () { - final newFollowedItemsLimit = Map.of( - UserLimitsConfigFixture.followedItemsLimit, - ); - newFollowedItemsLimit[AppUserRole.guestUser] = 6; - - final newSavedHeadlinesLimit = Map.of( - UserLimitsConfigFixture.savedHeadlinesLimit, - ); - newSavedHeadlinesLimit[AppUserRole.premiumUser] = 101; - - final updatedConfig = UserLimitsConfigFixture.copyWith( - followedItemsLimit: newFollowedItemsLimit, - savedHeadlinesLimit: newSavedHeadlinesLimit, - ); - - expect(updatedConfig.followedItemsLimit[AppUserRole.guestUser], 6); - expect( - updatedConfig.savedHeadlinesLimit[AppUserRole.guestUser], - UserLimitsConfigFixture.savedHeadlinesLimit[AppUserRole - .guestUser], - ); - expect(updatedConfig.savedHeadlinesLimit[AppUserRole.premiumUser], 101); - expect(updatedConfig, isNot(equals(UserLimitsConfigFixture))); - }); + test('props are correct', () { + expect( + userLimitsConfigFixture.props, + equals([ + userLimitsConfigFixture.followedItems, + userLimitsConfigFixture.savedHeadlines, + userLimitsConfigFixture.savedHeadlineFilters, + userLimitsConfigFixture.savedSourceFilters, + ]), + ); + }); - test('returns the same instance if no changes are made', () { - final updatedConfig = UserLimitsConfigFixture.copyWith(); - expect(updatedConfig, equals(UserLimitsConfigFixture)); - }); + test('can be created from JSON', () { + final fromJson = UserLimitsConfig.fromJson(json); + expect(fromJson, equals(userLimitsConfigFixture)); }); - group('Equatable', () { - test('instances with the same properties are equal', () { - final config1 = UserLimitsConfigFixture.copyWith(); - final config2 = UserLimitsConfigFixture.copyWith(); - expect(config1, config2); - }); + test('can be converted to JSON', () { + final toJson = userLimitsConfigFixture.toJson(); + expect(toJson, equals(json)); + }); - test('instances with different properties are not equal', () { - final config1 = UserLimitsConfigFixture.copyWith(); - final newFollowedItemsLimit = Map.of( - UserLimitsConfigFixture.followedItemsLimit, - ); - newFollowedItemsLimit[AppUserRole.guestUser] = 99; - final config2 = UserLimitsConfigFixture.copyWith( - followedItemsLimit: newFollowedItemsLimit, - ); - expect(config1, isNot(equals(config2))); - }); + test('copyWith creates a copy with updated values', () { + final updatedConfig = userLimitsConfigFixture.copyWith( + followedItems: {AppUserRole.guestUser: 100}, + ); + + expect(updatedConfig.followedItems[AppUserRole.guestUser], 100); + expect( + updatedConfig.savedHeadlines, + equals(userLimitsConfigFixture.savedHeadlines), + ); + expect(updatedConfig, isNot(equals(userLimitsConfigFixture))); }); + + test( + 'copyWith creates an identical copy when no arguments are provided', + () { + final copiedConfig = userLimitsConfigFixture.copyWith(); + expect(copiedConfig, equals(userLimitsConfigFixture)); + }, + ); }); } From ddde2196ce7f8650da8ef099e6da467c734c66fd Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:18:49 +0100 Subject: [PATCH 68/75] feat(models): add copyWith method to UserLimitsConfig - Implement copyWith method for UserLimitsConfig class - Allows creation of a new UserLimitsConfig instance with modified properties - Facilitates updating specific fields without creating a new instance from scratch --- lib/src/models/config/user_limits_config.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/src/models/config/user_limits_config.dart b/lib/src/models/config/user_limits_config.dart index 85c97764..c871b7ef 100644 --- a/lib/src/models/config/user_limits_config.dart +++ b/lib/src/models/config/user_limits_config.dart @@ -55,4 +55,20 @@ class UserLimitsConfig extends Equatable { savedHeadlineFilters, savedSourceFilters, ]; + + /// Creates a copy of this [UserLimitsConfig] but with the given fields + /// replaced with the new values. + UserLimitsConfig copyWith({ + Map? followedItems, + Map? savedHeadlines, + Map? savedHeadlineFilters, + Map? savedSourceFilters, + }) { + return UserLimitsConfig( + followedItems: followedItems ?? this.followedItems, + savedHeadlines: savedHeadlines ?? this.savedHeadlines, + savedHeadlineFilters: savedHeadlineFilters ?? this.savedHeadlineFilters, + savedSourceFilters: savedSourceFilters ?? this.savedSourceFilters, + ); + } } From 28a6170845d3c8549b14c38c0bcb32f743b25002 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:21:50 +0100 Subject: [PATCH 69/75] feat(UserConfig): add copyWith method for easier configuration updates - Implement copyWith method to create a new UserConfig instance with updated values - This change allows for more flexible and concise updates to user configuration --- lib/src/models/config/user_config.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/src/models/config/user_config.dart b/lib/src/models/config/user_config.dart index 2cce6e23..a0e63918 100644 --- a/lib/src/models/config/user_config.dart +++ b/lib/src/models/config/user_config.dart @@ -26,4 +26,14 @@ class UserConfig extends Equatable { @override List get props => [limits]; + + /// Creates a copy of this [UserConfig] but with the given fields + /// replaced with the new values. + UserConfig copyWith({ + UserLimitsConfig? limits, + }) { + return UserConfig( + limits: limits ?? this.limits, + ); + } } From 5d3922a2aa382cc7a5afbaa9ab45bcaeccad0451 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:23:37 +0100 Subject: [PATCH 70/75] test: update test cases to use new `Limits` model - Replace `userPreferenceConfig` with `user.limits` in test cases - Update `savedHeadlineFiltersLimit` to `savedHeadlineFilters` - Update `savedSourceFiltersLimit` to `savedSourceFilters` --- test/src/models/config/saved_filter_limits_test.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/src/models/config/saved_filter_limits_test.dart b/test/src/models/config/saved_filter_limits_test.dart index d5fdec6c..d3d2db1a 100644 --- a/test/src/models/config/saved_filter_limits_test.dart +++ b/test/src/models/config/saved_filter_limits_test.dart @@ -4,12 +4,14 @@ import 'package:test/test.dart'; void main() { group('SavedFilterLimits', () { final fullModel = remoteConfigsFixturesData[0] - .userPreferenceConfig - .savedHeadlineFiltersLimit[AppUserRole.standardUser]!; + .user + .limits + .savedHeadlineFilters[AppUserRole.standardUser]!; final minimalModel = remoteConfigsFixturesData[0] - .userPreferenceConfig - .savedSourceFiltersLimit[AppUserRole.standardUser]!; + .user + .limits + .savedSourceFilters[AppUserRole.standardUser]!; final fullJson = fullModel.toJson(); final minimalJson = minimalModel.toJson(); From 88c83c03d1cda4d4dac0f0a7b5c4fb5c989c8015 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:26:17 +0100 Subject: [PATCH 71/75] feat(UpdateConfig): add copyWith method for updating fields - Implement copyWith method to create a new UpdateConfig instance with modified fields - This enhancement allows for easier updates to the configuration without manually recreating the entire object --- lib/src/models/config/update_config.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/src/models/config/update_config.dart b/lib/src/models/config/update_config.dart index 38d40f81..ef0059f7 100644 --- a/lib/src/models/config/update_config.dart +++ b/lib/src/models/config/update_config.dart @@ -44,4 +44,20 @@ class UpdateConfig extends Equatable { iosUpdateUrl, androidUpdateUrl, ]; + + /// Creates a copy of this [UpdateConfig] but with the given fields + /// replaced with the new values. + UpdateConfig copyWith({ + String? latestAppVersion, + bool? isLatestVersionOnly, + String? iosUpdateUrl, + String? androidUpdateUrl, + }) { + return UpdateConfig( + latestAppVersion: latestAppVersion ?? this.latestAppVersion, + isLatestVersionOnly: isLatestVersionOnly ?? this.isLatestVersionOnly, + iosUpdateUrl: iosUpdateUrl ?? this.iosUpdateUrl, + androidUpdateUrl: androidUpdateUrl ?? this.androidUpdateUrl, + ); + } } From 1c2ae70f99f34b34bf4775675b1ab7e751279cdb Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:29:49 +0100 Subject: [PATCH 72/75] test(push_notifications): improve PushNotificationPayload test - Refactor test to use fixture data for consistency - Update props test to reflect current model structure - Enhance copyWith test to include additional fields - Simplify import statement for core package --- .../push_notification_payload_test.dart | 54 +++++++++---------- 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/test/src/models/push_notifications/push_notification_payload_test.dart b/test/src/models/push_notifications/push_notification_payload_test.dart index cb6a65a9..6d3e54b8 100644 --- a/test/src/models/push_notifications/push_notification_payload_test.dart +++ b/test/src/models/push_notifications/push_notification_payload_test.dart @@ -1,34 +1,15 @@ -import 'package:core/src/models/push_notifications/push_notification_payload.dart'; +import 'package:core/core.dart'; import 'package:test/test.dart'; void main() { group('PushNotificationPayload', () { - const title = 'Breaking News'; - const imageUrl = 'https://example.com/image.jpg'; - const data = { - 'contentType': 'headline', - 'id': 'headline-123', - }; - - const payload = PushNotificationPayload( - title: title, - imageUrl: imageUrl, - data: data, - ); - - final json = { - 'title': title, - 'imageUrl': imageUrl, - 'data': data, - }; + // Use a fixture to ensure consistency and avoid manual setup. + final payload = inAppNotificationsFixturesData.first.payload; + final json = payload.toJson(); test('supports value equality', () { // Arrange: Create another instance with the same values. - const anotherPayload = PushNotificationPayload( - title: title, - imageUrl: imageUrl, - data: data, - ); + final anotherPayload = payload.copyWith(); // Assert: The two instances should be equal. expect(payload, equals(anotherPayload)); @@ -36,7 +17,17 @@ void main() { test('props are correct', () { // Assert: The props list should contain all the fields. - expect(payload.props, equals([title, imageUrl, data])); + expect( + payload.props, + equals([ + payload.title, + payload.notificationId, + payload.notificationType, + payload.contentType, + payload.contentId, + payload.imageUrl, + ]), + ); }); test('can be created from JSON', () { @@ -58,17 +49,22 @@ void main() { test('copyWith creates a copy with updated values', () { // Arrange: Define the updated values. const newTitle = 'Updated News'; + const newContentId = 'new-content-id'; // Act: Create a copy with the updated values. - final copiedPayload = payload.copyWith(title: newTitle); + final copiedPayload = payload.copyWith( + title: newTitle, + contentId: newContentId, + ); // Assert: The new instance should have the updated values. expect(copiedPayload.title, equals(newTitle)); - expect(copiedPayload.imageUrl, equals(imageUrl)); - expect(copiedPayload.data, equals(data)); + expect(copiedPayload.contentId, equals(newContentId)); + expect(copiedPayload.imageUrl, equals(payload.imageUrl)); + expect(copiedPayload.notificationId, equals(payload.notificationId)); // Assert: The original instance should remain unchanged. - expect(payload.title, equals(title)); + expect(payload.title, isNot(equals(newTitle))); }); test( From 2087d58580880957639b6f867a670faced09f9ca Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:32:01 +0100 Subject: [PATCH 73/75] test(headline): update props count and list in headline test - Decrease expected props length from 13 to 12 - Update props list to reflect the correct number of properties --- test/src/models/entities/headline_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/src/models/entities/headline_test.dart b/test/src/models/entities/headline_test.dart index 2ada4ff5..b1d2af23 100644 --- a/test/src/models/entities/headline_test.dart +++ b/test/src/models/entities/headline_test.dart @@ -62,7 +62,7 @@ void main() { }); test('props list should contain all relevant fields', () { - expect(headlineFixture.props.length, 13); + expect(headlineFixture.props.length, 12); expect(headlineFixture.props, [ headlineFixture.id, headlineFixture.title, From 07e32df17fa44091219373bd6f74f3c638b13350 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:38:38 +0100 Subject: [PATCH 74/75] fix(remote_configs): add missing demo AdPlatformType entries - Added AdPlatformIdentifiers for AdPlatformType.demo with placeholder values - Ensures remote_configs.dart fixture now contains all necessary entries --- lib/src/fixtures/remote_configs.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/fixtures/remote_configs.dart b/lib/src/fixtures/remote_configs.dart index 82335b5a..3ceb256f 100644 --- a/lib/src/fixtures/remote_configs.dart +++ b/lib/src/fixtures/remote_configs.dart @@ -77,6 +77,11 @@ final remoteConfigsFixturesData = [ bannerAdId: 'ca-app-pub-3940256099942544/6300978111', interstitialAdId: 'ca-app-pub-3940256099942544/1033173712', ), + AdPlatformType.demo: AdPlatformIdentifiers( + nativeAdId: '_', + bannerAdId: '_', + interstitialAdId: '_', + ), }, feedAdConfiguration: FeedAdConfiguration( enabled: true, From 70c7c3aa405edf485987baedbb404084590dc787 Mon Sep 17 00:00:00 2001 From: fulleni Date: Tue, 25 Nov 2025 04:39:17 +0100 Subject: [PATCH 75/75] style: format --- lib/src/models/config/navigation_ad_frequency_config.dart | 2 +- lib/src/models/config/user_config.dart | 8 ++------ test/src/models/config/feed_decorator_config_test.dart | 6 ++++-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/src/models/config/navigation_ad_frequency_config.dart b/lib/src/models/config/navigation_ad_frequency_config.dart index 895e450d..517274a4 100644 --- a/lib/src/models/config/navigation_ad_frequency_config.dart +++ b/lib/src/models/config/navigation_ad_frequency_config.dart @@ -26,7 +26,7 @@ class NavigationAdFrequencyConfig extends Equatable { /// within the app before an interstitial ad is shown. final int internalNavigationsBeforeShowingInterstitialAd; - /// The number of external navigations a user needs to make + /// The number of external navigations a user needs to make /// within the app before an interstitial ad is shown. final int externalNavigationsBeforeShowingInterstitialAd; diff --git a/lib/src/models/config/user_config.dart b/lib/src/models/config/user_config.dart index a0e63918..adeef1b3 100644 --- a/lib/src/models/config/user_config.dart +++ b/lib/src/models/config/user_config.dart @@ -29,11 +29,7 @@ class UserConfig extends Equatable { /// Creates a copy of this [UserConfig] but with the given fields /// replaced with the new values. - UserConfig copyWith({ - UserLimitsConfig? limits, - }) { - return UserConfig( - limits: limits ?? this.limits, - ); + UserConfig copyWith({UserLimitsConfig? limits}) { + return UserConfig(limits: limits ?? this.limits); } } diff --git a/test/src/models/config/feed_decorator_config_test.dart b/test/src/models/config/feed_decorator_config_test.dart index df509f1c..0c4ba652 100644 --- a/test/src/models/config/feed_decorator_config_test.dart +++ b/test/src/models/config/feed_decorator_config_test.dart @@ -6,8 +6,10 @@ void main() { final remoteConfig = remoteConfigsFixturesData.first; final rateAppDecorator = remoteConfig.features.feed.decorators[FeedDecoratorType.rateApp]!; - final suggestedTopicsDecorator = - remoteConfig.features.feed.decorators[FeedDecoratorType.suggestedTopics]!; + final suggestedTopicsDecorator = remoteConfig + .features + .feed + .decorators[FeedDecoratorType.suggestedTopics]!; test('can be instantiated', () { expect(rateAppDecorator, isA());