-
Notifications
You must be signed in to change notification settings - Fork 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
Impression measurement config #1
base: master
Are you sure you want to change the base?
Changes from 9 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 { | ||
|
@@ -149,6 +156,11 @@ export default class ViewabilityTracker { | |
this._actualOffset = actualOffset; | ||
} | ||
|
||
public timerCleanup(): void { | ||
this.timers.forEach(clearTimeout); | ||
this.timers.clear(); | ||
} | ||
|
||
private _findFirstVisibleIndexOptimally(): number { | ||
let firstVisibleIndex = 0; | ||
|
||
|
@@ -185,7 +197,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 +255,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 +311,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) { | ||
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 |
||
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 { | ||
|
@@ -317,18 +354,43 @@ 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); | ||
// Adding default minimum view time of 250ms for performance optimization | ||
if (minimumViewTime && minimumViewTime >= Constants.DEFAULT_MIN_VIEW_TIME) { | ||
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. instead of not executing the minimumViewTime logic incase of DEFAULT_MIN_VIEW_TIME greater than minimumViewTime you could execute with minimumViewTime logic with default value. |
||
this.checkMinimumViewTime([...newItems], now, notNow, minimumViewTime, 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 : 250, | ||
}; |
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.