@droppii-org/chat-mobile 0.2.88 → 0.2.90

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 (68) hide show
  1. package/lib/module/components/ThreadCard/ThreadCard.js +0 -1
  2. package/lib/module/components/ThreadCard/ThreadCard.js.map +1 -1
  3. package/lib/module/components/messages/LinkifiedText.js +34 -0
  4. package/lib/module/components/messages/LinkifiedText.js.map +1 -0
  5. package/lib/module/components/messages/quotedMessage/index.js +14 -2
  6. package/lib/module/components/messages/quotedMessage/index.js.map +1 -1
  7. package/lib/module/components/messages/textMessage/index.js +15 -22
  8. package/lib/module/components/messages/textMessage/index.js.map +1 -1
  9. package/lib/module/core/useChatListener.js +2 -2
  10. package/lib/module/core/useChatListener.js.map +1 -1
  11. package/lib/module/hooks/message/useMessages.js +6 -0
  12. package/lib/module/hooks/message/useMessages.js.map +1 -1
  13. package/lib/module/hooks/message/useSendMessage.js +10 -3
  14. package/lib/module/hooks/message/useSendMessage.js.map +1 -1
  15. package/lib/module/screens/ResourceCenter/ResourceCenterTabScene.js.map +1 -1
  16. package/lib/module/screens/chat-detail/ChatDetail.js +2 -1
  17. package/lib/module/screens/chat-detail/ChatDetail.js.map +1 -1
  18. package/lib/module/screens/chat-detail/ChatListLegend.js +235 -123
  19. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  20. package/lib/module/screens/chat-detail/PinnedMessagesSheet.js +4 -1
  21. package/lib/module/screens/chat-detail/PinnedMessagesSheet.js.map +1 -1
  22. package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js +3 -1
  23. package/lib/module/screens/chat-detail/PinnedMessagesSheetBackup.js.map +1 -1
  24. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +11 -9
  25. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  26. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +37 -4
  27. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  28. package/lib/module/services/resourceCenter.js +45 -22
  29. package/lib/module/services/resourceCenter.js.map +1 -1
  30. package/lib/module/store/message.js +8 -1
  31. package/lib/module/store/message.js.map +1 -1
  32. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  33. package/lib/typescript/src/components/messages/LinkifiedText.d.ts +11 -0
  34. package/lib/typescript/src/components/messages/LinkifiedText.d.ts.map +1 -0
  35. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts +1 -0
  36. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
  37. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  38. package/lib/typescript/src/hooks/message/useMessages.d.ts.map +1 -1
  39. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  40. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabScene.d.ts.map +1 -1
  41. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  42. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
  43. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  44. package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheet.d.ts.map +1 -1
  45. package/lib/typescript/src/screens/chat-detail/PinnedMessagesSheetBackup.d.ts.map +1 -1
  46. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts +5 -3
  47. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  48. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +2 -0
  49. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  50. package/lib/typescript/src/services/resourceCenter.d.ts.map +1 -1
  51. package/lib/typescript/src/store/message.d.ts.map +1 -1
  52. package/package.json +2 -2
  53. package/src/components/ThreadCard/ThreadCard.tsx +0 -2
  54. package/src/components/messages/LinkifiedText.tsx +37 -0
  55. package/src/components/messages/quotedMessage/index.tsx +18 -2
  56. package/src/components/messages/textMessage/index.tsx +18 -27
  57. package/src/core/useChatListener.ts +2 -2
  58. package/src/hooks/message/useMessages.ts +6 -0
  59. package/src/hooks/message/useSendMessage.ts +7 -0
  60. package/src/screens/ResourceCenter/ResourceCenterTabScene.tsx +0 -1
  61. package/src/screens/chat-detail/ChatDetail.tsx +1 -0
  62. package/src/screens/chat-detail/ChatListLegend.tsx +236 -136
  63. package/src/screens/chat-detail/PinnedMessagesSheet.tsx +6 -1
  64. package/src/screens/chat-detail/PinnedMessagesSheetBackup.tsx +3 -1
  65. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +16 -11
  66. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +47 -2
  67. package/src/services/resourceCenter.ts +77 -36
  68. package/src/store/message.ts +7 -1
