-
Notifications
You must be signed in to change notification settings - Fork 265
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* WIP: Compute blank area and report it to JS side * Unit Tests for isWithinBounds method * Updated isWithinBounds tests * Clean up redundant files * - Exclude RNRecyclerFlatList from autolinking - Include RNRecyclerFlatList's test target * Move source files to other directories * Rename Android on blank area event to be consistent with Android * Move useOnNativeBlankEventArea to /src * Update Android to convert points received from JS to pixels * Remove conversion to pixels on Android native side * Update after PR comments * Update after PR comments * WIP: Compute blank area and report it to JS side * Unit Tests for isWithinBounds method * Updated isWithinBounds tests * Clean up redundant files * - Exclude RNRecyclerFlatList from autolinking - Include RNRecyclerFlatList's test target * Move source files to other directories * Rename Android on blank area event to be consistent with Android * Move useOnNativeBlankEventArea to /src * Update Android to convert points received from JS to pixels * Remove conversion to pixels on Android native side * Update after PR comments * Update after PR comments * Update after PR comments * Setup ReactNativePerformance Flipper plugin * Rename the blank area event: "instrumentation" -> "blankAreaEvent" * Add blankAreaStart and blankAreaEnd offsets to onBlankArea event on iOS * WIP: FlatList blank area on iOS * Fix onBlankArea event * WIP: Some changes * Minor changes * Android implementation of BlankAreaView * Minor changes * Remove redundant files that got added during rebase * Podfile changes * Address PR comments * Fix lint issues Co-authored-by: Elvira Burchik <elviraburchik@gmail.com>
- Loading branch information
1 parent
264d3f8
commit 5b32f90
Showing
16 changed files
with
266 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
android/src/main/kotlin/com/shopify/reactnative/recycler_flat_list/BlankAreaView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package com.shopify.reactnative.recycler_flat_list | ||
|
||
import android.content.Context | ||
import android.graphics.Canvas | ||
import android.util.DisplayMetrics | ||
import android.view.View | ||
import android.view.ViewGroup | ||
import com.facebook.react.bridge.Arguments | ||
import com.facebook.react.bridge.ReactContext | ||
import com.facebook.react.bridge.WritableMap | ||
import com.facebook.react.views.view.ReactViewGroup | ||
import com.facebook.react.modules.core.DeviceEventManagerModule.RCTDeviceEventEmitter | ||
|
||
|
||
class BlankAreaView(context: Context) : ReactViewGroup(context) { | ||
private var pixelDensity = 1.0; | ||
|
||
private val scrollView: View | ||
get() { | ||
return getChildAt(0) | ||
} | ||
|
||
private val horizontal: Boolean | ||
get() { | ||
return false | ||
} | ||
|
||
private val listSize: Int | ||
get() { | ||
return if (horizontal) scrollView.width else scrollView.height | ||
} | ||
|
||
private val scrollOffset: Int | ||
get() { | ||
return if (horizontal) scrollView.scrollX else scrollView.scrollY | ||
} | ||
|
||
override fun onAttachedToWindow() { | ||
super.onAttachedToWindow() | ||
val dm = DisplayMetrics() | ||
display.getRealMetrics(dm) | ||
pixelDensity = dm.density.toDouble() | ||
} | ||
|
||
override fun dispatchDraw(canvas: Canvas?) { | ||
super.dispatchDraw(canvas) | ||
|
||
val (blankOffsetTop, blankOffsetBottom) = computeBlankFromGivenOffset() | ||
emitBlankAreaEvent(blankOffsetTop, blankOffsetBottom) | ||
} | ||
|
||
fun computeBlankFromGivenOffset(): Pair<Int, Int> { | ||
val cells = ((scrollView as ViewGroup).getChildAt(0) as ViewGroup).getChildren().filterNotNull().map { it as ViewGroup } | ||
if (cells.isEmpty()) { | ||
return Pair(0, 0) | ||
} | ||
|
||
try { | ||
val firstCell = cells.first { isWithinBounds(it) && it.getChildren().isNotEmpty() } | ||
val lastCell = cells.last { isWithinBounds(it) && it.getChildren().isNotEmpty() } | ||
val blankOffsetTop = firstCell.top - scrollOffset | ||
val blankOffsetBottom = scrollOffset + listSize - lastCell.bottom | ||
return Pair(blankOffsetTop, blankOffsetBottom) | ||
} catch (e: NoSuchElementException) { | ||
return Pair(0, listSize) | ||
} | ||
} | ||
|
||
private fun emitBlankAreaEvent(blankOffsetTop: Int, blankOffsetBottom: Int) { | ||
val event: WritableMap = Arguments.createMap() | ||
event.putDouble("offsetStart", blankOffsetTop / pixelDensity) | ||
event.putDouble("offsetEnd", blankOffsetBottom / pixelDensity) | ||
event.putDouble("listSize", listSize / pixelDensity) | ||
val reactContext = context as ReactContext | ||
reactContext | ||
.getJSModule(RCTDeviceEventEmitter::class.java) | ||
.emit(Constants.EVENT_BLANK_AREA, event) | ||
} | ||
|
||
private fun isWithinBounds(view: View): Boolean { | ||
return if (!horizontal) { | ||
(view.top >= (scrollView.scrollY - scrollView.height) || view.bottom >= (scrollView.scrollY - scrollView.height)) && | ||
(view.top <= scrollView.scrollY + scrollView.height || view.bottom <= scrollView.scrollY + scrollView.height) | ||
} else { | ||
(view.left >= (scrollView.scrollX - scrollView.width) || view.right >= (scrollView.scrollX - scrollView.width)) && | ||
(view.left <= scrollView.scrollX + listSize || view.right <= scrollView.scrollX + scrollView.width) | ||
} | ||
} | ||
|
||
private fun ViewGroup.getChildren(): List<View?> { | ||
return (0..childCount).map { | ||
this.getChildAt(it) | ||
} | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
android/src/main/kotlin/com/shopify/reactnative/recycler_flat_list/BlankAreaViewManager.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.shopify.reactnative.recycler_flat_list | ||
|
||
import com.facebook.react.common.MapBuilder | ||
import com.facebook.react.module.annotations.ReactModule | ||
import com.facebook.react.uimanager.ThemedReactContext | ||
import com.facebook.react.views.view.ReactViewGroup | ||
import com.facebook.react.views.view.ReactViewManager | ||
|
||
@ReactModule(name = BlankAreaViewManager.REACT_CLASS) | ||
class BlankAreaViewManager: ReactViewManager() { | ||
companion object { | ||
const val REACT_CLASS = "BlankAreaView" | ||
} | ||
|
||
override fun getName(): String { | ||
return REACT_CLASS | ||
} | ||
|
||
override fun createViewInstance(context: ThemedReactContext): ReactViewGroup { | ||
return BlankAreaView(context) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import Foundation | ||
import UIKit | ||
import React | ||
|
||
@objc class BlankAreaView: UIView { | ||
private var observation: NSKeyValueObservation? | ||
private var scrollView: UIScrollView? { | ||
subviews.first?.subviews.first as? UIScrollView | ||
} | ||
private var isHorizontal: Bool { | ||
scrollView.map { $0.contentSize.width > $0.frame.width } ?? true | ||
} | ||
private var listSize: CGFloat { | ||
guard let scrollView = scrollView else { return 0 } | ||
return isHorizontal ? scrollView.frame.width : scrollView.frame.height | ||
} | ||
|
||
override func layoutSubviews() { | ||
super.layoutSubviews() | ||
guard | ||
observation == nil, | ||
let scrollView = scrollView | ||
else { return } | ||
observation = scrollView.observe(\.contentOffset, changeHandler: { [weak self] scrollView, _ in | ||
guard let self = self else { return } | ||
|
||
let (offsetStart, offsetEnd) = self.computeBlankFromGivenOffset(for: scrollView) | ||
|
||
BlankAreaEventEmitter.sharedInstance?.onBlankArea( | ||
offsetStart: offsetStart, | ||
offsetEnd: offsetEnd, | ||
listSize: self.listSize | ||
) ?? assertionFailure("BlankAreaEventEmitter.sharedInstance was not initialized") | ||
}) | ||
} | ||
|
||
private func computeBlankFromGivenOffset(for scrollView: UIScrollView) -> (CGFloat, CGFloat) { | ||
let cells = scrollView.subviews.first(where: { $0 is RCTScrollContentView })?.subviews ?? [] | ||
guard !cells.isEmpty else { return (0, 0) } | ||
|
||
let scrollOffset = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y | ||
guard | ||
let firstCell = cells.first(where: { scrollViewContains($0, scrollOffset: scrollOffset) && !$0.subviews.flatMap(\.subviews).isEmpty }), | ||
let lastCell = cells.last(where: { scrollViewContains($0, scrollOffset: scrollOffset) && !$0.subviews.flatMap(\.subviews).isEmpty }) | ||
else { | ||
return (0, listSize) | ||
} | ||
let blankOffsetTop: CGFloat | ||
let blankOffsetBottom: CGFloat | ||
if isHorizontal { | ||
blankOffsetTop = firstCell.frame.minX - scrollOffset | ||
blankOffsetBottom = scrollOffset + listSize - lastCell.frame.maxX | ||
} else { | ||
blankOffsetTop = firstCell.frame.minY - scrollOffset | ||
blankOffsetBottom = scrollOffset + listSize - lastCell.frame.maxY | ||
} | ||
return (blankOffsetTop, blankOffsetBottom) | ||
} | ||
|
||
private func scrollViewContains( | ||
_ cellView: UIView, | ||
scrollOffset: CGFloat | ||
) -> Bool { | ||
let boundsStart = scrollOffset | ||
let boundsEnd = scrollOffset + listSize | ||
let cellFrame = cellView.frame | ||
|
||
if isHorizontal { | ||
return (cellFrame.minX >= boundsStart || cellFrame.maxX >= boundsStart) && (cellFrame.minX <= boundsEnd || cellFrame.maxX <= boundsEnd) | ||
} else { | ||
return (cellFrame.minY >= boundsStart || cellFrame.maxY >= boundsStart) && (cellFrame.minY <= boundsEnd || cellFrame.maxY <= boundsEnd) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#import <Foundation/Foundation.h> | ||
#import <React/RCTViewManager.h> | ||
|
||
@interface RCT_EXTERN_MODULE(BlankAreaViewManager, RCTViewManager) | ||
|
||
@end |
Oops, something went wrong.