-
Notifications
You must be signed in to change notification settings - Fork 2
Impression measurement config #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0afc48d
96f8a59
79a439d
f94f8d4
fdd3de7
2066b1f
9c2be52
21c0318
db82652
b733a24
0ff8e70
0e748f5
0d26334
88b7feb
67db7b8
204576d
3c11259
02cf7a0
f765491
4f5c3aa
35e1bfa
8aba7e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
import BinarySearch from "../utils/BinarySearch"; | ||
import { Constants } from "./constants/Constants"; | ||
import { Dimension } from "./dependencies/LayoutProvider"; | ||
import { Layout } from "./layoutmanager/LayoutManager"; | ||
import { ViewabilityConfig } 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. | ||
|
@@ -34,12 +36,15 @@ export default class ViewabilityTracker { | |
private _isHorizontal: boolean; | ||
private _windowBound: number; | ||
private _visibleIndexes: number[]; | ||
private _lastReportedVisibleIndexes: number[]; | ||
private _engagedIndexes: number[]; | ||
private _layouts: Layout[] = []; | ||
private _actualOffset: number; | ||
private _defaultCorrection: WindowCorrection; | ||
private _viewabilityConfig: ViewabilityConfig | undefined; | ||
private timers: Set<number> = new Set(); | ||
|
||
constructor(renderAheadOffset: number, initialOffset: number) { | ||
constructor(renderAheadOffset: number, initialOffset: number, viewabilityConfig: ViewabilityConfig | undefined) { | ||
this._currentOffset = Math.max(0, initialOffset); | ||
this._maxOffset = 0; | ||
this._actualOffset = 0; | ||
|
@@ -52,12 +57,14 @@ export default class ViewabilityTracker { | |
|
||
this._visibleIndexes = []; //needs to be sorted | ||
this._engagedIndexes = []; //needs to be sorted | ||
this._lastReportedVisibleIndexes = []; | ||
|
||
this.onVisibleRowsChanged = null; | ||
this.onEngagedRowsChanged = null; | ||
|
||
this._relevantDim = { start: 0, end: 0 }; | ||
this._defaultCorrection = { startCorrection: 0, endCorrection: 0, windowShift: 0 }; | ||
this._viewabilityConfig = viewabilityConfig; | ||
} | ||
|
||
public init(windowCorrection: WindowCorrection): void { | ||
|
@@ -85,6 +92,10 @@ export default class ViewabilityTracker { | |
this.updateOffset(offset, false, this._defaultCorrection); | ||
} | ||
|
||
public updateViewabilityConfig(newViewabilityConfig: ViewabilityConfig): void { | ||
this._viewabilityConfig = newViewabilityConfig; | ||
} | ||
|
||
public updateOffset(offset: number, isActual: boolean, windowCorrection: WindowCorrection): void { | ||
let correctedOffset = offset; | ||
if (isActual) { | ||
|
@@ -142,13 +153,23 @@ export default class ViewabilityTracker { | |
this.forceRefreshWithOffset(this._currentOffset); | ||
} | ||
|
||
public resetViewabilityTracking(): void { | ||
this._visibleIndexes = []; | ||
this._lastReportedVisibleIndexes = []; | ||
} | ||
|
||
public getCurrentRenderAheadOffset(): number { | ||
return this._renderAheadOffset; | ||
} | ||
public setActualOffset(actualOffset: number): void { | ||
this._actualOffset = actualOffset; | ||
} | ||
|
||
public timerCleanup(): void { | ||
this.timers.forEach(clearTimeout); | ||
this.timers.clear(); | ||
} | ||
|
||
private _findFirstVisibleIndexOptimally(): number { | ||
let firstVisibleIndex = 0; | ||
|
||
|
@@ -185,7 +206,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 minimumItemViewPercentage = this._viewabilityConfig && this._viewabilityConfig.minimumItemViewPercentage || undefined; | ||
if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end, minimumItemViewPercentage)) { | ||
return i; | ||
} | ||
} | ||
|
@@ -242,7 +264,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._viewabilityConfig && this._viewabilityConfig.minimumItemViewPercentage || undefined; | ||
if (this._itemIntersectsVisibleWindow(relevantDim.start, relevantDim.end, mininumViewPercentage)) { | ||
if (insertOnTop) { | ||
newVisibleIndexes.splice(0, 0, index); | ||
newEngagedIndexes.splice(0, 0, index); | ||
|
@@ -297,8 +320,37 @@ 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); | ||
// Checks if list item is visible in viewport as per minimum view poercentage | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you add some comments explaining the if-else with some examples |
||
// 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; | ||
} | ||
|
||
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 { | ||
|
@@ -317,18 +369,46 @@ export default class ViewabilityTracker { | |
|
||
//TODO:Talha optimize this | ||
private _diffUpdateOriginalIndexesAndRaiseEvents(newVisibleItems: number[], newEngagedItems: number[]): void { | ||
this._diffArraysAndCallFunc(newVisibleItems, this._visibleIndexes, this.onVisibleRowsChanged); | ||
const minimumViewTime = this._viewabilityConfig && this._viewabilityConfig.minimumViewTime | ||
? this._viewabilityConfig.minimumViewTime | ||
: 0; | ||
this._diffArraysAndCallFunc(newVisibleItems, this._visibleIndexes, this.onVisibleRowsChanged, minimumViewTime); | ||
this._diffArraysAndCallFunc(newEngagedItems, this._engagedIndexes, this.onEngagedRowsChanged); | ||
this._visibleIndexes = newVisibleItems; | ||
this._engagedIndexes = newEngagedItems; | ||
} | ||
|
||
private _diffArraysAndCallFunc(newItems: number[], oldItems: number[], func: TOnItemStatusChanged | null): void { | ||
private checkMinimumViewTime = (all: number[], now: number[], notNow: number[], minimumViewTime: number, callbackFunc: TOnItemStatusChanged): void => { | ||
const that = this; | ||
const timeoutId = setTimeout(() => { | ||
that.timers.delete(timeoutId); | ||
|
||
const currAll = all.filter((index) => that._visibleIndexes.indexOf(index) >= 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the max array batch size of all: number[] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
const currNow = currAll.filter((index) => that._lastReportedVisibleIndexes.indexOf(index) === -1); | ||
const currNotNow = that._lastReportedVisibleIndexes.filter((index) => currAll.indexOf(index) === -1); | ||
|
||
if (currAll.length > 0 && (currNow.length > 0 || currNotNow.length > 0)) { | ||
that._lastReportedVisibleIndexes = currAll; | ||
callbackFunc(currAll, currNow, currNotNow); | ||
} | ||
}, minimumViewTime); | ||
this.timers.add(timeoutId); | ||
} | ||
|
||
private _diffArraysAndCallFunc(newItems: number[], oldItems: number[], func: TOnItemStatusChanged | null, minimumViewTime?: number): void { | ||
if (func) { | ||
const now = this._calculateArrayDiff(newItems, oldItems); | ||
const notNow = this._calculateArrayDiff(oldItems, newItems); | ||
if (now.length > 0 || notNow.length > 0) { | ||
func([...newItems], now, notNow); | ||
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); | ||
} | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export const Constants = { | ||
CONTEXT_PROVIDER_OFFSET_KEY_SUFFIX : "_offset", | ||
CONTEXT_PROVIDER_LAYOUT_KEY_SUFFIX: "_layouts", | ||
DEFAULT_MIN_VIEW_TIME : 50, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
viewabilityConfig is just initialized in the constructor any changes to viewabilityConfig after the first render will not be respected.
This use case would come for pagination later with multiple impression configs at the widget level.