Skip to content

Commit 54efad2

Browse files
committed
fix: KeyboardCompatibleView edge cases and android inset listeners
1 parent e1f555f commit 54efad2

File tree

5 files changed

+93
-69
lines changed

5 files changed

+93
-69
lines changed

examples/SampleApp/android/app/src/main/java/com/sampleapp/MainActivity.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,38 @@ import com.facebook.react.ReactActivityDelegate
55
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
66
import com.facebook.react.defaults.DefaultReactActivityDelegate
77

8+
import android.os.Bundle
9+
import android.os.Build
10+
import android.view.View
11+
import androidx.core.view.ViewCompat
12+
import androidx.core.view.WindowInsetsCompat
13+
import androidx.core.view.updatePadding
14+
815
class MainActivity : ReactActivity() {
16+
17+
override fun onCreate(savedInstanceState: Bundle?) {
18+
super.onCreate(null)
19+
20+
if (Build.VERSION.SDK_INT >= 35) {
21+
val rootView = findViewById<View>(android.R.id.content)
22+
23+
24+
ViewCompat.setOnApplyWindowInsetsListener(rootView) { view, insets ->
25+
val bars = insets.getInsets(
26+
WindowInsetsCompat.Type.systemBars()
27+
or WindowInsetsCompat.Type.displayCutout()
28+
or WindowInsetsCompat.Type.ime() // adding the ime's height
29+
)
30+
rootView.updatePadding(
31+
left = bars.left,
32+
top = bars.top,
33+
right = bars.right,
34+
bottom = bars.bottom
35+
)
36+
WindowInsetsCompat.CONSUMED
37+
}
38+
}
39+
}
940
/**
1041
* Returns the name of the main component registered from JavaScript. This is used to schedule
1142
* rendering of the component.
@@ -18,4 +49,4 @@ class MainActivity : ReactActivity() {
1849
*/
1950
override fun createReactActivityDelegate(): ReactActivityDelegate =
2051
DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
21-
}
52+
}

examples/SampleApp/ios/Podfile.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,7 +2425,7 @@ PODS:
24252425
- libwebp (~> 1.0)
24262426
- SDWebImage/Core (~> 5.10)
24272427
- SocketRocket (0.7.1)
2428-
- stream-chat-react-native (7.1.1):
2428+
- stream-chat-react-native (7.1.2):
24292429
- DoubleConversion
24302430
- glog
24312431
- hermes-engine
@@ -2777,7 +2777,7 @@ SPEC CHECKSUMS:
27772777
FirebaseRemoteConfigInterop: 7b74ceaa54e28863ed17fa39da8951692725eced
27782778
FirebaseSessions: eaa8ec037e7793769defe4201c20bd4d976f9677
27792779
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
2780-
glog: eb93e2f488219332457c3c4eafd2738ddc7e80b8
2780+
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
27812781
GoogleAppMeasurement: 0dfca1a4b534d123de3945e28f77869d10d0d600
27822782
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
27832783
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
@@ -2787,7 +2787,7 @@ SPEC CHECKSUMS:
27872787
op-sqlite: 6a5255f36253697406618ceba5212d6572012a9d
27882788
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
27892789
PromisesSwift: 9d77319bbe72ebf6d872900551f7eeba9bce2851
2790-
RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82
2790+
RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809
27912791
RCTDeprecation: c3e3f5b4ea83e7ff3bc86ce09e2a54b7affd687d
27922792
RCTRequired: ee438439880dffc9425930d1dd1a3c883ee6879c
27932793
RCTTypeSafety: fe728195791e1a0222aa83596a570cf377cd475e
@@ -2872,7 +2872,7 @@ SPEC CHECKSUMS:
28722872
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
28732873
SDWebImageWebPCoder: 908b83b6adda48effe7667cd2b7f78c897e5111d
28742874
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
2875-
stream-chat-react-native: 963e1146ba5f092aa8f6c59f4e20e431a9265a69
2875+
stream-chat-react-native: 45d46c6a3188edf8dfe9c85cd884457ccc232b74
28762876
Yoga: b2eaabf17044cd4273a661b14eb83f9fd2c90491
28772877

