@droppii-org/chat-mobile 0.2.93 → 0.2.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/lib/module/screens/chat-detail/ChatDetail.js +15 -11
  2. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  3. package/lib/module/screens/chat-detail/ChatListLegend.js +233 -74
  4. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  5. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +16 -9
  6. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  7. package/lib/module/store/message.js +9 -5
  8. package/lib/module/store/message.js.map +1 -1
  9. package/lib/module/types/events.js +1 -0
  10. package/lib/module/types/events.js.map +1 -1
  11. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  12. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  13. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  14. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts +3 -1
  15. package/lib/typescript/src/screens/chat-detail/messageToolbox/types.d.ts.map +1 -1
  16. package/lib/typescript/src/screens/chat-detail/types.d.ts +2 -2
  17. package/lib/typescript/src/screens/chat-detail/types.d.ts.map +1 -1
  18. package/lib/typescript/src/store/message.d.ts.map +1 -1
  19. package/lib/typescript/src/types/events.d.ts +2 -1
  20. package/lib/typescript/src/types/events.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/src/screens/chat-detail/ChatDetail.tsx +23 -14
  23. package/src/screens/chat-detail/ChatListLegend.tsx +249 -73
  24. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +26 -7
  25. package/src/screens/chat-detail/messageToolbox/types.ts +8 -1
  26. package/src/screens/chat-detail/types.ts +2 -2
  27. package/src/store/message.ts +14 -5
  28. package/src/types/events.ts +1 -0
@@ -24,7 +24,6 @@ import { ChatEvent } from "../../types/events.js";
24
24
  import { useSetIsShowFLashMessage } from "../../context/global.js";
25
25
  import { useJoinGroup } from "../../hooks/groups/useJoinGroup.js";
26
26
  import { useGroupAccessGuard } from "../../hooks/groups/useGroupAccessGuard.js";
27
- import { isMessageRevoked } from "../../utils/messageUtils.js";
28
27
  import { UIUtils } from "../../utils/UIUtils.js";
29
28
  import { trans } from "../../translation/index.js";
