@@ -165,13 +165,13 @@ class FeedDecoratorService {
165165 required List <FeedItem > feedItems,
166166 required User ? user,
167167 required AdConfig adConfig,
168- int currentFeedItemCount = 0 ,
168+ int processedContentItemCount = 0 ,
169169 }) {
170170 return _injectAds (
171171 feedItems: feedItems,
172172 user: user,
173173 adConfig: adConfig,
174- currentFeedItemCount : currentFeedItemCount ,
174+ processedContentItemCount : processedContentItemCount ,
175175 );
176176 }
177177
@@ -343,12 +343,26 @@ class FeedDecoratorService {
343343 }
344344 }
345345
346- /// Injects ads into a list of feed items based on frequency rules.
346+ /// Injects ads into a list of [FeedItem] s based on frequency rules.
347+ ///
348+ /// This method ensures that ads are placed according to the `adPlacementInterval`
349+ /// (initial buffer before the first ad) and `adFrequency` (subsequent ad spacing).
350+ /// It correctly accounts for content items only, ignoring previously injected ads
351+ /// when calculating placement.
352+ ///
353+ /// [feedItems] : The list of feed items (headlines, decorators) to inject ads into.
354+ /// [user] : The current authenticated user, used to determine ad configuration.
355+ /// [adConfig] : The remote configuration for ad display rules.
356+ /// [processedContentItemCount] : The count of *content items* (non-ad, non-decorator)
357+ /// that have already been processed in previous feed loads/pages. This is
358+ /// crucial for maintaining correct ad placement across pagination.
359+ ///
360+ /// Returns a new list of [FeedItem] objects, interspersed with ads.
347361 List <FeedItem > _injectAds ({
348362 required List <FeedItem > feedItems,
349363 required User ? user,
350364 required AdConfig adConfig,
351- int currentFeedItemCount = 0 ,
365+ int processedContentItemCount = 0 ,
352366 }) {
353367 final userRole = user? .appRole ?? AppUserRole .guestUser;
354368
@@ -374,21 +388,31 @@ class FeedDecoratorService {
374388 }
375389
376390 final result = < FeedItem > [];
377- var headlinesInBatch = 0 ;
391+ // This counter tracks the absolute number of *content items* (headlines,
392+ // topics, sources, countries) processed so far, including those from
393+ // previous pages. This is key for accurate ad placement.
394+ var currentContentItemCount = processedContentItemCount;
378395
379396 for (final item in feedItems) {
380397 result.add (item);
381- headlinesInBatch++ ;
382398
383- // Calculate the total number of items processed so far, including
384- // those from previous pages.
385- final totalItemsSoFar = currentFeedItemCount + result.length;
399+ // Only increment the content item counter if the current item is
400+ // a primary content type (not an ad or a decorator).
401+ // This ensures ad placement is based purely on content density.
402+ if (item is Headline ||
403+ item is Topic ||
404+ item is Source ||
405+ item is Country ) {
406+ currentContentItemCount++ ;
407+ }
386408
387- // Check if an ad should be injected.
388- // The total number of items must be past the initial placement interval,
389- // AND the number of content items in this batch must meet the frequency.
390- if (totalItemsSoFar >= adPlacementInterval &&
391- headlinesInBatch % adFrequency == 0 ) {
409+ // Check if an ad should be injected at the current position.
410+ // An ad is injected if:
411+ // 1. We have passed the initial placement interval.
412+ // 2. The number of content items *after* the initial interval is a
413+ // multiple of the ad frequency.
414+ if (currentContentItemCount > adPlacementInterval &&
415+ (currentContentItemCount - adPlacementInterval) % adFrequency == 0 ) {
392416 final adToInject = _getAdToInject ();
393417 if (adToInject != null ) {
394418 result.add (adToInject);
0 commit comments