Skip to content
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

fix: modal integration #466

Merged
merged 18 commits into from
Jul 25, 2024
Merged

fix: modal integration #466

merged 18 commits into from
Jul 25, 2024

Conversation

kirillzyusko
Copy link
Owner

@kirillzyusko kirillzyusko commented Jun 10, 2024

πŸ“œ Description

Fixed a problem of keyboard movement not being detected in Modal window on Android.

πŸ’‘ Motivation and Context

The most challenging in this PR is getting an access to dialog. Initially I thought to create an additional view (ModalKeyboardProvider) and then through this view get an access to the dialog, but after some experiments I realized, that we'll get an access to DialogRootViewGroup and this class is not holding a reference to dialog, so this idea failed.

Another approach was to listen to all events in eventDispatcher and when we detect onShow (topShow) event, then using uiManager we can resolve the view and get ReactModalHostView. This approach work, but it has one downside - we listen to all events. It doesn't hit the performance, but this approach looks like a workaround (it has too many transitive dependencies).

A PR facebook/react-native#45534 introduces a new API for detection show/hide modals. I hope it'll be merged at some point of time and we can start to use new API. In a meantime I'll use the current approach and will incapsulate all work in ModalAttachedWatcher - in this case we have our own interface for dealing with modals, and we can change internals without affecting other files 😎

Closes #369

Potentially helps to fix #387

πŸ“’ Changelog

E2E

  • added new modal test;

Android

  • added ModalAttachedWatcher class;
  • KeyboardAnimationCallback now accepts config to reduce amount of passed params (KeyboardAnimationCallbackConfig);
  • pass eventPropagationView to KeyboardAnimationCallback and FocusedInputObserver;
  • return WindowInsetsCompat.CONSUMED from onApplyWindowInsets instead of insets;
  • add syncKeyboardPosition to KeyboardAnimationCallback;
  • dispatch the same events through cycle instead of code duplication.

πŸ€” How Has This Been Tested?

Tested manually on:

  • Xiaomi Redmi Note 5 Pro (Android 9);
  • Pixel 7 Pro (Android 14);
  • Pixel 3A (Android 13, emulator);
  • Pixel 2 (AOSP, Android 9, e2e tests);
  • Pixel 2 (Android 11, emulator, e2e tests).

πŸ“Έ Screenshots (if appropriate):

Before After
Screenshot 2024-07-24 at 16 43 20 Screenshot 2024-07-24 at 16 41 39

πŸ“ Checklist

  • CI successfully passed
  • I added new mocks and corresponding unit-tests if library API was changed

@kirillzyusko kirillzyusko added πŸ› bug Something isn't working πŸ€– android Android specific 🌎 modal Anything that involves Modal usage labels Jun 10, 2024
@kirillzyusko kirillzyusko self-assigned this Jun 10, 2024
Copy link
Contributor

github-actions bot commented Jun 10, 2024

πŸ“Š Package size report

Current size Target Size Difference
148233 bytes 147315 bytes 918 bytes πŸ“ˆ

@kirillzyusko kirillzyusko marked this pull request as ready for review July 25, 2024 09:14
@kirillzyusko kirillzyusko merged commit 4a796eb into main Jul 25, 2024
18 checks passed
@kirillzyusko kirillzyusko deleted the fix/modal branch July 25, 2024 10:21
kirillzyusko added a commit that referenced this pull request Dec 3, 2024
## πŸ“œ Description

Fixed a crash when `progress` value becomes `Infinity` and can not be
serialized to JS.

## πŸ’‘ Motivation and Context

It happens when we try to divide any double/float number (except `0`) by
`0.0`. In our case we were trying to divide `height /
persistentKeyboardHeight`. To shed more light on the issue I modified
condition `it.isNan()` to `it.isNaN() || it.isInfinite()`, and we get:

```bash
 LOG  {"duration": 160, "eventName": "onKeyboardMoveStart", "height": 0, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 47.71428680419922, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 47.71428680419922, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 47.71428680419922, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 46.57143020629883, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 42.85714340209961, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 36.57143020629883, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 28, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 17.714284896850586, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 9.142857551574707, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 2.2857143878936768, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 0, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMove", "height": 0, "progress": 0, "target": -1}
 LOG  {"duration": 160, "eventName": "onKeyboardMoveEnd", "height": 0, "progress": 0, "target": -1}
```

In `onStart` the `keyboardHeight=0`, `isKeyboardVisible-false` and we
don't update `this.persistentKeyboardHeight` (it's 0.0 by default).

From logs above it looks like keyboard is animating from `47` pixels to
`0`, but in fact there is no keyboard animation on the screen at all! 🀯
So in my understand it's a bug somewhere in google code (or in my code -
but such animation is not correct). Below are the options that I
considered for a fix:


### 1️⃣ add a condition, that will check if a prev `height !== 0` and if
`nextHeight === 0` then ignore an animation

Such solution will add more complexity, more if-statements without an
explicit benefits and warranty that it will fix/work for other similar
scenarios.

### 2️⃣ check that target == -1 and ignore animation then

Not a reliable solution, because we start to depend on 3rd party
variables state. And it's a known fact, that if you get a transitive
dependencies, then the solution drastically becomes more complex.

### 3️⃣ add `isInfinite` check and set progress to `0` then

While it fixes a crash we still get an incorrect `height` values so any
animation that depends on these values will be running and it will look
strange 😬

### 4️⃣ don't attach keyboard callbacks to Modal window on Android 9

The most perspective solution - on Android < 12 all events are passing
through the main window (and actually attaching a callback is not
necessary there, but when I implemented
#466
and
#624
I decided to keep the code for consistency). So if we remove these
callbacks (if we attach them to window only for Android 12) then all
code works well on both platforms.

Closes
#714

## πŸ“’ Changelog

### Android

- don't attach keyboard callbacks on Android < 12 to `Modal` window;

## πŸ€” How Has This Been Tested?

Tested in reproduction example and on CI.

## πŸ“Έ Screenshots (if appropriate):


https://github.com/user-attachments/assets/76331e0d-6779-4a4b-a837-ba4802ac4139

## πŸ“ Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
change
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
πŸ€– android Android specific πŸ› bug Something isn't working e2e Anything about E2E tests 🌎 modal Anything that involves Modal usage
Projects
None yet
1 participant