Skip to content

Commit

Permalink
[android][ios] Upgrade @shopify/flash-list to 1.3.0 (#19317)
Browse files Browse the repository at this point in the history
# Why

upgrade vendored modules for sdk 47

# How

`et uvm -m @shopify/flash-list -c v1.3.0`

# Test Plan

- android unversioned expo go + NCL flashlist
- ios unversioned expo go + NCL flashlist
  • Loading branch information
Kudo committed Oct 3, 2022
1 parent f2b8341 commit bdc678b
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 40 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,7 @@ Package-specific changes not released in any SDK will be added here just before
### 📚 3rd party library updates

- Updated `@stripe/stripe-react-native` from `0.13.1` to `0.18.1` on iOS. ([#19055](https://github.com/expo/expo/pull/19055) by [@tsapeta](https://github.com/tsapeta))
- Updated `@shopify/flash-list` from `1.1.0` to `1.3.0`. ([#19317](https://github.com/expo/expo/pull/19317) by [@kudo](https://github.com/kudo))

### 🛠 Breaking changes

Expand Down
Expand Up @@ -10,6 +10,8 @@ class AutoLayoutShadow {
var blankOffsetAtStart = 0 // Tracks blank area from the top
var blankOffsetAtEnd = 0 // Tracks blank area from the bottom

var lastMaxBoundOverall = 0 // Tracks where the last pixel is drawn in the overall

private var lastMaxBound = 0 // Tracks where the last pixel is drawn in the visible window
private var lastMinBound = 0 // Tracks where first pixel is drawn in the visible window

Expand All @@ -19,6 +21,7 @@ class AutoLayoutShadow {
var maxBound = 0
var minBound = Int.MAX_VALUE
var maxBoundNeighbour = 0
lastMaxBoundOverall = 0
for (i in 0 until sortedItems.size - 1) {
val cell = sortedItems[i]
val neighbour = sortedItems[i + 1]
Expand Down Expand Up @@ -65,6 +68,8 @@ class AutoLayoutShadow {
}
}
}
lastMaxBoundOverall = kotlin.math.max(lastMaxBoundOverall, if (horizontal) cell.right else cell.bottom)
lastMaxBoundOverall = kotlin.math.max(lastMaxBoundOverall, if (horizontal) neighbour.right else neighbour.bottom)
}
lastMaxBound = maxBoundNeighbour
lastMinBound = minBound
Expand Down
Expand Up @@ -3,7 +3,11 @@ package com.shopify.reactnative.flash_list
import android.content.Context
import android.graphics.Canvas
import android.util.DisplayMetrics
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.ScrollView
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableMap
Expand All @@ -24,19 +28,19 @@ class AutoLayoutView(context: Context) : ReactViewGroup(context) {
* can still cause views to overlap. Therefore, it makes sense to override draw to do correction. */
override fun dispatchDraw(canvas: Canvas?) {
fixLayout()
fixFooter()
super.dispatchDraw(canvas)

if (enableInstrumentation && parent?.parent != null) {
val parentScrollView = getParentScrollView()
if (enableInstrumentation && parentScrollView != null) {
/** Since we need to call this method with scrollOffset on the UI thread and not with the one react has we're querying parent's parent
directly which will be a ScrollView. If it isn't reported values will be incorrect but the component will not break.
RecyclerListView is expected not to change the hierarchy of children. */

val scrollContainerSize = (parent.parent as View).let {
if (alShadow.horizontal) it.width else it.height
}
val scrollOffset = (parent.parent as View).let {
if (alShadow.horizontal) it.scrollX else it.scrollY
}
val scrollContainerSize = if (alShadow.horizontal) parentScrollView.width else parentScrollView.height

val scrollOffset = if (alShadow.horizontal) parentScrollView.scrollX else parentScrollView.scrollY

val startOffset = if (alShadow.horizontal) left else top
val endOffset = if (alShadow.horizontal) right else bottom

Expand Down Expand Up @@ -66,6 +70,73 @@ class AutoLayoutView(context: Context) : ReactViewGroup(context) {
}
}

/** Fixes footer position along with rest of the items */
private fun fixFooter() {
val parentScrollView = getParentScrollView()
if (disableAutoLayout || parentScrollView == null) {
return
}
val isAutoLayoutEndVisible = if (alShadow.horizontal) right <= parentScrollView.width else bottom <= parentScrollView.height
if (!isAutoLayoutEndVisible) {
return
}
val autoLayoutParent = parent as? View
val footer = getFooter();
val diff = getFooterDiff()
if (diff == 0 || footer == null || autoLayoutParent == null) {
return
}

if (alShadow.horizontal) {
footer.offsetLeftAndRight(diff)
right += diff
autoLayoutParent.right += diff
} else {
footer.offsetTopAndBottom(diff)
bottom += diff
autoLayoutParent.bottom += diff
}
}

private fun getFooterDiff(): Int {
if (childCount == 0) {
alShadow.lastMaxBoundOverall = 0
} else if (childCount == 1) {
val firstChild = getChildAt(0)
alShadow.lastMaxBoundOverall = if (alShadow.horizontal) {
firstChild.right
} else {
firstChild.bottom
}
}
val autoLayoutEnd = if (alShadow.horizontal) right - left else bottom - top
return alShadow.lastMaxBoundOverall - autoLayoutEnd
}

private fun getFooter(): View? {
return (parent as? ViewGroup)?.let {
for (i in 0 until it.childCount) {
val view = it.getChildAt(i)
if (view is CellContainer && view.index == -1) {
return@let view
}
}
return@let null
}
}

private fun getParentScrollView(): View? {
var autoLayoutParent = parent;
while (autoLayoutParent != null) {
if (autoLayoutParent is ScrollView || autoLayoutParent is HorizontalScrollView) {
return autoLayoutParent as View
}
autoLayoutParent = autoLayoutParent.parent;
}
return null
}


/** TODO: Check migration to Fabric */
private fun emitBlankAreaEvent() {
val event: WritableMap = Arguments.createMap()
Expand Down
2 changes: 1 addition & 1 deletion apps/native-component-list/package.json
Expand Up @@ -53,7 +53,7 @@
"@react-navigation/material-bottom-tabs": "~5.3.9",
"@react-navigation/native": "~5.9.8",
"@react-navigation/stack": "~5.12.6",
"@shopify/flash-list": "1.1.0",
"@shopify/flash-list": "1.3.0",
"@shopify/react-native-skia": "0.1.137",
"@use-expo/permissions": "^2.0.0",
"date-format": "^2.0.0",
Expand Down
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Expand Up @@ -2012,7 +2012,7 @@ PODS:
- React-jsi (= 0.70.1)
- React-logger (= 0.70.1)
- React-perflogger (= 0.70.1)
- RNFlashList (1.1.0):
- RNFlashList (1.3.0):
- React-Core
- RNGestureHandler (2.5.0):
- React-Core
Expand Down Expand Up @@ -3508,7 +3508,7 @@ SPEC CHECKSUMS:
React-RCTVibration: e9164827303fb6a5cf79e4c4af4846a09956b11f
React-runtimeexecutor: a11d0c2e14140baf1e449264ca9168ae9ae6bbd0
ReactCommon: 7f86326b92009925c6dcf93f8e825060171c379f
RNFlashList: 031c182b95ead44fd0715f6029a744cd5e10d51f
RNFlashList: 5116f2de2f543f01bfc30b22d5942d5af84b43df
RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50
RNReanimated: 5c8c17e26787fd8984cd5accdc70fef2ca70aafd
RNScreens: 4a1af06327774490d97342c00aee0c2bafb497b7
Expand Down
@@ -1,6 +1,6 @@
{
"name": "RNFlashList",
"version": "1.1.0",
"version": "1.3.0",
"summary": "FlashList is a more performant FlatList replacement",
"homepage": "https://shopify.github.io/flash-list/",
"license": "MIT",
Expand All @@ -11,7 +11,7 @@
},
"source": {
"git": "https://github.com/shopify/flash-list.git",
"tag": "v#{s.version}"
"tag": "v1.3.0"
},
"source_files": "ios/Sources/**/*",
"requires_arc": true,
Expand Down
Expand Up @@ -39,17 +39,20 @@ import UIKit
private var enableInstrumentation = false
private var disableAutoLayout = false

/// Tracks where the last pixel is drawn in the overall
private var lastMaxBoundOverall: CGFloat = 0
/// Tracks where the last pixel is drawn in the visible window
private var lastMaxBound: CGFloat = 0
/// Tracks where first pixel is drawn in the visible window
private var lastMinBound: CGFloat = 0

override func layoutSubviews() {
fixLayout()
fixFooter()
super.layoutSubviews()

let scrollView = sequence(first: self, next: { $0.superview }).first(where: { $0 is UIScrollView })
guard enableInstrumentation, let scrollView = scrollView as? UIScrollView else { return }
let scrollView = getScrollView()
guard enableInstrumentation, let scrollView = scrollView else { return }

let scrollContainerSize = horizontal ? scrollView.frame.width : scrollView.frame.height
let currentScrollOffset = horizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y
Expand All @@ -76,6 +79,10 @@ import UIKit
)
}

func getScrollView() -> UIScrollView? {
return sequence(first: self, next: { $0.superview }).first(where: { $0 is UIScrollView }) as? UIScrollView
}

/// Sorts views by index and then invokes clearGaps which does the correction.
/// Performance: Sort is needed. Given relatively low number of views in RecyclerListView render tree this should be a non issue.
private func fixLayout() {
Expand Down Expand Up @@ -105,7 +112,7 @@ import UIKit
var minBound: CGFloat = CGFloat(Int.max)
var maxBoundNextCell: CGFloat = 0
let correctedScrollOffset = scrollOffset - (horizontal ? frame.minX : frame.minY)

lastMaxBoundOverall = 0
cellContainers.indices.dropLast().forEach { index in
let cellContainer = cellContainers[index]
let cellTop = cellContainer.frame.minY
Expand All @@ -115,9 +122,7 @@ import UIKit

let nextCell = cellContainers[index + 1]
let nextCellTop = nextCell.frame.minY
let nextCellBottom = nextCell.frame.maxY
let nextCellLeft = nextCell.frame.minX
let nextCellRight = nextCell.frame.maxX


guard
Expand All @@ -128,7 +133,10 @@ import UIKit
windowSize: windowSize,
isHorizontal: horizontal
)
else { return }
else {
updateLastMaxBoundOverall(currentCell: cellContainer, nextCell: nextCell)
return
}
let isNextCellVisible = isWithinBounds(
nextCell,
scrollOffset: correctedScrollOffset,
Expand All @@ -152,7 +160,7 @@ import UIKit
nextCell.frame.origin.x = maxBound
}
if isNextCellVisible {
maxBoundNextCell = max(maxBound, nextCellRight)
maxBoundNextCell = max(maxBound, nextCell.frame.maxX)
}
} else {
maxBound = max(maxBound, cellBottom)
Expand All @@ -169,15 +177,20 @@ import UIKit
nextCell.frame.origin.y = maxBound
}
if isNextCellVisible {
maxBoundNextCell = max(maxBound, nextCellBottom)
maxBoundNextCell = max(maxBound, nextCell.frame.maxY)
}
}
updateLastMaxBoundOverall(currentCell: cellContainer, nextCell: nextCell)
}

lastMaxBound = maxBoundNextCell
lastMinBound = minBound
}

private func updateLastMaxBoundOverall(currentCell: CellContainer, nextCell: CellContainer) {
lastMaxBoundOverall = max(lastMaxBoundOverall, horizontal ? currentCell.frame.maxX : currentCell.frame.maxY, horizontal ? nextCell.frame.maxX : nextCell.frame.maxY)
}

func computeBlankFromGivenOffset(
_ actualScrollOffset: CGFloat,
filledBoundMin: CGFloat,
Expand Down Expand Up @@ -215,4 +228,44 @@ import UIKit
return (cellFrame.minY >= boundsStart || cellFrame.maxY >= boundsStart) && (cellFrame.minY <= boundsEnd || cellFrame.maxY <= boundsEnd)
}
}

/// Fixes footer position along with rest of the items
private func fixFooter() {
guard !disableAutoLayout, let parentScrollView = getScrollView() else {
return
}

let isAutoLayoutEndVisible = horizontal ? frame.maxX <= parentScrollView.frame.width : frame.maxY <= parentScrollView.frame.height
guard isAutoLayoutEndVisible, let footer = footer() else {
return
}

let diff = footerDiff()
guard diff != 0 else { return }

if horizontal {
footer.frame.origin.x += diff
frame.size.width += diff
superview?.frame.size.width += diff
} else {
footer.frame.origin.y += diff
frame.size.height += diff
superview?.frame.size.height += diff
}
}

private func footerDiff() -> CGFloat {
if subviews.count == 0 {
lastMaxBoundOverall = 0
} else if subviews.count == 1 {
let firstChild = subviews[0]
lastMaxBoundOverall = horizontal ? firstChild.frame.maxX : firstChild.frame.maxY
}
let autoLayoutEnd = horizontal ? frame.width : frame.height
return lastMaxBoundOverall - autoLayoutEnd
}

private func footer() -> UIView? {
return superview?.subviews.first(where:{($0 as? CellContainer)?.index == -1})
}
}
2 changes: 1 addition & 1 deletion packages/expo/bundledNativeModules.json
Expand Up @@ -105,6 +105,6 @@
"unimodules-app-loader": "~3.1.0",
"unimodules-image-loader-interface": "~6.1.0",
"@shopify/react-native-skia": "0.1.137",
"@shopify/flash-list": "1.1.0",
"@shopify/flash-list": "1.3.0",
"@sentry/react-native": "^4.1.3"
}

0 comments on commit bdc678b

Please sign in to comment.