@@ -1 +1 @@
1
- {"version":3,"names":["memo","useCallback","useEffect","useRef","DeviceEventEmitter","useKeyboardScrollToEnd","useSafeAreaInsets","KContainer","useNavigation","usePinMessage","PinnedMessageBar","openPinnedMessagesSheet","MediaViewerModal","useConversation","useConversationStore","useUserStore","ChatDetailHeader","ChatListLegend","ChatComposer","shouldShowAddMember","normalizeUrl","MessageToolboxOverlay","useGetConversationDetail","useGetConversationTarget","GroupJoinSource","useMessages","ChatEvent","useSetIsShowFLashMessage","useJoinGroup","useGroupAccessGuard","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","resolvedShowAddMember","insets","listRef","freeze","scrollMessageToEnd","handlePressUrl","url","handleMessageLongPress","message","rect","handlePressPinnedMessageBar","currentUserId","userID","onPressMessage","emit","ScrollToMessage","clientMessageId","clientMsgID","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","displayName"],"sourceRoot":"../../../../src","sources":["screens/chat-detail/ChatDetail.tsx"],"mappings":";;AAAA,SAASA,IAAI,EAAEC,WAAW,EAAEC,SAAS,EAAEC,MAAM,QAAQ,OAAO;AAC5D,SAASC,kBAAkB,QAAQ,cAAc;AAEjD,SAASC,sBAAsB,QAAQ,0BAA0B;AAEjE,SAASC,iBAAiB,QAAQ,gCAAgC;AAClE,SAASC,UAAU,QAAQ,eAAe;AAC1C,SAASC,aAAa,QAAQ,0BAA0B;AACxD,SAASC,aAAa,QAAQ,sCAAmC;AACjE,SAASC,gBAAgB,QAAQ,uBAAoB;AACrD,SAASC,uBAAuB,QAAQ,0BAAuB;AAC/D,SACEC,gBAAgB,QAEX,sCAAmC;AAC1C,SACEC,eAAe,EACfC,oBAAoB,EACpBC,YAAY,QACP,sBAAa;AACpB,OAAOC,gBAAgB,MAAM,uBAAoB;AACjD,SAASC,cAAc,QAAQ,qBAAkB;AACjD,SAASC,YAAY,QAAQ,mBAAgB;AAC7C,SAASC,mBAAmB,QAAQ,+BAA4B;AAEhE,SAASC,YAAY,QAAQ,oBAAiB;AAC9C,SAASC,qBAAqB,QAAQ,2CAAwC;AAE9E,SACEC,wBAAwB,EACxBC,wBAAwB,QACnB,sDAAmD;AAC1D,SACEC,eAAe,QAEV,+BAA+B;AACtC,SAASC,WAAW,QAAQ,oCAAiC;AAC7D,SAASC,SAAS,QAAQ,uBAAoB;AAC9C,SAASC,wBAAwB,QAAQ,yBAAsB;AAC/D,SAASC,YAAY,QAAQ,oCAAiC;AAC9D,SAASC,mBAAmB,QAAQ,2CAAwC;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAE7E,MAAMC,UAAU,gBAAGlC,IAAI,CACrB,CAAC;EACCmC,oBAAoB,GAAG,EAAE;EACzBC,YAAY,GAAG,EAAE;EACjBC,aAAa,GAAG,EAAE;EAClBC,WAAW,GAAG,EAAE;EAChBC,eAAe,GAAG,EAAE;EACpBC,aAAa;EACbC,MAAM;EACNC,aAAa;EACbC,gBAAgB;EAChBC,WAAW;EACXC,UAAU;EACVC,wBAAwB;EACxBC,aAAa;EACbC;AACe,CAAC,KAAK;EACrB,MAAMC,UAAU,GAAGzC,aAAa,CAAC,CAAC;EAClC,MAAM0C,cAAc,GAAG/C,MAAM,CAAyB,IAAI,CAAC;EAC3D,MAAMgD,iBAAiB,GAAGhD,MAAM,CAAoB,IAAI,CAAC;EACzD,MAAMiD,WAAW,GAAGrC,YAAY,CAAEsC,CAAC,IAAKA,CAAC,CAACC,IAAI,CAAC;EAE/C,MAAM;IAAEC,cAAc;IAAEC,SAAS,EAAEC;EAAsB,CAAC,GACxDnC,wBAAwB,CAAC;IACvBa,oBAAoB;IACpBC,YAAY;IACZC,aAAa;IACbC;EACF,CAAC,CAAC;EAEJ,MAAMoB,YAAY,GAAG7C,eAAe,CAAC0C,cAAc,CAAC;EAEpD,MAAM;IAAEI,IAAI,EAAEC;EAAO,CAAC,GAAGrC,wBAAwB,CAACgC,cAAc,CAAC;EACjE,MAAMM,iBAAiB,GAAG,CAAC,CAACD,MAAM,IAAI,CAACA,MAAM,CAACE,QAAQ;EAEtD,MAAMC,iBAAiB,GAAG9D,WAAW,CAAC,MAAM;IAC1CgD,UAAU,CAACe,MAAM,CAAC,CAAC;EACrB,CAAC,EAAE,CAACf,UAAU,CAAC,CAAC;EAEhB,MAAMgB,gBAAgB,GAAGhE,WAAW,CAClC,CACEiE,KAAoD,EACpDC,KAAc,KACX;IACHjB,cAAc,CAACkB,OAAO,EAAEC,IAAI,CAACH,KAAK,EAAEC,KAAK,CAAC;EAC5C,CAAC,EACD,EACF,CAAC;EAED,MAAM;IACJG,QAAQ;IACRC,WAAW;IACXC,cAAc;IACdC,cAAc;IACdC,kBAAkB;IAClBC,kBAAkB;IAClBC,UAAU;IACVC;EACF,CAAC,GAAGpD,WAAW,CAAC;IACd8B,cAAc;IACduB,gBAAgB,EAAEvC,eAAe;IACjCwC,QAAQ,EAAErB,YAAY,EAAEqB;EAC1B,CAAC,CAAC;EAEF,MAAM;IAAEC,MAAM,EAAEC,SAAS;IAAEC,SAAS,EAAEC;EAAe,CAAC,GAAGvD,YAAY,CAAC,CAAC;EAEvE,MAAMwD,mBAAmB,GAAGzD,wBAAwB,CAAC,CAAC;EAEtD,MAAM;IAAE0D,cAAc;IAAEC,UAAU;IAAEC;EAAa,CAAC,GAChD9E,aAAa,CAAC8C,cAAc,CAAC;EAE/BrD,SAAS,CAAC,MAAM;IACdY,oBAAoB,CACjB0E,QAAQ,CAAC,CAAC,CACVC,sBAAsB,CAAClC,cAAc,EAAEnB,YAAY,EAAEC,aAAa,CAAC;IACtE+C,mBAAmB,CAAC,KAAK,CAAC;IAC1B,OAAO,MAAM;MACXtE,oBAAoB,CACjB0E,QAAQ,CAAC,CAAC,CACVC,sBAAsB,CAACC,SAAS,EAAEA,SAAS,EAAEA,SAAS,CAAC;MAC1DN,mBAAmB,CAAC,IAAI,CAAC;IAC3B,CAAC;EACH,CAAC,EAAE,CAAC7B,cAAc,EAAEnB,YAAY,EAAEC,aAAa,EAAE+C,mBAAmB,CAAC,CAAC;EAEtE,MAAMO,mBAAmB,GAAG1F,WAAW,CAAC,MAAM;IAC5C,CAACwC,MAAM,IAAIsB,iBAAiB,EAAE,CAAC;EACjC,CAAC,EAAE,CAACtB,MAAM,EAAEsB,iBAAiB,CAAC,CAAC;EAE/B,MAAM;IAAE6B;EAAiB,CAAC,GAAG/D,mBAAmB,CAAC;IAC/CgE,OAAO,EAAEnC,YAAY,EAAEmC,OAAO,IAAIxD,aAAa;IAC/CyD,SAAS,EAAEH;EACb,CAAC,CAAC;EAEF,MAAMI,qBAAqB,GACzBvD,aAAa,IAAIrB,mBAAmB,CAACuC,YAAY,CAAC;EAEpD,MAAMsC,MAAM,GAAG1F,iBAAiB,CAAC,CAAC;EAElC,MAAM2F,OAAO,GAAG9F,MAAM,CAAgB,IAAI,CAAC;EAC3C,MAAM;IAAE+F,MAAM;IAAEC;EAAmB,CAAC,GAAG9F,sBAAsB,CAAC;IAC5D4F;EACF,CAAC,CAAC;EAEF,MAAMG,cAAc,GAAGnG,WAAW,CAC/BoG,GAAW,IAAK;IACfxD,UAAU,GAAGzB,YAAY,CAACiF,GAAG,CAAC,CAAC;EACjC,CAAC,EACD,CAACxD,UAAU,CACb,CAAC;EAED,MAAMyD,sBAAsB,GAAGrG,WAAW,CACxC,CAACsG,OAAoB,EAAEC,IAAkB,KAAK;IAC5CrD,iBAAiB,CAACiB,OAAO,EAAEC,IAAI,CAACkC,OAAO,EAAEC,IAAI,CAAC;EAChD,CAAC,EACD,EACF,CAAC;EAED,MAAMC,2BAA2B,GAAGxG,WAAW,CAAC,MAAM;IACpDU,uBAAuB,CAAC;MACtB+F,aAAa,EAAEtD,WAAW,EAAEuD,MAAM;MAClCpB,YAAY;MACZzC,wBAAwB;MACxB8D,cAAc,EAAGL,OAAoB,IAAK;QACxCnG,kBAAkB,CAACyG,IAAI,CAACnF,SAAS,CAACoF,eAAe,EAAE;UACjDC,eAAe,EAAER,OAAO,EAAES;QAC5B,CAAC,CAAC;MACJ;IACF,CAAC,CAAC;EACJ,CAAC,EAAE,CAAC5D,WAAW,EAAEmC,YAAY,EAAEzC,wBAAwB,CAAC,CAAC;EAEzD,MAAMmE,eAAe,GAAGhH,WAAW,CAAC,MAAM;IACxC2C,WAAW,GAAGW,cAAc,CAAC;EAC/B,CAAC,EAAE,CAACA,cAAc,EAAEX,WAAW,CAAC,CAAC;EAEjC,MAAMsE,oBAAoB,GAAGjH,WAAW,CAAC,MAAM;IAC7C,IAAI,CAACyD,YAAY,EAAEmC,OAAO,EAAE;IAC5BZ,SAAS,CAAC;MACRY,OAAO,EAAEnC,YAAY,EAAEmC,OAAO;MAC9BsB,MAAM,EAAE,EAAE;MACVC,UAAU,EAAEpE,eAAe,IAAIxB,eAAe,CAAC6F,MAAM;MACrDC,SAAS,EAAE5D,YAAY,EAAE6D,QAAQ,IAAI;IACvC,CAAC,CAAC;EACJ,CAAC,EAAE,CACDvE,eAAe,EACfU,YAAY,EAAEmC,OAAO,EACrBZ,SAAS,EACTvB,YAAY,EAAE6D,QAAQ,CACvB,CAAC;EAEF,oBACEtF,KAAA,CAAC1B,UAAU,CAACiH,IAAI;IAACC,IAAI;IAACC,KAAK,EAAE,CAAC,QAAQ,CAAE;IAAAC,QAAA,gBACtC5F,IAAA,CAACf,gBAAgB;MACfuC,cAAc,EAAEA,cAAe;MAC/BnB,YAAY,EAAEA,YAAa;MAC3BI,aAAa,EAAEuD,qBAAsB;MACrCtD,MAAM,EAAEA,MAAM,IAAIsB,iBAAkB;MACpCrB,aAAa,EAAEA,aAAc;MAC7BC,gBAAgB,EAAEA,gBAAiB;MACnCC,WAAW,EAAEqE,eAAgB;MAC7BW,aAAa,EAAEX;IAAgB,CAChC,CAAC,eAEFlF,IAAA,CAACrB,gBAAgB;MACf4D,QAAQ,EAAEe,cAAe;MACzBqB,aAAa,EAAEtD,WAAW,EAAEuD,MAAO;MACnCkB,OAAO,EAAEpB;IAA4B,CACtC,CAAC,eAEF1E,IAAA,CAACd,cAAc;MACbqD,QAAQ,EAAEA,QAAS;MACnBf,cAAc,EAAEA,cAAe;MAC/BT,wBAAwB,EAAEA,wBAAyB;MACnDU,SAAS,EAAEe,WAAW,IAAId,qBAAqB,IAAImC,gBAAiB;MACpEkC,aAAa,EAAEpD,kBAAmB;MAClCE,UAAU,EAAEA,UAAW;MACvBJ,cAAc,EAAEA,cAAe;MAC/BuD,aAAa,EAAEpD,kBAAmB;MAClCE,UAAU,EAAEA,UAAW;MACvBJ,cAAc,EAAEA,cAAe;MAC/BuD,YAAY,EAAE/D,gBAAiB;MAC/BpB,UAAU,EAAEuD,cAAe;MAC3B6B,WAAW,EAAE3B,sBAAuB;MACpCL,OAAO,EAAEA,OAAQ;MACjBC,MAAM,EAAEA,MAAO;MACfC,kBAAkB,EAAEA,kBAAmB;MACvC+B,cAAc,EAAElC,MAAM,CAACmC;IAAO,CAC/B,CAAC,eAEFpG,IAAA,CAACb,YAAY;MACXkH,cAAc,EAAE9D,QAAQ,EAAE+D,MAAM,KAAK,CAAE;MACvCtF,aAAa,EAAEA,aAAc;MAC7Bc,iBAAiB,EAAEA,iBAAkB;MACrCyE,gBAAgB,EAAEpB,oBAAqB;MACvC/B,cAAc,EAAEA;IAAe,CAChC,CAAC,eAEFpD,IAAA,CAACnB,gBAAgB;MAAC2H,GAAG,EAAErF;IAAe,CAAE,CAAC,eACzCnB,IAAA,CAACV,qBAAqB;MACpBkH,GAAG,EAAEpF,iBAAkB;MACvBuD,aAAa,EAAEtD,WAAW,EAAEuD,MAAO;MACnC7D,wBAAwB,EAAEA,wBAAyB;MACnD0F,YAAY,EAAElD,UAAW;MACzBmD,cAAc,EAAElD;IAAa,CAC9B,CAAC;EAAA,CACa,CAAC;AAEtB,CACF,CAAC;AAEDrD,UAAU,CAACwG,WAAW,GAAG,YAAY;AAErC,eAAexG,UAAU","ignoreList":[]}
1
+ {"version":3,"names":["memo","useCallback","useEffect","useRef","DeviceEventEmitter","useKeyboardScrollToEnd","useSafeAreaInsets","KContainer","useNavigation","usePinMessage","PinnedMessageBar","openPinnedMessagesSheet","MediaViewerModal","useConversation","useConversationStore","useUserStore","ChatDetailHeader","ChatListLegend","ChatComposer","shouldShowAddMember","normalizeUrl","MessageToolboxOverlay","useGetConversationDetail","useGetConversationTarget","GroupJoinSource","useMessages","ChatEvent","useSetIsShowFLashMessage","useJoinGroup","useGroupAccessGuard","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","resolvedShowAddMember","insets","listRef","freeze","scrollMessageToEnd","handlePressUrl","url","handleMessageLongPress","message","rect","handlePressPinnedMessageBar","currentUserId","userID","onPressMessage","emit","ScrollToMessage","clientMessageId","clientMsgID","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","displayName"],"sourceRoot":"../../../../src","sources":["screens/chat-detail/ChatDetail.tsx"],"mappings":"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","ignoreList":[]}
@@ -4,7 +4,7 @@ import { memo, useCallback, useMemo, useEffect, useRef, useState } from 'react';
4
4
  import { ActivityIndicator, DeviceEventEmitter, Platform, 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 { LegendList } from '@legendapp/list/react-native';
7
+ import { KeyboardEvents } 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";
@@ -35,11 +35,16 @@ import ChatImageDimens, { CHAT_IMAGE_VIEW_SOURCE } from "../../utils/chatImageDi
35
35
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
36
36
  const ROW_MARGIN_CLUSTER_END = KSpacingValue['0.5rem'];
37
37
  const ROW_MARGIN_IN_CLUSTER = KSpacingValue['0.25rem'];
38
- const getRowMarginBottom = position => position === 'last' || position === 'solo' ? ROW_MARGIN_CLUSTER_END : ROW_MARGIN_IN_CLUSTER;
38
+ const BUTTON_FADE_CONFIG = {
39
+ duration: 200,
40
+ easing: Easing.out(Easing.ease)
41
+ };
39
42
 
40
- // KeyboardAwareLegendList auto scroll when modal opened
41
- // TODO: Convert toolbox to absolute view
42
- const ListComponent = Platform.OS === 'ios' ? KeyboardAwareLegendList : LegendList;
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;
47
+ const getRowMarginBottom = position => position === 'last' || position === 'solo' ? ROW_MARGIN_CLUSTER_END : ROW_MARGIN_IN_CLUSTER;
43
48
  export const ChatListLegend = /*#__PURE__*/memo(({
44
49
  messages = [],
45
50
  conversationId,
@@ -48,7 +53,6 @@ export const ChatListLegend = /*#__PURE__*/memo(({
48
53
  moreOldLoading,
49
54
  hasMoreOld,
50
55
  onLoadMoreNew,
51
- moreNewLoading,
52
56
  hasMoreNew,
53
57
  onMediaPress,
54
58
  onPressUrl,
@@ -91,11 +95,37 @@ export const ChatListLegend = /*#__PURE__*/memo(({
91
95
  const buttonOpacity = useSharedValue(0);
92
96
  const isAtBottomRef = useRef(true);
93
97
  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.
100
+ const [keyboardInset, setKeyboardInset] = useState(0);
101
+ const hasSeenFirstKeyboardInsetRef = useRef(false);
94
102
  const rowRefsMap = useRef(new Map());
95
- // Flips true once LegendList's onLoad fires (initial layout + initial
96
- // scroll both settled) — before that, onStartReached/onEndReached can
97
- // fire purely from resting scroll position, not a real user gesture.
98
- const hasSettledRef = useRef(false);
103
+ // Set on a real touch-drag (`onScrollBeginDrag`), before onStartReached/
104
+ // onEndReached's threshold could fire. `onMomentumScrollBegin` was tried
105
+ // and reverted: it only fires after the finger lifts, so the first
106
+ // gesture's mid-drag threshold-crossing beats it, needing a wasted
107
+ // second gesture to actually unlock. See
108
+ // project_chatlistlegend_scroll_debugging for the full history.
109
+ const hasUserScrolledRef = useRef(false);
110
+ // Old Architecture defers recomputing `alignItemsAtEndPadding` when
111
+ // content shrinks while `initialScrollAtEnd` is active. `estimatedItemSize`
112
+ // below is a deliberate lower bound to mostly avoid that; this ref
113
+ // backstops the rest via one forced `scrollToEnd` on mount (`handleListLoad`).
114
+ const hasCorrectedInitialPaddingRef = useRef(false);
115
+ // `messages` starts empty, so `lastMessageId`'s first real value looks
116
+ // identical to a new message — tracking the previous id (not a one-shot
117
+ // flag) lets the effect below tell the two apart.
118
+ const previousLastMessageIdRef = useRef(null);
119
+ // Read via ref (not as a dependency) by the `lastMessageId` effect below —
120
+ // a loading-state flip alone shouldn't re-run it.
121
+ const moreOldLoadingRef = useRef(moreOldLoading);
122
+ moreOldLoadingRef.current = moreOldLoading;
123
+ // Read via ref (not as a dependency) by the ScrollToMessage subscription
124
+ // below — the whole array changes on every new message and every old-
125
+ // history page, which would otherwise tear down and re-add both
126
+ // DeviceEventEmitter listeners on every one of those.
127
+ const messagesRef = useRef(messages);
128
+ messagesRef.current = messages;
99
129
  const [showBadge, setShowBadge] = useState(false);
100
130
  const [highlightedId, setHighlightedId] = useState(null);
101
131
  const highlightTimerRef = useRef(null);
@@ -168,13 +198,14 @@ export const ChatListLegend = /*#__PURE__*/memo(({
168
198
  collapsable: false,
169
199
  children: /*#__PURE__*/_jsx(LegendChatMessage, {
170
200
  message: message,
201
+ messageId: messageId,
171
202
  isOutgoing: isOutgoing,
172
203
  createdAtTime: createdAt,
173
204
  onMediaPress: onMediaPress,
174
205
  onPressUrl: onPressUrl,
175
206
  onRetry: retryMessage,
176
- onPress: () => handleMessagePress(messageId),
177
- onLongPress: onLongPress ? () => handleMessageLongPress(messageId, message) : undefined,
207
+ onPress: handleMessagePress,
208
+ onLongPress: onLongPress ? handleMessageLongPress : undefined,
178
209
  customMessageItemFactory: customMessageItemFactory,
179
210
  isHighlighted: highlightedId === messageId,
180
211
  isTimeRevealed: revealedMessageId === messageId,
@@ -201,48 +232,33 @@ export const ChatListLegend = /*#__PURE__*/memo(({
201
232
  }
202
233
  }, [currentUserId, lastMessageId, messageDataMap, isGroupConversation, isBotConversation, onMediaPress, onPressUrl, onLongPress, handleMessageLongPress, customMessageItemFactory, onResetFlow, onRunFlow, conversationFlow?.flowCurrentStep, conversationFlow?.havingBotMessage, retryMessage, highlightedId, revealedMessageId, handleMessagePress]);
203
234
 
204
- // const maintainScrollConfig = useMemo(
205
- // () => ({
206
- // animated: false,
207
- // on: {
208
- // dataChange: !moreOldLoading,
209
- // layout: !moreOldLoading,
210
- // // Must be disabled while prepending older history; otherwise re-measuring
211
- // // the freshly prepended items snaps the list back to the bottom.
212
- // itemLayout: !moreOldLoading,
213
- // },
214
- // }),
215
- // [moreOldLoading]
216
- // );
217
-
218
- // Stable config so the native maintainVisibleContentPosition prop isn't toggled
219
- // mid-prepend (toggling forces an extra layout recompute). `data` anchoring keeps
220
- // the viewport pinned to the item being read when older messages are added on top.
235
+ // `maintainVisibleContentPosition` anchors a prepend to whatever native
236
+ // view sits at the top. Default `drawDistance` (250px) doesn't survive a
237
+ // ~1-1.5s history fetch if the user keeps scrolling away — the anchor
238
+ // unmounts and the native compensation silently drops (the "list drifts
239
+ // after loading old history" bug). `drawDistance` below is raised to
240
+ // outlast a realistic scroll during that wait (measured ~300-350px).
241
+ // `recycleItems` is safe again since @legendapp/list 3.3.7 fixed
242
+ // recycled rows flashing wrong content on prepend.
221
243
  const maintainVisiblePositionConfig = useMemo(() => ({
222
244
  data: true
223
245
  }), []);
224
246
 
225
- // Buckets averaging (`averageSizes[itemType]`, internal to LegendList) per
226
- // content type, so a batch of images landing early doesn't pollute the
227
- // estimate used for still-unmeasured text rows and vice versa. Items
228
- // rendered via `customMessageItemFactory` (host apps can override ANY
229
- // content type with an arbitrary custom node — see `LegendChatMessage`'s
230
- // `customNode` short-circuit, checked before its own contentType switch)
231
- // get their own bucket per content type, so a custom node's real measured
232
- // height never pollutes the native component's average for that same type.
247
+ // Buckets LegendList's size-averaging per content type so a batch of
248
+ // images landing early doesn't pollute the estimate for still-unmeasured
249
+ // text rows. `customMessageItemFactory` nodes get their own bucket per
250
+ // type too, so a custom node's real height doesn't pollute that type's average.
233
251
  const getItemType = useCallback(item => {
234
252
  const isCustom = !!customMessageItemFactory?.(item);
235
253
  return `${item?.contentType ?? 'default'}${isCustom ? '-custom' : ''}`;
236
254
  }, [customMessageItemFactory]);
237
255
 
238
- // Exact size for content types that are knowable ahead of render — see
239
- // `plans/260728-1400-chatlist-legend-fixed-item-size/plan.md`. Returns
240
- // `undefined` (defer to dynamic measurement) for anything whose size can't
241
- // be predicted, AND for any item carrying extra chrome this function
242
- // doesn't account for (day-divider above it, trailing flow-input-buttons
243
- // row) — `getFixedItemSize` describes the ENTIRE item LegendList renders,
244
- // and once it resolves a number that item is never measured again, so
245
- // guessing wrong here is a permanent regression, not just an approximation.
256
+ // Exact size for content types knowable ahead of render (see
257
+ // `plans/260728-1400-chatlist-legend-fixed-item-size/plan.md`). Returns
258
+ // `undefined` (defer to measurement) for anything unpredictable, or
259
+ // carrying chrome this function doesn't account for (day-divider, flow-
260
+ // input-buttons row) — a resolved number is never re-measured, so
261
+ // guessing wrong here is permanent, not just approximate.
246
262
  const getFixedItemSize = useCallback((item, index) => {
247
263
  const message = item;
248
264
  const messageId = getMessageRenderId(message, index);
@@ -294,13 +310,10 @@ export const ChatListLegend = /*#__PURE__*/memo(({
294
310
  return candidateSize;
295
311
  }, [messageDataMap, customMessageItemFactory, currentUserId, isGroupConversation, lastMessageId, isBotConversation, conversationFlow?.flowCurrentStep, windowWidth]);
296
312
 
297
- // LegendList memoises each rendered item on [itemKey, item, extraData], so
298
- // an item whose own object is unchanged will not re-render. Cluster
299
- // position depends on a message's NEIGHBOURS, which can change while the
300
- // message itself does not — prepending older history is enough to turn a
301
- // 'first' into a 'middle'. messageDataMap is memoised on exactly the inputs
302
- // that can move those boundaries, so including it here is what keeps a
303
- // stale tail from being left behind.
313
+ // LegendList only re-renders an item when [itemKey, item, extraData]
314
+ // changes. Cluster position depends on a message's NEIGHBOURS, which can
315
+ // shift (prepending history can turn a 'first' into a 'middle') without
316
+ // the message itself changing — messageDataMap here is what catches that.
304
317
  const listExtraData = useMemo(() => ({
305
318
  messageDataMap,
306
319
  flowStepId: conversationFlow?.flowCurrentStep?.id ?? '',
@@ -309,39 +322,23 @@ export const ChatListLegend = /*#__PURE__*/memo(({
309
322
  revealedMessageId
310
323
  }), [messageDataMap, conversationFlow?.flowCurrentStep?.id, lastMessageId, highlightedId, revealedMessageId]);
311
324
  const handleStartReached = useCallback(() => {
312
- const willLoad = hasMoreOld && !moreOldLoading;
313
- if (__DEV__) {
314
- console.log(willLoad && !hasSettledRef.current ? '[LegendList] ⚠️ onStartReached SPURIOUS mount-time load' : '[LegendList] onStartReached triggered', {
315
- conversationId,
316
- messagesLength: messages.length,
317
- firstMessageId: messages[0]?.clientMsgID || messages[0]?.serverMsgID,
318
- hasMoreOld,
319
- moreOldLoading,
320
- willLoad,
321
- duringInitialSettle: !hasSettledRef.current
322
- });
323
- }
324
- if (willLoad) {
325
+ // Both thresholds are checked continuously, including at mount before
326
+ // any gesture — `hasUserScrolledRef` is what keeps that from firing a
327
+ // load-more the user never asked for.
328
+ if (!hasUserScrolledRef.current) return;
329
+ if (hasMoreOld && !useMessageStore.getState().moreOldLoading) {
325
330
  onLoadMoreOld?.(true);
326
331
  }
327
- }, [conversationId, messages, hasMoreOld, moreOldLoading, onLoadMoreOld]);
332
+ }, [hasMoreOld, onLoadMoreOld]);
328
333
  const handleEndReached = useCallback(() => {
329
- const willLoad = hasMoreNew && !moreNewLoading;
330
- if (__DEV__) {
331
- console.log(willLoad && !hasSettledRef.current ? '[LegendList] ⚠️ onEndReached SPURIOUS mount-time load' : '[LegendList] onEndReached triggered', {
332
- conversationId,
333
- messagesLength: messages.length,
334
- lastMessageId: messages[messages.length - 1]?.clientMsgID,
335
- hasMoreNew,
336
- moreNewLoading,
337
- willLoad,
338
- duringInitialSettle: !hasSettledRef.current
339
- });
340
- }
341
- if (willLoad) {
334
+ if (!hasUserScrolledRef.current) return;
335
+ if (hasMoreNew && !useMessageStore.getState().moreNewLoading) {
342
336
  onLoadMoreNew?.(true);
343
337
  }
344
- }, [conversationId, messages, hasMoreNew, moreNewLoading, onLoadMoreNew]);
338
+ }, [hasMoreNew, onLoadMoreNew]);
339
+ const handleScrollBeginDrag = useCallback(() => {
340
+ hasUserScrolledRef.current = true;
341
+ }, []);
345
342
  const handleScroll = useCallback(e => {
346
343
  const {
347
344
  contentOffset,
@@ -357,10 +354,7 @@ export const ChatListLegend = /*#__PURE__*/memo(({
357
354
  scrollDebounceRef.current = setTimeout(() => {
358
355
  if (isAtBottomRef.current !== isAtBottom) {
359
356
  isAtBottomRef.current = isAtBottom;
360
- buttonOpacity.value = withTiming(isAtBottom ? 0 : 1, {
361
- duration: 200,
362
- easing: Easing.out(Easing.ease)
363
- });
357
+ buttonOpacity.value = withTiming(isAtBottom ? 0 : 1, BUTTON_FADE_CONFIG);
364
358
  }
365
359
  if (isAtBottom) {
366
360
  ChatMessageAPI.markConversationAsRead(conversationId || '');
@@ -370,18 +364,20 @@ export const ChatListLegend = /*#__PURE__*/memo(({
370
364
  const handleScrollToBottom = useCallback(() => {
371
365
  if (hasMoreNew) {
372
366
  onLoadMoreOld?.(false);
373
- } else {
374
- scrollMessageToEnd({
375
- animated: true,
376
- closeKeyboard: false
377
- });
378
- buttonOpacity.value = withTiming(0, {
379
- duration: 200,
380
- easing: Easing.out(Easing.ease)
381
- });
382
- isAtBottomRef.current = true;
383
- setShowBadge(false);
367
+ return;
384
368
  }
369
+ // `ChatEvent.ScrollToBottom` fires after every self-sent message, but
370
+ // the `lastMessageId` effect below already animates to it — re-scrolling
371
+ // here raced it and flickered. Only act when not already at bottom
372
+ // (FAB tap, or a send while scrolled away).
373
+ if (isAtBottomRef.current) return;
374
+ scrollMessageToEnd({
375
+ animated: true,
376
+ closeKeyboard: false
377
+ });
378
+ buttonOpacity.value = withTiming(0, BUTTON_FADE_CONFIG);
379
+ isAtBottomRef.current = true;
380
+ setShowBadge(false);
385
381
  }, [hasMoreNew, onLoadMoreOld, scrollMessageToEnd, buttonOpacity]);
386
382
  const animatedButtonStyle = useAnimatedStyle(() => ({
387
383
  opacity: buttonOpacity.value,
@@ -397,19 +393,115 @@ export const ChatListLegend = /*#__PURE__*/memo(({
397
393
  };
398
394
  }, []);
399
395
 
400
- // Debug-only: this component can stay mounted across a conversationId
401
- // change (e.g. navigation.replace reusing the same screen instance), so
402
- // hasSettledRef must reset per-conversation for the spurious-load log
403
- // above to stay accurate for the newly opened conversation.
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.
403
+ //
404
+ // JS-thread only (no worklet), so none of the reentrant-lock crash risk
405
+ // that reaching into `useKeyboardHandler`'s worklets carries.
404
406
  useEffect(() => {
405
- hasSettledRef.current = false;
407
+ let pendingHide;
408
+ const clearPendingHide = () => {
409
+ if (pendingHide) {
410
+ clearTimeout(pendingHide);
411
+ pendingHide = undefined;
412
+ }
413
+ };
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);
427
+ }
428
+ setKeyboardInset(nextInset);
429
+ };
430
+ const willShow = KeyboardEvents.addListener('keyboardWillShow', e => {
431
+ clearPendingHide();
432
+ applyInset(Math.max(e.height - keyboardOffset, 0));
433
+ });
434
+ const willHide = KeyboardEvents.addListener('keyboardWillHide', () => {
435
+ clearPendingHide();
436
+ pendingHide = setTimeout(() => applyInset(0), KEYBOARD_HIDE_DEBOUNCE_MS);
437
+ });
438
+ return () => {
439
+ clearPendingHide();
440
+ willShow.remove();
441
+ willHide.remove();
442
+ };
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]);
461
+
462
+ // This component can stay mounted across a conversationId change (e.g.
463
+ // navigation.replace reusing the same screen instance), so this ref must
464
+ // reset per-conversation — otherwise a leftover "user scrolled" flag from
465
+ // the previous conversation would wrongly let load-more fire on the
466
+ // newly opened one before any real scroll there.
467
+ useEffect(() => {
468
+ hasUserScrolledRef.current = false;
469
+ hasCorrectedInitialPaddingRef.current = false;
470
+ previousLastMessageIdRef.current = null;
406
471
  }, [conversationId]);
472
+
473
+ // Single source of truth for "scroll to bottom on a new message" (self-
474
+ // sent or received). Skipped while prepending old history or scrolled
475
+ // away from the bottom. `moreOldLoading` is read via ref, not a
476
+ // dependency, so its own start/finish toggle doesn't re-run this.
477
+ useEffect(() => {
478
+ const previousLastMessageId = previousLastMessageIdRef.current;
479
+ previousLastMessageIdRef.current = lastMessageId;
480
+
481
+ // null → a real id is the initial fetch landing (initialScrollAtEnd +
482
+ // handleListLoad's job), not a new message; a real id → a different
483
+ // one is.
484
+ const shouldFire = previousLastMessageId !== null && !!lastMessageId && !moreOldLoadingRef.current && isAtBottomRef.current;
485
+ if (!shouldFire) return;
486
+ scrollMessageToEnd({
487
+ animated: true,
488
+ closeKeyboard: false
489
+ });
490
+ }, [lastMessageId, scrollMessageToEnd]);
491
+ const handleListLoad = useCallback(() => {
492
+ if (hasCorrectedInitialPaddingRef.current) return;
493
+ hasCorrectedInitialPaddingRef.current = true;
494
+ scrollMessageToEnd({
495
+ animated: false,
496
+ closeKeyboard: false
497
+ });
498
+ }, [scrollMessageToEnd]);
407
499
  useEffect(() => {
408
500
  const bottomSubscription = DeviceEventEmitter.addListener(ChatEvent.ScrollToBottom, handleScrollToBottom);
409
501
  const subscription = DeviceEventEmitter.addListener(ChatEvent.ScrollToMessage, ({
410
502
  clientMessageId
411
503
  }) => {
412
- const index = messages.findIndex(m => m.clientMsgID === clientMessageId);
504
+ const index = messagesRef.current.findIndex(m => m.clientMsgID === clientMessageId);
413
505
  if (index >= 0) {
414
506
  try {
415
507
  listRef.current?.scrollToIndex({
@@ -441,7 +533,7 @@ export const ChatListLegend = /*#__PURE__*/memo(({
441
533
  bottomSubscription.remove();
442
534
  subscription.remove();
443
535
  };
444
- }, [messages, applicationType, searchMessages, conversationId, buttonOpacity, handleScrollToBottom, listRef]);
536
+ }, [applicationType, searchMessages, conversationId, handleScrollToBottom, listRef]);
445
537
  if (isLoading) {
446
538
  return /*#__PURE__*/_jsx(KContainer.View, {
447
539
  flex: true,
@@ -453,27 +545,51 @@ export const ChatListLegend = /*#__PURE__*/memo(({
453
545
  })
454
546
  });
455
547
  }
456
- console.log('messages length: ', +messages.length);
457
548
  return /*#__PURE__*/_jsxs(KContainer.View, {
458
549
  flex: true,
459
550
  background: KColors.white,
460
551
  style: styles.container,
461
- children: [/*#__PURE__*/_jsx(ListComponent, {
552
+ children: [/*#__PURE__*/_jsx(KeyboardAwareLegendList, {
462
553
  ref: listRef,
463
554
  data: messages,
464
- renderItem: renderItem,
465
- estimatedItemSize: 200,
555
+ renderItem: renderItem
556
+ // Deliberate lower bound (not a best-guess average) — see the
557
+ // comment above `hasCorrectedInitialPaddingRef`. A single-line
558
+ // bubble floors out around 38-44px, so this stays safely under it.
559
+ ,
560
+ estimatedItemSize: 36,
466
561
  getItemType: getItemType,
467
562
  getFixedItemSize: getFixedItemSize,
468
563
  keyExtractor: item => String(item.clientMsgID || item.serverMsgID),
469
- recycleItems: false,
564
+ recycleItems: true,
470
565
  extraData: listExtraData,
471
566
  alignItemsAtEnd: true,
472
- maintainVisibleContentPosition: maintainVisiblePositionConfig,
567
+ alwaysRender: {
568
+ top: 10
569
+ },
570
+ maintainScrollAtEnd: true,
571
+ maintainVisibleContentPosition: maintainVisiblePositionConfig
572
+ // See the comment above `maintainVisiblePositionConfig` — this is
573
+ // what keeps a history prepend's anchor item mounted through a slow
574
+ // fetch instead of the default 250px window losing it.
575
+ ,
576
+ drawDistance: 800,
473
577
  freeze: freeze,
474
- keyboardOffset: keyboardOffset,
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.
587
+ ,
588
+ keyboardLiftBehavior: "never",
475
589
  scrollEventThrottle: 16,
476
590
  onScroll: handleScroll,
591
+ onScrollBeginDrag: handleScrollBeginDrag,
592
+ onLoad: handleListLoad,
477
593
  contentContainerStyle: styles.content,
478
594
  scrollIndicatorInsets: {
479
595
  right: 1
@@ -481,22 +597,18 @@ export const ChatListLegend = /*#__PURE__*/memo(({
481
597
  ListHeaderComponent: /*#__PURE__*/_jsx(LegendChatLoadEarlier, {
482
598
  isLoading: moreOldLoading
483
599
  }),
484
- onLoad: info => {
485
- hasSettledRef.current = true;
486
- if (__DEV__) {
487
- console.log('[LegendList] onLoad (initial settle done)', {
488
- conversationId,
489
- messagesLength: messages.length,
490
- elapsedTimeInMs: info.elapsedTimeInMs
491
- });
492
- }
493
- },
494
- onStartReached: handleStartReached,
495
- onStartReachedThreshold: 0.5,
600
+ onStartReached: handleStartReached
601
+ // LegendList only re-fires onStartReached after scrolling away from
602
+ // the threshold by 1.3x and back. At 0.5 that distance exceeded a
603
+ // short conversation's whole scrollable range, so the mount-time
604
+ // firing we suppress in handleStartReached "used up" the only
605
+ // trigger. 0.3 keeps that reset distance safely under a
606
+ // ~1.5-screen conversation; shorter ones can still hit it.
607
+ ,
608
+ onStartReachedThreshold: 0.2,
496
609
  onEndReached: handleEndReached,
497
610
  onEndReachedThreshold: 0.2,
498
- showsVerticalScrollIndicator: false,
499
- initialScrollIndex: messages.length - 1
611
+ initialScrollAtEnd: true
500
612
  }), /*#__PURE__*/_jsx(Animated.View, {
501
613
  style: [styles.scrollToBottomButton, animatedButtonStyle],
502
614
  children: /*#__PURE__*/_jsxs(KContainer.Touchable, {