Skip to content
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
460d1ae
refactor(core): remove unused AdConfig model
fulleni Aug 28, 2025
f1e3106
feat: add AdPlatformType enum
fulleni Aug 28, 2025
77506bc
build(deps): update multiple dependencies
fulleni Aug 28, 2025
7281fdc
feat(enums): add InArticleAdSlotType enum
fulleni Aug 28, 2025
33f0f84
feat(models): add local ad model for custom advertisements
fulleni Aug 28, 2025
e2f1f2b
refactor: add `meta` dependency to `local_ad.dart`
fulleni Aug 28, 2025
a6add14
feat(models): add AdPlatformIdentifiers class
fulleni Aug 28, 2025
d00b109
feat(models): add FeedAdFrequencyConfig model
fulleni Aug 28, 2025
89d5d0d
feat(core): add model for article interstitial ad configuration
fulleni Aug 28, 2025
a5aa57c
feat(core): add article ad configuration model
fulleni Aug 28, 2025
1e0bb2b
feat(models): add ArticleInterstitialAdFrequencyConfig model
fulleni Aug 28, 2025
e9a2006
feat(core): add feed ad configuration model
fulleni Aug 28, 2025
3dfe16d
feat(config): add in-article ad slot configuration model
fulleni Aug 28, 2025
e3170de
style: Remove unnecessary blank line
fulleni Aug 28, 2025
bfc9aa4
feat(core): add AdConfig model for ad-related configurations
fulleni Aug 28, 2025
2a8d7b2
chore: build files
fulleni Aug 28, 2025
5fd612d
chore: sync barrel files
fulleni Aug 28, 2025
5f04805
refactor(config): update remote config fixtures with detailed ad sett…
fulleni Aug 28, 2025
6340e19
hcore: lint
fulleni Aug 28, 2025
d80011e
test(core): add AdPlatformType enum tests
fulleni Aug 28, 2025
d2d40d6
test(core): add InArticleAdSlotType enum tests
fulleni Aug 28, 2025
d13ee94
test(core): add LocalAd model tests
fulleni Aug 28, 2025
3556c46
test(core): add AdPlatformIdentifiers model tests
fulleni Aug 28, 2025
dd8fff9
test(core): add ArticleInterstitialAdConfiguration tests
fulleni Aug 28, 2025
7967b1a
test(core): add ArticleInterstitialAdFrequencyConfig tests
fulleni Aug 28, 2025
9aa63d4
test(config): add FeedAdConfiguration model tests
fulleni Aug 28, 2025
b6b5d6f
test(core): add FeedAdFrequencyConfig model tests
fulleni Aug 28, 2025
8739a49
test(core): add InArticleAdSlotConfiguration model tests
fulleni Aug 28, 2025
fe66563
test(core): add ArticleAdConfiguration tests
fulleni Aug 28, 2025
a3a15bb
test(core): add AdConfig model tests
fulleni Aug 28, 2025
0a747c6
test(remote_config): add adConfig to copyWith and update test case
fulleni Aug 28, 2025
dd4d353
docs(config): clarify ad injection logic in FeedAdFrequencyConfig
fulleni Aug 28, 2025
1150fa4
style: format
fulleni Aug 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions lib/src/enums/ad_platform_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/// {@template ad_platform_type}
/// Defines the single ad platform to be used across the entire application.
/// {@endtemplate}
enum AdPlatformType {
/// Google AdMob platform.
admob,

/// Local custom ad platform.
local,
}
2 changes: 2 additions & 0 deletions lib/src/enums/enums.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'ad_platform_type.dart';
export 'app_accent_theme.dart';
export 'app_base_theme.dart';
export 'app_font_weight.dart';
Expand All @@ -10,5 +11,6 @@ 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 'sort_order.dart';
export 'source_type.dart';
13 changes: 13 additions & 0 deletions lib/src/enums/in_article_ad_slot_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// {@template in_article_ad_slot_type}
/// Defines specific, standard locations for ads within an article's content.
/// {@endtemplate}
enum InArticleAdSlotType {
/// Ad placed directly below the main article image.
belowMainArticleImage,

/// Ad placed just before a "Continue Reading" button.
aboveArticleContinueReadingButton,

/// Ad placed just after a "Continue Reading" button.
belowArticleContinueReadingButton,
}
107 changes: 98 additions & 9 deletions lib/src/fixtures/remote_configs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,104 @@ final List<RemoteConfig> remoteConfigsFixturesData = [
premiumSavedHeadlinesLimit: 100,
),
adConfig: const AdConfig(
guestAdFrequency: 5,
guestAdPlacementInterval: 3,
authenticatedAdFrequency: 10,
authenticatedAdPlacementInterval: 5,
premiumAdFrequency: 0,
premiumAdPlacementInterval: 0,
guestArticlesToReadBeforeShowingInterstitialAds: 5,
standardUserArticlesToReadBeforeShowingInterstitialAds: 10,
premiumUserArticlesToReadBeforeShowingInterstitialAds: 50000,
primaryAdPlatform: AdPlatformType.admob,
platformAdIdentifiers: {
AdPlatformType.admob: AdPlatformIdentifiers(
feedNativeAdId: 'ca-app-pub-3940256099942544/2247696110',
feedBannerAdId: 'ca-app-pub-3940256099942544/6300978111',
articleInterstitialAdId: 'ca-app-pub-3940256099942544/1033173712',
inArticleNativeAdId: 'ca-app-pub-3940256099942544/3986624511',
inArticleBannerAdId: 'ca-app-pub-3940256099942544/6300978111',
),
AdPlatformType.local: AdPlatformIdentifiers(
feedNativeAdId: 'local_feed_native_ad_id',
feedBannerAdId: 'local_feed_banner_ad_id',
articleInterstitialAdId: 'local_article_interstitial_ad_id',
inArticleNativeAdId: 'local_in_article_native_ad_id',
inArticleBannerAdId: 'local_in_article_banner_ad_id',
),
},
localAdsCatalog: {
'local_feed_native_ad_id': LocalAd(
id: 'local_feed_native_ad_id',
title: 'Local Native Ad Title',
subtitle: 'This is a local native ad description.',
imageUrl: 'https://example.com/local_native_ad.png',
targetUrl: 'https://example.com/local_native_ad_target',
adType: AdType.native,
),
'local_feed_banner_ad_id': LocalAd(
id: 'local_feed_banner_ad_id',
title: 'Local Banner Ad Title',
subtitle: 'This is a local banner ad description.',
imageUrl: 'https://example.com/local_banner_ad.png',
targetUrl: 'https://example.com/local_banner_ad_target',
adType: AdType.banner,
),
'local_article_interstitial_ad_id': LocalAd(
id: 'local_article_interstitial_ad_id',
title: 'Local Interstitial Ad Title',
subtitle: 'This is a local interstitial ad description.',
imageUrl: 'https://example.com/local_interstitial_ad.png',
targetUrl: 'https://example.com/local_interstitial_ad_target',
adType: AdType.interstitial,
),
'local_in_article_native_ad_id': LocalAd(
id: 'local_in_article_native_ad_id',
title: 'Local In-Article Native Ad Title',
subtitle: 'This is a local in-article native ad description.',
imageUrl: 'https://example.com/local_in_article_native_ad.png',
targetUrl: 'https://example.com/local_in_article_native_ad_target',
adType: AdType.native,
),
'local_in_article_banner_ad_id': LocalAd(
id: 'local_in_article_banner_ad_id',
title: 'Local In-Article Banner Ad Title',
subtitle: 'This is a local in-article banner ad description.',
imageUrl: 'https://example.com/local_in_article_banner_ad.png',
targetUrl: 'https://example.com/local_in_article_banner_ad_target',
adType: AdType.banner,
),
},
feedAdConfiguration: FeedAdConfiguration(
enabled: true,
adType: AdType.native,
frequencyConfig: FeedAdFrequencyConfig(
guestAdFrequency: 5,
guestAdPlacementInterval: 3,
authenticatedAdFrequency: 10,
authenticatedAdPlacementInterval: 5,
premiumAdFrequency: 0,
premiumAdPlacementInterval: 0,
),
),
articleAdConfiguration: ArticleAdConfiguration(
enabled: true,
defaultInArticleAdType: AdType.native,
interstitialAdConfiguration: ArticleInterstitialAdConfiguration(
enabled: true,
adType: AdType.interstitial,
frequencyConfig: ArticleInterstitialAdFrequencyConfig(
guestArticlesToReadBeforeShowingInterstitialAds: 5,
standardUserArticlesToReadBeforeShowingInterstitialAds: 10,
premiumUserArticlesToReadBeforeShowingInterstitialAds: 50000,
),
),
inArticleAdSlotConfigurations: [
InArticleAdSlotConfiguration(
slotType: InArticleAdSlotType.belowMainArticleImage,
enabled: true,
),
InArticleAdSlotConfiguration(
slotType: InArticleAdSlotType.aboveArticleContinueReadingButton,
enabled: true,
),
InArticleAdSlotConfiguration(
slotType: InArticleAdSlotType.belowArticleContinueReadingButton,
enabled: true,
),
],
),
),
feedDecoratorConfig: const {
FeedDecoratorType.rateApp: FeedDecoratorConfig(
Expand Down
142 changes: 44 additions & 98 deletions lib/src/models/config/ad_config.dart
Original file line number Diff line number Diff line change
@@ -1,131 +1,77 @@
import 'package:core/src/models/config/remote_config.dart';
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/local_ad.dart';
import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';

part 'ad_config.g.dart';

/// {@template ad_config}
/// Defines configuration settings related to ad injection and display,
/// tiered by user role.
///
/// This model is part of the overall [RemoteConfig] and is used to control
/// how ads are integrated into the application's feed or other content areas
/// based on the user's authentication status or subscription level.
///
/// **Ad Injection Logic Explained:**
///
/// - __`AdFrequency`__: This determines *how often* an ad *can* be injected relative to the number of primary content items. For example, an `adFrequency` of 5 means an ad *could* be placed after every 5 primary items. It sets the overall density of ads in the feed.
///
/// - __`AdPlacementInterval`__: This sets a *minimum number of primary items* that must appear *before* the *first* ad is placed. It prevents ads from appearing right at the very beginning of the feed, ensuring the user sees some initial content first.
///
/// So, `AdFrequency` controls the spacing of ads *throughout* the feed (after the initial interval), while `AdPlacementInterval` controls where the *very first* ad can appear.
///
/// Think of it like this:
///
/// - `AdPlacementInterval` = 3: No ads will appear in the first 3 primary items.
/// - `AdFrequency` = 5: After the first 3 items, an ad *could* appear after item #5, then potentially after item #10, #15, etc.
/// This is the main container for all ad-related configurations.
/// {@endtemplate}
@immutable
@JsonSerializable(explicitToJson: true, includeIfNull: true, checked: true)
class AdConfig extends Equatable {
/// {@macro ad_config}
const AdConfig({
required this.guestAdFrequency,
required this.guestAdPlacementInterval,
required this.authenticatedAdFrequency,
required this.authenticatedAdPlacementInterval,
required this.premiumAdFrequency,
required this.premiumAdPlacementInterval,
required this.guestArticlesToReadBeforeShowingInterstitialAds,
required this.standardUserArticlesToReadBeforeShowingInterstitialAds,
required this.premiumUserArticlesToReadBeforeShowingInterstitialAds,
required this.primaryAdPlatform,
required this.platformAdIdentifiers,
required this.localAdsCatalog,
required this.feedAdConfiguration,
required this.articleAdConfiguration,
});

/// Factory method to create an [AdConfig] instance from a JSON map.
/// Creates an [AdConfig] from JSON data.
factory AdConfig.fromJson(Map<String, dynamic> json) =>
_$AdConfigFromJson(json);

/// See class documentation for details on AdFrequency.
final int guestAdFrequency;

/// See class documentation for details on AdPlacementInterval.
final int guestAdPlacementInterval;

/// See class documentation for details on AdFrequency.
final int authenticatedAdFrequency;

/// See class documentation for details on AdPlacementInterval.
final int authenticatedAdPlacementInterval;
/// Converts this [AdConfig] instance to JSON data.
Map<String, dynamic> toJson() => _$AdConfigToJson(this);

/// See class documentation for details on AdFrequency.
final int premiumAdFrequency;
/// Global choice: AdMob or Local.
final AdPlatformType primaryAdPlatform;

/// See class documentation for details on AdPlacementInterval.
final int premiumAdPlacementInterval;
/// Map to store identifiers for all platforms.
final Map<AdPlatformType, AdPlatformIdentifiers> platformAdIdentifiers;

/// The number of articles a guest user needs to read before an
/// interstitial ad is shown.
final int guestArticlesToReadBeforeShowingInterstitialAds;
/// All defined local ads by ID.
final Map<String, LocalAd> localAdsCatalog;

/// The number of articles a standard user needs to read before an
/// interstitial ad is shown.
final int standardUserArticlesToReadBeforeShowingInterstitialAds;
/// Configuration for main feed, search feed, similar headlines feed.
final FeedAdConfiguration feedAdConfiguration;

/// The number of articles a premium user needs to read before an
/// interstitial ad is shown.
final int premiumUserArticlesToReadBeforeShowingInterstitialAds;
/// Configuration for article page ads.
final ArticleAdConfiguration articleAdConfiguration;

/// Converts this [AdConfig] instance to a JSON map.
Map<String, dynamic> toJson() => _$AdConfigToJson(this);
@override
List<Object> get props => [
primaryAdPlatform,
platformAdIdentifiers,
localAdsCatalog,
feedAdConfiguration,
articleAdConfiguration,
];

/// Creates a copy of this [AdConfig] but with the given fields replaced
/// with the new values.
AdConfig copyWith({
int? guestAdFrequency,
int? guestAdPlacementInterval,
int? authenticatedAdFrequency,
int? authenticatedAdPlacementInterval,
int? premiumAdFrequency,
int? premiumAdPlacementInterval,
int? guestArticlesToReadBeforeShowingInterstitialAds,
int? standardUserArticlesToReadBeforeShowingInterstitialAds,
int? premiumUserArticlesToReadBeforeShowingInterstitialAds,
AdPlatformType? primaryAdPlatform,
Map<AdPlatformType, AdPlatformIdentifiers>? platformAdIdentifiers,
Map<String, LocalAd>? localAdsCatalog,
FeedAdConfiguration? feedAdConfiguration,
ArticleAdConfiguration? articleAdConfiguration,
}) {
return AdConfig(
guestAdFrequency: guestAdFrequency ?? this.guestAdFrequency,
guestAdPlacementInterval:
guestAdPlacementInterval ?? this.guestAdPlacementInterval,
authenticatedAdFrequency:
authenticatedAdFrequency ?? this.authenticatedAdFrequency,
authenticatedAdPlacementInterval:
authenticatedAdPlacementInterval ??
this.authenticatedAdPlacementInterval,
premiumAdFrequency: premiumAdFrequency ?? this.premiumAdFrequency,
premiumAdPlacementInterval:
premiumAdPlacementInterval ?? this.premiumAdPlacementInterval,
guestArticlesToReadBeforeShowingInterstitialAds:
guestArticlesToReadBeforeShowingInterstitialAds ??
this.guestArticlesToReadBeforeShowingInterstitialAds,
standardUserArticlesToReadBeforeShowingInterstitialAds:
standardUserArticlesToReadBeforeShowingInterstitialAds ??
this.standardUserArticlesToReadBeforeShowingInterstitialAds,
premiumUserArticlesToReadBeforeShowingInterstitialAds:
premiumUserArticlesToReadBeforeShowingInterstitialAds ??
this.premiumUserArticlesToReadBeforeShowingInterstitialAds,
primaryAdPlatform: primaryAdPlatform ?? this.primaryAdPlatform,
platformAdIdentifiers:
platformAdIdentifiers ?? this.platformAdIdentifiers,
localAdsCatalog: localAdsCatalog ?? this.localAdsCatalog,
feedAdConfiguration: feedAdConfiguration ?? this.feedAdConfiguration,
articleAdConfiguration:
articleAdConfiguration ?? this.articleAdConfiguration,
);
}

@override
List<Object> get props => [
guestAdFrequency,
guestAdPlacementInterval,
authenticatedAdFrequency,
authenticatedAdPlacementInterval,
premiumAdFrequency,
premiumAdPlacementInterval,
guestArticlesToReadBeforeShowingInterstitialAds,
standardUserArticlesToReadBeforeShowingInterstitialAds,
premiumUserArticlesToReadBeforeShowingInterstitialAds,
];
}
Loading
Loading