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

feat: add possibility to listen to react model open/request close #45534

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

maciekstosio
Copy link

@maciekstosio maciekstosio commented Jul 19, 2024

Summary:

At Reanimated we currently facing an issue that we're not able to listing to keyboard height changes in modal. This is due to the fact that android dialogs are attached next to the view tree, so we need to start observing insets on them separately, thus we need to listen to dialog open. To my knowledge, currently, there is no way to detect/listen when modal (dialog) is opened in a way that we can have access to its window (which we need to observe insets). The only hack that I found is listening to all events and act on “showModal”, which seems like hacky approach. In this PR I added observer that would be notified by ReactModalHostManager and would allow to attach listeners in other places. With this change in other packages we could get an instance and listen for those events. That should allow us to fix software-mansion/react-native-reanimated#5916 and may help with kirillzyusko/react-native-keyboard-controller#369.

Changelog:

[ANDROID] [ADDED] - Allow to listen to RN Modal events in native code.

Test Plan:

Tested on clean project from npx @react-native-community/cli@next init MyApp --version next --skip-install, which installs RN: 0.75.0-rc.5.

Changes in RN Core as in the PR.

Part added to MainApplication.kt onCreate:
ModalEventManager.addModalListener(object: ModalCallback {
      override fun onModalOpen(dialog: DialogInterface?, viewTag: Int) {
        println("Open")
        (dialog as Dialog).setOnDismissListener({
          println("Close")
        })
      }

      override fun onModalRequestClose(dialog: DialogInterface?, viewTag: Int) {
        println("onRequestClose")
      }
    })
  }
App.ts
import * as React from 'react';
import {
  TextInput,
  View,
  Button,
  Modal,
  SafeAreaView,
  StyleSheet,
} from 'react-native';

function MyModal({ visible, hide }) {
  const [isVisible, setIsVisible] = React.useState(false);

  return (
    <Modal transparent visible={visible} onDismiss={hide} onRequestClose={hide} onLayout={(layout) => console.log('layout', layout)}>
      <View style={styles.modalContainer}>
        <View style={styles.modalContent}>
          <Button onPress={hide} title="Close Modal" />
          <TextInput style={styles.textInput} placeholder="Inside a modal" />
          <Button onPress={() => setIsVisible(visible => !visible)} title="Toggle Component With Animated Keyboard" />
        </View>
      </View>
    </Modal>
  );
}

export default function EmptyExample() {
  const [visible, setVisible] = React.useState(false);

  return (
    <>
      <SafeAreaView style={styles.container}>
        <Button onPress={() => setVisible(true)} title="Open Modal" />
        <TextInput style={styles.textInput} placeholder="Outside a modal" />
      </SafeAreaView>
      <MyModal visible={visible} hide={() => setVisible(false)} />
    </>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'space-between',
    backgroundColor: '#ecf0f1',
    paddingHorizontal: 8,
    paddingBottom: 50,
    paddingTop: 8,
  },
  modalContainer: {
    ...StyleSheet.absoluteFillObject,
    flex: 1,
    justifyContent: 'center',
    backgroundColor: 'rgba(0,0,0,0.2)',
  },
  modalContent: {
    justifyContent: 'space-between',
    backgroundColor: 'white',
    borderRadius: 24,
    padding: 24,
    minHeight: 256,
  },
  textInput: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 8,
    backgroundColor: 'silver',
    fontSize: 18,
    textAlign: 'center',
  },
});
Screen.Recording.2024-07-18.at.15.47.57.mov

@facebook-github-bot
Copy link
Contributor

Hi @maciekstosio!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@maciekstosio maciekstosio changed the title @maciekstosio/add possibility to listen to react model open/request close feat: add possibility to listen to react model open/request close Jul 19, 2024
@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@facebook-github-bot facebook-github-bot added CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. labels Jul 19, 2024
kirillzyusko added a commit to kirillzyusko/react-native-keyboard-controller that referenced this pull request Jul 25, 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

<!-- High level overview of important changes -->
<!-- For example: fixed status bar manipulation; added new types
declarations; -->
<!-- If your changes don't affect one of platform/language below - then
remove this platform/language -->

### 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|
|-------|-----|
|<img width="358" alt="Screenshot 2024-07-24 at 16 43 20"
src="https://github.com/user-attachments/assets/bb3f5a2a-799a-4e46-af4f-4616d163ebbe">|<img
width="360" alt="Screenshot 2024-07-24 at 16 41 39"
src="https://github.com/user-attachments/assets/c87fffce-b8ef-4bf3-a4f3-0985e5a030ff">|


## 📝 Checklist

- [x] CI successfully passed
- [x] I added new mocks and corresponding unit-tests if library API was
changed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants