From 0afc48deaa923b7aaf047291dbf113a47ccf4217 Mon Sep 17 00:00:00 2001 From: Prachi Date: Wed, 1 Feb 2023 12:22:34 +0530 Subject: [PATCH 01/17] Adding impression measurement : adding minimum view percentage for checking recyclerlist view item impression --- src/core/RecyclerListView.tsx | 8 ++++++- src/core/ViewabilityTracker.ts | 38 +++++++++++++++++++++++++++++----- src/core/VirtualRenderer.ts | 12 ++++++++--- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index 95dd87be..9d263b5c 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -76,6 +76,10 @@ export interface OnRecreateParams { lastOffset?: number; } +export interface ImpressionTrackingConfig { + minimumViewabilityPercentage: number; +} + export interface RecyclerListViewProps { layoutProvider: BaseLayoutProvider; dataProvider: BaseDataProvider; @@ -110,6 +114,7 @@ export interface RecyclerListViewProps { //For all props that need to be proxied to inner/external scrollview. Put them in an object and they'll be spread //and passed down. For better typescript support. scrollViewProps?: object; + impressionTrackingConfig?: ImpressionTrackingConfig; applyWindowCorrection?: (offsetX: number, offsetY: number, windowCorrection: WindowCorrection) => void; onItemLayout?: (index: number) => void; windowCorrectionConfig?: { value?: WindowCorrection, applyToInitialOffset?: boolean, applyToItemScroll?: boolean }; @@ -141,6 +146,7 @@ export default class RecyclerListView

{ return this.props.dataProvider.getStableId(index); - }, !props.disableRecycling); + }, !props.disableRecycling, props.impressionTrackingConfig); if (this.props.windowCorrectionConfig) { let windowCorrection; diff --git a/src/core/ViewabilityTracker.ts b/src/core/ViewabilityTracker.ts index 4f807631..a39178b3 100644 --- a/src/core/ViewabilityTracker.ts +++ b/src/core/ViewabilityTracker.ts @@ -1,6 +1,7 @@ import BinarySearch from "../utils/BinarySearch"; import { Dimension } from "./dependencies/LayoutProvider"; import { Layout } from "./layoutmanager/LayoutManager"; +import { ImpressionTrackingConfig } from "./RecyclerListView"; /*** * Given an offset this utility can compute visible items. Also tracks previously visible items to compute items which get hidden or visible * Virtual renderer uses callbacks from this utility to main recycle pool and the render stack. @@ -38,8 +39,9 @@ export default class ViewabilityTracker { private _layouts: Layout[] = []; private _actualOffset: number; private _defaultCorrection: WindowCorrection; + private _impressionTrackingConfig: ImpressionTrackingConfig | undefined; - constructor(renderAheadOffset: number, initialOffset: number) { + constructor(renderAheadOffset: number, initialOffset: number, impressionTrackingConfig: ImpressionTrackingConfig | undefined) { this._currentOffset = Math.max(0, initialOffset); this._maxOffset = 0; this._actualOffset = 0; @@ -58,6 +60,7 @@ export default class ViewabilityTracker { this._relevantDim = { start: 0, end: 0 }; this._defaultCorrection = { startCorrection: 0, endCorrection: 0, windowShift: 0 }; + this._impressionTrackingConfig = impressionTrackingConfig; } public init(windowCorrection: WindowCorrection): void { @@ -185,7 +188,8 @@ export default class ViewabilityTracker { for (let i = 0; i < count; i++) { itemRect = this._layouts[i]; this._setRelevantBounds(itemRect, relevantDim); - if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end)) { + const minimumViewabilityPercentage = this._impressionTrackingConfig && this._impressionTrackingConfig.minimumViewabilityPercentage || undefined; + if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end, minimumViewabilityPercentage)) { return i; } } @@ -242,7 +246,8 @@ export default class ViewabilityTracker { const itemRect = this._layouts[index]; let isFound = false; this._setRelevantBounds(itemRect, relevantDim); - if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end)) { + const mininumViewPercentage = this._impressionTrackingConfig && this._impressionTrackingConfig.minimumViewabilityPercentage || undefined; + if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end, mininumViewPercentage)) { if (insertOnTop) { newVisibleIndexes.splice(0, 0, index); newEngagedIndexes.splice(0, 0, index); @@ -297,8 +302,31 @@ export default class ViewabilityTracker { return this._itemIntersectsWindow(this._engagedWindow, startBound, endBound); } - private _itemIntersectsVisibleWindow(startBound: number, endBound: number): boolean { - return this._itemIntersectsWindow(this._visibleWindow, startBound, endBound); + private _isItemInVisibleBounds(window: Range, itemStartBound: number, itemEndBound: number, mininumViewPercentage: number | undefined): boolean { + let visibleItemContent = 0; + const itemSize = itemEndBound - itemStartBound; + + if (window.start >= itemStartBound && window.end >= itemEndBound) { + visibleItemContent = itemEndBound - window.start; + } else if (window.start <= itemStartBound && window.end <= itemEndBound) { + visibleItemContent = window.end - itemStartBound; + } else if (window.start <= itemStartBound && window.end >= itemEndBound) { + visibleItemContent = itemEndBound - itemStartBound; + } else if (window.start >= itemStartBound && window.end <= itemEndBound) { + return true; + } else { + return false; + } + + const isVisible = mininumViewPercentage + ? visibleItemContent / itemSize * 100 >= mininumViewPercentage + : visibleItemContent > 0; + return isVisible; + } + + private _itemIntersectsVisibleWindow(startBound: number, endBound: number, mininumViewPercentage?: number): boolean { + return this._isItemInVisibleBounds(this._visibleWindow, startBound, endBound, mininumViewPercentage) || + this._isZeroHeightEdgeElement(this._visibleWindow, startBound, endBound); } private _updateTrackingWindows(offset: number, correction: WindowCorrection): void { diff --git a/src/core/VirtualRenderer.ts b/src/core/VirtualRenderer.ts index d5ae0af4..3d6c02c0 100644 --- a/src/core/VirtualRenderer.ts +++ b/src/core/VirtualRenderer.ts @@ -7,6 +7,7 @@ import ViewabilityTracker, { TOnItemStatusChanged, WindowCorrection } from "./Vi import { ObjectUtil, Default } from "ts-object-utils"; import TSCast from "../utils/TSCast"; import { BaseDataProvider } from "./dependencies/DataProvider"; +import { ImpressionTrackingConfig } from "./RecyclerListView"; /*** * Renderer which keeps track of recyclable items and the currently rendered items. Notifies list view to re render if something changes, like scroll offset @@ -52,11 +53,13 @@ export default class VirtualRenderer { private _viewabilityTracker: ViewabilityTracker | null = null; private _dimensions: Dimension | null; private _optimizeForAnimations: boolean = false; + private _impressionTrackingConfig: ImpressionTrackingConfig | undefined = undefined; constructor(renderStackChanged: (renderStack: RenderStack) => void, scrollOnNextUpdate: (point: Point) => void, fetchStableId: StableIdProvider, - isRecyclingEnabled: boolean) { + isRecyclingEnabled: boolean, + impressionTrackingConfig?: ImpressionTrackingConfig) { //Keeps track of items that need to be rendered in the next render cycle this._renderStack = {}; @@ -78,6 +81,8 @@ export default class VirtualRenderer { this._startKey = 0; this.onVisibleItemsChanged = null; + + this._impressionTrackingConfig = impressionTrackingConfig; } public getLayoutDimension(): Dimension { @@ -192,9 +197,10 @@ export default class VirtualRenderer { if (this._params) { this._viewabilityTracker = new ViewabilityTracker( Default.value(this._params.renderAheadOffset, 0), - Default.value(this._params.initialOffset, 0)); + Default.value(this._params.initialOffset, 0), + this._impressionTrackingConfig); } else { - this._viewabilityTracker = new ViewabilityTracker(0, 0); + this._viewabilityTracker = new ViewabilityTracker(0, 0, this._impressionTrackingConfig); } this._prepareViewabilityTracker(); } From 96f8a598f581d9b6f25f94939c9fcbeb8ffa0ec5 Mon Sep 17 00:00:00 2001 From: Prachi Date: Thu, 2 Feb 2023 14:56:38 +0530 Subject: [PATCH 02/17] adding minimum item view time logic for impression measurement --- src/core/RecyclerListView.tsx | 12 ++++---- src/core/ViewabilityTracker.ts | 52 +++++++++++++++++++++++++++------- src/core/VirtualRenderer.ts | 17 +++++++---- 3 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index 9d263b5c..d7821f29 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -76,8 +76,9 @@ export interface OnRecreateParams { lastOffset?: number; } -export interface ImpressionTrackingConfig { - minimumViewabilityPercentage: number; +export interface ViewabilityConfig { + minimumItemViewPercentage: number; + minimumViewTime: number; } export interface RecyclerListViewProps { @@ -114,7 +115,7 @@ export interface RecyclerListViewProps { //For all props that need to be proxied to inner/external scrollview. Put them in an object and they'll be spread //and passed down. For better typescript support. scrollViewProps?: object; - impressionTrackingConfig?: ImpressionTrackingConfig; + viewabilityConfig?: ViewabilityConfig; applyWindowCorrection?: (offsetX: number, offsetY: number, windowCorrection: WindowCorrection) => void; onItemLayout?: (index: number) => void; windowCorrectionConfig?: { value?: WindowCorrection, applyToInitialOffset?: boolean, applyToItemScroll?: boolean }; @@ -146,7 +147,7 @@ export default class RecyclerListView

{ return this.props.dataProvider.getStableId(index); - }, !props.disableRecycling, props.impressionTrackingConfig); + }, !props.disableRecycling, props.viewabilityConfig); if (this.props.windowCorrectionConfig) { let windowCorrection; @@ -268,6 +269,7 @@ export default class RecyclerListView

= itemStartBound && window.end >= itemEndBound) { + // List item is visible in viewport from screen top visibleItemContent = itemEndBound - window.start; } else if (window.start <= itemStartBound && window.end <= itemEndBound) { + // List item is visible in viewport from screen bottom visibleItemContent = window.end - itemStartBound; } else if (window.start <= itemStartBound && window.end >= itemEndBound) { + // Entire list item is visible in viewport visibleItemContent = itemEndBound - itemStartBound; } else if (window.start >= itemStartBound && window.end <= itemEndBound) { + // List item is covering the entire screen return true; } else { + // List item is not visible in viewport return false; } @@ -385,9 +395,12 @@ export default class ViewabilityTracker { const now = this._calculateArrayDiff(newItems, oldItems); const notNow = this._calculateArrayDiff(oldItems, newItems); if (now.length > 0 || notNow.length > 0) { - // Adding default minimum view time of 250ms for performance optimization - if (minimumViewTime && minimumViewTime >= Constants.DEFAULT_MIN_VIEW_TIME) { - this.checkMinimumViewTime([...newItems], now, notNow, minimumViewTime, func); + if (minimumViewTime) { + // Adding default minimum view time for performance optimization + const finalMinViewTime = minimumViewTime <= Constants.DEFAULT_MIN_VIEW_TIME + ? Constants.DEFAULT_MIN_VIEW_TIME + : minimumViewTime; + this.checkMinimumViewTime([...newItems], now, notNow, finalMinViewTime, func); } else { func([...newItems], now, notNow); } diff --git a/src/core/VirtualRenderer.ts b/src/core/VirtualRenderer.ts index fde955fd..c749f893 100644 --- a/src/core/VirtualRenderer.ts +++ b/src/core/VirtualRenderer.ts @@ -117,6 +117,13 @@ export default class VirtualRenderer { this.onVisibleItemsChanged = callback; } + public updateViewabilityConfig(newViewabilityConfig: ViewabilityConfig): void { + this._viewabilityConfig = newViewabilityConfig; + if (this._viewabilityTracker) { + this._viewabilityTracker.updateViewabilityConfig(newViewabilityConfig); + } + } + public timerCleanup(): void { if (!this._viewabilityTracker) { return; diff --git a/src/core/constants/Constants.ts b/src/core/constants/Constants.ts index 4b7567fb..4d98027a 100644 --- a/src/core/constants/Constants.ts +++ b/src/core/constants/Constants.ts @@ -1,5 +1,5 @@ export const Constants = { CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX : "_offset", CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX: "_layouts", - DEFAULT_MIN_VIEW_TIME : 250, + DEFAULT_MIN_VIEW_TIME : 50, }; From 0e748f58e2cf3ee82153ea002485286d92deef1f Mon Sep 17 00:00:00 2001 From: Nitish Phanse Date: Fri, 14 Apr 2023 13:43:18 +0530 Subject: [PATCH 12/17] added lodash type for jenkins fail fix --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1553bd5e..2b34812a 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ }, "homepage": "https://github.com/Flipkart/recyclerlistview", "dependencies": { + "@types/lodash": "4.14.182", "lodash.debounce": "4.0.8", "prop-types": "15.8.1", "ts-object-utils": "0.0.5" From 0d263349085a1d405676aa6b2fc9f92ad8b54134 Mon Sep 17 00:00:00 2001 From: Nitish Phanse Date: Wed, 10 May 2023 14:47:57 +0530 Subject: [PATCH 13/17] added forced resolution of version for package lock for yarn installer deps --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 2b34812a..f1234803 100644 --- a/package.json +++ b/package.json @@ -53,5 +53,8 @@ "file-directives": "1.4.6", "tslint": "5.11.0", "typescript": "3.3.1" + }, + "resolutions": { + "@types/lodash": "4.14.182" } } From 88b7febb844d143b665930ccf251613ca29de2e3 Mon Sep 17 00:00:00 2001 From: Ashish Pal Date: Fri, 14 Jul 2023 16:26:50 +0530 Subject: [PATCH 14/17] resetting last visible and resported indexes --- src/core/RecyclerListView.tsx | 7 +++++++ src/core/ViewabilityTracker.ts | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index 33884555..87757d9b 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -322,6 +322,13 @@ export default class RecyclerListView

Date: Mon, 17 Jul 2023 12:52:07 +0530 Subject: [PATCH 15/17] Revert "Reset viewability tracker" --- src/core/RecyclerListView.tsx | 7 ------- src/core/ViewabilityTracker.ts | 5 ----- 2 files changed, 12 deletions(-) diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index 87757d9b..33884555 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -322,13 +322,6 @@ export default class RecyclerListView

Date: Mon, 17 Jul 2023 13:01:01 +0530 Subject: [PATCH 16/17] resooved lint issues --- src/core/RecyclerListView.tsx | 4 ++-- src/core/ViewabilityTracker.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/RecyclerListView.tsx b/src/core/RecyclerListView.tsx index 87757d9b..e9c23802 100644 --- a/src/core/RecyclerListView.tsx +++ b/src/core/RecyclerListView.tsx @@ -322,10 +322,10 @@ export default class RecyclerListView

Date: Tue, 21 Nov 2023 12:56:45 +0530 Subject: [PATCH 17/17] fixed version for @types/resize-observer-browser removed ^ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f1234803..e11df4d6 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/prop-types": "15.5.2", "@types/react-native": "0.49.5", "@types/react": "16.4.7", - "@types/resize-observer-browser": "^0.1.7", + "@types/resize-observer-browser": "0.1.7", "file-directives": "1.4.6", "tslint": "5.11.0", "typescript": "3.3.1"