@droppii-org/chat-mobile 0.2.88 → 0.2.89

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 (59) 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 +259 -117
  19. package/lib/module/screens/chat-detail/ChatListLegend.js.map +1 -1
  20. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js +6 -5
  21. package/lib/module/screens/chat-detail/legend/LegendChatMessage.js.map +1 -1
  22. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js +36 -4
  23. package/lib/module/screens/chat-detail/messageToolbox/MessageToolboxOverlay.js.map +1 -1
  24. package/lib/module/services/resourceCenter.js +45 -22
  25. package/lib/module/services/resourceCenter.js.map +1 -1
  26. package/lib/module/store/message.js +8 -1
  27. package/lib/module/store/message.js.map +1 -1
  28. package/lib/typescript/src/components/ThreadCard/ThreadCard.d.ts.map +1 -1
  29. package/lib/typescript/src/components/messages/LinkifiedText.d.ts +11 -0
  30. package/lib/typescript/src/components/messages/LinkifiedText.d.ts.map +1 -0
  31. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts +1 -0
  32. package/lib/typescript/src/components/messages/quotedMessage/index.d.ts.map +1 -1
  33. package/lib/typescript/src/components/messages/textMessage/index.d.ts.map +1 -1
  34. package/lib/typescript/src/hooks/message/useMessages.d.ts.map +1 -1
  35. package/lib/typescript/src/hooks/message/useSendMessage.d.ts.map +1 -1
  36. package/lib/typescript/src/screens/ResourceCenter/ResourceCenterTabScene.d.ts.map +1 -1
  37. package/lib/typescript/src/screens/chat-detail/ChatDetail.d.ts.map +1 -1
  38. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts +1 -1
  39. package/lib/typescript/src/screens/chat-detail/ChatListLegend.d.ts.map +1 -1
  40. package/lib/typescript/src/screens/chat-detail/legend/LegendChatMessage.d.ts.map +1 -1
  41. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts +2 -0
  42. package/lib/typescript/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.d.ts.map +1 -1
  43. package/lib/typescript/src/services/resourceCenter.d.ts.map +1 -1
  44. package/lib/typescript/src/store/message.d.ts.map +1 -1
  45. package/package.json +2 -2
  46. package/src/components/ThreadCard/ThreadCard.tsx +0 -2
  47. package/src/components/messages/LinkifiedText.tsx +37 -0
  48. package/src/components/messages/quotedMessage/index.tsx +18 -2
  49. package/src/components/messages/textMessage/index.tsx +18 -27
  50. package/src/core/useChatListener.ts +2 -2
  51. package/src/hooks/message/useMessages.ts +6 -0
  52. package/src/hooks/message/useSendMessage.ts +7 -0
  53. package/src/screens/ResourceCenter/ResourceCenterTabScene.tsx +0 -1
  54. package/src/screens/chat-detail/ChatDetail.tsx +1 -0
  55. package/src/screens/chat-detail/ChatListLegend.tsx +292 -130
  56. package/src/screens/chat-detail/legend/LegendChatMessage.tsx +7 -5
  57. package/src/screens/chat-detail/messageToolbox/MessageToolboxOverlay.tsx +46 -2
  58. package/src/services/resourceCenter.ts +77 -36
  59. 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,15 @@ 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;
39
38
 
