refactor(message): improve message synchronization and hook reliability
Refactor the message management system to address synchronization issues and improve performance through request deduplication and enhanced lifecycle management. - Implement in-flight promise tracking in `MessageSyncService` to prevent redundant network requests for conversations and messages. - Enhance `useMessages` hook with `useFocusEffect` to trigger automatic synchronization when a chat screen regains focus. - Add `forceSync` capability to `MessageManager` and `MessageSyncService` to allow manual overrides of loading states during re-entry. - Consolidate WebSocket synchronization logic in `WSMessageHandler` using a throttled and deduplicated trigger mechanism. - Clean up unused styles and deprecated hooks (`baseHooks.ts`, `bubbleStyles.ts`, `inputStyles.ts`). - Update `metro.config.js` and `package.json` to include `@expo/ui` and optimize resolver settings.
This commit is contained in:
@@ -7,9 +7,6 @@ const config = getDefaultConfig(__dirname);
|
|||||||
// Add wasm asset support
|
// Add wasm asset support
|
||||||
config.resolver.assetExts.push('wasm');
|
config.resolver.assetExts.push('wasm');
|
||||||
|
|
||||||
// Fix for livekit-client event-target-shim import warning
|
|
||||||
config.resolver.unstable_enablePackageExports = false;
|
|
||||||
|
|
||||||
// Support TypeScript path aliases (@/ -> ./src/)
|
// Support TypeScript path aliases (@/ -> ./src/)
|
||||||
config.resolver.alias = {
|
config.resolver.alias = {
|
||||||
'@': path.resolve(__dirname, 'src'),
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
|||||||
393
package-lock.json
generated
393
package-lock.json
generated
@@ -8,6 +8,7 @@
|
|||||||
"name": "with_you",
|
"name": "with_you",
|
||||||
"version": "0.0.2",
|
"version": "0.0.2",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@expo/ui": "^56.0.15",
|
||||||
"@expo/vector-icons": "^15.1.1",
|
"@expo/vector-icons": "^15.1.1",
|
||||||
"@livekit/react-native": "^2.11.0",
|
"@livekit/react-native": "^2.11.0",
|
||||||
"@livekit/react-native-webrtc": "^144.1.0",
|
"@livekit/react-native-webrtc": "^144.1.0",
|
||||||
@@ -101,20 +102,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.29.0",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz",
|
||||||
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
|
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.29.0",
|
"@babel/code-frame": "^7.29.7",
|
||||||
"@babel/generator": "^7.29.0",
|
"@babel/generator": "^7.29.7",
|
||||||
"@babel/helper-compilation-targets": "^7.28.6",
|
"@babel/helper-compilation-targets": "^7.29.7",
|
||||||
"@babel/helper-module-transforms": "^7.28.6",
|
"@babel/helper-module-transforms": "^7.29.7",
|
||||||
"@babel/helpers": "^7.28.6",
|
"@babel/helpers": "^7.29.7",
|
||||||
"@babel/parser": "^7.29.0",
|
"@babel/parser": "^7.29.7",
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/template": "^7.29.7",
|
||||||
"@babel/traverse": "^7.29.0",
|
"@babel/traverse": "^7.29.7",
|
||||||
"@babel/types": "^7.29.0",
|
"@babel/types": "^7.29.7",
|
||||||
"@jridgewell/remapping": "^2.3.5",
|
"@jridgewell/remapping": "^2.3.5",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
@@ -390,13 +391,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.29.2",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.29.7.tgz",
|
||||||
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
|
"integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.28.6",
|
"@babel/template": "^7.29.7",
|
||||||
"@babel/types": "^7.29.0"
|
"@babel/types": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -561,12 +562,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-arrow-functions": {
|
"node_modules/@babel/plugin-transform-arrow-functions": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz",
|
||||||
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
|
"integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -1033,12 +1034,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-shorthand-properties": {
|
"node_modules/@babel/plugin-transform-shorthand-properties": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz",
|
||||||
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
|
"integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -1048,12 +1049,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-template-literals": {
|
"node_modules/@babel/plugin-transform-template-literals": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz",
|
||||||
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
|
"integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -1082,13 +1083,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/plugin-transform-unicode-regex": {
|
"node_modules/@babel/plugin-transform-unicode-regex": {
|
||||||
"version": "7.27.1",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz",
|
||||||
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
|
"integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-create-regexp-features-plugin": "^7.27.1",
|
"@babel/helper-create-regexp-features-plugin": "^7.29.7",
|
||||||
"@babel/helper-plugin-utils": "^7.27.1"
|
"@babel/helper-plugin-utils": "^7.29.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -1117,9 +1118,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.29.2",
|
"version": "7.29.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz",
|
||||||
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
|
"integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@@ -3029,9 +3030,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@react-native-community/cli/node_modules/semver": {
|
"node_modules/@react-native-community/cli/node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.1.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -3566,12 +3567,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "25.5.0",
|
"version": "25.9.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/node/-/node-25.9.1.tgz",
|
||||||
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
|
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.18.0"
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/pako": {
|
"node_modules/@types/pako": {
|
||||||
@@ -3636,9 +3637,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@ungap/structured-clone": {
|
"node_modules/@ungap/structured-clone": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
|
||||||
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
|
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/@vscode/sudo-prompt": {
|
"node_modules/@vscode/sudo-prompt": {
|
||||||
@@ -3649,9 +3650,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.11",
|
"version": "0.8.13",
|
||||||
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.13.tgz",
|
||||||
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
"integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
@@ -3704,12 +3705,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.4",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
|
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/anser": {
|
"node_modules/anser": {
|
||||||
@@ -3969,31 +3973,6 @@
|
|||||||
"proxy-from-env": "^2.1.0"
|
"proxy-from-env": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/axios/node_modules/agent-base": {
|
|
||||||
"version": "6.0.2",
|
|
||||||
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz",
|
|
||||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"debug": "4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/axios/node_modules/https-proxy-agent": {
|
|
||||||
"version": "5.0.1",
|
|
||||||
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
|
||||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"agent-base": "6",
|
|
||||||
"debug": "4"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/babel-plugin-polyfill-corejs2": {
|
"node_modules/babel-plugin-polyfill-corejs2": {
|
||||||
"version": "0.4.17",
|
"version": "0.4.17",
|
||||||
"resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
|
"resolved": "https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
|
||||||
@@ -4149,12 +4128,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/barcode-detector": {
|
"node_modules/barcode-detector": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/barcode-detector/-/barcode-detector-3.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/barcode-detector/-/barcode-detector-3.1.3.tgz",
|
||||||
"integrity": "sha512-ghWlEAV93ZCUniO7Co3ih/01XPm+U30CV+NoPbO6Chj5lZzHydDAqKlrBEd+37TkoR+QTH3tnnwd8k8epGTfIg==",
|
"integrity": "sha512-omL3/x26oU9jlR0gUQcGdXIjQtMlrUGKF7xRFO1RwrQkRkRU7WLz0mgQEsdUtYBm2uX3JH+HQLrKlyTS/BxZRw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"zxing-wasm": "3.0.1"
|
"zxing-wasm": "3.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/base64-js": {
|
"node_modules/base64-js": {
|
||||||
@@ -4178,9 +4157,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.10.9",
|
"version": "2.10.33",
|
||||||
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.9.tgz",
|
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz",
|
||||||
"integrity": "sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==",
|
"integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"baseline-browser-mapping": "dist/cli.cjs"
|
"baseline-browser-mapping": "dist/cli.cjs"
|
||||||
@@ -4257,9 +4236,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.6",
|
||||||
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz",
|
||||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
"integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^4.0.2"
|
"balanced-match": "^4.0.2"
|
||||||
@@ -4281,9 +4260,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.28.1",
|
"version": "4.28.2",
|
||||||
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz",
|
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.2.tgz",
|
||||||
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
"integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -4300,11 +4279,11 @@
|
|||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.10.12",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001782",
|
||||||
"electron-to-chromium": "^1.5.263",
|
"electron-to-chromium": "^1.5.328",
|
||||||
"node-releases": "^2.0.27",
|
"node-releases": "^2.0.36",
|
||||||
"update-browserslist-db": "^1.2.0"
|
"update-browserslist-db": "^1.2.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"browserslist": "cli.js"
|
"browserslist": "cli.js"
|
||||||
@@ -4431,9 +4410,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001780",
|
"version": "1.0.30001793",
|
||||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
|
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
|
||||||
"integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
|
"integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -5099,9 +5078,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.321",
|
"version": "1.5.365",
|
||||||
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
|
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.365.tgz",
|
||||||
"integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
|
"integrity": "sha512-xfip4u1QF1s+URFqpA6N+OeFpDGpN7VJz1f3MO3bVL0QYBjpGiZ5/Of7kugvM+o8TTqmanUlviHN3c8M9vYWCw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emoji-regex": {
|
"node_modules/emoji-regex": {
|
||||||
@@ -5304,9 +5283,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/es-object-atoms": {
|
"node_modules/es-object-atoms": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -7134,16 +7113,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/https-proxy-agent": {
|
"node_modules/https-proxy-agent": {
|
||||||
"version": "7.0.6",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"agent-base": "^7.1.2",
|
"agent-base": "6",
|
||||||
"debug": "4"
|
"debug": "4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
@@ -7851,9 +7830,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-util/node_modules/picomatch": {
|
"node_modules/jest-util/node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@@ -7955,9 +7934,19 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.1",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz",
|
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.2.0.tgz",
|
||||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
"integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/puzrin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/nodeca"
|
||||||
|
}
|
||||||
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
@@ -8237,6 +8226,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8257,6 +8249,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8277,6 +8272,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"glibc"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8297,6 +8295,9 @@
|
|||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
"libc": [
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
@@ -8850,6 +8851,28 @@
|
|||||||
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
|
"node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/metro-cache/node_modules/agent-base": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/metro-cache/node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/metro-config": {
|
"node_modules/metro-config": {
|
||||||
"version": "0.84.4",
|
"version": "0.84.4",
|
||||||
"resolved": "https://registry.npmmirror.com/metro-config/-/metro-config-0.84.4.tgz",
|
"resolved": "https://registry.npmmirror.com/metro-config/-/metro-config-0.84.4.tgz",
|
||||||
@@ -9110,9 +9133,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/micromatch/node_modules/picomatch": {
|
"node_modules/micromatch/node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@@ -9184,12 +9207,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.4",
|
"version": "10.2.5",
|
||||||
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
|
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^5.0.2"
|
"brace-expansion": "^5.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || >=22"
|
"node": "18 || 20 || >=22"
|
||||||
@@ -9320,9 +9343,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-forge": {
|
"node_modules/node-forge": {
|
||||||
"version": "1.3.3",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.3.3.tgz",
|
"resolved": "https://registry.npmmirror.com/node-forge/-/node-forge-1.4.0.tgz",
|
||||||
"integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
|
"integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
|
||||||
"license": "(BSD-3-Clause OR GPL-2.0)",
|
"license": "(BSD-3-Clause OR GPL-2.0)",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.13.0"
|
"node": ">= 6.13.0"
|
||||||
@@ -9350,10 +9373,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.36",
|
"version": "2.0.47",
|
||||||
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.36.tgz",
|
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.47.tgz",
|
||||||
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
|
"integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/node-stream-zip": {
|
"node_modules/node-stream-zip": {
|
||||||
"version": "1.15.0",
|
"version": "1.15.0",
|
||||||
@@ -9740,9 +9766,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||||
"version": "11.2.7",
|
"version": "11.5.1",
|
||||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.7.tgz",
|
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||||
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
|
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "20 || >=22"
|
"node": "20 || >=22"
|
||||||
@@ -9767,12 +9793,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/plist": {
|
"node_modules/plist": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/plist/-/plist-3.1.1.tgz",
|
||||||
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
|
"integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xmldom/xmldom": "^0.8.8",
|
"@xmldom/xmldom": "^0.9.10",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
"xmlbuilder": "^15.1.1"
|
"xmlbuilder": "^15.1.1"
|
||||||
},
|
},
|
||||||
@@ -9780,6 +9806,15 @@
|
|||||||
"node": ">=10.4.0"
|
"node": ">=10.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/plist/node_modules/@xmldom/xmldom": {
|
||||||
|
"version": "0.9.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
|
||||||
|
"integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pngjs": {
|
"node_modules/pngjs": {
|
||||||
"version": "3.4.0",
|
"version": "3.4.0",
|
||||||
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz",
|
"resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-3.4.0.tgz",
|
||||||
@@ -10069,9 +10104,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-devtools-core/node_modules/ws": {
|
"node_modules/react-devtools-core/node_modules/ws": {
|
||||||
"version": "7.5.10",
|
"version": "7.5.11",
|
||||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz",
|
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.11.tgz",
|
||||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
"integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=8.3.0"
|
||||||
@@ -10120,9 +10155,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "19.2.4",
|
"version": "19.2.6",
|
||||||
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.4.tgz",
|
"resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.2.6.tgz",
|
||||||
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
|
"integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/react-native": {
|
"node_modules/react-native": {
|
||||||
@@ -10295,9 +10330,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native-reanimated/node_modules/semver": {
|
"node_modules/react-native-reanimated/node_modules/semver": {
|
||||||
"version": "7.7.3",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.1.tgz",
|
||||||
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -10450,9 +10485,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native/node_modules/semver": {
|
"node_modules/react-native/node_modules/semver": {
|
||||||
"version": "7.7.4",
|
"version": "7.8.1",
|
||||||
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz",
|
"resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.1.tgz",
|
||||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"semver": "bin/semver.js"
|
"semver": "bin/semver.js"
|
||||||
@@ -10462,9 +10497,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-native/node_modules/ws": {
|
"node_modules/react-native/node_modules/ws": {
|
||||||
"version": "7.5.10",
|
"version": "7.5.11",
|
||||||
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.10.tgz",
|
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.11.tgz",
|
||||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
"integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=8.3.0"
|
||||||
@@ -10678,9 +10713,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/regjsparser": {
|
"node_modules/regjsparser": {
|
||||||
"version": "0.13.0",
|
"version": "0.13.1",
|
||||||
"resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.0.tgz",
|
"resolved": "https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.1.tgz",
|
||||||
"integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
|
"integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jsesc": "~3.1.0"
|
"jsesc": "~3.1.0"
|
||||||
@@ -11158,13 +11193,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/side-channel-list": {
|
"node_modules/side-channel-list": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz",
|
||||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
"integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
"object-inspect": "^1.13.3"
|
"object-inspect": "^1.13.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -11294,9 +11329,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/slugify": {
|
"node_modules/slugify": {
|
||||||
"version": "1.6.8",
|
"version": "1.6.9",
|
||||||
"resolved": "https://registry.npmmirror.com/slugify/-/slugify-1.6.8.tgz",
|
"resolved": "https://registry.npmmirror.com/slugify/-/slugify-1.6.9.tgz",
|
||||||
"integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==",
|
"integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
@@ -11648,9 +11683,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/terser": {
|
"node_modules/terser": {
|
||||||
"version": "5.46.1",
|
"version": "5.48.0",
|
||||||
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.46.1.tgz",
|
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.48.0.tgz",
|
||||||
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
|
"integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/source-map": "^0.3.3",
|
"@jridgewell/source-map": "^0.3.3",
|
||||||
@@ -11945,9 +11980,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.18.2",
|
"version": "7.24.6",
|
||||||
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
|
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.24.6.tgz",
|
||||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||||
@@ -12511,9 +12546,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.8.2",
|
"version": "2.9.0",
|
||||||
"resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz",
|
"resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz",
|
||||||
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
|
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
@@ -12604,22 +12639,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zxing-wasm": {
|
"node_modules/zxing-wasm": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmmirror.com/zxing-wasm/-/zxing-wasm-3.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/zxing-wasm/-/zxing-wasm-3.0.3.tgz",
|
||||||
"integrity": "sha512-3CLj6iaGkpqPWXAB4pIWkFOR63MwqGekpMzaROFKto4dFowiPmLlC56KoMoOSXzqOCOpI5DAvMdB8ku2va6fUg==",
|
"integrity": "sha512-DdOn/G5F+qvZELWeO5ZFFwcN611TfMybxPV0LUUoutUmiH2t47MZSB7gLV9O9YLhvudBdnzQNAoFOu4Xz8eOrQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/emscripten": "^1.41.5",
|
"@types/emscripten": "^1.41.5",
|
||||||
"type-fest": "^5.4.4"
|
"type-fest": "^5.6.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/emscripten": ">=1.39.6"
|
"@types/emscripten": ">=1.39.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zxing-wasm/node_modules/type-fest": {
|
"node_modules/zxing-wasm/node_modules/type-fest": {
|
||||||
"version": "5.5.0",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-5.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-5.7.0.tgz",
|
||||||
"integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
|
"integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==",
|
||||||
"license": "(MIT OR CC0-1.0)",
|
"license": "(MIT OR CC0-1.0)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tagged-tag": "^1.0.0"
|
"tagged-tag": "^1.0.0"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"ios:submit": "eas submit --platform ios --profile production"
|
"ios:submit": "eas submit --platform ios --profile production"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@expo/ui": "^56.0.15",
|
||||||
"@expo/vector-icons": "^15.1.1",
|
"@expo/vector-icons": "^15.1.1",
|
||||||
"@livekit/react-native": "^2.11.0",
|
"@livekit/react-native": "^2.11.0",
|
||||||
"@livekit/react-native-webrtc": "^144.1.0",
|
"@livekit/react-native-webrtc": "^144.1.0",
|
||||||
|
|||||||
@@ -1,317 +0,0 @@
|
|||||||
/**
|
|
||||||
* 消息气泡样式工具
|
|
||||||
* 参考 Element X 设计,实现动态圆角和现代化气泡样式
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { StyleSheet, ViewStyle, TextStyle } from 'react-native';
|
|
||||||
import { spacing, type AppColors } from '../../../../theme';
|
|
||||||
import { useChatSettingsStore } from '../../../../stores/settings';
|
|
||||||
|
|
||||||
// 默认圆角,但会从 store 读取
|
|
||||||
export const BUBBLE_RADIUS = 16;
|
|
||||||
|
|
||||||
// 获取动态圆角值
|
|
||||||
export function useBubbleRadius(): number {
|
|
||||||
return useChatSettingsStore((s) => s.messageRadius);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBubbleColors(colors: AppColors, outgoingBubbleColor?: string) {
|
|
||||||
return {
|
|
||||||
outgoing: {
|
|
||||||
background: outgoingBubbleColor || colors.chat.bubbleOutgoing,
|
|
||||||
text: colors.chat.textPrimary,
|
|
||||||
},
|
|
||||||
incoming: {
|
|
||||||
background: colors.chat.bubbleIncoming,
|
|
||||||
text: colors.chat.textPrimary,
|
|
||||||
},
|
|
||||||
replyHighlight: {
|
|
||||||
background: colors.chat.replyTint,
|
|
||||||
borderLeft: colors.chat.replyBorder,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type MessageGroupPosition = 'single' | 'first' | 'middle' | 'last';
|
|
||||||
|
|
||||||
// 使用动态圆角的 hook
|
|
||||||
export function useBubbleBorderRadius(isMe: boolean, position: MessageGroupPosition): ViewStyle {
|
|
||||||
const radius = useBubbleRadius();
|
|
||||||
|
|
||||||
if (isMe) {
|
|
||||||
switch (position) {
|
|
||||||
case 'single':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'first':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'middle':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: 4,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'last':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: 4,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (position) {
|
|
||||||
case 'single':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'first':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: 4,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'middle':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: 4,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'last':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保持原有的静态函数用于兼容
|
|
||||||
export const getBubbleBorderRadius = (isMe: boolean, position: MessageGroupPosition): ViewStyle => {
|
|
||||||
const radius = BUBBLE_RADIUS;
|
|
||||||
|
|
||||||
if (isMe) {
|
|
||||||
switch (position) {
|
|
||||||
case 'single':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'first':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'middle':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: 4,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: 4,
|
|
||||||
};
|
|
||||||
case 'last':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: 4,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
switch (position) {
|
|
||||||
case 'single':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'first':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: 4,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'middle':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: 4,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: 4,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
case 'last':
|
|
||||||
return {
|
|
||||||
borderTopLeftRadius: radius,
|
|
||||||
borderTopRightRadius: radius,
|
|
||||||
borderBottomLeftRadius: radius,
|
|
||||||
borderBottomRightRadius: radius,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMessageGroupPosition = (
|
|
||||||
index: number,
|
|
||||||
messages: Array<{ sender_id: string }>,
|
|
||||||
currentUserId: string
|
|
||||||
): MessageGroupPosition => {
|
|
||||||
const currentMessage = messages[index];
|
|
||||||
if (!currentMessage) return 'single';
|
|
||||||
|
|
||||||
const prevMessage = index > 0 ? messages[index - 1] : null;
|
|
||||||
const nextMessage = index < messages.length - 1 ? messages[index + 1] : null;
|
|
||||||
|
|
||||||
const isSameSenderAsPrev = prevMessage && prevMessage.sender_id === currentMessage.sender_id;
|
|
||||||
const isSameSenderAsNext = nextMessage && nextMessage.sender_id === currentMessage.sender_id;
|
|
||||||
|
|
||||||
if (!isSameSenderAsPrev && !isSameSenderAsNext) {
|
|
||||||
return 'single';
|
|
||||||
} else if (!isSameSenderAsPrev && isSameSenderAsNext) {
|
|
||||||
return 'first';
|
|
||||||
} else if (isSameSenderAsPrev && isSameSenderAsNext) {
|
|
||||||
return 'middle';
|
|
||||||
} else {
|
|
||||||
return 'last';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const shouldShowSenderInfo = (
|
|
||||||
index: number,
|
|
||||||
messages: Array<{ sender_id: string }>
|
|
||||||
): boolean => {
|
|
||||||
if (index === 0) return true;
|
|
||||||
|
|
||||||
const currentMessage = messages[index];
|
|
||||||
const prevMessage = messages[index - 1];
|
|
||||||
|
|
||||||
return prevMessage.sender_id !== currentMessage.sender_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getBubbleBaseStyle = (isMe: boolean, colors: AppColors, outgoingBubbleColor?: string): ViewStyle => {
|
|
||||||
const bc = getBubbleColors(colors, outgoingBubbleColor);
|
|
||||||
return {
|
|
||||||
paddingHorizontal: spacing.md,
|
|
||||||
paddingVertical: spacing.sm + 4,
|
|
||||||
minWidth: 60,
|
|
||||||
maxWidth: '75%',
|
|
||||||
backgroundColor: isMe ? bc.outgoing.background : bc.incoming.background,
|
|
||||||
shadowColor: colors.chat.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 1 },
|
|
||||||
shadowOpacity: 0.06,
|
|
||||||
shadowRadius: 2,
|
|
||||||
elevation: 2,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// 使用动态字号的 hook
|
|
||||||
export function useChatFontSize(): number {
|
|
||||||
return useChatSettingsStore((s) => s.fontSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getBubbleTextStyle = (isMe: boolean, colors: AppColors, customFontSize?: number): TextStyle => {
|
|
||||||
const bc = getBubbleColors(colors);
|
|
||||||
const fontSize = customFontSize ?? 16;
|
|
||||||
return {
|
|
||||||
color: isMe ? bc.outgoing.text : bc.incoming.text,
|
|
||||||
fontSize,
|
|
||||||
lineHeight: Math.round(fontSize * 1.4),
|
|
||||||
letterSpacing: 0.2,
|
|
||||||
fontWeight: '400',
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function createBubbleStyles(colors: AppColors, customFontSize?: number, outgoingBubbleColor?: string) {
|
|
||||||
const bc = getBubbleColors(colors, outgoingBubbleColor);
|
|
||||||
const fontSize = customFontSize ?? 16;
|
|
||||||
return StyleSheet.create({
|
|
||||||
bubble: {
|
|
||||||
paddingHorizontal: spacing.md,
|
|
||||||
paddingVertical: spacing.sm + 4,
|
|
||||||
minWidth: 60,
|
|
||||||
},
|
|
||||||
outgoing: {
|
|
||||||
backgroundColor: bc.outgoing.background,
|
|
||||||
},
|
|
||||||
incoming: {
|
|
||||||
backgroundColor: bc.incoming.background,
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
fontSize,
|
|
||||||
lineHeight: Math.round(fontSize * 1.4),
|
|
||||||
letterSpacing: 0.2,
|
|
||||||
fontWeight: '400',
|
|
||||||
},
|
|
||||||
shadow: {
|
|
||||||
shadowColor: colors.chat.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 1 },
|
|
||||||
shadowOpacity: 0.06,
|
|
||||||
shadowRadius: 2,
|
|
||||||
elevation: 2,
|
|
||||||
},
|
|
||||||
longPressShadow: {
|
|
||||||
shadowColor: colors.chat.shadow,
|
|
||||||
shadowOffset: { width: 0, height: 3 },
|
|
||||||
shadowOpacity: 0.12,
|
|
||||||
shadowRadius: 8,
|
|
||||||
elevation: 6,
|
|
||||||
},
|
|
||||||
replyHighlight: {
|
|
||||||
borderLeftWidth: 3,
|
|
||||||
borderLeftColor: colors.primary.main,
|
|
||||||
backgroundColor: bc.replyHighlight.background,
|
|
||||||
},
|
|
||||||
recalled: {
|
|
||||||
backgroundColor: colors.chat.surfaceMuted,
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: colors.chat.border,
|
|
||||||
borderStyle: 'dashed',
|
|
||||||
borderRadius: 12,
|
|
||||||
},
|
|
||||||
systemNotice: {
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: spacing.sm,
|
|
||||||
},
|
|
||||||
systemNoticeText: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: colors.chat.textSecondary,
|
|
||||||
backgroundColor: colors.chat.surfaceMuted,
|
|
||||||
paddingHorizontal: spacing.md,
|
|
||||||
paddingVertical: spacing.xs,
|
|
||||||
borderRadius: 12,
|
|
||||||
overflow: 'hidden',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
BUBBLE_RADIUS,
|
|
||||||
getBubbleColors,
|
|
||||||
getBubbleBorderRadius,
|
|
||||||
getMessageGroupPosition,
|
|
||||||
shouldShowSenderInfo,
|
|
||||||
getBubbleBaseStyle,
|
|
||||||
getBubbleTextStyle,
|
|
||||||
createBubbleStyles,
|
|
||||||
};
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* 聊天设置页 ChatSettingsScreen
|
* 聊天设置页 ChatSettingsScreen
|
||||||
* 威友 - 聊天个性化设置
|
* 威友 - 聊天个性化设置
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ScrollView,
|
ScrollView,
|
||||||
Dimensions,
|
|
||||||
PanResponder,
|
PanResponder,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
@@ -30,7 +29,6 @@ import {
|
|||||||
CHAT_THEMES,
|
CHAT_THEMES,
|
||||||
} from '../../stores/settings';
|
} from '../../stores/settings';
|
||||||
|
|
||||||
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
||||||
const CARD_MAX_WIDTH = 720;
|
const CARD_MAX_WIDTH = 720;
|
||||||
|
|
||||||
interface SliderProps {
|
interface SliderProps {
|
||||||
@@ -43,7 +41,6 @@ interface SliderProps {
|
|||||||
showValue?: boolean;
|
showValue?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自定义滑块组件(支持拖动)
|
|
||||||
const Slider: React.FC<SliderProps> = ({
|
const Slider: React.FC<SliderProps> = ({
|
||||||
value,
|
value,
|
||||||
min,
|
min,
|
||||||
@@ -177,7 +174,6 @@ function createStyles(colors: AppColors) {
|
|||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
letterSpacing: 0.5,
|
letterSpacing: 0.5,
|
||||||
},
|
},
|
||||||
// 聊天预览样式
|
|
||||||
previewContainer: {
|
previewContainer: {
|
||||||
backgroundColor: colors.primary.light + '20',
|
backgroundColor: colors.primary.light + '20',
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
@@ -211,7 +207,6 @@ function createStyles(colors: AppColors) {
|
|||||||
previewText: {
|
previewText: {
|
||||||
color: colors.text.primary,
|
color: colors.text.primary,
|
||||||
},
|
},
|
||||||
// 主题选择样式
|
|
||||||
themeList: {
|
themeList: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
gap: spacing.sm,
|
gap: spacing.sm,
|
||||||
@@ -254,12 +249,6 @@ function createStyles(colors: AppColors) {
|
|||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
themeName: {
|
|
||||||
fontSize: fontSizes.xs,
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 4,
|
|
||||||
color: colors.text.secondary,
|
|
||||||
},
|
|
||||||
sliderContainer: {
|
sliderContainer: {
|
||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
},
|
},
|
||||||
@@ -272,7 +261,6 @@ export const ChatSettingsScreen: React.FC = () => {
|
|||||||
const styles = useMemo(() => createStyles(colors), [colors]);
|
const styles = useMemo(() => createStyles(colors), [colors]);
|
||||||
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
const responsivePadding = useResponsiveSpacing({ xs: 8, sm: 12, md: 16, lg: 24, xl: 32 });
|
||||||
|
|
||||||
// 从 store 获取状态
|
|
||||||
const fontSize = useChatSettingsStore((s) => s.fontSize);
|
const fontSize = useChatSettingsStore((s) => s.fontSize);
|
||||||
const messageRadius = useChatSettingsStore((s) => s.messageRadius);
|
const messageRadius = useChatSettingsStore((s) => s.messageRadius);
|
||||||
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
|
const themeIndex = useChatSettingsStore((s) => s.themeIndex);
|
||||||
@@ -281,7 +269,6 @@ export const ChatSettingsScreen: React.FC = () => {
|
|||||||
|
|
||||||
const currentTheme = CHAT_THEMES[themeIndex];
|
const currentTheme = CHAT_THEMES[themeIndex];
|
||||||
|
|
||||||
// 渲染聊天预览
|
|
||||||
const renderChatPreview = () => (
|
const renderChatPreview = () => (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>预览</Text>
|
<Text style={styles.sectionTitle}>预览</Text>
|
||||||
@@ -316,7 +303,6 @@ export const ChatSettingsScreen: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 渲染字号滑块
|
|
||||||
const renderFontSizeSlider = () => (
|
const renderFontSizeSlider = () => (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>消息字号</Text>
|
<Text style={styles.sectionTitle}>消息字号</Text>
|
||||||
@@ -333,7 +319,6 @@ export const ChatSettingsScreen: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 渲染主题颜色选择
|
|
||||||
const renderThemeColors = () => (
|
const renderThemeColors = () => (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>主题颜色</Text>
|
<Text style={styles.sectionTitle}>主题颜色</Text>
|
||||||
@@ -366,7 +351,6 @@ export const ChatSettingsScreen: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
// 渲染圆角滑块
|
|
||||||
const renderRadiusSlider = () => (
|
const renderRadiusSlider = () => (
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>消息圆角</Text>
|
<Text style={styles.sectionTitle}>消息圆角</Text>
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export const VerificationSettingsScreen: React.FC = () => {
|
|||||||
contentContainerStyle={styles.scrollContent}
|
contentContainerStyle={styles.scrollContent}
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
>
|
>
|
||||||
{/* 状态卡片 */}
|
{/* 状态卡片 */}
|
||||||
<View style={styles.statusCard}>
|
<View style={styles.statusCard}>
|
||||||
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
|
<View style={[styles.statusIconContainer, { backgroundColor: statusConfig.color + '20' }]}>
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
|
|||||||
@@ -200,8 +200,8 @@ class MessageManager {
|
|||||||
return this.syncService.fetchConversationDetail(conversationId);
|
return this.syncService.fetchConversationDetail(conversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMessages(conversationId: string, afterSeq?: number): Promise<void> {
|
async fetchMessages(conversationId: string, afterSeq?: number, force?: boolean): Promise<void> {
|
||||||
return this.syncService.fetchMessages(conversationId, afterSeq);
|
return this.syncService.fetchMessages(conversationId, afterSeq, force);
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadMoreMessages(conversationId: string, beforeSeq: number, limit = 20): Promise<MessageResponse[]> {
|
async loadMoreMessages(conversationId: string, beforeSeq: number, limit = 20): Promise<MessageResponse[]> {
|
||||||
@@ -343,6 +343,11 @@ class MessageManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// forceSync 时清除可能残留的 loading 锁,防止 fetchMessages 被阻断
|
||||||
|
if (options?.forceSync) {
|
||||||
|
useMessageStore.getState().setLoadingMessages(normalizedId, false);
|
||||||
|
}
|
||||||
|
|
||||||
const task = (async () => {
|
const task = (async () => {
|
||||||
// 确保会话数据在 store 中(JPUSH 通知进入时 store 可能还没有此会话)
|
// 确保会话数据在 store 中(JPUSH 通知进入时 store 可能还没有此会话)
|
||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
|
|||||||
@@ -1,477 +0,0 @@
|
|||||||
/**
|
|
||||||
* 消息模块 React Hooks
|
|
||||||
*
|
|
||||||
* 提供React组件与消息状态管理的集成
|
|
||||||
* 所有hooks都基于zustand store的selector机制
|
|
||||||
* 确保组件能实时获取状态更新
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
|
||||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
|
||||||
import { useMessageStore, normalizeConversationId } from './store';
|
|
||||||
|
|
||||||
// ==================== useConversations - 获取会话列表 ====================
|
|
||||||
|
|
||||||
interface UseConversationsReturn {
|
|
||||||
conversations: ConversationResponse[];
|
|
||||||
isLoading: boolean;
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
loadMore: () => Promise<void>;
|
|
||||||
hasMore: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取会话列表
|
|
||||||
* 用于 MessageListScreen 等需要显示会话列表的组件
|
|
||||||
* 使用 Zustand selector 自动订阅状态变化
|
|
||||||
*/
|
|
||||||
export function useConversations(
|
|
||||||
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
|
|
||||||
loadMoreConversations: () => Promise<void>,
|
|
||||||
canLoadMore: () => boolean,
|
|
||||||
initialize: () => Promise<void>
|
|
||||||
): UseConversationsReturn {
|
|
||||||
// 使用 Zustand selector 直接订阅状态
|
|
||||||
const conversations = useMessageStore(state => state.conversationList);
|
|
||||||
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
|
||||||
const [hasMore, setHasMore] = useState(() => canLoadMore());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 初始化
|
|
||||||
initialize().catch(error => {
|
|
||||||
console.error('[useConversations] 初始化失败:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 冷启动兜底:延迟做一次强制刷新
|
|
||||||
const coldStartSyncTimer = setTimeout(() => {
|
|
||||||
fetchConversations(true, 'hooks-initial-refresh').catch(error => {
|
|
||||||
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
|
||||||
});
|
|
||||||
}, 1200);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
clearTimeout(coldStartSyncTimer);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 监听 hasMore 变化
|
|
||||||
useEffect(() => {
|
|
||||||
setHasMore(canLoadMore());
|
|
||||||
}, [conversations, canLoadMore]);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
|
||||||
await fetchConversations(true, 'hooks-manual-refresh');
|
|
||||||
}, [fetchConversations]);
|
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
|
||||||
await loadMoreConversations();
|
|
||||||
}, [loadMoreConversations]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
conversations,
|
|
||||||
isLoading,
|
|
||||||
refresh,
|
|
||||||
loadMore,
|
|
||||||
hasMore,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useMessages - 获取指定会话的消息 ====================
|
|
||||||
|
|
||||||
interface UseMessagesReturn {
|
|
||||||
messages: MessageResponse[];
|
|
||||||
isLoading: boolean;
|
|
||||||
hasMore: boolean;
|
|
||||||
loadMore: () => Promise<void>;
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定会话的消息
|
|
||||||
* 使用 Zustand selector 自动订阅消息变化
|
|
||||||
*/
|
|
||||||
export function useMessages(
|
|
||||||
conversationId: string,
|
|
||||||
fetchMessages: (conversationId: string) => Promise<void>,
|
|
||||||
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
|
|
||||||
): UseMessagesReturn {
|
|
||||||
const normalizedConversationId = normalizeConversationId(conversationId);
|
|
||||||
|
|
||||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
|
||||||
const messages = useMessageStore(
|
|
||||||
useShallow(state => state.messagesMap.get(normalizedConversationId) || [])
|
|
||||||
);
|
|
||||||
const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId));
|
|
||||||
const getActiveConversation = useMessageStore.getState().getActiveConversation;
|
|
||||||
const setCurrentConversation = useMessageStore.getState().setCurrentConversation;
|
|
||||||
const [hasMore, setHasMore] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 激活会话
|
|
||||||
fetchMessages(normalizedConversationId).catch(error => {
|
|
||||||
console.error('[useMessages] 激活会话失败:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
// 清理活动会话
|
|
||||||
if (getActiveConversation() === normalizedConversationId) {
|
|
||||||
setCurrentConversation(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}, [normalizedConversationId]);
|
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
|
||||||
const currentMessages = messages;
|
|
||||||
if (currentMessages.length === 0) return;
|
|
||||||
|
|
||||||
// 消息数组在 store 内保持 seq 升序,首项即最早消息
|
|
||||||
const minSeq = currentMessages[0]?.seq;
|
|
||||||
if (minSeq === undefined) return;
|
|
||||||
|
|
||||||
const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20);
|
|
||||||
setHasMore(loadedMessages.length > 0);
|
|
||||||
}, [conversationId, messages, loadMoreMessages]);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
|
||||||
await fetchMessages(conversationId);
|
|
||||||
}, [conversationId, fetchMessages]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
messages,
|
|
||||||
isLoading: isLoadingMessages,
|
|
||||||
hasMore,
|
|
||||||
loadMore,
|
|
||||||
refresh,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useSendMessage - 发送消息 ====================
|
|
||||||
|
|
||||||
interface UseSendMessageReturn {
|
|
||||||
sendMessage: (segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>;
|
|
||||||
isSending: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送消息
|
|
||||||
*/
|
|
||||||
export function useSendMessage(
|
|
||||||
conversationId: string,
|
|
||||||
sendMessageService: (conversationId: string, segments: MessageSegment[], options?: { replyToId?: string }) => Promise<MessageResponse | null>
|
|
||||||
): UseSendMessageReturn {
|
|
||||||
const [isSending, setIsSending] = useState(false);
|
|
||||||
|
|
||||||
const sendMessage = useCallback(async (segments: MessageSegment[], options?: { replyToId?: string }) => {
|
|
||||||
setIsSending(true);
|
|
||||||
try {
|
|
||||||
const message = await sendMessageService(conversationId, segments, options);
|
|
||||||
return message;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[useSendMessage] 发送消息失败:', error);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
setIsSending(false);
|
|
||||||
}
|
|
||||||
}, [conversationId, sendMessageService]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sendMessage,
|
|
||||||
isSending,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useMarkAsRead - 标记已读 ====================
|
|
||||||
|
|
||||||
interface UseMarkAsReadReturn {
|
|
||||||
markAsRead: (seq: number) => Promise<void>;
|
|
||||||
markAllAsRead: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记已读
|
|
||||||
*/
|
|
||||||
export function useMarkAsRead(
|
|
||||||
conversationId: string,
|
|
||||||
markAsReadService: (conversationId: string, seq: number) => Promise<void>,
|
|
||||||
markAllAsReadService: () => Promise<void>
|
|
||||||
): UseMarkAsReadReturn {
|
|
||||||
const markAsRead = useCallback(async (seq: number) => {
|
|
||||||
await markAsReadService(conversationId, seq);
|
|
||||||
}, [conversationId, markAsReadService]);
|
|
||||||
|
|
||||||
const markAllAsRead = useCallback(async () => {
|
|
||||||
await markAllAsReadService();
|
|
||||||
}, [markAllAsReadService]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
markAsRead,
|
|
||||||
markAllAsRead,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useUnreadCount - 获取未读数 ====================
|
|
||||||
|
|
||||||
interface UseUnreadCountReturn {
|
|
||||||
totalUnreadCount: number;
|
|
||||||
systemUnreadCount: number;
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取未读数
|
|
||||||
* 使用 Zustand selector 直接订阅未读数变化
|
|
||||||
*/
|
|
||||||
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnreadCountReturn {
|
|
||||||
// 使用 Zustand selector 直接订阅未读数
|
|
||||||
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
|
||||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// 初始获取
|
|
||||||
fetchUnreadCount();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
|
||||||
await fetchUnreadCount();
|
|
||||||
}, [fetchUnreadCount]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
totalUnreadCount,
|
|
||||||
systemUnreadCount,
|
|
||||||
refresh,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useSystemUnreadCount - 获取系统消息未读数 ====================
|
|
||||||
|
|
||||||
interface UseSystemUnreadCountReturn {
|
|
||||||
systemUnreadCount: number;
|
|
||||||
setSystemUnreadCount: (count: number) => void;
|
|
||||||
incrementSystemUnreadCount: () => void;
|
|
||||||
decrementSystemUnreadCount: (count?: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取系统消息未读数
|
|
||||||
* 使用 Zustand selector 直接订阅
|
|
||||||
*/
|
|
||||||
export function useSystemUnreadCount(
|
|
||||||
setSystemUnreadCountService: (count: number) => void,
|
|
||||||
incrementSystemUnreadCountService: () => void,
|
|
||||||
decrementSystemUnreadCountService: (count?: number) => void
|
|
||||||
): UseSystemUnreadCountReturn {
|
|
||||||
// 使用 Zustand selector 直接订阅
|
|
||||||
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
|
||||||
|
|
||||||
const setSystemUnreadCount = useCallback((count: number) => {
|
|
||||||
setSystemUnreadCountService(count);
|
|
||||||
}, [setSystemUnreadCountService]);
|
|
||||||
|
|
||||||
const incrementSystemUnreadCount = useCallback(() => {
|
|
||||||
incrementSystemUnreadCountService();
|
|
||||||
}, [incrementSystemUnreadCountService]);
|
|
||||||
|
|
||||||
const decrementSystemUnreadCount = useCallback((count = 1) => {
|
|
||||||
decrementSystemUnreadCountService(count);
|
|
||||||
}, [decrementSystemUnreadCountService]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
systemUnreadCount,
|
|
||||||
setSystemUnreadCount,
|
|
||||||
incrementSystemUnreadCount,
|
|
||||||
decrementSystemUnreadCount,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useConversation - 获取单个会话 ====================
|
|
||||||
|
|
||||||
interface UseConversationReturn {
|
|
||||||
conversation: ConversationResponse | null;
|
|
||||||
refresh: () => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取单个会话
|
|
||||||
* 使用 Zustand selector 直接订阅会话变化
|
|
||||||
*/
|
|
||||||
export function useConversation(
|
|
||||||
conversationId: string,
|
|
||||||
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
|
|
||||||
): UseConversationReturn {
|
|
||||||
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
|
||||||
const conversation = useMessageStore(
|
|
||||||
useShallow(state => state.conversations.get(conversationId) || null)
|
|
||||||
);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
|
||||||
await fetchConversationDetail(conversationId);
|
|
||||||
}, [conversationId, fetchConversationDetail]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
conversation,
|
|
||||||
refresh,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useMessageManager - 通用状态 ====================
|
|
||||||
|
|
||||||
interface UseMessageManagerReturn {
|
|
||||||
isConnected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通用消息管理状态
|
|
||||||
* 使用 Zustand selector 直接订阅连接状态
|
|
||||||
*/
|
|
||||||
export function useMessageManager(initialize: () => Promise<void>): UseMessageManagerReturn {
|
|
||||||
// 使用 Zustand selector 直接订阅连接状态
|
|
||||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
initialize().catch(error => {
|
|
||||||
console.error('[useMessageManager] 初始化失败:', error);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isConnected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useCreateConversation - 创建会话 ====================
|
|
||||||
|
|
||||||
interface UseCreateConversationReturn {
|
|
||||||
createConversation: (userId: string) => Promise<ConversationResponse | null>;
|
|
||||||
isCreating: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建会话
|
|
||||||
*/
|
|
||||||
export function useCreateConversation(
|
|
||||||
createConversationService: (userId: string) => Promise<ConversationResponse | null>
|
|
||||||
): UseCreateConversationReturn {
|
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
|
||||||
|
|
||||||
const createConversation = useCallback(async (userId: string) => {
|
|
||||||
setIsCreating(true);
|
|
||||||
try {
|
|
||||||
const conversation = await createConversationService(userId);
|
|
||||||
return conversation;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[useCreateConversation] 创建会话失败:', error);
|
|
||||||
return null;
|
|
||||||
} finally {
|
|
||||||
setIsCreating(false);
|
|
||||||
}
|
|
||||||
}, [createConversationService]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
createConversation,
|
|
||||||
isCreating,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useUpdateConversation - 更新会话 ====================
|
|
||||||
|
|
||||||
interface UseUpdateConversationReturn {
|
|
||||||
updateConversation: (conversationId: string, updates: Partial<ConversationResponse>) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新会话
|
|
||||||
*/
|
|
||||||
export function useUpdateConversation(
|
|
||||||
updateConversationService: (conversationId: string, updates: Partial<ConversationResponse>) => void
|
|
||||||
): UseUpdateConversationReturn {
|
|
||||||
const updateConversation = useCallback((conversationId: string, updates: Partial<ConversationResponse>) => {
|
|
||||||
updateConversationService(conversationId, updates);
|
|
||||||
}, [updateConversationService]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
updateConversation,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useGroupTyping - 群聊输入状态 ====================
|
|
||||||
|
|
||||||
interface UseGroupTypingReturn {
|
|
||||||
typingUsers: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取群聊输入状态
|
|
||||||
* 使用 Zustand selector 直接订阅
|
|
||||||
*/
|
|
||||||
export function useGroupTyping(groupId: string): UseGroupTypingReturn {
|
|
||||||
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
|
||||||
const typingUsers = useMessageStore(
|
|
||||||
useShallow(state => state.typingUsersMap.get(groupId) || [])
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
typingUsers,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useGroupMuted - 群聊禁言状态 ====================
|
|
||||||
|
|
||||||
interface UseGroupMutedReturn {
|
|
||||||
isMuted: boolean;
|
|
||||||
setMutedStatus: (muted: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取群聊禁言状态
|
|
||||||
* 使用 Zustand selector 直接订阅
|
|
||||||
*/
|
|
||||||
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
|
||||||
// 使用 Zustand selector 直接订阅禁言状态
|
|
||||||
const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false);
|
|
||||||
|
|
||||||
const setMutedStatus = useCallback((muted: boolean) => {
|
|
||||||
useMessageStore.getState().setMutedStatus(groupId, muted);
|
|
||||||
}, [groupId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isMuted,
|
|
||||||
setMutedStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useConnectionStatus - 连接状态 ====================
|
|
||||||
|
|
||||||
interface UseConnectionStatusReturn {
|
|
||||||
isConnected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取连接状态
|
|
||||||
* 使用 Zustand selector 直接订阅
|
|
||||||
*/
|
|
||||||
export function useConnectionStatus(): UseConnectionStatusReturn {
|
|
||||||
const isConnected = useMessageStore(state => state.isWSConnected);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isConnected,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== useIsInitialized - 初始化状态 ====================
|
|
||||||
|
|
||||||
interface UseIsInitializedReturn {
|
|
||||||
isInitialized: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取初始化状态
|
|
||||||
* 使用 Zustand selector 直接订阅
|
|
||||||
*/
|
|
||||||
export function useIsInitialized(): UseIsInitializedReturn {
|
|
||||||
const isInitialized = useMessageStore(state => state.isInitialized);
|
|
||||||
|
|
||||||
return {
|
|
||||||
isInitialized,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
|
import { useFocusEffect } from 'expo-router';
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
||||||
import { messageManager } from './MessageManager';
|
import { messageManager } from './MessageManager';
|
||||||
@@ -96,6 +97,11 @@ interface UseMessagesReturn {
|
|||||||
/**
|
/**
|
||||||
* 获取指定会话的消息
|
* 获取指定会话的消息
|
||||||
* 使用 Zustand selector 自动订阅消息变化
|
* 使用 Zustand selector 自动订阅消息变化
|
||||||
|
*
|
||||||
|
* 核心修复:
|
||||||
|
* 1. useFocusEffect:屏幕重新获得焦点时强制同步消息,解决"需要退出重进才能看到新消息"的问题
|
||||||
|
* 2. 清理 isLoadingMessages:组件卸载时清除残留的 loading 锁,防止下次进入被阻断
|
||||||
|
* 3. 重入时 forceSync:相同 conversationId 重复进入时强制拉取最新消息
|
||||||
*/
|
*/
|
||||||
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||||
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
||||||
@@ -112,25 +118,63 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
|
|||||||
);
|
);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const loadMoreInFlightRef = useRef(false);
|
const loadMoreInFlightRef = useRef(false);
|
||||||
|
// 追踪上次激活的 conversationId,避免同一会话重复激活时不必要的 forceSync
|
||||||
|
const lastActivatedIdRef = useRef<string | null>(null);
|
||||||
|
// 追踪 useEffect 激活时间,与 useFocusEffect 去重
|
||||||
|
const lastActivatedAtRef = useRef<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!normalizedConversationId) {
|
if (!normalizedConversationId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastActivatedAtRef.current = Date.now();
|
||||||
|
|
||||||
|
const isReentry = lastActivatedIdRef.current === normalizedConversationId;
|
||||||
|
|
||||||
// 架构入口:激活会话并完成初始化/同步
|
// 架构入口:激活会话并完成初始化/同步
|
||||||
messageManager.activateConversation(normalizedConversationId).catch(error => {
|
// 重入同一会话时 forceSync,确保拿到最新消息
|
||||||
|
messageManager.activateConversation(normalizedConversationId, { forceSync: isReentry }).catch(error => {
|
||||||
console.error('[useMessages] 激活会话失败:', error);
|
console.error('[useMessages] 激活会话失败:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lastActivatedIdRef.current = normalizedConversationId;
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// 清理活动会话
|
// 清理活动会话 + 清除残留的 loading 锁
|
||||||
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
||||||
messageManager.setActiveConversation(null);
|
messageManager.setActiveConversation(null);
|
||||||
}
|
}
|
||||||
|
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
|
||||||
};
|
};
|
||||||
}, [normalizedConversationId]);
|
}, [normalizedConversationId]);
|
||||||
|
|
||||||
|
// 屏幕重新获得焦点时,同步活动会话的最新消息
|
||||||
|
useFocusEffect(
|
||||||
|
useCallback(() => {
|
||||||
|
if (!normalizedConversationId) return;
|
||||||
|
|
||||||
|
// 如果刚被 useEffect 激活(500ms 内),跳过焦点同步,避免重复
|
||||||
|
if (Date.now() - lastActivatedAtRef.current < 500) return;
|
||||||
|
|
||||||
|
// 先清除可能残留的 loading 锁,防止上次退出时未正常清理
|
||||||
|
useMessageStore.getState().setLoadingMessages(normalizedConversationId, false);
|
||||||
|
|
||||||
|
// 增量同步最新消息(从当前内存最高 seq 开始拉取)
|
||||||
|
const currentMessages = useMessageStore.getState().getMessages(normalizedConversationId);
|
||||||
|
const maxSeq = currentMessages.reduce((max, m) => Math.max(max, m.seq || 0), 0);
|
||||||
|
if (maxSeq > 0) {
|
||||||
|
messageManager.fetchMessages(normalizedConversationId, maxSeq).catch(error => {
|
||||||
|
console.error('[useMessages] 焦点同步消息失败:', error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
messageManager.fetchMessages(normalizedConversationId).catch(error => {
|
||||||
|
console.error('[useMessages] 焦点同步消息(冷启动)失败:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [normalizedConversationId])
|
||||||
|
);
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,15 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
|
|
||||||
/** 正在加载会话列表下一页 */
|
/** 正在加载会话列表下一页 */
|
||||||
private loadingMoreConversations = false;
|
private loadingMoreConversations = false;
|
||||||
/** fetchUnreadCount 去重:复用 in-flight promise */
|
/** fetchUnreadCount 去重:复用 in-flight promise */
|
||||||
private fetchUnreadCountPromise: Promise<void> | null = null;
|
private fetchUnreadCountPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
// ---- 请求去重:in-flight promise maps ----
|
||||||
|
private inflightFetches: Map<string, Promise<void>> = new Map();
|
||||||
|
private inflightFetchConversations: Promise<void> | null = null;
|
||||||
|
private inflightSyncBySeq: Promise<boolean> | null = null;
|
||||||
|
private inflightSyncByVersion: Promise<boolean> | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
getCurrentUserId: () => string | null,
|
getCurrentUserId: () => string | null,
|
||||||
readReceiptManager: ReadReceiptManager,
|
readReceiptManager: ReadReceiptManager,
|
||||||
@@ -54,9 +60,22 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会话列表
|
* 获取会话列表 — 去重入口
|
||||||
*/
|
*/
|
||||||
async fetchConversations(forceRefresh = false, source: string = 'unknown'): Promise<void> {
|
async fetchConversations(forceRefresh = false, source: string = 'unknown'): Promise<void> {
|
||||||
|
if (this.inflightFetchConversations) return this.inflightFetchConversations;
|
||||||
|
this.inflightFetchConversations = this._doFetchConversations(forceRefresh, source);
|
||||||
|
try {
|
||||||
|
await this.inflightFetchConversations;
|
||||||
|
} finally {
|
||||||
|
this.inflightFetchConversations = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话列表 — 核心实现
|
||||||
|
*/
|
||||||
|
private async _doFetchConversations(forceRefresh: boolean, source: string): Promise<void> {
|
||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
|
|
||||||
if (store.isLoading() && !forceRefresh) {
|
if (store.isLoading() && !forceRefresh) {
|
||||||
@@ -177,15 +196,29 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会话消息(增量同步)
|
* 获取会话消息(增量同步)— 去重入口
|
||||||
*/
|
*/
|
||||||
async fetchMessages(conversationId: string, afterSeq?: number): Promise<void> {
|
async fetchMessages(conversationId: string, afterSeq?: number, force?: boolean): Promise<void> {
|
||||||
const store = useMessageStore.getState();
|
const key = `${conversationId}:${afterSeq ?? 'all'}`;
|
||||||
|
const existing = this.inflightFetches.get(key);
|
||||||
|
if (existing) return existing;
|
||||||
|
|
||||||
// 防止重复加载
|
if (!force && useMessageStore.getState().isLoadingMessages(conversationId)) return;
|
||||||
if (store.isLoadingMessages(conversationId)) {
|
|
||||||
return;
|
const promise = this._doFetchMessages(conversationId, afterSeq);
|
||||||
|
this.inflightFetches.set(key, promise);
|
||||||
|
try {
|
||||||
|
await promise;
|
||||||
|
} finally {
|
||||||
|
this.inflightFetches.delete(key);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取会话消息(增量同步)— 核心实现
|
||||||
|
*/
|
||||||
|
private async _doFetchMessages(conversationId: string, afterSeq?: number): Promise<void> {
|
||||||
|
const store = useMessageStore.getState();
|
||||||
|
|
||||||
store.setLoadingMessages(conversationId, true);
|
store.setLoadingMessages(conversationId, true);
|
||||||
|
|
||||||
@@ -398,10 +431,22 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于 seq 的轻量增量同步(用于重连场景,替代全量刷新)
|
* 基于 seq 的轻量增量同步 — 去重入口
|
||||||
* 返回 true 表示同步成功,false 表示需要退化为全量刷新
|
|
||||||
*/
|
*/
|
||||||
async syncBySeq(): Promise<boolean> {
|
async syncBySeq(): Promise<boolean> {
|
||||||
|
if (this.inflightSyncBySeq) return this.inflightSyncBySeq;
|
||||||
|
this.inflightSyncBySeq = this._doSyncBySeq();
|
||||||
|
try {
|
||||||
|
return await this.inflightSyncBySeq;
|
||||||
|
} finally {
|
||||||
|
this.inflightSyncBySeq = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 seq 的轻量增量同步 — 核心实现
|
||||||
|
*/
|
||||||
|
private async _doSyncBySeq(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const serverItems = await messageService.getSyncData();
|
const serverItems = await messageService.getSyncData();
|
||||||
if (!serverItems || serverItems.length === 0) return false;
|
if (!serverItems || serverItems.length === 0) return false;
|
||||||
@@ -462,10 +507,22 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 基于版本号的增量同步(替代 syncBySeq,减少带宽)
|
* 基于版本号的增量同步 — 去重入口
|
||||||
* 需要本地存储 syncVersion,首次同步时 version=0 会触发 full_sync
|
|
||||||
*/
|
*/
|
||||||
async syncByVersion(): Promise<boolean> {
|
async syncByVersion(): Promise<boolean> {
|
||||||
|
if (this.inflightSyncByVersion) return this.inflightSyncByVersion;
|
||||||
|
this.inflightSyncByVersion = this._doSyncByVersion();
|
||||||
|
try {
|
||||||
|
return await this.inflightSyncByVersion;
|
||||||
|
} finally {
|
||||||
|
this.inflightSyncByVersion = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于版本号的增量同步 — 核心实现
|
||||||
|
*/
|
||||||
|
private async _doSyncByVersion(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
const version = store.syncVersion ?? 0;
|
const version = store.syncVersion ?? 0;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import type {
|
|||||||
IUserCacheService,
|
IUserCacheService,
|
||||||
HandleNewMessageOptions,
|
HandleNewMessageOptions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { MAX_FLUSH_ITERATIONS } from '../constants';
|
import { MAX_FLUSH_ITERATIONS, MIN_RECONNECT_SYNC_INTERVAL } from '../constants';
|
||||||
import { useMessageStore, normalizeConversationId } from '../store';
|
import { useMessageStore, normalizeConversationId } from '../store';
|
||||||
|
|
||||||
export class WSMessageHandler implements IWSMessageHandler {
|
export class WSMessageHandler implements IWSMessageHandler {
|
||||||
@@ -41,6 +41,10 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
private isBootstrapping: boolean = false;
|
private isBootstrapping: boolean = false;
|
||||||
private lastReconnectSyncAt: number = 0;
|
private lastReconnectSyncAt: number = 0;
|
||||||
|
|
||||||
|
// 同步触发器状态:防止 onConnect 和 sync_required 并发
|
||||||
|
private syncInProgress: boolean = false;
|
||||||
|
private lastSyncTriggerAt: number = 0;
|
||||||
|
|
||||||
// 系统通知去重(防止重连时重复递增系统未读)
|
// 系统通知去重(防止重连时重复递增系统未读)
|
||||||
private processedNotificationIds: Set<string> = new Set();
|
private processedNotificationIds: Set<string> = new Set();
|
||||||
|
|
||||||
@@ -166,67 +170,13 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
// 监听连接状态
|
// 监听连接状态
|
||||||
wsService.onConnect(() => {
|
wsService.onConnect(() => {
|
||||||
useMessageStore.getState().setSSEConnected(true);
|
useMessageStore.getState().setSSEConnected(true);
|
||||||
|
this.triggerSync('sse-reconnect');
|
||||||
// 冷启动/重连兜底
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - this.lastReconnectSyncAt > 1500) {
|
|
||||||
this.lastReconnectSyncAt = now;
|
|
||||||
|
|
||||||
// 优先尝试 seq 驱动的轻量同步(内部会为 stale 的活动会话拉取消息)
|
|
||||||
this.syncBySeqCallback().then(ok => {
|
|
||||||
if (!ok) {
|
|
||||||
// 增量同步失败或差异过大,退化为全量刷新
|
|
||||||
this.fetchConversationsCallback(true, 'sse-reconnect').catch(error => {
|
|
||||||
console.error('[WSMessageHandler] 连接后同步会话失败:', error);
|
|
||||||
});
|
|
||||||
// 退化场景下仍需同步活动会话消息
|
|
||||||
const activeConv = useMessageStore.getState().getActiveConversation();
|
|
||||||
if (activeConv) {
|
|
||||||
this.fetchMessagesCallback(activeConv).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.fetchUnreadCountCallback().catch(error => {
|
|
||||||
console.error('[WSMessageHandler] 连接后同步未读数失败:', error);
|
|
||||||
});
|
|
||||||
}).catch(() => {
|
|
||||||
// syncBySeq 异常,退化为全量
|
|
||||||
this.fetchConversationsCallback(true, 'sse-reconnect').catch(() => {});
|
|
||||||
const activeConv = useMessageStore.getState().getActiveConversation();
|
|
||||||
if (activeConv) {
|
|
||||||
this.fetchMessagesCallback(activeConv).catch(() => {});
|
|
||||||
}
|
|
||||||
this.fetchUnreadCountCallback().catch(() => {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 sync_required 事件,触发增量同步
|
// 监听 sync_required 事件,触发增量同步
|
||||||
wsService.on('sync_required', () => {
|
wsService.on('sync_required', () => {
|
||||||
console.log('[WSMessageHandler] 收到 sync_required,开始增量同步');
|
console.log('[WSMessageHandler] 收到 sync_required,开始增量同步');
|
||||||
|
this.triggerSync('sync_required');
|
||||||
// 先尝试 seq 驱动的轻量同步(内部会为 stale 的活动会话拉取消息)
|
|
||||||
this.syncBySeqCallback().then(ok => {
|
|
||||||
if (!ok) {
|
|
||||||
this.fetchConversationsCallback(true, 'sync_required').catch(error => {
|
|
||||||
console.error('[WSMessageHandler] sync_required 同步会话失败:', error);
|
|
||||||
});
|
|
||||||
// 退化场景下仍需同步活动会话消息
|
|
||||||
const activeConv = useMessageStore.getState().getActiveConversation();
|
|
||||||
if (activeConv) {
|
|
||||||
this.fetchMessagesCallback(activeConv).catch(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.fetchUnreadCountCallback().catch(error => {
|
|
||||||
console.error('[WSMessageHandler] sync_required 同步未读数失败:', error);
|
|
||||||
});
|
|
||||||
}).catch(() => {
|
|
||||||
this.fetchConversationsCallback(true, 'sync_required').catch(() => {});
|
|
||||||
const activeConv = useMessageStore.getState().getActiveConversation();
|
|
||||||
if (activeConv) {
|
|
||||||
this.fetchMessagesCallback(activeConv).catch(() => {});
|
|
||||||
}
|
|
||||||
this.fetchUnreadCountCallback().catch(() => {});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
wsService.onDisconnect(() => {
|
wsService.onDisconnect(() => {
|
||||||
@@ -259,6 +209,41 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
this.isBootstrapping = value;
|
this.isBootstrapping = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一同步入口 — 节流 + 去重
|
||||||
|
* onConnect 和 sync_required 共用此入口,防止并发触发重复同步
|
||||||
|
*/
|
||||||
|
private async triggerSync(source: string): Promise<void> {
|
||||||
|
const now = Date.now();
|
||||||
|
// 最小间隔节流
|
||||||
|
if (now - this.lastSyncTriggerAt < MIN_RECONNECT_SYNC_INTERVAL) return;
|
||||||
|
if (this.syncInProgress) return;
|
||||||
|
|
||||||
|
this.syncInProgress = true;
|
||||||
|
this.lastSyncTriggerAt = now;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ok = await this.syncBySeqCallback();
|
||||||
|
if (!ok) {
|
||||||
|
await this.fetchConversationsCallback(true, source);
|
||||||
|
const activeConv = useMessageStore.getState().getActiveConversation();
|
||||||
|
if (activeConv) {
|
||||||
|
await this.fetchMessagesCallback(activeConv).catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.fetchUnreadCountCallback().catch(() => {});
|
||||||
|
} catch {
|
||||||
|
await this.fetchConversationsCallback(true, source).catch(() => {});
|
||||||
|
const activeConv = useMessageStore.getState().getActiveConversation();
|
||||||
|
if (activeConv) {
|
||||||
|
await this.fetchMessagesCallback(activeConv).catch(() => {});
|
||||||
|
}
|
||||||
|
await this.fetchUnreadCountCallback().catch(() => {});
|
||||||
|
} finally {
|
||||||
|
this.syncInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化完成后,处理缓冲的 SSE 事件
|
* 初始化完成后,处理缓冲的 SSE 事件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export interface IMessageSyncService {
|
|||||||
fetchConversations(forceRefresh?: boolean, source?: string): Promise<void>;
|
fetchConversations(forceRefresh?: boolean, source?: string): Promise<void>;
|
||||||
loadMoreConversations(): Promise<void>;
|
loadMoreConversations(): Promise<void>;
|
||||||
fetchConversationDetail(conversationId: string): Promise<ConversationResponse | null>;
|
fetchConversationDetail(conversationId: string): Promise<ConversationResponse | null>;
|
||||||
fetchMessages(conversationId: string, afterSeq?: number): Promise<void>;
|
fetchMessages(conversationId: string, afterSeq?: number, force?: boolean): Promise<void>;
|
||||||
loadMoreMessages(conversationId: string, beforeSeq: number, limit?: number): Promise<MessageResponse[]>;
|
loadMoreMessages(conversationId: string, beforeSeq: number, limit?: number): Promise<MessageResponse[]>;
|
||||||
fetchUnreadCount(): Promise<void>;
|
fetchUnreadCount(): Promise<void>;
|
||||||
syncBySeq(): Promise<boolean>;
|
syncBySeq(): Promise<boolean>;
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
/**
|
|
||||||
* 输入框辅助样式
|
|
||||||
* 用于处理React Native Web在浏览器中的默认focus outline样式
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 移除浏览器默认focus outline的样式
|
|
||||||
* 这个样式可以覆盖浏览器原生的黑框/蓝框
|
|
||||||
*/
|
|
||||||
export const noFocusOutline = {
|
|
||||||
// 移除浏览器默认的outline(针对Web)
|
|
||||||
outlineWidth: 0,
|
|
||||||
outline: 'none' as const,
|
|
||||||
outlineColor: 'transparent',
|
|
||||||
// 兼容React Native Web
|
|
||||||
':focus': {
|
|
||||||
outlineWidth: 0,
|
|
||||||
outline: 'none',
|
|
||||||
},
|
|
||||||
// Webkit浏览器兼容
|
|
||||||
'::placeholder': {
|
|
||||||
outlineWidth: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为TextInput添加无outline样式
|
|
||||||
*/
|
|
||||||
export const inputNoFocusOutline = {
|
|
||||||
...noFocusOutline,
|
|
||||||
// 确保placeholder也没有outline
|
|
||||||
placeholderTextColor: undefined, // 会在使用时覆盖
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user