30
29
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
@@ -110,8 +109,19 @@ const ChatDetail = /*#__PURE__*/memo(({
110
109
  onBlocked: handleAccessBlocked
111
110
  });
112
111
 
113
- // Scroll to the target message if it exists in the current message list
114
- // And show a toast if the message is revoked.
112
+ // Go back if the target message is not available
113
+ useEffect(() => {
114
+ const subscription = DeviceEventEmitter.addListener(ChatEvent.TargetMessageUnavailable, () => {
115
+ UIUtils.toast.open({
116
+ title: trans('chat:target_message_unavailable'),
117
+ theme: 'error'
118
+ });
119
+ (onBack ?? handleBackDefault)();
120
+ });
121
+ return () => subscription.remove();
122
+ }, [onBack, handleBackDefault]);
123
+
124
+ // Handle re-open the same deeplink
115
125
  const params = useRoute().params;
116
126
  useEffect(() => {
117
127
  if (!targetMessageId || initLoading) return;
@@ -120,12 +130,6 @@ const ChatDetail = /*#__PURE__*/memo(({
120
130
  DeviceEventEmitter.emit(ChatEvent.ScrollToMessage, {
121
131
  clientMessageId: targetMessageId
122
132
  });
123
- if (isMessageRevoked(targetMessage)) {
124
- UIUtils.toast.open({
125
- title: trans('chat:target_message_unavailable'),
126
- theme: 'error'
127
- });
128
- }
129
133
  // eslint-disable-next-line react-hooks/exhaustive-deps
130
134
  }, [params]);
131
135
  const resolvedShowAddMember = showAddMember ?? shouldShowAddMember(conversation);
@@ -140,8 +144,8 @@ const ChatDetail = /*#__PURE__*/memo(({
140
144
  const handlePressUrl = useCallback(url => {
141
145
  onPressUrl?.(normalizeUrl(url));
142
146
  }, [onPressUrl]);
143
- const handleMessageLongPress = useCallback((message, rect) => {
144
- messageToolboxRef.current?.show(message, rect);
147
+ const handleMessageLongPress = useCallback((message, rect, groupPosition) => {
148
+ messageToolboxRef.current?.show(message, rect, groupPosition);
145
149
  }, []);
146
150
  const handlePressPinnedMessageBar = useCallback(() => {
147
151
  openPinnedMessagesSheet({
@@ -1 +1 @@
1
- {"version":3,"names":["memo","useCallback","useEffect","useRef","ActivityIndicator","DeviceEventEmitter","useKeyboardScrollToEnd","useSafeAreaInsets","KColors","KContainer","KNavigation","useNavigation","useRoute","usePinMessage","PinnedMessageBar","openPinnedMessagesSheet","MediaViewerModal","useConversation","useConversationStore","useUserStore","ChatDetailHeader","ChatListLegend","ChatComposer","shouldShowAddMember","normalizeUrl","MessageToolboxOverlay","useGetConversationDetail","useGetConversationTarget","GroupJoinSource","useMessages","ChatEvent","useSetIsShowFLashMessage","useJoinGroup","useGroupAccessGuard","isMessageRevoked","UIUtils","trans","jsx","_jsx","jsxs","_jsxs","ChatDetail","targetConversationId","targetUserId","targetGroupId","targetBotId","targetMessageId","showAddMember","onBack","onPressSearch","onPressAddMember","onPressMenu","onPressUrl","customMessageItemFactory","pannelActions","groupJoinSource","navigation","mediaViewerRef","messageToolboxRef","currentUser","s","user","conversationId","isLoading","isLoadingConversation","conversation","data","target","isComposeDisabled","isActive","handleBackDefault","goBack","handleMediaPress","items","index","current","show","messages","initLoading","moreOldLoading","moreNewLoading","getMoreOldMessages","getMoreNewMessages","hasMoreOld","hasMoreNew","startClientMsgId","peerType","mutate","joinGroup","isPending","isJoiningGroup","setShowFLashMessage","pinnedMessages","pinMessage","unpinMessage","getState","setCurrentConversation","undefined","handleAccessBlocked","isCheckingAccess","groupID","onBlocked","params","targetMessage","find","m","clientMsgID","emit","ScrollToMessage","clientMessageId","toast","open","title","theme","resolvedShowAddMember","insets","listRef","freeze","scrollMessageToEnd","handlePressUrl","url","handleMessageLongPress","message","rect","handlePressPinnedMessageBar","currentUserId","userID","onPressMessage","handlePressMenu","handlePressJoinGroup","reqMsg","joinSource","Search","groupName","showName","Page","flex","edges","children","onPressAvatar","onPress","onLoadMoreOld","onLoadMoreNew","onMediaPress","onLongPress","keyboardOffset","bottom","isEmptyMessage","length","onPressJoinGroup","ref","onPinMessage","onUnpinMessage","props","isUserLoaded","Header","alignment","size","disableCenterSpace","shadow","leftButton","id","icon","vectorName","vectorProvider","tintColor","primary","normal","View","center","color"],"sourceRoot":"../../../../src","sources":["screens/chat-detail/ChatDetail.tsx"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["memo","useCallback","useEffect","useRef","ActivityIndicator","DeviceEventEmitter","useKeyboardScrollToEnd","useSafeAreaInsets","KColors","KContainer","KNavigation","useNavigation","useRoute","usePinMessage","PinnedMessageBar","openPinnedMessagesSheet","MediaViewerModal","useConversation","useConversationStore","useUserStore","ChatDetailHeader","ChatListLegend","ChatComposer","shouldShowAddMember","normalizeUrl","MessageToolboxOverlay","useGetConversationDetail","useGetConversationTarget","GroupJoinSource","useMessages","ChatEvent","useSetIsShowFLashMessage","useJoinGroup","useGroupAccessGuard","UIUtils","trans","jsx","_jsx","jsxs","_jsxs","ChatDetail","targetConversationId","targetUserId","targetGroupId","targetBotId","targetMessageId","showAddMember","onBack","onPressSearch","onPressAddMember","onPressMenu","onPressUrl","customMessageItemFactory","pannelActions","groupJoinSource","navigation","mediaViewerRef","messageToolboxRef","currentUser","s","user","conversationId","isLoading","isLoadingConversation","conversation","data","target","isComposeDisabled","isActive","handleBackDefault","goBack","handleMediaPress","items","index","current","show","messages","initLoading","moreOldLoading","moreNewLoading","getMoreOldMessages","getMoreNewMessages","hasMoreOld","hasMoreNew","startClientMsgId","peerType","mutate","joinGroup","isPending","isJoiningGroup","setShowFLashMessage","pinnedMessages","pinMessage","unpinMessage","getState","setCurrentConversation","undefined","handleAccessBlocked","isCheckingAccess","groupID","onBlocked","subscription","addListener","TargetMessageUnavailable","toast","open","title","theme","remove","params","targetMessage","find","m","clientMsgID","emit","ScrollToMessage","clientMessageId","resolvedShowAddMember","insets","listRef","freeze","scrollMessageToEnd","handlePressUrl","url","handleMessageLongPress","message","rect","groupPosition","handlePressPinnedMessageBar","currentUserId","userID","onPressMessage","handlePressMenu","handlePressJoinGroup","reqMsg","joinSource","Search","groupName","showName","Page","flex","edges","children","onPressAvatar","onPress","onLoadMoreOld","onLoadMoreNew","onMediaPress","onLongPress","keyboardOffset","bottom","isEmptyMessage","length","onPressJoinGroup","ref","onPinMessage","onUnpinMessage","props","isUserLoaded","Header","alignment","size","disableCenterSpace","shadow","leftButton","id","icon","vectorName","vectorProvider","tintColor","primary","normal","View","center","color"],"sourceRoot":"../../../../src","sources":["screens/chat-detail/ChatDetail.tsx"],"mappings":"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","ignoreList":[]}
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
2
 
3
3
  import { memo, useCallback, useMemo, useEffect, useRef, useState } from 'react';
4
- import { ActivityIndicator, DeviceEventEmitter, Platform, StyleSheet, useWindowDimensions } from 'react-native';
4
+ import { ActivityIndicator, DeviceEventEmitter, StyleSheet, useWindowDimensions } from 'react-native';
5
5
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, Easing } from 'react-native-reanimated';
6
6
  import { KeyboardAwareLegendList } from '@legendapp/list/keyboard';
7
- import { KeyboardEvents } from 'react-native-keyboard-controller';
7
+ import { KeyboardEvents, useGenericKeyboardHandler } from 'react-native-keyboard-controller';
8
8
  import { KContainer, KColors, KSpacingValue, KLabel, KRadiusValue, KImage } from '@droppii/libs';
9
9
  import { precomputeLegendListData } from "../../utils/legendListMessage.js";
10
10
  import { getMessageRenderId } from "../../utils/message.js";
@@ -39,11 +39,36 @@ const BUTTON_FADE_CONFIG = {
39
39
  duration: 200,
40
40
  easing: Easing.out(Easing.ease)
41
41
  };
42
+ const CONTENT_PADDING_VERTICAL = KSpacingValue['0.75rem'];
42
43
 
43
- // Long enough to swallow Android's willShow→willHide→willShow flapping
44
- // (~1.3s period, measured), short enough that a real keyboard close still
45
- // settles within one animation.
46
- const KEYBOARD_HIDE_DEBOUNCE_MS = 300;
44
+ // How long past the duration the keyboard event reported the following loop
45
+ // keeps waiting for the live height to actually reach its target before giving
46
+ // up and snapping to it. Only a fallback: normally the loop ends the frame the
47
+ // keyboard does.
48
+ const FOLLOW_FALLBACK_MS = 120;
49
+
50
+ // How long the keyboard-following loop keeps claiming the list after its last
51
+ // frame. That frame's `setState` renders, lays out and emits its scroll event
52
+ // after the loop has returned, and that trailing event carries the same
53
+ // half-applied geometry the rest of the transition does.
54
+ const FOLLOW_SETTLE_MS = 200;
55
+
56
+ // Frames the keyboard-following loop keeps pinning after the reserved room has
57
+ // reached its target. The pin in the frame that sets the final padding runs
58
+ // before React has rendered it and before the native layout has applied, so it
59
+ // finds no room to scroll into. On iOS that goes unnoticed — the loop has many
60
+ // frames and a later one does the work — but Android's first loop frame
61
+ // already reports the finished height, so without a tail nothing ever scrolls
62
+ // and the last message stays behind the keyboard.
63
+ const FOLLOW_TAIL_FRAMES = 3;
64
+
65
+ // Passed as the list's `keyboardOffset` to switch KeyboardChatScrollView's own
66
+ // native `contentInset` off while leaving the rest of the component in place.
67
+ // Its `getEffectiveHeight` interpolates the live keyboard height into
68
+ // `[0, max(keyboardHeight - offset, 0)]`, so an offset no keyboard can exceed
69
+ // pins the inset at 0 for every frame of the transition. The keyboard's room is
70
+ // reserved as real content padding instead — see the keyboard effect below.
71
+ const DISABLE_NATIVE_KEYBOARD_INSET = 1_000_000;
47
72
  const getRowMarginBottom = position => position === 'last' || position === 'solo' ? ROW_MARGIN_CLUSTER_END : ROW_MARGIN_IN_CLUSTER;
48
73
  export const ChatListLegend = /*#__PURE__*/memo(({
49
74
  messages = [],
@@ -95,10 +120,36 @@ export const ChatListLegend = /*#__PURE__*/memo(({
95
120
  const buttonOpacity = useSharedValue(0);
96
121
  const isAtBottomRef = useRef(true);
97
122
  const scrollDebounceRef = useRef(undefined);
98
- // Current keyboard height, and the trigger for the re-anchor effect
99
- // below — see the keyboard listener for how each platform uses it.
123
+ // The state drives the content's bottom padding; the ref is the last
124
+ // value acted on, read synchronously inside the keyboard listener to
125
+ // drop Android's repeat events before they reach React.
126
+ const keyboardInsetRef = useRef(0);
100
127
  const [keyboardInset, setKeyboardInset] = useState(0);
101
- const hasSeenFirstKeyboardInsetRef = useRef(false);
128
+ // The keyboard's live height, written on the UI thread and sampled from
129
+ // the JS thread by the tween below, so the content tracks the keyboard's
130
+ // real position instead of a guessed easing curve.
131
+ //
132
+ // `useGenericKeyboardHandler` rather than `useKeyboardHandler` /
133
+ // `useReanimatedKeyboardAnimation`: those call `useResizeMode()`, which
134
+ // reaches in and sets Android's soft input mode — `KeyboardProvider`
135
+ // already owns that here. The worklet only writes a SharedValue; calling
136
+ // `runOnJS` (or anything that reaches JS) from a keyboard worklet
137
+ // reliably crashes Reanimated with a reentrant-lock assertion.
138
+ // True while the keyboard-following loop below is driving the list.
139
+ const isFollowingKeyboardRef = useRef(false);
140
+ const liveKeyboardHeight = useSharedValue(0);
141
+ useGenericKeyboardHandler({
142
+ onMove: e => {
143
+ 'worklet';
144
+
145
+ liveKeyboardHeight.value = e.height;
146
+ },
147
+ onEnd: e => {
148
+ 'worklet';
149
+
150
+ liveKeyboardHeight.value = e.height;
151
+ }
152
+ }, []);
102
153
  const rowRefsMap = useRef(new Map());
103
154
  // Set on a real touch-drag (`onScrollBeginDrag`), before onStartReached/
104
155
  // onEndReached's threshold could fire. `onMomentumScrollBegin` was tried
@@ -140,14 +191,15 @@ export const ChatListLegend = /*#__PURE__*/memo(({
140
191
  const node = rowRefsMap.current.get(messageId);
141
192
  node?.measureInWindow((x, y, width, height) => {
142
193
  if (x == null || y == null || width == null || height == null) return;
194
+ const groupPosition = messageDataMap.get(messageId)?.groupPosition;
143
195
  onLongPress?.(message, {
144
196
  x,
145
197
  y,
146
198
  width,
147
199
  height
148
- });
200
+ }, groupPosition);
149
201
  });
150
- }, [onLongPress]);
202
+ }, [onLongPress, messageDataMap]);
151
203
  const onRunFlow = useCallback(payload => {
152
204
  runFlow(payload);
153
205
  }, [runFlow]);
@@ -340,6 +392,14 @@ export const ChatListLegend = /*#__PURE__*/memo(({
340
392
  hasUserScrolledRef.current = true;
341
393
  }, []);
342
394
  const handleScroll = useCallback(e => {
395
+ // The keyboard-following loop re-pins to the end on every frame, so
396
+ // the list is at the bottom for the whole transition by construction.
397
+ // The intermediate events it produces are not: content size and
398
+ // offset move a frame apart, and a reading taken between them says
399
+ // "not at bottom" and latches the scroll-to-bottom button on. The
400
+ // loop only runs when the list was already at the bottom, so there is
401
+ // nothing here to detect while it does.
402
+ if (isFollowingKeyboardRef.current) return;
343
403
  const {
344
404
  contentOffset,
345
405
  contentSize,
@@ -379,6 +439,13 @@ export const ChatListLegend = /*#__PURE__*/memo(({
379
439
  isAtBottomRef.current = true;
380
440
  setShowBadge(false);
381
441
  }, [hasMoreNew, onLoadMoreOld, scrollMessageToEnd, buttonOpacity]);
442
+
443
+ // `paddingBottom` here replaces the one `styles.content` sets via
444
+ // `paddingVertical`, so the base spacing has to be added back or the last
445
+ // message ends up flush against the composer.
446
+ const contentContainerStyle = useMemo(() => [styles.content, {
447
+ paddingBottom: CONTENT_PADDING_VERTICAL + keyboardInset
448
+ }], [keyboardInset]);
382
449
  const animatedButtonStyle = useAnimatedStyle(() => ({
383
450
  opacity: buttonOpacity.value,
384
451
  pointerEvents: buttonOpacity.value === 0 ? 'none' : 'auto'
@@ -393,71 +460,157 @@ export const ChatListLegend = /*#__PURE__*/memo(({
393
460
  };
394
461
  }, []);
395
462
 
396
- // Tracks the keyboard's height as the trigger for the re-anchor below.
397
- // The `willHide` side is debounced because Android's event stream flaps
398
- // willShow→willHide→willShow roughly every 1.3s for as long as the
399
- // keyboard stays open (measured on device) — without this, every flap
400
- // would fire another scroll. A real close still lands, just one tick
401
- // later. Setting the same height twice is a no-op for the effect below,
402
- // so a stable open only ever re-anchors once.
463
+ // Reserves the keyboard's room on the content and keeps the list pinned
464
+ // to the end for the length of each keyboard transition.
403
465
  //
404
- // JS-thread only (no worklet), so none of the reentrant-lock crash risk
405
- // that reaching into `useKeyboardHandler`'s worklets carries.
466
+ // JS thread only: no worklet, so none of the reentrant-lock crash risk
467
+ // that reaching into `useKeyboardHandler`'s worklets carries. The one
468
+ // worklet involved only writes `liveKeyboardHeight`, which this reads.
406
469
  useEffect(() => {
407
- let pendingHide;
408
- const clearPendingHide = () => {
409
- if (pendingHide) {
410
- clearTimeout(pendingHide);
411
- pendingHide = undefined;
470
+ let pendingSettle;
471
+ let pendingFrame;
472
+ const clearPending = () => {
473
+ if (pendingSettle) {
474
+ clearTimeout(pendingSettle);
475
+ pendingSettle = undefined;
412
476
  }
477
+ if (pendingFrame !== undefined) {
478
+ cancelAnimationFrame(pendingFrame);
479
+ pendingFrame = undefined;
480
+ }
481
+ isFollowingKeyboardRef.current = false;
482
+ };
483
+
484
+ // Follows the keyboard frame by frame instead of setting the reserved
485
+ // room in one step.
486
+ //
487
+ // Set in one step the layout lift lands in a single frame — measured,
488
+ // 72 of the 302px the content had to travel arrived as an instant jump
489
+ // while only the remaining 230 were animated, and `scrollToEnd`'s own
490
+ // ~324ms curve then finished about 100ms after the keyboard had already
491
+ // stopped. Sampling `liveKeyboardHeight` each frame instead means the
492
+ // padding, and with it the content, is wherever the keyboard actually
493
+ // is — no easing curve to guess at, and the two cannot drift apart.
494
+ //
495
+ // `duration` is only a bound, for a transition whose reported height
496
+ // never quite reaches the event's target.
497
+ const followKeyboard = (to, duration) => {
498
+ const startedAt = Date.now();
499
+ const startedFrom = Math.max(Math.abs(liveKeyboardHeight.value) - keyboardOffset, 0);
500
+ let tailFrames = 0;
501
+ isFollowingKeyboardRef.current = true;
502
+ const step = () => {
503
+ const live = Math.max(Math.abs(liveKeyboardHeight.value) - keyboardOffset, 0);
504
+ // Tracks the keyboard rather than a timer, so the content never
505
+ // keeps travelling after the keyboard has stopped. The elapsed
506
+ // check is only a fallback for a transition whose reported height
507
+ // never quite reaches the target.
508
+ const reached = Math.abs(live - to) <= 1;
509
+ const timedOut = !reached && Date.now() - startedAt >= duration + FOLLOW_FALLBACK_MS;
510
+ // Whether the keyboard moved at all decides what a timeout means. A
511
+ // `keyboardWillHide` the keyboard does not act on leaves the live
512
+ // height exactly where it was, and forcing the target there would
513
+ // collapse the room out from under a keyboard that is still up —
514
+ // which is what the old hide debounce was guarding against, at the
515
+ // cost of the list dropping a beat after the keyboard had gone. A
516
+ // real transition that merely stops a few px short of the height
517
+ // the event reported (iOS, measured ~4px) still has to land exactly
518
+ // on the target.
519
+ const moved = Math.abs(live - startedFrom) > 1;
520
+ if (reached) {
521
+ tailFrames += 1;
522
+ }
523
+ const done = timedOut || tailFrames >= FOLLOW_TAIL_FRAMES;
524
+ const settleTo = reached || timedOut && moved ? to : live;
525
+ setKeyboardInset(settleTo);
526
+ if (settleTo !== to) {
527
+ // Record what the room actually ended up as, or the dedupe in
528
+ // `applyInset` would swallow the next real transition to `to`.
529
+ keyboardInsetRef.current = settleTo;
530
+ }
531
+ // Re-pin every frame. Growing the padding only moves the content on
532
+ // its own while there is `alignItemsAtEndPadding` left to absorb
533
+ // it; once that is gone — any conversation taller than the viewport
534
+ // — the padding just extends the range and nothing moves until
535
+ // something scrolls, and `maintainScrollAtEnd` does not follow a
536
+ // content size change frame by frame (measured: one scroll event,
537
+ // at the very end). Unanimated on purpose: the keyboard is the
538
+ // animation, and an animated scroll here would start a second curve
539
+ // on top of it every frame.
540
+ listRef.current?.scrollToEnd({
541
+ animated: false
542
+ });
543
+ if (done) {
544
+ pendingFrame = undefined;
545
+ // The loop only runs from a list that was at the bottom and it
546
+ // has just pinned it there again, so record that rather than
547
+ // waiting to re-derive it from a scroll event.
548
+ isAtBottomRef.current = true;
549
+ buttonOpacity.value = withTiming(0, BUTTON_FADE_CONFIG);
550
+ setShowBadge(false);
551
+ pendingSettle = setTimeout(() => {
552
+ pendingSettle = undefined;
553
+ isFollowingKeyboardRef.current = false;
554
+ }, FOLLOW_SETTLE_MS);
555
+ return;
556
+ }
557
+ pendingFrame = requestAnimationFrame(step);
558
+ };
559
+ pendingFrame = requestAnimationFrame(step);
413
560
  };
414
- const applyInset = nextInset => {
415
- // Android only: the bottom inset the library applies there is a
416
- // synthetic prop that `onScroll` does not report (its own
417
- // `ScrollViewWithBottomPadding` says as much), so LegendList never
418
- // learns about it, `alignItemsAtEndPadding` never shrinks, and the
419
- // re-anchor below has no room to scroll into. Reporting it closes
420
- // that gap. iOS needs none of this — there the inset is real and
421
- // arrives through `onScroll` on its own, and overriding it here
422
- // would double-count.
423
- if (Platform.OS === 'android') {
424
- listRef.current?.reportContentInset(nextInset > 0 ? {
425
- bottom: nextInset
426
- } : null);
561
+ const applyInset = (nextInset, duration) => {
562
+ // Android's event stream flaps willShow -> willHide -> willShow every
563
+ // ~1.3s for as long as the keyboard stays open (measured), so a
564
+ // repeated height must not restart the follow loop.
565
+ if (keyboardInsetRef.current === nextInset) return;
566
+ keyboardInsetRef.current = nextInset;
567
+
568
+ // Reserve the keyboard's room as real bottom padding on the content
569
+ // and let LegendList own it end to end. The native `contentInset` is
570
+ // switched off (see `keyboardOffset` on the list below), so this is
571
+ // the only thing compensating for the keyboard.
572
+ //
573
+ // The point is an invariant, not an offset: the scroll view's
574
+ // maximum content offset has to BE the visually correct position.
575
+ // Otherwise there is a band of legal resting offsets that all look
576
+ // wrong, and a drag settles somewhere inside it — which is the blank
577
+ // gap above the composer.
578
+ //
579
+ // A native inset cannot hold that invariant here. It widens the
580
+ // scroll range by the full keyboard height, but LegendList
581
+ // independently shrinks `alignItemsAtEndPadding` by the same amount
582
+ // and lifts the content through layout as well. Measured on a short
583
+ // conversation: the layout lift covered 258 of the 302 needed, so
584
+ // only 44 of scrolling was left to do while the range still ran to
585
+ // 302 — 258px of pure slack. As content padding the same 302 goes
586
+ // into `contentSize`, so `alignItemsAtEndPadding` falls out to zero,
587
+ // the range is exactly the lift required, and `scrollToEnd` (which
588
+ // reads LegendList's own metrics, and already offsets itself by
589
+ // `stylePaddingBottom`) lands on it.
590
+ //
591
+ // Skipped when the user has scrolled away from the bottom — opening
592
+ // the keyboard shouldn't yank them back down — but the padding still
593
+ // has to change, or the range would be wrong for them too.
594
+ if (!isAtBottomRef.current) {
595
+ setKeyboardInset(nextInset);
596
+ return;
427
597
  }
428
- setKeyboardInset(nextInset);
598
+ followKeyboard(nextInset, duration);
429
599
  };
430
600
  const willShow = KeyboardEvents.addListener('keyboardWillShow', e => {
431
- clearPendingHide();
432
- applyInset(Math.max(e.height - keyboardOffset, 0));
601
+ clearPending();
602
+ applyInset(Math.max(e.height - keyboardOffset, 0), e.duration);
433
603
  });
434
- const willHide = KeyboardEvents.addListener('keyboardWillHide', () => {
435
- clearPendingHide();
436
- pendingHide = setTimeout(() => applyInset(0), KEYBOARD_HIDE_DEBOUNCE_MS);
604
+ const willHide = KeyboardEvents.addListener('keyboardWillHide', e => {
605
+ clearPending();
606
+ applyInset(0, e.duration);
437
607
  });
438
608
  return () => {
439
- clearPendingHide();
609
+ clearPending();
440
610
  willShow.remove();
441
611
  willHide.remove();
442
612
  };
443
- }, [keyboardOffset, listRef]);
444
-
445
- // Re-anchors to the end once the keyboard's inset has landed, which puts
446
- // the last message at exactly the spacing the closed-keyboard layout has
447
- // (verified on device against both a 7-message and a long conversation).
448
- // Skipped when the user has scrolled away from the bottom — opening the
449
- // keyboard shouldn't yank them back down.
450
- useEffect(() => {
451
- if (!hasSeenFirstKeyboardInsetRef.current) {
452
- hasSeenFirstKeyboardInsetRef.current = true;
453
- return;
454
- }
455
- if (!isAtBottomRef.current) return;
456
- scrollMessageToEnd({
457
- animated: true,
458
- closeKeyboard: false
459
- });
460
- }, [keyboardInset, scrollMessageToEnd]);
613
+ }, [buttonOpacity, keyboardOffset, listRef, liveKeyboardHeight]);
461
614
 
462
615
  // This component can stay mounted across a conversationId change (e.g.
463
616
  // navigation.replace reusing the same screen instance), so this ref must
@@ -575,22 +728,28 @@ export const ChatListLegend = /*#__PURE__*/memo(({
575
728
  ,
576
729
  drawDistance: 800,
577
730
  freeze: freeze,
578
- keyboardOffset: keyboardOffset
579
- // The list's own keyboard compensation is off on both platforms —
580
- // measured, it overshoots badly either way. On iOS it scrolls on
581
- // top of React Native's `automaticallyAdjustContentInsets` (which
582
- // already insets the scroll view by the full keyboard height), and
583
- // that double lift is the blank gap. On Android it overshot by
584
- // ~690px against a 128px target. What both platforms do keep from
585
- // the library is the bottom inset it sets, which is the room the
586
- // re-anchor above scrolls into.
731
+ keyboardOffset: DISABLE_NATIVE_KEYBOARD_INSET
732
+ // iOS only. Left on, React Native recomputes and applies its own
733
+ // `contentInset` every time the keyboard moves — measured here as
734
+ // {top: 59, bottom: keyboardHeight}, both wrong. The bottom value
735
+ // over-insets by the safe area, and the phantom top one is worse:
736
+ // iOS rests a scroll view at `-contentInset.top`.
737
+ // That resting offset is what put the content 59pt low and hid the
738
+ // last message behind the composer the moment a touch let UIKit
739
+ // re-clamp. legend-list's own KeyboardAvoidingLegendList disables
740
+ // this for the same reason.
741
+ ,
742
+ automaticallyAdjustContentInsets: false
743
+ // No scroll shift from the library either — the content padding
744
+ // above is the single compensator. Measured with it on: a 302px
745
+ // keyboard produced a 604px lift, exactly double.
587
746
  ,
588
747
  keyboardLiftBehavior: "never",
589
748
  scrollEventThrottle: 16,
590
749
  onScroll: handleScroll,
591
750
  onScrollBeginDrag: handleScrollBeginDrag,
592
751
  onLoad: handleListLoad,
593
- contentContainerStyle: styles.content,
752
+ contentContainerStyle: contentContainerStyle,
594
753
  scrollIndicatorInsets: {
595
754
  right: 1
596
755
  },
@@ -635,7 +794,7 @@ const styles = StyleSheet.create({
635
794
  position: 'relative'
636
795
  },
637
796
  content: {
638
- paddingVertical: KSpacingValue['0.75rem'],
797
+ paddingVertical: CONTENT_PADDING_VERTICAL,
639
798
  flexGrow: 1
640
799
  },
641
800
  scrollToBottomButton: {