40
- // KeyboardAwareLegendList auto scroll when modal opened
41
- // TODO: Convert toolbox to absolute view
42
- const ListComponent = Platform.OS === 'ios' ? KeyboardAwareLegendList : LegendList;
39
+ // Tags every __DEV__ diagnostic log below with the platform, so logs from an
40
+ // iOS and an Android run can be told apart when compared side by side.
41
+ const LOG_TAG = `[ChatListLegend:${Platform.OS}]`;
42
+ const BUTTON_FADE_CONFIG = {
43
+ duration: 200,
44
+ easing: Easing.out(Easing.ease)
45
+ };
46
+ const getRowMarginBottom = position => position === 'last' || position === 'solo' ? ROW_MARGIN_CLUSTER_END : ROW_MARGIN_IN_CLUSTER;
43
47
  export const ChatListLegend = /*#__PURE__*/memo(({
44
48
  messages = [],
45
49
  conversationId,
@@ -48,7 +52,6 @@ export const ChatListLegend = /*#__PURE__*/memo(({
48
52
  moreOldLoading,
49
53
  hasMoreOld,
50
54
  onLoadMoreNew,
51
- moreNewLoading,
52
55
  hasMoreNew,
53
56
  onMediaPress,
54
57
  onPressUrl,
@@ -82,6 +85,16 @@ export const ChatListLegend = /*#__PURE__*/memo(({
82
85
  const last = messages[messages.length - 1];
83
86
  return last?.clientMsgID || last?.serverMsgID || null;
84
87
  }, [messages]);
88
+ // Stopgap for the keyboard-open blank-gap bug (unresolved upstream —
89
+ // see project_chatlistlegend_scroll_debugging memory): below this
90
+ // threshold, disable `alignItemsAtEnd` and let plain CSS `flex-end`
91
+ // anchor short content to the bottom instead. Known trade-off (same
92
+ // memory, bug #17/#19): disabling `alignItemsAtEnd` removes the extra
93
+ // scrollable room `clampScrollOffset` needs, so sending a message while
94
+ // the keyboard is open on one of these same short conversations can
95
+ // land the new message away from the composer instead of scrolling to
96
+ // it — verify that scenario specifically after any change here.
97
+ const isVeryShortConversation = messages.length < 15;
85
98
  const {
86
99
  mutate: runFlow
87
100
  } = useRunFlow();
@@ -91,11 +104,42 @@ export const ChatListLegend = /*#__PURE__*/memo(({
91
104
  const buttonOpacity = useSharedValue(0);
92
105
  const isAtBottomRef = useRef(true);
93
106
  const scrollDebounceRef = useRef(undefined);
107
+ // Diagnostic only (__DEV__): the keyboard-open bottom inset `useChatKeyboard`
108
+ // computes internally (raw keyboard height minus `keyboardOffset`, matching
109
+ // how much the composer's KeyboardStickyView actually rises). Compared
110
+ // against the list's real, native-reported `contentInset.bottom` in
111
+ // `handleScroll` below to auto-detect the known issue where that native
112
+ // inset silently fails to apply (confirmed via manual trace: contentOffset
113
+ // scrolls correctly but contentInset stays {bottom:0} on this RN/Reanimated/
114
+ // Old-Architecture combo, leaving a blank gap past the real content end).
115
+ const expectedKeyboardInsetRef = useRef(0);
94
116
  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);
117
+ // Set on a real touch-drag (`onScrollBeginDrag`), before onStartReached/
118
+ // onEndReached's threshold could fire. `onMomentumScrollBegin` was tried
119
+ // and reverted: it only fires after the finger lifts, so the first
120
+ // gesture's mid-drag threshold-crossing beats it, needing a wasted
121
+ // second gesture to actually unlock. See
122
+ // project_chatlistlegend_scroll_debugging for the full history.
123
+ const hasUserScrolledRef = useRef(false);
124
+ // Old Architecture defers recomputing `alignItemsAtEndPadding` when
125
+ // content shrinks while `initialScrollAtEnd` is active. `estimatedItemSize`
126
+ // below is a deliberate lower bound to mostly avoid that; this ref
127
+ // backstops the rest via one forced `scrollToEnd` on mount (`handleListLoad`).
128
+ const hasCorrectedInitialPaddingRef = useRef(false);
129
+ // `messages` starts empty, so `lastMessageId`'s first real value looks
130
+ // identical to a new message — tracking the previous id (not a one-shot
131
+ // flag) lets the effect below tell the two apart.
132
+ const previousLastMessageIdRef = useRef(null);
133
+ // Read via ref (not as a dependency) by the `lastMessageId` effect below —
134
+ // a loading-state flip alone shouldn't re-run it.
135
+ const moreOldLoadingRef = useRef(moreOldLoading);
136
+ moreOldLoadingRef.current = moreOldLoading;
137
+ // Read via ref (not as a dependency) by the ScrollToMessage subscription
138
+ // below — the whole array changes on every new message and every old-
139
+ // history page, which would otherwise tear down and re-add both
140
+ // DeviceEventEmitter listeners on every one of those.
141
+ const messagesRef = useRef(messages);
142
+ messagesRef.current = messages;
99
143
  const [showBadge, setShowBadge] = useState(false);
100
144
  const [highlightedId, setHighlightedId] = useState(null);
101
145
  const highlightTimerRef = useRef(null);
@@ -201,48 +245,33 @@ export const ChatListLegend = /*#__PURE__*/memo(({
201
245
  }
202
246
  }, [currentUserId, lastMessageId, messageDataMap, isGroupConversation, isBotConversation, onMediaPress, onPressUrl, onLongPress, handleMessageLongPress, customMessageItemFactory, onResetFlow, onRunFlow, conversationFlow?.flowCurrentStep, conversationFlow?.havingBotMessage, retryMessage, highlightedId, revealedMessageId, handleMessagePress]);
203
247
 
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.
248
+ // `maintainVisibleContentPosition` anchors a prepend to whatever native
249
+ // view sits at the top. Default `drawDistance` (250px) doesn't survive a
250
+ // ~1-1.5s history fetch if the user keeps scrolling away — the anchor
251
+ // unmounts and the native compensation silently drops (the "list drifts
252
+ // after loading old history" bug). `drawDistance` below is raised to
253
+ // outlast a realistic scroll during that wait (measured ~300-350px).
254
+ // `recycleItems` is safe again since @legendapp/list 3.3.7 fixed
255
+ // recycled rows flashing wrong content on prepend.
221
256
  const maintainVisiblePositionConfig = useMemo(() => ({
222
257
  data: true
223
258
  }), []);
224
259
 
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.
260
+ // Buckets LegendList's size-averaging per content type so a batch of
261
+ // images landing early doesn't pollute the estimate for still-unmeasured
262
+ // text rows. `customMessageItemFactory` nodes get their own bucket per
263
+ // type too, so a custom node's real height doesn't pollute that type's average.
233
264
  const getItemType = useCallback(item => {
234
265
  const isCustom = !!customMessageItemFactory?.(item);
235
266
  return `${item?.contentType ?? 'default'}${isCustom ? '-custom' : ''}`;
236
267
  }, [customMessageItemFactory]);
237
268
 
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.
269
+ // Exact size for content types knowable ahead of render (see
270
+ // `plans/260728-1400-chatlist-legend-fixed-item-size/plan.md`). Returns
271
+ // `undefined` (defer to measurement) for anything unpredictable, or
272
+ // carrying chrome this function doesn't account for (day-divider, flow-
273
+ // input-buttons row) — a resolved number is never re-measured, so
274
+ // guessing wrong here is permanent, not just approximate.
246
275
  const getFixedItemSize = useCallback((item, index) => {
247
276
  const message = item;
248
277
  const messageId = getMessageRenderId(message, index);
@@ -294,13 +323,10 @@ export const ChatListLegend = /*#__PURE__*/memo(({
294
323
  return candidateSize;
295
324
  }, [messageDataMap, customMessageItemFactory, currentUserId, isGroupConversation, lastMessageId, isBotConversation, conversationFlow?.flowCurrentStep, windowWidth]);
296
325
 
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.
326
+ // LegendList only re-renders an item when [itemKey, item, extraData]
327
+ // changes. Cluster position depends on a message's NEIGHBOURS, which can
328
+ // shift (prepending history can turn a 'first' into a 'middle') without
329
+ // the message itself changing — messageDataMap here is what catches that.
304
330
  const listExtraData = useMemo(() => ({
305
331
  messageDataMap,
306
332
  flowStepId: conversationFlow?.flowCurrentStep?.id ?? '',
@@ -309,46 +335,75 @@ export const ChatListLegend = /*#__PURE__*/memo(({
309
335
  revealedMessageId
310
336
  }), [messageDataMap, conversationFlow?.flowCurrentStep?.id, lastMessageId, highlightedId, revealedMessageId]);
311
337
  const handleStartReached = useCallback(() => {
312
- const willLoad = hasMoreOld && !moreOldLoading;
313
338
  if (__DEV__) {
314
- console.log(willLoad && !hasSettledRef.current ? '[LegendList] ⚠️ onStartReached SPURIOUS mount-time load' : '[LegendList] onStartReached triggered', {
339
+ console.log(`${LOG_TAG} onStartReached fired`, {
315
340
  conversationId,
316
- messagesLength: messages.length,
317
- firstMessageId: messages[0]?.clientMsgID || messages[0]?.serverMsgID,
341
+ hasUserScrolled: hasUserScrolledRef.current,
318
342
  hasMoreOld,
319
- moreOldLoading,
320
- willLoad,
321
- duringInitialSettle: !hasSettledRef.current
343
+ moreOldLoading: useMessageStore.getState().moreOldLoading
322
344
  });
323
345
  }
324
- if (willLoad) {
346
+ if (!hasUserScrolledRef.current) {
347
+ const wouldHaveLoaded = __DEV__ && hasMoreOld && !useMessageStore.getState().moreOldLoading;
348
+ if (wouldHaveLoaded) {
349
+ console.warn(`${LOG_TAG} onStartReached fired before user scroll — suppressed`, {
350
+ conversationId,
351
+ hasMoreOld
352
+ });
353
+ }
354
+ return;
355
+ }
356
+ if (hasMoreOld && !useMessageStore.getState().moreOldLoading) {
325
357
  onLoadMoreOld?.(true);
326
358
  }
327
- }, [conversationId, messages, hasMoreOld, moreOldLoading, onLoadMoreOld]);
359
+ }, [conversationId, hasMoreOld, onLoadMoreOld]);
328
360
  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
- });
361
+ if (!hasUserScrolledRef.current) {
362
+ const wouldHaveLoaded = __DEV__ && hasMoreNew && !useMessageStore.getState().moreNewLoading;
363
+ if (wouldHaveLoaded) {
364
+ console.warn(`${LOG_TAG} onEndReached fired before user scroll — suppressed`, {
365
+ conversationId,
366
+ hasMoreNew
367
+ });
368
+ }
369
+ return;
340
370
  }
341
- if (willLoad) {
371
+ if (hasMoreNew && !useMessageStore.getState().moreNewLoading) {
372
+ if (__DEV__) console.log(`${LOG_TAG} handleEndReached`);
342
373
  onLoadMoreNew?.(true);
343
374
  }
344
- }, [conversationId, messages, hasMoreNew, moreNewLoading, onLoadMoreNew]);
375
+ }, [conversationId, hasMoreNew, onLoadMoreNew]);
376
+ const handleScrollBeginDrag = useCallback(() => {
377
+ hasUserScrolledRef.current = true;
378
+ }, []);
345
379
  const handleScroll = useCallback(e => {
346
380
  const {
347
381
  contentOffset,
348
382
  contentSize,
349
- layoutMeasurement
383
+ layoutMeasurement,
384
+ contentInset
350
385
  } = e.nativeEvent;
351
386
  const isAtBottom = contentOffset.y + layoutMeasurement.height >= contentSize.height - 100;
387
+ if (__DEV__) {
388
+ const expected = expectedKeyboardInsetRef.current;
389
+ const actual = contentInset?.bottom ?? 0;
390
+ // `contentInset` failing to apply is a constant, systemic mismatch
391
+ // (present regardless of conversation length) — but it only shows
392
+ // up as a visible gap once the scroll position has actually moved
393
+ // past the real end of content. Gate on that so the warning fires
394
+ // only when the bug is actually visible, not on every keyboard
395
+ // open in every conversation.
396
+ const scrolledPastRealContentEnd = contentOffset.y + layoutMeasurement.height > contentSize.height;
397
+ if (expected > 0 && Math.abs(actual - expected) > 5 && scrolledPastRealContentEnd) {
398
+ console.warn(`${LOG_TAG} keyboard contentInset mismatch — expected bottom inset ~${expected} (keyboard open) but native reports ${actual}, and scroll position is past the real content end. This reproduces the known "blank gap" issue: contentOffset scrolls correctly but contentInset silently fails to apply.`, {
399
+ expected,
400
+ actual,
401
+ contentOffsetY: contentOffset.y,
402
+ contentSizeHeight: contentSize.height,
403
+ layoutHeight: layoutMeasurement.height
404
+ });
405
+ }
406
+ }
352
407
 
353
408
  // Debounce animation updates
354
409
  if (scrollDebounceRef.current) {
@@ -357,10 +412,7 @@ export const ChatListLegend = /*#__PURE__*/memo(({
357
412
  scrollDebounceRef.current = setTimeout(() => {
358
413
  if (isAtBottomRef.current !== isAtBottom) {
359
414
  isAtBottomRef.current = isAtBottom;
360
- buttonOpacity.value = withTiming(isAtBottom ? 0 : 1, {
361
- duration: 200,
362
- easing: Easing.out(Easing.ease)
363
- });
415
+ buttonOpacity.value = withTiming(isAtBottom ? 0 : 1, BUTTON_FADE_CONFIG);
364
416
  }
365
417
  if (isAtBottom) {
366
418
  ChatMessageAPI.markConversationAsRead(conversationId || '');
@@ -370,18 +422,20 @@ export const ChatListLegend = /*#__PURE__*/memo(({
370
422
  const handleScrollToBottom = useCallback(() => {
371
423
  if (hasMoreNew) {
372
424
  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);
425
+ return;
384
426
  }
427
+ // `ChatEvent.ScrollToBottom` fires after every self-sent message, but
428
+ // the `lastMessageId` effect below already animates to it — re-scrolling
429
+ // here raced it and flickered. Only act when not already at bottom
430
+ // (FAB tap, or a send while scrolled away).
431
+ if (isAtBottomRef.current) return;
432
+ scrollMessageToEnd({
433
+ animated: true,
434
+ closeKeyboard: false
435
+ });
436
+ buttonOpacity.value = withTiming(0, BUTTON_FADE_CONFIG);
437
+ isAtBottomRef.current = true;
438
+ setShowBadge(false);
385
439
  }, [hasMoreNew, onLoadMoreOld, scrollMessageToEnd, buttonOpacity]);
386
440
  const animatedButtonStyle = useAnimatedStyle(() => ({
387
441
  opacity: buttonOpacity.value,
@@ -397,19 +451,94 @@ export const ChatListLegend = /*#__PURE__*/memo(({
397
451
  };
398
452
  }, []);
399
453
 
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.
454
+ // Diagnostic only (__DEV__) — keeps `expectedKeyboardInsetRef` in sync so
455
+ // `handleScroll` can detect the known contentInset-not-applying issue.
456
+ // JS-thread only (no worklet involved), so this carries none of the
457
+ // reentrant-lock crash risk `useKeyboardHandler`'s own worklet hit.
404
458
  useEffect(() => {
405
- hasSettledRef.current = false;
459
+ if (!__DEV__) return;
460
+ const willShow = KeyboardEvents.addListener('keyboardWillShow', e => {
461
+ expectedKeyboardInsetRef.current = Math.max(e.height - keyboardOffset, 0);
462
+ });
463
+ const willHide = KeyboardEvents.addListener('keyboardWillHide', () => {
464
+ expectedKeyboardInsetRef.current = 0;
465
+ });
466
+ return () => {
467
+ willShow.remove();
468
+ willHide.remove();
469
+ };
470
+ }, [keyboardOffset]);
471
+
472
+ // This component can stay mounted across a conversationId change (e.g.
473
+ // navigation.replace reusing the same screen instance), so this ref must
474
+ // reset per-conversation — otherwise a leftover "user scrolled" flag from
475
+ // the previous conversation would wrongly let load-more fire on the
476
+ // newly opened one before any real scroll there.
477
+ useEffect(() => {
478
+ hasUserScrolledRef.current = false;
479
+ hasCorrectedInitialPaddingRef.current = false;
480
+ previousLastMessageIdRef.current = null;
406
481
  }, [conversationId]);
482
+
483
+ // Single source of truth for "scroll to bottom on a new message" (self-
484
+ // sent or received). Skipped while prepending old history or scrolled
485
+ // away from the bottom. `moreOldLoading` is read via ref, not a
486
+ // dependency, so its own start/finish toggle doesn't re-run this.
487
+ useEffect(() => {
488
+ const previousLastMessageId = previousLastMessageIdRef.current;
489
+ previousLastMessageIdRef.current = lastMessageId;
490
+
491
+ // null → a real id is the initial fetch landing (initialScrollAtEnd +
492
+ // handleListLoad's job), not a new message; a real id → a different
493
+ // one is.
494
+ const shouldFire = previousLastMessageId !== null && !!lastMessageId && !moreOldLoadingRef.current && isAtBottomRef.current;
495
+ if (__DEV__) {
496
+ console.log(`${LOG_TAG} lastMessageId effect — ${shouldFire ? 'firing animated scroll' : 'skipped'}`, {
497
+ conversationId,
498
+ lastMessageId,
499
+ initialPopulation: previousLastMessageId === null,
500
+ moreOldLoading: moreOldLoadingRef.current,
501
+ isAtBottom: isAtBottomRef.current
502
+ });
503
+ }
504
+ if (!shouldFire) return;
505
+ scrollMessageToEnd({
506
+ animated: true,
507
+ closeKeyboard: false
508
+ });
509
+ }, [lastMessageId, scrollMessageToEnd, conversationId]);
510
+ const handleListLoad = useCallback(info => {
511
+ if (hasCorrectedInitialPaddingRef.current) return;
512
+ hasCorrectedInitialPaddingRef.current = true;
513
+
514
+ // Diagnostic: how long bootstrap + the flush below each take.
515
+ if (__DEV__) {
516
+ console.log(`${LOG_TAG} onLoad (bootstrap settle done)`, {
517
+ conversationId,
518
+ messagesLength: messages.length,
519
+ elapsedTimeInMs: info.elapsedTimeInMs
520
+ });
521
+ }
522
+ const startedAt = __DEV__ ? Date.now() : 0;
523
+ const flushPromise = scrollMessageToEnd({
524
+ animated: false,
525
+ closeKeyboard: false
526
+ });
527
+ if (__DEV__) {
528
+ flushPromise.then(() => {
529
+ console.log(`${LOG_TAG} handleListLoad flush resolved`, {
530
+ conversationId,
531
+ tookMs: Date.now() - startedAt
532
+ });
533
+ });
534
+ }
535
+ }, [scrollMessageToEnd, conversationId, messages.length]);
407
536
  useEffect(() => {
408
537
  const bottomSubscription = DeviceEventEmitter.addListener(ChatEvent.ScrollToBottom, handleScrollToBottom);
409
538
  const subscription = DeviceEventEmitter.addListener(ChatEvent.ScrollToMessage, ({
410
539
  clientMessageId
411
540
  }) => {
412
- const index = messages.findIndex(m => m.clientMsgID === clientMessageId);
541
+ const index = messagesRef.current.findIndex(m => m.clientMsgID === clientMessageId);
413
542
  if (index >= 0) {
414
543
  try {
415
544
  listRef.current?.scrollToIndex({
@@ -441,7 +570,7 @@ export const ChatListLegend = /*#__PURE__*/memo(({
441
570
  bottomSubscription.remove();
442
571
  subscription.remove();
443
572
  };
444
- }, [messages, applicationType, searchMessages, conversationId, buttonOpacity, handleScrollToBottom, listRef]);
573
+ }, [applicationType, searchMessages, conversationId, handleScrollToBottom, listRef]);
445
574
  if (isLoading) {
446
575
  return /*#__PURE__*/_jsx(KContainer.View, {
447
576
  flex: true,
@@ -453,50 +582,60 @@ export const ChatListLegend = /*#__PURE__*/memo(({
453
582
  })
454
583
  });
455
584
  }
456
- console.log('messages length: ', +messages.length);
457
585
  return /*#__PURE__*/_jsxs(KContainer.View, {
458
586
  flex: true,
459
587
  background: KColors.white,
460
588
  style: styles.container,
461
- children: [/*#__PURE__*/_jsx(ListComponent, {
589
+ children: [/*#__PURE__*/_jsx(KeyboardAwareLegendList, {
462
590
  ref: listRef,
463
591
  data: messages,
464
- renderItem: renderItem,
465
- estimatedItemSize: 200,
592
+ renderItem: renderItem
593
+ // Deliberate lower bound (not a best-guess average) — see the
594
+ // comment above `hasCorrectedInitialPaddingRef`. A single-line
595
+ // bubble floors out around 38-44px, so this stays safely under it.
596
+ ,
597
+ estimatedItemSize: 36,
466
598
  getItemType: getItemType,
467
599
  getFixedItemSize: getFixedItemSize,
468
600
  keyExtractor: item => String(item.clientMsgID || item.serverMsgID),
469
- recycleItems: false,
601
+ recycleItems: true,
470
602
  extraData: listExtraData,
471
- alignItemsAtEnd: true,
472
- maintainVisibleContentPosition: maintainVisiblePositionConfig,
603
+ alignItemsAtEnd: !isVeryShortConversation,
604
+ alwaysRender: {
605
+ top: 10
606
+ },
607
+ maintainScrollAtEnd: true,
608
+ maintainVisibleContentPosition: maintainVisiblePositionConfig
609
+ // See the comment above `maintainVisiblePositionConfig` — this is
610
+ // what keeps a history prepend's anchor item mounted through a slow
611
+ // fetch instead of the default 250px window losing it.
612
+ ,
613
+ drawDistance: 800,
473
614
  freeze: freeze,
474
615
  keyboardOffset: keyboardOffset,
475
616
  scrollEventThrottle: 16,
476
617
  onScroll: handleScroll,
477
- contentContainerStyle: styles.content,
618
+ onScrollBeginDrag: handleScrollBeginDrag,
619
+ onLoad: handleListLoad,
620
+ contentContainerStyle: isVeryShortConversation ? [styles.content, styles.contentBottomAnchored] : styles.content,
478
621
  scrollIndicatorInsets: {
479
622
  right: 1
480
623
  },
481
624
  ListHeaderComponent: /*#__PURE__*/_jsx(LegendChatLoadEarlier, {
482
625
  isLoading: moreOldLoading
483
626
  }),
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,
627
+ onStartReached: handleStartReached
628
+ // LegendList only re-fires onStartReached after scrolling away from
629
+ // the threshold by 1.3x and back. At 0.5 that distance exceeded a
630
+ // short conversation's whole scrollable range, so the mount-time
631
+ // firing we suppress in handleStartReached "used up" the only
632
+ // trigger. 0.3 keeps that reset distance safely under a
633
+ // ~1.5-screen conversation; shorter ones can still hit it.
634
+ ,
635
+ onStartReachedThreshold: 0.2,
496
636
  onEndReached: handleEndReached,
497
637
  onEndReachedThreshold: 0.2,
498
- showsVerticalScrollIndicator: false,
499
- initialScrollIndex: messages.length - 1
638
+ initialScrollAtEnd: true
500
639
  }), /*#__PURE__*/_jsx(Animated.View, {
501
640
  style: [styles.scrollToBottomButton, animatedButtonStyle],
502
641
  children: /*#__PURE__*/_jsxs(KContainer.Touchable, {
@@ -526,6 +665,9 @@ const styles = StyleSheet.create({
526
665
  paddingVertical: KSpacingValue['0.75rem'],
527
666
  flexGrow: 1
528
667
  },
668
+ contentBottomAnchored: {
669
+ justifyContent: 'flex-end'
670
+ },
529
671
  scrollToBottomButton: {
530
672
  position: 'absolute',
531
673
  bottom: 20,