28782878
PODFILE CHECKSUM: 4f662370295f8f9cee909f1a4c59a614999a209d

examples/SampleApp/src/screens/ThreadScreen.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React, { useEffect } from 'react';
2-
import { StyleSheet, View } from 'react-native';
2+
import { Platform, StyleSheet, View } from 'react-native';
33
import { SafeAreaView } from 'react-native-safe-area-context';
44
import {
55
Channel,
@@ -79,7 +79,7 @@ export const ThreadScreen: React.FC<ThreadScreenProps> = ({
7979
audioRecordingEnabled={true}
8080
channel={channel}
8181
enforceUniqueReaction
82-
keyboardVerticalOffset={0}
82+
keyboardVerticalOffset={Platform.OS === 'ios' ? 0 : -300}
8383
thread={thread}
8484
threadList
8585
>

examples/SampleApp/yarn.lock

Lines changed: 17 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1373,14 +1373,6 @@
13731373
resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz#a73bab8eb491d7b8b7be2f0e6c310647835afe83"
13741374
integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==
13751375

1376-
"@gorhom/bottom-sheet@^5.1.1":
1377-
version "5.1.1"
1378-
resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.1.tgz#43ecb9e7b4d4ca4b4cefdf3b6b497f7715f350bc"
1379-
integrity sha512-Y8FiuRmeZYaP+ZGQ0axDwWrrKqVp4ByYRs1D2fTJTxHMt081MHHRQsqmZ3SK7AFp3cSID+vTqnD8w/KAASpy+w==
1380-
dependencies:
1381-
"@gorhom/portal" "1.0.14"
1382-
invariant "^2.2.4"
1383-
13841376
"@gorhom/bottom-sheet@^5.1.6":
13851377
version "5.1.6"
13861378
resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.6.tgz#92365894ae4d4eefdbaa577408cfaf62463a9490"
@@ -3881,11 +3873,6 @@ data-view-byte-offset@^1.0.1:
38813873
es-errors "^1.3.0"
38823874
is-data-view "^1.0.1"
38833875

3884-
3885-
version "1.10.5"
3886-
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986"
3887-
integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g==
3888-
38893876
[email protected], dayjs@^1.8.15:
38903877
version "1.11.13"
38913878
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
@@ -4067,7 +4054,7 @@ emittery@^0.13.1:
40674054
resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad"
40684055
integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==
40694056

4070-
emoji-regex@^10.3.0, emoji-regex@^10.4.0:
4057+
emoji-regex@^10.4.0:
40714058
version "10.4.0"
40724059
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4"
40734060
integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
@@ -5108,7 +5095,7 @@ hyochan-welcome@^1.0.0:
51085095
resolved "https://registry.yarnpkg.com/hyochan-welcome/-/hyochan-welcome-1.0.1.tgz#a949de8bc3c1e18fe096016bc273aa191c844971"
51095096
integrity sha512-WRZNH5grESkOXP/r7xc7TMhO9cUqxaJIuZcQDAjzHWs6viGP+sWtVbiBigxc9YVRrw3hnkESQWwzqg+oOga65A==
51105097

5111-
i18next@^21.10.0, i18next@^21.6.14:
5098+
i18next@^21.10.0:
51125099
version "21.10.0"
51135100
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d"
51145101
integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==
@@ -6197,7 +6184,7 @@ lines-and-columns@^1.1.6:
61976184
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
61986185
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
61996186

6200-
linkifyjs@^4.1.1, linkifyjs@^4.2.0:
6187+
linkifyjs@^4.2.0:
62016188
version "4.2.0"
62026189
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08"
62036190
integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw==
@@ -6594,7 +6581,7 @@ [email protected]:
65946581
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447"
65956582
integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==
65966583

6597-
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.34, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34:
6584+
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34:
65986585
version "2.1.35"
65996586
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
66006587
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -7332,13 +7319,6 @@ react-native-svg@^15.12.0:
73327319
css-tree "^1.1.3"
73337320
warn-once "0.1.1"
73347321

7335-
react-native-url-polyfill@^1.3.0:
7336-
version "1.3.0"
7337-
resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a"
7338-
integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ==
7339-
dependencies:
7340-
whatwg-url-without-unicode "8.0.0-3"
7341-
73427322
react-native-url-polyfill@^2.0.0:
73437323
version "2.0.0"
73447324
resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589"
@@ -7887,24 +7867,24 @@ statuses@~1.5.0:
78877867
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
78887868
integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==
78897869

7890-
7891-
version "7.1.1"
7892-
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.1.tgz#b22faf35fa5defd24c730873aeba30c172556089"
7893-
integrity sha512-9AkSKWzywN2FfsMgDfeoCatr/qoG+zJzM2u5j3PU6WU7qIhZtM/7+2UB0WKAY7fA5MjaoMEzV1mBF+hILP1KOw==
7870+
7871+
version "7.1.2"
7872+
resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.2.tgz#5870a1188ecbf8c3b705d74379d19ff77efce2c5"
7873+
integrity sha512-Ob+V8tt+7L+7BRkWyWbFlju6E/8MAoB/NUZ8ENtEEijq5QBNWnVvZctQSZuekIOVrfoP9EenlIOPadHnN/mvYA==
78947874
dependencies:
7895-
"@gorhom/bottom-sheet" "^5.1.1"
7896-
dayjs "1.10.5"
7897-
emoji-regex "^10.3.0"
7898-
i18next "^21.6.14"
7875+
"@gorhom/bottom-sheet" "^5.1.6"
7876+
dayjs "1.11.13"
7877+
emoji-regex "^10.4.0"
7878+
i18next "^21.10.0"
78997879
intl-pluralrules "^2.0.1"
7900-
linkifyjs "^4.1.1"
7880+
linkifyjs "^4.3.1"
79017881
lodash-es "4.17.21"
7902-
mime-types "^2.1.34"
7882+
mime-types "^2.1.35"
79037883
path "0.12.7"
79047884
react-native-markdown-package "1.8.2"
7905-
react-native-url-polyfill "^1.3.0"
7906-
stream-chat "^9.3.0"
7907-
use-sync-external-store "^1.4.0"
7885+
react-native-url-polyfill "^2.0.0"
7886+
stream-chat "^9.7.0"
7887+
use-sync-external-store "^1.5.0"
79087888

79097889
"stream-chat-react-native-core@link:../../package":
79107890
version "0.0.0"
@@ -7914,21 +7894,6 @@ [email protected]:
79147894
version "0.0.0"
79157895
uid ""
79167896

7917-
stream-chat@^9.3.0:
7918-
version "9.3.0"
7919-
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.3.0.tgz#35ca4db9e841eb92d07413ae156de0500ad77b23"
7920-
integrity sha512-S73B3HrvmQvJjq58Zjo50vh74juhsWsVRpT+OBjGAxSGxlA+ITkZ3vKs8Y/r2eDK7mBTMmX5QCruFaDJH5dRuw==
7921-
dependencies:
7922-
"@types/jsonwebtoken" "^9.0.8"
7923-
"@types/ws" "^8.5.14"
7924-
axios "^1.6.0"
7925-
base64-js "^1.5.1"
7926-
form-data "^4.0.0"
7927-
isomorphic-ws "^5.0.0"
7928-
jsonwebtoken "^9.0.2"
7929-
linkifyjs "^4.2.0"
7930-
ws "^8.18.1"
7931-
79327897
stream-chat@^9.7.0:
79337898
version "9.7.0"
79347899
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.7.0.tgz#8302a4dfd2b68115c57cd0a102976542a79cf132"
@@ -8391,11 +8356,6 @@ use-latest-callback@^0.2.3:
83918356
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16"
83928357
integrity sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ==
83938358

8394-
use-sync-external-store@^1.4.0:
8395-
version "1.4.0"
8396-
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
8397-
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==
8398-
83998359
use-sync-external-store@^1.5.0:
84008360
version "1.5.0"
84018361
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"

package/src/components/KeyboardCompatibleView/KeyboardCompatibleView.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ import {
2121

2222
import { KeyboardProvider } from '../../contexts/keyboardContext/KeyboardContext';
2323

24+
type State = {
25+
bottom: number;
26+
};
27+
2428
/**
2529
* View that moves out of the way when the keyboard appears by automatically
2630
* adjusting its height, position, or bottom padding.
@@ -46,6 +50,8 @@ export class KeyboardCompatibleView extends React.Component<
4650
_appStateSubscription: NativeEventSubscription | null = null;
4751
viewRef: React.RefObject<View | null>;
4852
_initialFrameHeight = 0;
53+
_bottom: number = 0;
54+
4955
constructor(props: KeyboardAvoidingViewProps) {
5056
super(props);
5157
this.state = {
@@ -93,28 +99,49 @@ export class KeyboardCompatibleView extends React.Component<
9399
};
94100

95101
_onLayout: (event: LayoutChangeEvent) => void = (event) => {
102+
event.persist();
103+
104+
const oldFrame = this._frame;
96105
this._frame = event.nativeEvent.layout;
97106
if (!this._initialFrameHeight) {
98107
// save the initial frame height, before the keyboard is visible
99108
this._initialFrameHeight = this._frame.height;
100109
}
101110

102-
this._updateBottomIfNecessary();
111+
// update bottom height for the first time or when the height is changed
112+
if (!oldFrame || oldFrame.height !== this._frame.height) {
113+
this._updateBottomIfNecessary();
114+
}
115+
116+
if (this.props.onLayout) {
117+
this.props.onLayout(event);
118+
}
119+
};
120+
121+
// Avoid unnecessary renders if the KeyboardAvoidingView is disabled.
122+
_setBottom = (value: number) => {
123+
const enabled = this.props.enabled ?? true;
124+
this._bottom = value;
125+
if (enabled) {
126+
this.setState({ bottom: value });
127+
}
103128
};
104129

105130
_updateBottomIfNecessary = () => {
106-
if (this._keyboardEvent === null) {
107-
this.setState({ bottom: 0 });
131+
if (this._keyboardEvent == null) {
132+
this._setBottom(0);
108133
return;
109134
}
110135

111136
const { duration, easing, endCoordinates } = this._keyboardEvent;
112137
const height = this._relativeKeyboardHeight(endCoordinates);
113138

114-
if (this.state.bottom === height) {
139+
if (this._bottom === height) {
115140
return;
116141
}
117142

143+
this._setBottom(height);
144+
118145
if (duration && easing) {
119146
LayoutAnimation.configureNext({
120147
// We have to pass the duration equal to minimal accepted duration defined here: RCTLayoutAnimation.m
@@ -125,7 +152,6 @@ export class KeyboardCompatibleView extends React.Component<
125152
},
126153
});
127154
}
128-
this.setState({ bottom: height });
129155
};
130156

131157
_handleAppStateChange = (nextAppState: AppStateStatus) => {
@@ -184,6 +210,13 @@ export class KeyboardCompatibleView extends React.Component<
184210
});
185211
};
186212

213+
componentDidUpdate(_: KeyboardAvoidingViewProps, prevState: State): void {
214+
const enabled = this.props.enabled ?? true;
215+
if (enabled && this._bottom !== prevState.bottom) {
216+
this.setState({ bottom: this._bottom });
217+
}
218+
}
219+
187220
componentDidMount() {
188221
this._appStateSubscription = AppState.addEventListener('change', this._handleAppStateChange);
189222
this.setKeyboardListeners();

0 commit comments

Comments
 (0)