Skip to content

Commit

Permalink
[WIP] feat: new scroll implementation on iOS (#53)
Browse files Browse the repository at this point in the history
* feat: new scroll implementation on iOS

* fix

* clean

* clean

* clean

* bail out on no window change, ios scroll

* More wip

* wip
  • Loading branch information
janicduplessis committed Aug 29, 2023
1 parent 38ff476 commit 8761edd
Show file tree
Hide file tree
Showing 34 changed files with 512 additions and 828 deletions.
6 changes: 4 additions & 2 deletions android/src/main/java/com/wishlist/Orchestrator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Orchestrator(private val mWishlist: Wishlist, wishlistId: String, viewport
external fun renderAsync(
width: Float,
height: Float,
initialOffset: Float,
initialContentSize: Float,
originItem: Int,
templatesRef: Int,
names: List<String>,
Expand All @@ -29,8 +29,10 @@ class Orchestrator(private val mWishlist: Wishlist, wishlistId: String, viewport

external fun scrollToItem(index: Int)

external fun didUpdateContentOffset()

@DoNotStrip
private fun scrollToOffset(offset: Float) {
private fun scrollToOffset(offset: Float, animated: Boolean) {
mWishlist.reactSmoothScrollTo(0, PixelUtil.toPixelFromDIP(offset).toInt())
}
}
37 changes: 34 additions & 3 deletions android/src/main/java/com/wishlist/Wishlist.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ class Wishlist(reactContext: Context) :
private var templatesRef: Int? = null
private var names: List<String>? = null
private var didInitialScroll = false
private val initialOffset = 100000f
private val initialContentSize = 100000f
private var pendingScrollOffset = Int.MIN_VALUE
private var ignoreScrollEvents = false

fun setTemplates(templatesRef: Int, names: List<String>) {
this.templatesRef = templatesRef
Expand All @@ -42,7 +44,7 @@ class Wishlist(reactContext: Context) :
orchestrator.renderAsync(
PixelUtil.toDIPFromPixel(width.toFloat()),
PixelUtil.toDIPFromPixel(height.toFloat()),
initialOffset,
initialContentSize,
initialIndex,
templatesRef,
names,
Expand All @@ -57,7 +59,7 @@ class Wishlist(reactContext: Context) :
if (contentView == null || contentView.height == 0) {
return
}
scrollTo(0, PixelUtil.toPixelFromDIP(initialOffset).toInt())
scrollTo(0, PixelUtil.toPixelFromDIP(initialContentSize / 2).toInt())
didInitialScroll = true
}

Expand All @@ -74,18 +76,24 @@ class Wishlist(reactContext: Context) :
) {
super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
initialScrollIfReady()
maybeScrollToOffsetForContentChange()
}

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)

renderIfReady()
initialScrollIfReady()
maybeScrollToOffsetForContentChange()
}

override fun onScrollChanged(x: Int, y: Int, oldX: Int, oldY: Int) {
super.onScrollChanged(x, y, oldX, oldY)

if (ignoreScrollEvents) {
return
}

orchestrator?.didScrollAsync(
PixelUtil.toDIPFromPixel(width.toFloat()),
PixelUtil.toDIPFromPixel(height.toFloat()),
Expand All @@ -96,4 +104,27 @@ class Wishlist(reactContext: Context) :
fun scrollToItem(index: Int, animated: Boolean) {
orchestrator?.scrollToItem(index)
}

fun scrollToOffsetForContentChange(offset: Float) {
// State is updated before content view is laid out so update the
// scroll position in layout handler.
pendingScrollOffset = PixelUtil.toPixelFromDIP(offset).toInt()
}

private fun maybeScrollToOffsetForContentChange() {
if (pendingScrollOffset == Int.MIN_VALUE) {
return
}
val contentView = getChildAt(0)
if (contentView == null ||
contentView.height == 0 ||
pendingScrollOffset > contentView.height) {
return
}
ignoreScrollEvents = true
scrollTo(0, pendingScrollOffset)
orchestrator?.didUpdateContentOffset()
ignoreScrollEvents = false
pendingScrollOffset = Int.MIN_VALUE
}
}
4 changes: 4 additions & 0 deletions android/src/main/java/com/wishlist/WishlistViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class WishlistViewManager : ViewGroupManager<Wishlist>(), MGWishlistManagerInter
stateWrapper: StateWrapper?
): Any? {
view.fabricViewStateManager.setStateWrapper(stateWrapper)
val stateData = stateWrapper?.stateData
if (stateData != null && stateData.hasKey("contentOffset")) {
view.scrollToOffsetForContentChange(stateData.getDouble("contentOffset").toFloat())
}
return null
}

Expand Down
21 changes: 11 additions & 10 deletions android/src/main/jni/Orchestrator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Orchestrator::Orchestrator(
void Orchestrator::renderAsync(
float width,
float height,
float initialOffset,
float initialContentSize,
int originItem,
int templatesRef,
jni::alias_ref<jni::JList<jni::JString>> namesList,
Expand All @@ -69,12 +69,11 @@ void Orchestrator::renderAsync(
alreadyRendered_ = true;
width_ = width;
height_ = height;
contentOffset_ = initialOffset;
inflatorId_ = inflatorId;

di_->getViewportCarer()->initialRenderAsync(
{width, height},
initialOffset,
initialContentSize,
originItem,
templates,
jListToVector(namesList),
Expand All @@ -94,18 +93,18 @@ void Orchestrator::didScrollAsync(
std::string inflatorId) {
width_ = width;
height_ = height;
contentOffset_ = contentOffset;
inflatorId_ = inflatorId;
handleVSync();
di_->getViewportCarer()->didScrollAsync(
{width, height}, contentOffset, inflatorId);
}

void Orchestrator::handleVSync() {
// TODO: These do not seem to be needed.
auto templates =
std::vector<std::shared_ptr<facebook::react::ShadowNode const>>();
auto names = std::vector<std::string>();
di_->getViewportCarer()->didScrollAsync(
{width_, height_}, templates, names, contentOffset_, inflatorId_);
{width_, height_}, MG_NO_OFFSET, inflatorId_);
}

void Orchestrator::didUpdateContentOffset() {
di_->getViewportCarer()->didUpdateContentOffset();
}

void Orchestrator::scrollToItem(int index) {
Expand Down Expand Up @@ -160,6 +159,8 @@ void Orchestrator::registerNatives() {
{makeNativeMethod("initHybrid", Orchestrator::initHybrid),
makeNativeMethod("renderAsync", Orchestrator::renderAsync),
makeNativeMethod("didScrollAsync", Orchestrator::didScrollAsync),
makeNativeMethod(
"didUpdateContentOffset", Orchestrator::didUpdateContentOffset),
makeNativeMethod("scrollToItem", Orchestrator::scrollToItem)});
}

Expand Down
5 changes: 3 additions & 2 deletions android/src/main/jni/Orchestrator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Orchestrator : public jni::HybridClass<Orchestrator> {
void renderAsync(
float width,
float height,
float initialOffset,
float initialContentSize,
int originItem,
int templatesRef,
jni::alias_ref<jni::JList<jni::JString>> names,
Expand All @@ -41,6 +41,8 @@ class Orchestrator : public jni::HybridClass<Orchestrator> {

void handleVSync();

void didUpdateContentOffset();

void scrollToItem(int index);

void didPushChildren(std::vector<Item> items);
Expand Down Expand Up @@ -68,7 +70,6 @@ class Orchestrator : public jni::HybridClass<Orchestrator> {
std::shared_ptr<Adapter> adapter_;
float width_;
float height_;
float contentOffset_;
std::string inflatorId_;
std::vector<Item> items_;
int pendingScrollToItem_;
Expand Down
1 change: 0 additions & 1 deletion cpp/ItemProvider/ShadowNodeBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ Value ShadowNodeBinding::get(Runtime &rt, const PropNameID &nameProp) {
std::string callbackName = args[0].asString(rt).utf8(rt);
int tag = sn_->getTag();
std::string eventName = std::to_string(tag) + callbackName;
std::cout << "register native for name " << eventName << std::endl;
jsi::Function callback = args[1].asObject(rt).asFunction(rt);

auto handlerRegistry = rt.global()
Expand Down
10 changes: 5 additions & 5 deletions cpp/MGViewportCarer/MGViewportCarer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ namespace Wishlist {

using namespace facebook::react;

static float MG_NO_OFFSET = std::numeric_limits<float>::min();

struct MGDims {
float width;
float height;
Expand All @@ -23,20 +25,18 @@ class MGViewportCarer {
public:
virtual void initialRenderAsync(
MGDims dimensions,
float initialOffset,
float initialContentSize,
int originItem,
const std::vector<std::shared_ptr<ShadowNode const>> &registeredViews,
const std::vector<std::string> &names,
const std::string &inflatorId) = 0;

virtual void didScrollAsync(
MGDims dimensions,
const std::vector<std::shared_ptr<ShadowNode const>> &registeredViews,
const std::vector<std::string> &names,
float newOffset,
float contentOffset,
const std::string &inflatorId) = 0;

virtual ~MGViewportCarer() {}
virtual void didUpdateContentOffset() = 0;
};

}; // namespace Wishlist
Loading

0 comments on commit 8761edd

Please sign in to comment.