diff --git a/argon-pro-react-native/.gitignore b/MeAgent/.gitignore similarity index 100% rename from argon-pro-react-native/.gitignore rename to MeAgent/.gitignore diff --git a/argon-pro-react-native/.watchmanconfig b/MeAgent/.watchmanconfig similarity index 100% rename from argon-pro-react-native/.watchmanconfig rename to MeAgent/.watchmanconfig diff --git a/argon-pro-react-native/App.js b/MeAgent/App.js similarity index 100% rename from argon-pro-react-native/App.js rename to MeAgent/App.js diff --git a/argon-pro-react-native/AuthKey_HSF578B626.p8 b/MeAgent/AuthKey_HSF578B626.p8 similarity index 100% rename from argon-pro-react-native/AuthKey_HSF578B626.p8 rename to MeAgent/AuthKey_HSF578B626.p8 diff --git a/argon-pro-react-native/README.md b/MeAgent/README.md similarity index 100% rename from argon-pro-react-native/README.md rename to MeAgent/README.md diff --git a/argon-pro-react-native/app.json b/MeAgent/app.json similarity index 96% rename from argon-pro-react-native/app.json rename to MeAgent/app.json index 0b90eb7d..9da3017a 100644 --- a/argon-pro-react-native/app.json +++ b/MeAgent/app.json @@ -24,6 +24,7 @@ "ios": { "supportsTablet": true, "bundleIdentifier": "com.valuefrontier.meagent", + "deploymentTarget": "15.1", "infoPlist": { "UIBackgroundModes": ["remote-notification"] } diff --git a/argon-pro-react-native/assets/config/argon.json b/MeAgent/assets/config/argon.json similarity index 100% rename from argon-pro-react-native/assets/config/argon.json rename to MeAgent/assets/config/argon.json diff --git a/argon-pro-react-native/assets/font/OpenSans-Bold.ttf b/MeAgent/assets/font/OpenSans-Bold.ttf similarity index 100% rename from argon-pro-react-native/assets/font/OpenSans-Bold.ttf rename to MeAgent/assets/font/OpenSans-Bold.ttf diff --git a/argon-pro-react-native/assets/font/OpenSans-Light.ttf b/MeAgent/assets/font/OpenSans-Light.ttf similarity index 100% rename from argon-pro-react-native/assets/font/OpenSans-Light.ttf rename to MeAgent/assets/font/OpenSans-Light.ttf diff --git a/argon-pro-react-native/assets/font/OpenSans-Regular.ttf b/MeAgent/assets/font/OpenSans-Regular.ttf similarity index 100% rename from argon-pro-react-native/assets/font/OpenSans-Regular.ttf rename to MeAgent/assets/font/OpenSans-Regular.ttf diff --git a/argon-pro-react-native/assets/font/argon.ttf b/MeAgent/assets/font/argon.ttf similarity index 100% rename from argon-pro-react-native/assets/font/argon.ttf rename to MeAgent/assets/font/argon.ttf diff --git a/argon-pro-react-native/assets/icon.png b/MeAgent/assets/icon.png similarity index 100% rename from argon-pro-react-native/assets/icon.png rename to MeAgent/assets/icon.png diff --git a/argon-pro-react-native/assets/imgs/android.png b/MeAgent/assets/imgs/android.png similarity index 100% rename from argon-pro-react-native/assets/imgs/android.png rename to MeAgent/assets/imgs/android.png diff --git a/argon-pro-react-native/assets/imgs/argon-logo-onboarding.png b/MeAgent/assets/imgs/argon-logo-onboarding.png similarity index 100% rename from argon-pro-react-native/assets/imgs/argon-logo-onboarding.png rename to MeAgent/assets/imgs/argon-logo-onboarding.png diff --git a/argon-pro-react-native/assets/imgs/argon-logo-onboarding@2x.png b/MeAgent/assets/imgs/argon-logo-onboarding@2x.png similarity index 100% rename from argon-pro-react-native/assets/imgs/argon-logo-onboarding@2x.png rename to MeAgent/assets/imgs/argon-logo-onboarding@2x.png diff --git a/argon-pro-react-native/assets/imgs/argon-logo.png b/MeAgent/assets/imgs/argon-logo.png similarity index 100% rename from argon-pro-react-native/assets/imgs/argon-logo.png rename to MeAgent/assets/imgs/argon-logo.png diff --git a/argon-pro-react-native/assets/imgs/argon-logo@2x.png b/MeAgent/assets/imgs/argon-logo@2x.png similarity index 100% rename from argon-pro-react-native/assets/imgs/argon-logo@2x.png rename to MeAgent/assets/imgs/argon-logo@2x.png diff --git a/argon-pro-react-native/assets/imgs/argonlogo.png b/MeAgent/assets/imgs/argonlogo.png similarity index 100% rename from argon-pro-react-native/assets/imgs/argonlogo.png rename to MeAgent/assets/imgs/argonlogo.png diff --git a/argon-pro-react-native/assets/imgs/bg.png b/MeAgent/assets/imgs/bg.png similarity index 100% rename from argon-pro-react-native/assets/imgs/bg.png rename to MeAgent/assets/imgs/bg.png diff --git a/argon-pro-react-native/assets/imgs/getPro-bg.png b/MeAgent/assets/imgs/getPro-bg.png similarity index 100% rename from argon-pro-react-native/assets/imgs/getPro-bg.png rename to MeAgent/assets/imgs/getPro-bg.png diff --git a/argon-pro-react-native/assets/imgs/getPro-bg@2x.png b/MeAgent/assets/imgs/getPro-bg@2x.png similarity index 100% rename from argon-pro-react-native/assets/imgs/getPro-bg@2x.png rename to MeAgent/assets/imgs/getPro-bg@2x.png diff --git a/argon-pro-react-native/assets/imgs/icon.png b/MeAgent/assets/imgs/icon.png similarity index 100% rename from argon-pro-react-native/assets/imgs/icon.png rename to MeAgent/assets/imgs/icon.png diff --git a/argon-pro-react-native/assets/imgs/ios.png b/MeAgent/assets/imgs/ios.png similarity index 100% rename from argon-pro-react-native/assets/imgs/ios.png rename to MeAgent/assets/imgs/ios.png diff --git a/MeAgent/assets/imgs/luoxi.jpg b/MeAgent/assets/imgs/luoxi.jpg new file mode 100644 index 00000000..4134069e Binary files /dev/null and b/MeAgent/assets/imgs/luoxi.jpg differ diff --git a/argon-pro-react-native/assets/imgs/profile-img.jpg b/MeAgent/assets/imgs/profile-img.jpg similarity index 100% rename from argon-pro-react-native/assets/imgs/profile-img.jpg rename to MeAgent/assets/imgs/profile-img.jpg diff --git a/argon-pro-react-native/assets/imgs/profile-screen-bg.png b/MeAgent/assets/imgs/profile-screen-bg.png similarity index 100% rename from argon-pro-react-native/assets/imgs/profile-screen-bg.png rename to MeAgent/assets/imgs/profile-screen-bg.png diff --git a/argon-pro-react-native/assets/imgs/register-bg.png b/MeAgent/assets/imgs/register-bg.png similarity index 100% rename from argon-pro-react-native/assets/imgs/register-bg.png rename to MeAgent/assets/imgs/register-bg.png diff --git a/argon-pro-react-native/assets/imgs/splash.png b/MeAgent/assets/imgs/splash.png similarity index 100% rename from argon-pro-react-native/assets/imgs/splash.png rename to MeAgent/assets/imgs/splash.png diff --git a/argon-pro-react-native/assets/logo.jpg b/MeAgent/assets/logo.jpg similarity index 100% rename from argon-pro-react-native/assets/logo.jpg rename to MeAgent/assets/logo.jpg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/bag-17.svg b/MeAgent/assets/nucleo icons/svg/bag-17.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/bag-17.svg rename to MeAgent/assets/nucleo icons/svg/bag-17.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/basket.svg b/MeAgent/assets/nucleo icons/svg/basket.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/basket.svg rename to MeAgent/assets/nucleo icons/svg/basket.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/bell.svg b/MeAgent/assets/nucleo icons/svg/bell.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/bell.svg rename to MeAgent/assets/nucleo icons/svg/bell.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/calendar-date.svg b/MeAgent/assets/nucleo icons/svg/calendar-date.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/calendar-date.svg rename to MeAgent/assets/nucleo icons/svg/calendar-date.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/chart-pie-35.svg b/MeAgent/assets/nucleo icons/svg/chart-pie-35.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/chart-pie-35.svg rename to MeAgent/assets/nucleo icons/svg/chart-pie-35.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/diamond.svg b/MeAgent/assets/nucleo icons/svg/diamond.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/diamond.svg rename to MeAgent/assets/nucleo icons/svg/diamond.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/engine-start.svg b/MeAgent/assets/nucleo icons/svg/engine-start.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/engine-start.svg rename to MeAgent/assets/nucleo icons/svg/engine-start.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/g-check.svg b/MeAgent/assets/nucleo icons/svg/g-check.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/g-check.svg rename to MeAgent/assets/nucleo icons/svg/g-check.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/hat-3.svg b/MeAgent/assets/nucleo icons/svg/hat-3.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/hat-3.svg rename to MeAgent/assets/nucleo icons/svg/hat-3.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/ic_grain_48px.svg b/MeAgent/assets/nucleo icons/svg/ic_grain_48px.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/ic_grain_48px.svg rename to MeAgent/assets/nucleo icons/svg/ic_grain_48px.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/ic_mail_24px.svg b/MeAgent/assets/nucleo icons/svg/ic_mail_24px.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/ic_mail_24px.svg rename to MeAgent/assets/nucleo icons/svg/ic_mail_24px.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/map-big.svg b/MeAgent/assets/nucleo icons/svg/map-big.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/map-big.svg rename to MeAgent/assets/nucleo icons/svg/map-big.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/menu-8.svg b/MeAgent/assets/nucleo icons/svg/menu-8.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/menu-8.svg rename to MeAgent/assets/nucleo icons/svg/menu-8.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/nav-down.svg b/MeAgent/assets/nucleo icons/svg/nav-down.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/nav-down.svg rename to MeAgent/assets/nucleo icons/svg/nav-down.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/nav-left.svg b/MeAgent/assets/nucleo icons/svg/nav-left.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/nav-left.svg rename to MeAgent/assets/nucleo icons/svg/nav-left.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/nav-right.svg b/MeAgent/assets/nucleo icons/svg/nav-right.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/nav-right.svg rename to MeAgent/assets/nucleo icons/svg/nav-right.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/padlock-unlocked.svg b/MeAgent/assets/nucleo icons/svg/padlock-unlocked.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/padlock-unlocked.svg rename to MeAgent/assets/nucleo icons/svg/padlock-unlocked.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/palette.svg b/MeAgent/assets/nucleo icons/svg/palette.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/palette.svg rename to MeAgent/assets/nucleo icons/svg/palette.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/search-zoom-in.svg b/MeAgent/assets/nucleo icons/svg/search-zoom-in.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/search-zoom-in.svg rename to MeAgent/assets/nucleo icons/svg/search-zoom-in.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/shop.svg b/MeAgent/assets/nucleo icons/svg/shop.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/shop.svg rename to MeAgent/assets/nucleo icons/svg/shop.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/spaceship.svg b/MeAgent/assets/nucleo icons/svg/spaceship.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/spaceship.svg rename to MeAgent/assets/nucleo icons/svg/spaceship.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/support.svg b/MeAgent/assets/nucleo icons/svg/support.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/support.svg rename to MeAgent/assets/nucleo icons/svg/support.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/switches.svg b/MeAgent/assets/nucleo icons/svg/switches.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/switches.svg rename to MeAgent/assets/nucleo icons/svg/switches.svg diff --git a/argon-pro-react-native/assets/nucleo icons/svg/ungroup.svg b/MeAgent/assets/nucleo icons/svg/ungroup.svg similarity index 100% rename from argon-pro-react-native/assets/nucleo icons/svg/ungroup.svg rename to MeAgent/assets/nucleo icons/svg/ungroup.svg diff --git a/argon-pro-react-native/assets/splash.png b/MeAgent/assets/splash.png similarity index 100% rename from argon-pro-react-native/assets/splash.png rename to MeAgent/assets/splash.png diff --git a/argon-pro-react-native/babel.config.js b/MeAgent/babel.config.js similarity index 100% rename from argon-pro-react-native/babel.config.js rename to MeAgent/babel.config.js diff --git a/argon-pro-react-native/components/Button.js b/MeAgent/components/Button.js similarity index 100% rename from argon-pro-react-native/components/Button.js rename to MeAgent/components/Button.js diff --git a/argon-pro-react-native/components/Card.js b/MeAgent/components/Card.js similarity index 100% rename from argon-pro-react-native/components/Card.js rename to MeAgent/components/Card.js diff --git a/argon-pro-react-native/components/DrawerItem.js b/MeAgent/components/DrawerItem.js similarity index 100% rename from argon-pro-react-native/components/DrawerItem.js rename to MeAgent/components/DrawerItem.js diff --git a/argon-pro-react-native/components/Header.js b/MeAgent/components/Header.js similarity index 100% rename from argon-pro-react-native/components/Header.js rename to MeAgent/components/Header.js diff --git a/argon-pro-react-native/components/Icon.js b/MeAgent/components/Icon.js similarity index 100% rename from argon-pro-react-native/components/Icon.js rename to MeAgent/components/Icon.js diff --git a/argon-pro-react-native/components/Input.js b/MeAgent/components/Input.js similarity index 100% rename from argon-pro-react-native/components/Input.js rename to MeAgent/components/Input.js diff --git a/argon-pro-react-native/components/Notification.js b/MeAgent/components/Notification.js similarity index 100% rename from argon-pro-react-native/components/Notification.js rename to MeAgent/components/Notification.js diff --git a/argon-pro-react-native/components/Select.js b/MeAgent/components/Select.js similarity index 100% rename from argon-pro-react-native/components/Select.js rename to MeAgent/components/Select.js diff --git a/argon-pro-react-native/components/Switch.js b/MeAgent/components/Switch.js similarity index 100% rename from argon-pro-react-native/components/Switch.js rename to MeAgent/components/Switch.js diff --git a/argon-pro-react-native/components/Tabs.js b/MeAgent/components/Tabs.js similarity index 100% rename from argon-pro-react-native/components/Tabs.js rename to MeAgent/components/Tabs.js diff --git a/argon-pro-react-native/components/index.js b/MeAgent/components/index.js similarity index 100% rename from argon-pro-react-native/components/index.js rename to MeAgent/components/index.js diff --git a/argon-pro-react-native/constants/Images.js b/MeAgent/constants/Images.js similarity index 100% rename from argon-pro-react-native/constants/Images.js rename to MeAgent/constants/Images.js diff --git a/argon-pro-react-native/constants/Theme.js b/MeAgent/constants/Theme.js similarity index 100% rename from argon-pro-react-native/constants/Theme.js rename to MeAgent/constants/Theme.js diff --git a/argon-pro-react-native/constants/articles.js b/MeAgent/constants/articles.js similarity index 100% rename from argon-pro-react-native/constants/articles.js rename to MeAgent/constants/articles.js diff --git a/argon-pro-react-native/constants/cart.js b/MeAgent/constants/cart.js similarity index 100% rename from argon-pro-react-native/constants/cart.js rename to MeAgent/constants/cart.js diff --git a/argon-pro-react-native/constants/categories.js b/MeAgent/constants/categories.js similarity index 100% rename from argon-pro-react-native/constants/categories.js rename to MeAgent/constants/categories.js diff --git a/argon-pro-react-native/constants/deals.js b/MeAgent/constants/deals.js similarity index 100% rename from argon-pro-react-native/constants/deals.js rename to MeAgent/constants/deals.js diff --git a/argon-pro-react-native/constants/index.js b/MeAgent/constants/index.js similarity index 100% rename from argon-pro-react-native/constants/index.js rename to MeAgent/constants/index.js diff --git a/argon-pro-react-native/constants/tabs.js b/MeAgent/constants/tabs.js similarity index 100% rename from argon-pro-react-native/constants/tabs.js rename to MeAgent/constants/tabs.js diff --git a/argon-pro-react-native/constants/utils.js b/MeAgent/constants/utils.js similarity index 100% rename from argon-pro-react-native/constants/utils.js rename to MeAgent/constants/utils.js diff --git a/MeAgent/eas.json b/MeAgent/eas.json new file mode 100644 index 00000000..51284cc2 --- /dev/null +++ b/MeAgent/eas.json @@ -0,0 +1,28 @@ +{ + "cli": { + "version": ">= 12.0.0" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": false + } + }, + "preview": { + "distribution": "internal", + "ios": { + "buildConfiguration": "Release" + } + }, + "production": { + "ios": { + "buildConfiguration": "Release" + } + } + }, + "submit": { + "production": {} + } +} diff --git a/argon-pro-react-native/index.js b/MeAgent/index.js similarity index 100% rename from argon-pro-react-native/index.js rename to MeAgent/index.js diff --git a/MeAgent/ios/.gitignore b/MeAgent/ios/.gitignore new file mode 100644 index 00000000..8beb3443 --- /dev/null +++ b/MeAgent/ios/.gitignore @@ -0,0 +1,30 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace +.xcode.env.local + +# Bundle artifacts +*.jsbundle + +# CocoaPods +/Pods/ diff --git a/MeAgent/ios/.xcode.env b/MeAgent/ios/.xcode.env new file mode 100644 index 00000000..3d5782c7 --- /dev/null +++ b/MeAgent/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/MeAgent/ios/Podfile b/MeAgent/ios/Podfile new file mode 100644 index 00000000..87b0637d --- /dev/null +++ b/MeAgent/ios/Podfile @@ -0,0 +1,87 @@ +require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking") +require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods") + +require 'json' +podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {} + +ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0' +ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR'] + +use_autolinking_method_symbol = ('use' + '_native' + '_modules!').to_sym +origin_autolinking_method = self.method(use_autolinking_method_symbol) +self.define_singleton_method(use_autolinking_method_symbol) do |*args| + if ENV['EXPO_UNSTABLE_CORE_AUTOLINKING'] == '1' + Pod::UI.puts('Using expo-modules-autolinking as core autolinking source'.green) + config_command = [ + 'node', + '--no-warnings', + '--eval', + 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))', + 'react-native-config', + '--json', + '--platform', + 'ios' + ] + origin_autolinking_method.call(config_command) + else + origin_autolinking_method.call() + end +end + +platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4' +install! 'cocoapods', + :deterministic_uuids => false + +prepare_react_native_project! + +target 'app' do + use_expo_modules! + config = use_native_modules! + + use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks'] + use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS'] + + use_react_native!( + :path => config[:reactNativePath], + :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes', + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/..", + :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false', + ) + + post_install do |installer| + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true', + ) + + # This is necessary for Xcode 14, because it signs resource bundles by default + # when building for devices. + installer.target_installation_results.pod_target_installation_results + .each do |pod_name, target_installation_result| + target_installation_result.resource_bundle_targets.each do |resource_bundle_target| + resource_bundle_target.build_configurations.each do |config| + config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO' + end + end + end + + # 抑制第三方库的警告并统一部署目标 + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['GCC_WARN_INHIBIT_ALL_WARNINGS'] = 'YES' + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.1' + end + end + end + + post_integrate do |installer| + begin + expo_patch_react_imports!(installer) + rescue => e + Pod::UI.warn e + end + end +end diff --git a/MeAgent/ios/Podfile.lock b/MeAgent/ios/Podfile.lock new file mode 100644 index 00000000..cc030802 --- /dev/null +++ b/MeAgent/ios/Podfile.lock @@ -0,0 +1,1658 @@ +PODS: + - boost (1.83.0) + - DoubleConversion (1.1.6) + - EXApplication (5.9.1): + - ExpoModulesCore + - EXConstants (16.0.2): + - ExpoModulesCore + - EXNotifications (0.28.19): + - ExpoModulesCore + - Expo (51.0.39): + - ExpoModulesCore + - ExpoAsset (10.0.10): + - ExpoModulesCore + - ExpoBlur (13.0.3): + - ExpoModulesCore + - ExpoClipboard (6.0.3): + - ExpoModulesCore + - ExpoDevice (6.0.2): + - ExpoModulesCore + - ExpoFileSystem (17.0.1): + - ExpoModulesCore + - ExpoFont (12.0.10): + - ExpoModulesCore + - ExpoKeepAwake (13.0.2): + - ExpoModulesCore + - ExpoLinearGradient (13.0.2): + - ExpoModulesCore + - ExpoModulesCore (1.12.26): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsinspector + - React-NativeModulesApple + - React-RCTAppDelegate + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - ExpoWebBrowser (13.0.3): + - ExpoModulesCore + - EXSplashScreen (0.27.7): + - DoubleConversion + - ExpoModulesCore + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - FBLazyVector (0.74.5) + - fmt (9.1.0) + - glog (0.3.5) + - hermes-engine (0.74.5): + - hermes-engine/Pre-built (= 0.74.5) + - hermes-engine/Pre-built (0.74.5) + - RCT-Folly (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Default (= 2024.01.01.00) + - RCT-Folly/Default (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Fabric (2024.01.01.00): + - boost + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCTDeprecation (0.74.5) + - RCTRequired (0.74.5) + - RCTTypeSafety (0.74.5): + - FBLazyVector (= 0.74.5) + - RCTRequired (= 0.74.5) + - React-Core (= 0.74.5) + - React (0.74.5): + - React-Core (= 0.74.5) + - React-Core/DevSupport (= 0.74.5) + - React-Core/RCTWebSocket (= 0.74.5) + - React-RCTActionSheet (= 0.74.5) + - React-RCTAnimation (= 0.74.5) + - React-RCTBlob (= 0.74.5) + - React-RCTImage (= 0.74.5) + - React-RCTLinking (= 0.74.5) + - React-RCTNetwork (= 0.74.5) + - React-RCTSettings (= 0.74.5) + - React-RCTText (= 0.74.5) + - React-RCTVibration (= 0.74.5) + - React-callinvoker (0.74.5) + - React-Codegen (0.74.5): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - React-Core (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.74.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/CoreModulesHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/Default (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/DevSupport (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.74.5) + - React-Core/RCTWebSocket (= 0.74.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTActionSheetHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTAnimationHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTBlobHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTImageHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTLinkingHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTNetworkHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTSettingsHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTTextHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTVibrationHeaders (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-Core/RCTWebSocket (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTDeprecation + - React-Core/Default (= 0.74.5) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket (= 0.7.0) + - Yoga + - React-CoreModules (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety (= 0.74.5) + - React-Codegen + - React-Core/CoreModulesHeaders (= 0.74.5) + - React-jsi (= 0.74.5) + - React-jsinspector + - React-NativeModulesApple + - React-RCTBlob + - React-RCTImage (= 0.74.5) + - ReactCommon + - SocketRocket (= 0.7.0) + - React-cxxreact (0.74.5): + - boost (= 1.83.0) + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.74.5) + - React-debug (= 0.74.5) + - React-jsi (= 0.74.5) + - React-jsinspector + - React-logger (= 0.74.5) + - React-perflogger (= 0.74.5) + - React-runtimeexecutor (= 0.74.5) + - React-debug (0.74.5) + - React-Fabric (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animations (= 0.74.5) + - React-Fabric/attributedstring (= 0.74.5) + - React-Fabric/componentregistry (= 0.74.5) + - React-Fabric/componentregistrynative (= 0.74.5) + - React-Fabric/components (= 0.74.5) + - React-Fabric/core (= 0.74.5) + - React-Fabric/imagemanager (= 0.74.5) + - React-Fabric/leakchecker (= 0.74.5) + - React-Fabric/mounting (= 0.74.5) + - React-Fabric/scheduler (= 0.74.5) + - React-Fabric/telemetry (= 0.74.5) + - React-Fabric/templateprocessor (= 0.74.5) + - React-Fabric/textlayoutmanager (= 0.74.5) + - React-Fabric/uimanager (= 0.74.5) + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/animations (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/attributedstring (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistry (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/componentregistrynative (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/components/inputaccessory (= 0.74.5) + - React-Fabric/components/legacyviewmanagerinterop (= 0.74.5) + - React-Fabric/components/modal (= 0.74.5) + - React-Fabric/components/rncore (= 0.74.5) + - React-Fabric/components/root (= 0.74.5) + - React-Fabric/components/safeareaview (= 0.74.5) + - React-Fabric/components/scrollview (= 0.74.5) + - React-Fabric/components/text (= 0.74.5) + - React-Fabric/components/textinput (= 0.74.5) + - React-Fabric/components/unimplementedview (= 0.74.5) + - React-Fabric/components/view (= 0.74.5) + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/inputaccessory (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/legacyviewmanagerinterop (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/modal (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/rncore (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/root (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/safeareaview (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/scrollview (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/text (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/textinput (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/unimplementedview (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/components/view (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - Yoga + - React-Fabric/core (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/imagemanager (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/leakchecker (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/mounting (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/scheduler (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/telemetry (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/templateprocessor (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/textlayoutmanager (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/uimanager + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-Fabric/uimanager (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-graphics + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - React-FabricImage (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - RCTRequired (= 0.74.5) + - RCTTypeSafety (= 0.74.5) + - React-Fabric + - React-graphics + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.74.5) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - Yoga + - React-featureflags (0.74.5) + - React-graphics (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-Core/Default (= 0.74.5) + - React-utils + - React-hermes (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-cxxreact (= 0.74.5) + - React-jsi + - React-jsiexecutor (= 0.74.5) + - React-jsinspector + - React-perflogger (= 0.74.5) + - React-runtimeexecutor + - React-ImageManager (0.74.5): + - glog + - RCT-Folly/Fabric + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - React-jserrorhandler (0.74.5): + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-debug + - React-jsi + - React-Mapbuffer + - React-jsi (0.74.5): + - boost (= 1.83.0) + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-jsiexecutor (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-cxxreact (= 0.74.5) + - React-jsi (= 0.74.5) + - React-jsinspector + - React-perflogger (= 0.74.5) + - React-jsinspector (0.74.5): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-featureflags + - React-jsi + - React-runtimeexecutor (= 0.74.5) + - React-jsitracing (0.74.5): + - React-jsi + - React-logger (0.74.5): + - glog + - React-Mapbuffer (0.74.5): + - glog + - React-debug + - react-native-safe-area-context (4.10.5): + - React-Core + - react-native-webview (13.8.6): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - React-nativeconfig (0.74.5) + - React-NativeModulesApple (0.74.5): + - glog + - hermes-engine + - React-callinvoker + - React-Core + - React-cxxreact + - React-jsi + - React-jsinspector + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - React-perflogger (0.74.5) + - React-RCTActionSheet (0.74.5): + - React-Core/RCTActionSheetHeaders (= 0.74.5) + - React-RCTAnimation (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Codegen + - React-Core/RCTAnimationHeaders + - React-jsi + - React-NativeModulesApple + - ReactCommon + - React-RCTAppDelegate (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-CoreModules + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-nativeconfig + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-RCTNetwork + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - ReactCommon + - React-RCTBlob (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-Codegen + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-NativeModulesApple + - React-RCTNetwork + - ReactCommon + - React-RCTFabric (0.74.5): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-Core + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-jsinspector + - React-nativeconfig + - React-RCTImage + - React-RCTText + - React-rendererdebug + - React-runtimescheduler + - React-utils + - Yoga + - React-RCTImage (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Codegen + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTNetwork + - ReactCommon + - React-RCTLinking (0.74.5): + - React-Codegen + - React-Core/RCTLinkingHeaders (= 0.74.5) + - React-jsi (= 0.74.5) + - React-NativeModulesApple + - ReactCommon + - ReactCommon/turbomodule/core (= 0.74.5) + - React-RCTNetwork (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Codegen + - React-Core/RCTNetworkHeaders + - React-jsi + - React-NativeModulesApple + - ReactCommon + - React-RCTSettings (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - RCTTypeSafety + - React-Codegen + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - ReactCommon + - React-RCTText (0.74.5): + - React-Core/RCTTextHeaders (= 0.74.5) + - Yoga + - React-RCTVibration (0.74.5): + - RCT-Folly (= 2024.01.01.00) + - React-Codegen + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - ReactCommon + - React-rendererdebug (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - RCT-Folly (= 2024.01.01.00) + - React-debug + - React-rncore (0.74.5) + - React-RuntimeApple (0.74.5): + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-callinvoker + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-utils + - React-RuntimeCore (0.74.5): + - glog + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - React-runtimeexecutor (0.74.5): + - React-jsi (= 0.74.5) + - React-RuntimeHermes (0.74.5): + - hermes-engine + - RCT-Folly/Fabric (= 2024.01.01.00) + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsitracing + - React-nativeconfig + - React-RuntimeCore + - React-utils + - React-runtimescheduler (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - React-rendererdebug + - React-runtimeexecutor + - React-utils + - React-utils (0.74.5): + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-debug + - React-jsi (= 0.74.5) + - ReactCommon (0.74.5): + - ReactCommon/turbomodule (= 0.74.5) + - ReactCommon/turbomodule (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.74.5) + - React-cxxreact (= 0.74.5) + - React-jsi (= 0.74.5) + - React-logger (= 0.74.5) + - React-perflogger (= 0.74.5) + - ReactCommon/turbomodule/bridging (= 0.74.5) + - ReactCommon/turbomodule/core (= 0.74.5) + - ReactCommon/turbomodule/bridging (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.74.5) + - React-cxxreact (= 0.74.5) + - React-jsi (= 0.74.5) + - React-logger (= 0.74.5) + - React-perflogger (= 0.74.5) + - ReactCommon/turbomodule/core (0.74.5): + - DoubleConversion + - fmt (= 9.1.0) + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - React-callinvoker (= 0.74.5) + - React-cxxreact (= 0.74.5) + - React-debug (= 0.74.5) + - React-jsi (= 0.74.5) + - React-logger (= 0.74.5) + - React-perflogger (= 0.74.5) + - React-utils (= 0.74.5) + - RNCAsyncStorage (1.23.1): + - React-Core + - RNCMaskedView (0.3.1): + - React-Core + - RNGestureHandler (2.16.2): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNReanimated (3.10.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNScreens (3.31.1): + - DoubleConversion + - glog + - hermes-engine + - RCT-Folly (= 2024.01.01.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - RNSVG (15.2.0): + - React-Core + - SocketRocket (0.7.0) + - Yoga (0.0.0) + +DEPENDENCIES: + - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../node_modules/expo-application/ios`) + - EXConstants (from `../node_modules/expo-constants/ios`) + - EXNotifications (from `../node_modules/expo-notifications/ios`) + - Expo (from `../node_modules/expo`) + - ExpoAsset (from `../node_modules/expo-asset/ios`) + - ExpoBlur (from `../node_modules/expo-blur/ios`) + - ExpoClipboard (from `../node_modules/expo-clipboard/ios`) + - ExpoDevice (from `../node_modules/expo-device/ios`) + - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) + - ExpoFont (from `../node_modules/expo-font/ios`) + - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) + - ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`) + - ExpoModulesCore (from `../node_modules/expo-modules-core`) + - ExpoWebBrowser (from `../node_modules/expo-web-browser/ios`) + - EXSplashScreen (from `../node_modules/expo-splash-screen/ios`) + - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../node_modules/react-native/`) + - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - React-Codegen (from `build/generated/ios`) + - React-Core (from `../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../node_modules/react-native/`) + - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) + - React-Fabric (from `../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) + - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) + - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) + - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - react-native-webview (from `../node_modules/react-native-webview`) + - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) + - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) + - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../node_modules/react-native/React`) + - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) + - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) + - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" + - "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)" + - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../node_modules/react-native-reanimated`) + - RNScreens (from `../node_modules/react-native-screens`) + - RNSVG (from `../node_modules/react-native-svg`) + - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + +SPEC REPOS: + trunk: + - SocketRocket + +EXTERNAL SOURCES: + boost: + :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + DoubleConversion: + :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + EXApplication: + :path: "../node_modules/expo-application/ios" + EXConstants: + :path: "../node_modules/expo-constants/ios" + EXNotifications: + :path: "../node_modules/expo-notifications/ios" + Expo: + :path: "../node_modules/expo" + ExpoAsset: + :path: "../node_modules/expo-asset/ios" + ExpoBlur: + :path: "../node_modules/expo-blur/ios" + ExpoClipboard: + :path: "../node_modules/expo-clipboard/ios" + ExpoDevice: + :path: "../node_modules/expo-device/ios" + ExpoFileSystem: + :path: "../node_modules/expo-file-system/ios" + ExpoFont: + :path: "../node_modules/expo-font/ios" + ExpoKeepAwake: + :path: "../node_modules/expo-keep-awake/ios" + ExpoLinearGradient: + :path: "../node_modules/expo-linear-gradient/ios" + ExpoModulesCore: + :path: "../node_modules/expo-modules-core" + ExpoWebBrowser: + :path: "../node_modules/expo-web-browser/ios" + EXSplashScreen: + :path: "../node_modules/expo-splash-screen/ios" + FBLazyVector: + :path: "../node_modules/react-native/Libraries/FBLazyVector" + fmt: + :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" + glog: + :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + hermes-engine: + :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 + RCT-Folly: + :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + RCTDeprecation: + :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../node_modules/react-native/Libraries/Required" + RCTTypeSafety: + :path: "../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../node_modules/react-native/" + React-callinvoker: + :path: "../node_modules/react-native/ReactCommon/callinvoker" + React-Codegen: + :path: build/generated/ios + React-Core: + :path: "../node_modules/react-native/" + React-CoreModules: + :path: "../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../node_modules/react-native/ReactCommon/react/debug" + React-Fabric: + :path: "../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../node_modules/react-native/ReactCommon/react/featureflags" + React-graphics: + :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../node_modules/react-native/ReactCommon/hermes" + React-ImageManager: + :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-jserrorhandler: + :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsitracing: + :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../node_modules/react-native/ReactCommon" + react-native-safe-area-context: + :path: "../node_modules/react-native-safe-area-context" + react-native-webview: + :path: "../node_modules/react-native-webview" + React-nativeconfig: + :path: "../node_modules/react-native/ReactCommon" + React-NativeModulesApple: + :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-perflogger: + :path: "../node_modules/react-native/ReactCommon/reactperflogger" + React-RCTActionSheet: + :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../node_modules/react-native/React" + React-RCTImage: + :path: "../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../node_modules/react-native/Libraries/Network" + React-RCTSettings: + :path: "../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../node_modules/react-native/Libraries/Vibration" + React-rendererdebug: + :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + React-rncore: + :path: "../node_modules/react-native/ReactCommon" + React-RuntimeApple: + :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-utils: + :path: "../node_modules/react-native/ReactCommon/react/utils" + ReactCommon: + :path: "../node_modules/react-native/ReactCommon" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" + RNCMaskedView: + :path: "../node_modules/@react-native-masked-view/masked-view" + RNGestureHandler: + :path: "../node_modules/react-native-gesture-handler" + RNReanimated: + :path: "../node_modules/react-native-reanimated" + RNScreens: + :path: "../node_modules/react-native-screens" + RNSVG: + :path: "../node_modules/react-native-svg" + Yoga: + :path: "../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + boost: d3f49c53809116a5d38da093a8aa78bf551aed09 + DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5 + EXApplication: ec862905fdab3a15bf6bd8ca1a99df7fc02d7762 + EXConstants: 89d35611505a8ce02550e64e43cd05565da35f9a + EXNotifications: 6ce128c0d3d3d161cd68bfd07d593db40e140396 + Expo: ed0a748eb6be0efd2c3df7f6de3f3158a14464c9 + ExpoAsset: 286fee7ba711ce66bf20b315e68106b13b8629fc + ExpoBlur: 99901a4531f5d3ac4a19b362907b8f75da4ed9c8 + ExpoClipboard: 243e22ff4161bbffcd3d2db469ae860ddc1156be + ExpoDevice: 84b3ed79df1234c17edfbf335f6ecf3c636f74de + ExpoFileSystem: 2988caaf68b7cb706e36d382829d99811d9d76a5 + ExpoFont: 38dddf823e32740c2a9f37c926a33aeca736b5c4 + ExpoKeepAwake: dd02e65d49f1cfd9194640028ae2857e536eb1c9 + ExpoLinearGradient: 4c44b3803b441724874b232e6520b51ca6a50db1 + ExpoModulesCore: 9ac73e2f60e0ea1d30137ca96cfc8c2aa34ef2b2 + ExpoWebBrowser: cf10afe886891ab495877dada977fe6c269614a4 + EXSplashScreen: a4ce3dd5d28d48e8b9132bcd9b58ee8e340db78c + FBLazyVector: ac12dc084d1c8ec4cc4d7b3cf1b0ebda6dab85af + fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 + glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f + hermes-engine: 8c1577f3fdb849cbe7729c2e7b5abc4b845e88f8 + RCT-Folly: 5dc73daec3476616d19e8a53f0156176f7b55461 + RCTDeprecation: 3afceddffa65aee666dafd6f0116f1d975db1584 + RCTRequired: ec1239bc9d8bf63e10fb92bd8b26171a9258e0c1 + RCTTypeSafety: f5ecbc86c5c5fa163c05acb7a1c5012e15b5f994 + React: fc9fa7258eff606f44d58c5b233a82dc9cf09018 + React-callinvoker: e3fab14d69607fb7e8e3a57e5a415aed863d3599 + React-Codegen: 3963186cb6a4ef21b5e67dcf7badf359867ff6df + React-Core: c3f589f104983dec3c3eeec5e70d61aa811bc236 + React-CoreModules: 864932ddae3ead5af5bfb05f9bbc2cedcb958b39 + React-cxxreact: bd9146108c44e6dbb99bba4568ce7af0304a2419 + React-debug: d30893c49ae1bce4037ea5cd8bb2511d2a38d057 + React-Fabric: a171830e52baf8ec2b175c6a3791e01bbb92f1fb + React-FabricImage: ad154af0067f4b5dc5a41f607e48ee343641e903 + React-featureflags: 4ae83e72d9a92452793601ac9ac7d2280e486089 + React-graphics: ed7d57965140168de86835946e8f1210c72c65dc + React-hermes: 177b1efdf3b8f10f4ca12b624b83fb4d4ccb2884 + React-ImageManager: 3a50d0ee0bf81b1a6f23a0c5b30388293bcd6004 + React-jserrorhandler: dcd62f5ca1c724c19637595ef7f45b78018e758f + React-jsi: 0abe1b0881b67caf8d8df6a57778dd0d3bb9d9a5 + React-jsiexecutor: f6ca8c04f19f6a3acaa9610f7fb728f39d6e3248 + React-jsinspector: db98771eae84e6f86f0ca5d9dcc572baadbfefc0 + React-jsitracing: f8367edacc50bb3f9f056a5aeafb8cee5849fafb + React-logger: 780b9ee9cec7d44eabc4093de90107c379078cb6 + React-Mapbuffer: f544f00b98dbdd8cbae96dd2bdb8b47f719976e0 + react-native-safe-area-context: df9763c5de6fa38883028e243a0b60123acb8858 + react-native-webview: a4483a25c71098e407df1c1d9056ab907647d7c7 + React-nativeconfig: ba9a2e54e2f0882cf7882698825052793ed4c851 + React-NativeModulesApple: 84aaad2b0e546d7b839837ca537f6e72804a4cad + React-perflogger: ed4e0c65781521e0424f2e5e40b40cc7879d737e + React-RCTActionSheet: 49d53ff03bb5688ca4606c55859053a0cd129ea5 + React-RCTAnimation: 3075449f26cb98a52bcbf51cccd0c7954e2a71db + React-RCTAppDelegate: 9a419c4dda9dd039ad851411546dd297b930c454 + React-RCTBlob: e81ab773a8fc1e9dceed953e889f936a7b7b3aa6 + React-RCTFabric: 47a87a3e3fa751674f7e64d0bcd58976b8c57db9 + React-RCTImage: d570531201c6dce7b5b63878fa8ecec0cc311c4c + React-RCTLinking: af888972b925d2811633d47853c479e88c35eb4d + React-RCTNetwork: 5728a06ff595003eca628f43f112a804f4a9a970 + React-RCTSettings: ba3665b0569714a8aaceee5c7d23b943e333fa55 + React-RCTText: b733fa984f0336b072e47512898ba91214f66ddb + React-RCTVibration: 0cbcbbd8781b6f6123671bae9ee5dd20d621af6c + React-rendererdebug: 9fc8f7d0bd19f2a3fe3791982af550b5e1535ff7 + React-rncore: 4013508a2f3fcf46c961919bbbd4bfdda198977e + React-RuntimeApple: a852a6e06ab20711658873f39cb10b0033bea19d + React-RuntimeCore: 12e5e176c0cb09926f3e6f37403a84d2e0f203a7 + React-runtimeexecutor: 0e688aefc14c6bc8601f4968d8d01c3fb6446844 + React-RuntimeHermes: 80c03a5215520c9733764ba11cbe535053c9746d + React-runtimescheduler: 2cbd0f3625b30bba08e8768776107f6f0203159b + React-utils: 9fa4e5d0b5e6c6c85c958f19d6ef854337886417 + ReactCommon: 9f285823dbe955099978d9bff65a7653ca029256 + RNCAsyncStorage: aa75595c1aefa18f868452091fa0c411a516ce11 + RNCMaskedView: de80352547bd4f0d607bf6bab363d826822bd126 + RNGestureHandler: 326e35460fb6c8c64a435d5d739bea90d7ed4e49 + RNReanimated: def444e044c354f38bb0a5926a8583ba19d944c1 + RNScreens: a2d8a2555b4653d7a19706eb172f855657ac30d7 + RNSVG: 0e7deccab0678200815104223aadd5ca734dd41d + SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d + Yoga: 950bbfd7e6f04790fdb51149ed51df41f329fcc8 + +PODFILE CHECKSUM: b710501c35a8aa0e7f8bc9bef05d8871882e1580 + +COCOAPODS: 1.16.2 diff --git a/MeAgent/ios/Podfile.properties.json b/MeAgent/ios/Podfile.properties.json new file mode 100644 index 00000000..3540391c --- /dev/null +++ b/MeAgent/ios/Podfile.properties.json @@ -0,0 +1,5 @@ +{ + "expo.jsEngine": "hermes", + "EX_DEV_CLIENT_NETWORK_INSPECTOR": "true", + "ios.deploymentTarget": "15.1" +} diff --git a/MeAgent/ios/app.xcodeproj/project.pbxproj b/MeAgent/ios/app.xcodeproj/project.pbxproj new file mode 100644 index 00000000..59461e5a --- /dev/null +++ b/MeAgent/ios/app.xcodeproj/project.pbxproj @@ -0,0 +1,559 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; }; + 5EF73AEC3ABB8432EA5E20D1 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 50B8B0092BBC618FDE9C0B04 /* PrivacyInfo.xcprivacy */; }; + 96905EF65AED1B983A6B3ABC /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-app.a */; }; + B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */; }; + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; }; + E7343D6D90954730BB45D8E1 /* noop-file.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2985DBCB70452992045F05 /* noop-file.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13B07F961A680F5B00A75B9A /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = app/AppDelegate.mm; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = app/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = app/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = app/main.m; sourceTree = ""; }; + 50B8B0092BBC618FDE9C0B04 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = app/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C2E3173556A471DD304B334 /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = ""; }; + 7A4D352CD337FB3A3BF06240 /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = ""; }; + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = app/SplashScreen.storyboard; sourceTree = ""; }; + BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = ""; }; + E24A9282120D4E608D7E34DA /* app-Bridging-Header.h */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.c.h; name = "app-Bridging-Header.h"; path = "app/app-Bridging-Header.h"; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-app/ExpoModulesProvider.swift"; sourceTree = ""; }; + FE2985DBCB70452992045F05 /* noop-file.swift */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 4; includeInIndex = 0; lastKnownFileType = sourcecode.swift; name = "noop-file.swift"; path = "app/noop-file.swift"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 96905EF65AED1B983A6B3ABC /* libPods-app.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 13B07FAE1A68108700A75B9A /* app */ = { + isa = PBXGroup; + children = ( + BB2F792B24A3F905000567C9 /* Supporting */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.mm */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB71A68108700A75B9A /* main.m */, + AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */, + FE2985DBCB70452992045F05 /* noop-file.swift */, + E24A9282120D4E608D7E34DA /* app-Bridging-Header.h */, + 50B8B0092BBC618FDE9C0B04 /* PrivacyInfo.xcprivacy */, + ); + name = app; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 58EEBF8E8E6FB1BC6CAF49B5 /* libPods-app.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* app */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + D65327D7A22EEC0BE12398D9 /* Pods */, + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* app.app */, + ); + name = Products; + sourceTree = ""; + }; + 92DBD88DE9BF7D494EA9DA96 /* app */ = { + isa = PBXGroup; + children = ( + FAC715A2D49A985799AEE119 /* ExpoModulesProvider.swift */, + ); + name = app; + sourceTree = ""; + }; + BB2F792B24A3F905000567C9 /* Supporting */ = { + isa = PBXGroup; + children = ( + BB2F792C24A3F905000567C9 /* Expo.plist */, + ); + name = Supporting; + path = app/Supporting; + sourceTree = ""; + }; + D65327D7A22EEC0BE12398D9 /* Pods */ = { + isa = PBXGroup; + children = ( + 6C2E3173556A471DD304B334 /* Pods-app.debug.xcconfig */, + 7A4D352CD337FB3A3BF06240 /* Pods-app.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + D7E4C46ADA2E9064B798F356 /* ExpoModulesProviders */ = { + isa = PBXGroup; + children = ( + 92DBD88DE9BF7D494EA9DA96 /* app */, + ); + name = ExpoModulesProviders; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* app */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */; + buildPhases = ( + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */, + F50DEC99269F1C170908EB3D /* [Expo] Configure project */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */, + 4E9C7677DD0DF9724695D156 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = app; + productName = app; + productReference = 13B07F961A680F5B00A75B9A /* app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* app */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BB2F792D24A3F905000567C9 /* Expo.plist in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */, + 5EF73AEC3ABB8432EA5E20D1 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n"; + }; + 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-app-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 4E9C7677DD0DF9724695D156 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-frameworks.sh", + "${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes", + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh", + "${PODS_CONFIGURATION_BUILD_DIR}/EXApplication/ExpoApplication_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/EXConstants.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXConstants/ExpoConstants_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/EXNotifications/ExpoNotifications_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoDevice/ExpoDevice_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/ExpoFileSystem/ExpoFileSystem_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/RNCAsyncStorage/RNCAsyncStorage_resources.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/RCTI18nStrings.bundle", + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoApplication_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXConstants.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoConstants_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoNotifications_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoDevice_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoFileSystem_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNCAsyncStorage_resources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RCTI18nStrings.bundle", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-app/Pods-app-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + F50DEC99269F1C170908EB3D /* [Expo] Configure project */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "[Expo] Configure project"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-app/expo-configure-project.sh\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + B18059E884C0ABDD17F3DC3D /* ExpoModulesProvider.swift in Sources */, + E7343D6D90954730BB45D8E1 /* noop-file.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6C2E3173556A471DD304B334 /* Pods-app.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = app/app.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XML2LHR2J; + ENABLE_BITCODE = NO; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "FB_SONARKIT_ENABLED=1", + ); + INFOPLIST_FILE = app/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = com.valuefrontier.meagent; + PRODUCT_NAME = app; + SWIFT_OBJC_BRIDGING_HEADER = "app/app-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7A4D352CD337FB3A3BF06240 /* Pods-app.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = app/app.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 6XML2LHR2J; + INFOPLIST_FILE = app/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE"; + PRODUCT_BUNDLE_IDENTIFIER = com.valuefrontier.meagent; + PRODUCT_NAME = app; + SWIFT_OBJC_BRIDGING_HEADER = "app/app-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CXX = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CXX = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_LDFLAGS = ( + "$(inherited)", + " ", + ); + REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + SDKROOT = iphoneos; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "app" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/MeAgent/ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme b/MeAgent/ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme new file mode 100644 index 00000000..07f5cb69 --- /dev/null +++ b/MeAgent/ios/app.xcodeproj/xcshareddata/xcschemes/app.xcscheme @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MeAgent/ios/app.xcworkspace/contents.xcworkspacedata b/MeAgent/ios/app.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..b83e63c3 --- /dev/null +++ b/MeAgent/ios/app.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/MeAgent/ios/app/AppDelegate.h b/MeAgent/ios/app/AppDelegate.h new file mode 100644 index 00000000..1658a437 --- /dev/null +++ b/MeAgent/ios/app/AppDelegate.h @@ -0,0 +1,7 @@ +#import +#import +#import + +@interface AppDelegate : EXAppDelegateWrapper + +@end diff --git a/MeAgent/ios/app/AppDelegate.mm b/MeAgent/ios/app/AppDelegate.mm new file mode 100644 index 00000000..b27f8328 --- /dev/null +++ b/MeAgent/ios/app/AppDelegate.mm @@ -0,0 +1,62 @@ +#import "AppDelegate.h" + +#import +#import + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ + self.moduleName = @"main"; + + // You can add your custom initial props in the dictionary below. + // They will be passed down to the ViewController used by React Native. + self.initialProps = @{}; + + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ + return [self bundleURL]; +} + +- (NSURL *)bundleURL +{ +#if DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"]; +#else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif +} + +// Linking API +- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *)options { + return [super application:application openURL:url options:options] || [RCTLinkingManager application:application openURL:url options:options]; +} + +// Universal Links +- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray> * _Nullable))restorationHandler { + BOOL result = [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; + return [super application:application continueUserActivity:userActivity restorationHandler:restorationHandler] || result; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + return [super application:application didFailToRegisterForRemoteNotificationsWithError:error]; +} + +// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} + +@end diff --git a/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png b/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png new file mode 100644 index 00000000..bb5119fb Binary files /dev/null and b/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/App-Icon-1024x1024@1x.png differ diff --git a/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json b/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..90d8d4c2 --- /dev/null +++ b/MeAgent/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images": [ + { + "filename": "App-Icon-1024x1024@1x.png", + "idiom": "universal", + "platform": "ios", + "size": "1024x1024" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/MeAgent/ios/app/Images.xcassets/Contents.json b/MeAgent/ios/app/Images.xcassets/Contents.json new file mode 100644 index 00000000..ed285c2e --- /dev/null +++ b/MeAgent/ios/app/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "expo" + } +} diff --git a/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/Contents.json b/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/Contents.json new file mode 100644 index 00000000..3cf84897 --- /dev/null +++ b/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "image.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/image.png b/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/image.png new file mode 100644 index 00000000..e803295a Binary files /dev/null and b/MeAgent/ios/app/Images.xcassets/SplashScreen.imageset/image.png differ diff --git a/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/Contents.json b/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/Contents.json new file mode 100644 index 00000000..3cf84897 --- /dev/null +++ b/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images": [ + { + "idiom": "universal", + "filename": "image.png", + "scale": "1x" + }, + { + "idiom": "universal", + "scale": "2x" + }, + { + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "expo" + } +} \ No newline at end of file diff --git a/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/image.png b/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/image.png new file mode 100644 index 00000000..837b3d57 Binary files /dev/null and b/MeAgent/ios/app/Images.xcassets/SplashScreenBackground.imageset/image.png differ diff --git a/MeAgent/ios/app/Info.plist b/MeAgent/ios/app/Info.plist new file mode 100644 index 00000000..4bef78a4 --- /dev/null +++ b/MeAgent/ios/app/Info.plist @@ -0,0 +1,76 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 价值前沿 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + com.valuefrontier.meagent + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsLocalNetworking + + + UIBackgroundModes + + remote-notification + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + arm64 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + + \ No newline at end of file diff --git a/MeAgent/ios/app/PrivacyInfo.xcprivacy b/MeAgent/ios/app/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..c6b452ea --- /dev/null +++ b/MeAgent/ios/app/PrivacyInfo.xcprivacy @@ -0,0 +1,48 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + 0A2A.1 + 3B52.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + 85F4.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/MeAgent/ios/app/SplashScreen.storyboard b/MeAgent/ios/app/SplashScreen.storyboard new file mode 100644 index 00000000..ed03a529 --- /dev/null +++ b/MeAgent/ios/app/SplashScreen.storyboard @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/MeAgent/ios/app/Supporting/Expo.plist b/MeAgent/ios/app/Supporting/Expo.plist new file mode 100644 index 00000000..750be020 --- /dev/null +++ b/MeAgent/ios/app/Supporting/Expo.plist @@ -0,0 +1,12 @@ + + + + + EXUpdatesCheckOnLaunch + ALWAYS + EXUpdatesEnabled + + EXUpdatesLaunchWaitMs + 0 + + \ No newline at end of file diff --git a/MeAgent/ios/app/app-Bridging-Header.h b/MeAgent/ios/app/app-Bridging-Header.h new file mode 100644 index 00000000..e11d920b --- /dev/null +++ b/MeAgent/ios/app/app-Bridging-Header.h @@ -0,0 +1,3 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// diff --git a/MeAgent/ios/app/app.entitlements b/MeAgent/ios/app/app.entitlements new file mode 100644 index 00000000..018a6e20 --- /dev/null +++ b/MeAgent/ios/app/app.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + \ No newline at end of file diff --git a/MeAgent/ios/app/main.m b/MeAgent/ios/app/main.m new file mode 100644 index 00000000..25181b6c --- /dev/null +++ b/MeAgent/ios/app/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + diff --git a/MeAgent/ios/app/noop-file.swift b/MeAgent/ios/app/noop-file.swift new file mode 100644 index 00000000..b2ffafbf --- /dev/null +++ b/MeAgent/ios/app/noop-file.swift @@ -0,0 +1,4 @@ +// +// @generated +// A blank Swift file must be created for native modules with Swift files to work correctly. +// diff --git a/argon-pro-react-native/logo.jpg b/MeAgent/logo.jpg similarity index 100% rename from argon-pro-react-native/logo.jpg rename to MeAgent/logo.jpg diff --git a/MeAgent/navigation/Menu.js b/MeAgent/navigation/Menu.js new file mode 100644 index 00000000..1aa9a697 --- /dev/null +++ b/MeAgent/navigation/Menu.js @@ -0,0 +1,415 @@ +import React from "react"; +import { + ScrollView, + StyleSheet, + Dimensions, + Image, + TouchableOpacity, + Linking, +} from "react-native"; +import { Block, Text, theme } from "galio-framework"; +import { useSafeArea } from "react-native-safe-area-context"; +import { Box, HStack, VStack, Icon, Pressable, Spinner } from "native-base"; +import { Ionicons } from "@expo/vector-icons"; +import { LinearGradient } from "expo-linear-gradient"; +import Images from "../constants/Images"; +import { DrawerItem as DrawerCustomItem } from "../components/index"; +import { useAuth } from "../src/contexts/AuthContext"; + +const { width } = Dimensions.get("screen"); + +// 金色主题色 +const GOLD_PRIMARY = '#D4AF37'; + +// 用户卡片组件 +const UserCard = ({ navigation }) => { + const { user, isLoggedIn, isLoading, subscription, logout } = useAuth(); + + const handleLoginPress = () => { + navigation.closeDrawer(); + // 使用 getParent 获取根导航器来导航到 Login + navigation.getParent()?.navigate("Login"); + }; + + const handleLogoutPress = async () => { + await logout(); + }; + + // 获取订阅显示文本 + const getSubscriptionText = () => { + if (!subscription || !subscription.is_active) { + return "免费用户"; + } + const typeMap = { pro: "Pro 会员", max: "Max 会员" }; + return typeMap[subscription.type] || "免费用户"; + }; + + if (isLoading) { + return ( + + + + + 加载中... + + + + ); + } + + if (!isLoggedIn) { + return ( + + + + + + + + + 登录/注册 + + + 登录解锁更多功能 + + + + + + + ); + } + + // 已登录状态 + return ( + + + + + {(user?.username || user?.nickname || "U").charAt(0).toUpperCase()} + + + + + {user?.nickname || user?.username || "用户"} + + + + + {getSubscriptionText()} + + + + + + + + + + ); +}; + +function CustomDrawerContent({ + drawerPosition, + navigation, + profile, + focused, + state, + ...rest +}) { + const insets = useSafeArea(); + // 菜单项配置 + const screens = [ + { title: "事件中心", navigateTo: "EventsDrawer", icon: "flash", gradient: ["#7C3AED", "#A78BFA"] }, + { title: "市场热点", navigateTo: "MarketDrawer", icon: "flame", gradient: ["#F59E0B", "#FBBF24"] }, + { title: "概念中心", navigateTo: "ConceptsDrawer", icon: "bulb", gradient: ["#06B6D4", "#22D3EE"] }, + { title: "我的自选", navigateTo: "WatchlistDrawer", icon: "star", gradient: ["#EC4899", "#F472B6"] }, + { title: "社区论坛", navigateTo: "CommunityDrawer", icon: "chatbubbles", gradient: ["#10B981", "#34D399"] }, + { title: "AI 助手", navigateTo: "AgentDrawer", icon: "sparkles", gradient: ["#8B5CF6", "#EC4899"] }, + { title: "个人中心", navigateTo: "ProfileDrawerNew", icon: "person", gradient: ["#8B5CF6", "#A78BFA"] }, + ]; + return ( + + {/* 品牌头部 - 黑金主题 */} + + + + + + 价值前沿 + + + VALUE FRONTIER + + + + + + + {/* 用户卡片 - 深色版本 */} + + + + + {/* 导航菜单 */} + + {screens.map((item, index) => { + const isFocused = state.index === index; + return ( + navigation.navigate(item.navigateTo)} + mb={2} + > + {isFocused ? ( + + + + + + + {item.title} + + + + ) : ( + + + + + + + {item.title} + + + + )} + + ); + })} + + + {/* 底部版本信息 */} + + + + 价值前沿 v1.0.0 + + + + + + ); +} + +// 深色主题用户卡片 +const UserCardDark = ({ navigation }) => { + const { user, isLoggedIn, isLoading, subscription, logout } = useAuth(); + + const handleLoginPress = () => { + navigation.closeDrawer(); + navigation.getParent()?.navigate("Login"); + }; + + const handleLogoutPress = async () => { + await logout(); + }; + + const getSubscriptionText = () => { + if (!subscription || !subscription.is_active) return "免费用户"; + const typeMap = { pro: "Pro 会员", max: "Max 会员" }; + return typeMap[subscription.type] || "免费用户"; + }; + + if (isLoading) { + return ( + + + + 加载中... + + + ); + } + + if (!isLoggedIn) { + return ( + + + + + + + + + 登录/注册 + + + 登录解锁更多功能 + + + + + + + ); + } + + return ( + + + + + {(user?.username || user?.nickname || "U").charAt(0).toUpperCase()} + + + + + {user?.nickname || user?.username || "用户"} + + + + {getSubscriptionText()} + + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + paddingHorizontal: 28, + paddingBottom: theme.SIZES.BASE, + paddingTop: theme.SIZES.BASE * 3, + justifyContent: "center", + }, +}); + +export default CustomDrawerContent; diff --git a/argon-pro-react-native/navigation/Screens.js b/MeAgent/navigation/Screens.js similarity index 76% rename from argon-pro-react-native/navigation/Screens.js rename to MeAgent/navigation/Screens.js index 9a266804..a3be405f 100644 --- a/argon-pro-react-native/navigation/Screens.js +++ b/MeAgent/navigation/Screens.js @@ -39,11 +39,35 @@ import { createStackNavigator } from "@react-navigation/stack"; import { EventList, EventDetail } from "../src/screens/Events"; // 市场热点页面 -import { MarketHot, SectorDetail, EventCalendar, StockDetail, TodayStats } from "../src/screens/Market"; +import { MarketHot, SectorDetail, EventCalendar, TodayStats } from "../src/screens/Market"; + +// 概念中心页面 +import { ConceptList } from "../src/screens/Concepts"; + +// 自选股页面 +import WatchlistScreen from "../src/screens/Watchlist/WatchlistScreen"; + +// 新股票详情页面 +import { StockDetailScreen } from "../src/screens/StockDetail"; + +// 社区页面 +import CommunityHome from "../src/screens/Community"; +import ChannelDetail from "../src/screens/Community/ChannelDetail"; +import ForumChannel from "../src/screens/Community/ForumChannel"; +import PostDetail from "../src/screens/Community/PostDetail"; +import CreatePost from "../src/screens/Community/CreatePost"; +import CreateChannel from "../src/screens/Community/CreateChannel"; +import MemberList from "../src/screens/Community/MemberList"; + +// 新个人中心页面 +import { ProfileScreen as NewProfileScreen } from "../src/screens/Profile"; // 认证页面 import { LoginScreen } from "../src/screens/Auth"; +// AI 助手页面 +import { AgentChatScreen } from "../src/screens/Agent"; + // 推送通知处理 import PushNotificationHandler from "../src/components/PushNotificationHandler"; @@ -264,6 +288,13 @@ function EventsStack(props) { cardStyle: { backgroundColor: "#0F172A" }, }} /> + ); } @@ -292,6 +323,8 @@ function MarketStack(props) {
@@ -309,9 +342,9 @@ function MarketStack(props) { /> + + + ); +} + +// 自选股导航栈 +function WatchlistStack(props) { + return ( + + + + + + ); +} + +// 社区导航栈 +function CommunityStack(props) { + return ( + + + + + + + + + + ); +} + +// 新个人中心导航栈 +function NewProfileStack(props) { + return ( + + + + ); +} + +// AI 助手导航栈 +function AgentStack(props) { + return ( + + + + ); +} + function ProfileStack(props) { return ( } drawerStyle={{ - backgroundColor: "white", + backgroundColor: "#0F172A", width: width * 0.8, }} screenOptions={{ @@ -598,6 +787,41 @@ function AppStack(props) { headerShown: false, }} /> + + + + + { + const insets = useSafeAreaInsets(); + + // 自动跳转到主页 + useEffect(() => { + const timer = setTimeout(() => { + navigation.replace("App"); + }, 1500); // 1.5秒后跳转 + + return () => clearTimeout(timer); + }, [navigation]); + + return ( + + + + {/* 背景装饰 */} + + {/* 顶部金色光晕 */} + + {/* 底部深色渐变 */} + + + + {/* 主内容 */} + + {/* Logo 区域 */} + + + + + {/* 品牌名称 */} + + + 价值前沿 + + + VALUE FRONTIER + + + + {/* 特性描述 */} + + + 智能投资决策平台 + + + 发现市场热点,把握投资机会 + + + + {/* 加载指示器 */} + + + + + + ); +}; + +const styles = StyleSheet.create({ + logoContainer: { + shadowColor: '#D4AF37', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.3, + shadowRadius: 30, + elevation: 10, + }, + logo: { + width: 160, + height: 160, + }, + brandText: { + color: '#D4AF37', + textShadowColor: 'rgba(212, 175, 55, 0.5)', + textShadowOffset: { width: 0, height: 0 }, + textShadowRadius: 20, + }, +}); + +export default Pro; diff --git a/argon-pro-react-native/screens/Product.js b/MeAgent/screens/Product.js similarity index 100% rename from argon-pro-react-native/screens/Product.js rename to MeAgent/screens/Product.js diff --git a/argon-pro-react-native/screens/Profile.js b/MeAgent/screens/Profile.js similarity index 100% rename from argon-pro-react-native/screens/Profile.js rename to MeAgent/screens/Profile.js diff --git a/argon-pro-react-native/screens/Register.js b/MeAgent/screens/Register.js similarity index 100% rename from argon-pro-react-native/screens/Register.js rename to MeAgent/screens/Register.js diff --git a/argon-pro-react-native/screens/Search.js b/MeAgent/screens/Search.js similarity index 100% rename from argon-pro-react-native/screens/Search.js rename to MeAgent/screens/Search.js diff --git a/argon-pro-react-native/screens/Settings.js b/MeAgent/screens/Settings.js similarity index 100% rename from argon-pro-react-native/screens/Settings.js rename to MeAgent/screens/Settings.js diff --git a/argon-pro-react-native/screens/SystemNotifications.js b/MeAgent/screens/SystemNotifications.js similarity index 100% rename from argon-pro-react-native/screens/SystemNotifications.js rename to MeAgent/screens/SystemNotifications.js diff --git a/MeAgent/src/components/AddWatchlistButton.js b/MeAgent/src/components/AddWatchlistButton.js new file mode 100644 index 00000000..67150d2d --- /dev/null +++ b/MeAgent/src/components/AddWatchlistButton.js @@ -0,0 +1,267 @@ +/** + * 通用加自选按钮组件 + * 可用于股票和事件的加自选/关注操作 + */ + +import React, { memo, useState, useCallback } from 'react'; +import { + Box, + HStack, + Text, + Icon, + Pressable, + Spinner, +} from 'native-base'; +import { Ionicons } from '@expo/vector-icons'; +import { useWatchlist } from '../hooks/useWatchlist'; + +/** + * 股票加自选按钮 + * @param {object} props + * @param {string} props.stockCode - 股票代码 + * @param {string} props.stockName - 股票名称 + * @param {string} props.size - 按钮大小 'sm' | 'md' | 'lg' + * @param {string} props.variant - 样式变体 'solid' | 'outline' | 'icon' + * @param {function} props.onSuccess - 成功回调 + * @param {function} props.onError - 失败回调 + */ +export const StockWatchlistButton = memo(({ + stockCode, + stockName, + size = 'sm', + variant = 'outline', + onSuccess, + onError, +}) => { + const [isLoading, setIsLoading] = useState(false); + const { isInWatchlist, toggleStock } = useWatchlist({ autoLoad: false }); + + const inWatchlist = isInWatchlist(stockCode); + + const handlePress = useCallback(async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const result = await toggleStock(stockCode, stockName); + if (result.success) { + onSuccess?.(inWatchlist ? 'removed' : 'added'); + } else { + onError?.(result.error); + } + } catch (error) { + onError?.(error.message); + } finally { + setIsLoading(false); + } + }, [stockCode, stockName, inWatchlist, toggleStock, isLoading, onSuccess, onError]); + + // 尺寸配置 + const sizeConfig = { + sm: { px: 2, py: 1, fontSize: 10, iconSize: 'xs' }, + md: { px: 3, py: 1.5, fontSize: 11, iconSize: 'sm' }, + lg: { px: 4, py: 2, fontSize: 12, iconSize: 'sm' }, + }; + + const config = sizeConfig[size] || sizeConfig.sm; + + // 只显示图标 + if (variant === 'icon') { + return ( + + + {isLoading ? ( + + ) : ( + + )} + + + ); + } + + // 带文字的按钮 + const bgColor = inWatchlist + ? 'rgba(239, 68, 68, 0.15)' + : 'rgba(59, 130, 246, 0.15)'; + const borderColor = inWatchlist + ? 'rgba(239, 68, 68, 0.3)' + : 'rgba(59, 130, 246, 0.3)'; + const textColor = inWatchlist ? '#EF4444' : '#3B82F6'; + + return ( + + {({ pressed }) => ( + + + {isLoading ? ( + + ) : ( + <> + + + {inWatchlist ? '已自选' : '加自选'} + + + )} + + + )} + + ); +}); + +StockWatchlistButton.displayName = 'StockWatchlistButton'; + +/** + * 事件关注按钮 + * @param {object} props + * @param {number|string} props.eventId - 事件ID + * @param {string} props.size - 按钮大小 'sm' | 'md' | 'lg' + * @param {string} props.variant - 样式变体 'solid' | 'outline' | 'icon' + * @param {function} props.onSuccess - 成功回调 + * @param {function} props.onError - 失败回调 + */ +export const EventFollowButton = memo(({ + eventId, + size = 'sm', + variant = 'outline', + onSuccess, + onError, +}) => { + const [isLoading, setIsLoading] = useState(false); + const { isEventFollowed, toggleEventFollow } = useWatchlist({ autoLoad: false }); + + const isFollowed = isEventFollowed(eventId); + + const handlePress = useCallback(async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const result = await toggleEventFollow(eventId); + if (result.success) { + onSuccess?.(isFollowed ? 'unfollowed' : 'followed'); + } else { + onError?.(result.error); + } + } catch (error) { + onError?.(error.message); + } finally { + setIsLoading(false); + } + }, [eventId, isFollowed, toggleEventFollow, isLoading, onSuccess, onError]); + + // 尺寸配置 + const sizeConfig = { + sm: { px: 2, py: 1, fontSize: 10, iconSize: 'xs' }, + md: { px: 3, py: 1.5, fontSize: 11, iconSize: 'sm' }, + lg: { px: 4, py: 2, fontSize: 12, iconSize: 'sm' }, + }; + + const config = sizeConfig[size] || sizeConfig.sm; + + // 只显示图标 + if (variant === 'icon') { + return ( + + + {isLoading ? ( + + ) : ( + + )} + + + ); + } + + // 带文字的按钮 + const bgColor = isFollowed + ? 'rgba(236, 72, 153, 0.15)' + : 'rgba(124, 58, 237, 0.15)'; + const borderColor = isFollowed + ? 'rgba(236, 72, 153, 0.3)' + : 'rgba(124, 58, 237, 0.3)'; + const textColor = isFollowed ? '#EC4899' : '#7C3AED'; + + return ( + + {({ pressed }) => ( + + + {isLoading ? ( + + ) : ( + <> + + + {isFollowed ? '已关注' : '关注'} + + + )} + + + )} + + ); +}); + +EventFollowButton.displayName = 'EventFollowButton'; + +export default { + StockWatchlistButton, + EventFollowButton, +}; diff --git a/MeAgent/src/components/MiniKlineChart.js b/MeAgent/src/components/MiniKlineChart.js new file mode 100644 index 00000000..3934e5c6 --- /dev/null +++ b/MeAgent/src/components/MiniKlineChart.js @@ -0,0 +1,152 @@ +/** + * Mini K线图组件 + * 轻量级蜡烛图,用于自选股列表显示 + */ + +import React, { memo, useMemo } from 'react'; +import { View, StyleSheet } from 'react-native'; +import Svg, { Rect, Line, G } from 'react-native-svg'; + +// 涨跌颜色 +const COLORS = { + up: '#EF4444', // 红色(涨) + down: '#22C55E', // 绿色(跌) + flat: '#6B7280', // 灰色(平) +}; + +/** + * Mini K线图 + * @param {Array} data - K线数据 [{ open, high, low, close }, ...] + * @param {number} width - 图表宽度 + * @param {number} height - 图表高度 + * @param {number} candleCount - 显示的K线数量(默认显示最近20根) + */ +const MiniKlineChart = memo(({ + data = [], + width = 80, + height = 40, + candleCount = 20, +}) => { + // 计算K线数据 + const candles = useMemo(() => { + if (!data || data.length === 0) { + return []; + } + + // 取最近 N 根K线 + const recentData = data.slice(-candleCount); + if (recentData.length === 0) return []; + + // 计算价格范围 + let minPrice = Infinity; + let maxPrice = -Infinity; + + recentData.forEach(d => { + const high = d.high || d.close || 0; + const low = d.low || d.close || 0; + if (high > maxPrice) maxPrice = high; + if (low < minPrice) minPrice = low; + }); + + if (minPrice === Infinity || maxPrice === -Infinity) { + return []; + } + + const priceRange = maxPrice - minPrice || 1; + const padding = priceRange * 0.1; + const effectiveMin = minPrice - padding; + const effectiveMax = maxPrice + padding; + const effectiveRange = effectiveMax - effectiveMin; + + // K线间距和宽度 + const totalCandles = recentData.length; + const gap = 1; // K线间距 + const candleWidth = Math.max(2, (width - gap * (totalCandles - 1)) / totalCandles); + + // 生成K线数据 + return recentData.map((d, index) => { + const open = d.open || d.close || 0; + const close = d.close || 0; + const high = d.high || Math.max(open, close); + const low = d.low || Math.min(open, close); + + // 判断涨跌 + const isUp = close >= open; + const color = close > open ? COLORS.up : close < open ? COLORS.down : COLORS.flat; + + // 计算坐标 + const x = index * (candleWidth + gap); + const bodyTop = height - ((Math.max(open, close) - effectiveMin) / effectiveRange) * height; + const bodyBottom = height - ((Math.min(open, close) - effectiveMin) / effectiveRange) * height; + const bodyHeight = Math.max(1, bodyBottom - bodyTop); // 最小高度1 + + const wickTop = height - ((high - effectiveMin) / effectiveRange) * height; + const wickBottom = height - ((low - effectiveMin) / effectiveRange) * height; + + return { + x, + bodyTop, + bodyHeight, + wickTop, + wickBottom, + candleWidth, + color, + isUp, + }; + }); + }, [data, width, height, candleCount]); + + if (candles.length === 0) { + // 无数据时显示占位 + return ( + + + + ); + } + + return ( + + + {candles.map((candle, index) => ( + + {/* 上下影线 */} + + {/* K线实体 */} + + + ))} + + + ); +}); + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + }, + placeholder: { + flex: 1, + backgroundColor: 'rgba(255,255,255,0.03)', + borderRadius: 4, + }, +}); + +MiniKlineChart.displayName = 'MiniKlineChart'; + +export default MiniKlineChart; diff --git a/MeAgent/src/components/MiniTimelineChart.js b/MeAgent/src/components/MiniTimelineChart.js new file mode 100644 index 00000000..05040a03 --- /dev/null +++ b/MeAgent/src/components/MiniTimelineChart.js @@ -0,0 +1,152 @@ +/** + * Mini 分时图组件 + * 轻量级折线图,用于自选股列表显示 + */ + +import React, { memo, useMemo } from 'react'; +import { View, StyleSheet } from 'react-native'; +import Svg, { Path, Defs, LinearGradient, Stop, Line } from 'react-native-svg'; + +/** + * Mini 分时图 + * @param {Array} data - 分时数据 [{ close, change_pct }, ...] + * @param {number} width - 图表宽度 + * @param {number} height - 图表高度 + * @param {string} color - 线条颜色(可选,根据涨跌自动计算) + * @param {boolean} showBaseline - 是否显示基准线(开盘价位置) + */ +const MiniTimelineChart = memo(({ + data = [], + width = 80, + height = 40, + color, + showBaseline = true, +}) => { + // 计算图表路径 + const { linePath, areaPath, lineColor, baselineY } = useMemo(() => { + if (!data || data.length < 2) { + return { linePath: '', areaPath: '', lineColor: '#6B7280', baselineY: height / 2 }; + } + + // 提取收盘价数据 + const prices = data.map(d => d.close || d.price || 0).filter(p => p > 0); + if (prices.length < 2) { + return { linePath: '', areaPath: '', lineColor: '#6B7280', baselineY: height / 2 }; + } + + // 计算最高最低价(留出边距) + const minPrice = Math.min(...prices); + const maxPrice = Math.max(...prices); + const priceRange = maxPrice - minPrice || 1; + const padding = priceRange * 0.1; // 10% 边距 + + const effectiveMin = minPrice - padding; + const effectiveMax = maxPrice + padding; + const effectiveRange = effectiveMax - effectiveMin; + + // 计算第一个价格(基准线位置) + const firstPrice = prices[0]; + const lastPrice = prices[prices.length - 1]; + + // 根据涨跌确定颜色 + const autoColor = lastPrice >= firstPrice ? '#EF4444' : '#22C55E'; // 红涨绿跌 + const finalColor = color || autoColor; + + // 计算坐标点 + const points = prices.map((price, index) => { + const x = (index / (prices.length - 1)) * width; + const y = height - ((price - effectiveMin) / effectiveRange) * height; + return { x, y }; + }); + + // 生成折线路径 + let pathD = `M ${points[0].x} ${points[0].y}`; + for (let i = 1; i < points.length; i++) { + pathD += ` L ${points[i].x} ${points[i].y}`; + } + + // 生成填充区域路径 + let areaD = pathD; + areaD += ` L ${width} ${height}`; + areaD += ` L 0 ${height}`; + areaD += ' Z'; + + // 计算基准线 Y 坐标 + const baseY = height - ((firstPrice - effectiveMin) / effectiveRange) * height; + + return { + linePath: pathD, + areaPath: areaD, + lineColor: finalColor, + baselineY: baseY, + }; + }, [data, width, height, color]); + + if (!linePath) { + // 无数据时显示占位 + return ( + + + + ); + } + + const gradientId = `gradient-${Math.random().toString(36).substr(2, 9)}`; + + return ( + + + + + + + + + + {/* 填充区域 */} + + + {/* 基准线(虚线) */} + {showBaseline && ( + + )} + + {/* 折线 */} + + + + ); +}); + +const styles = StyleSheet.create({ + container: { + overflow: 'hidden', + }, + placeholder: { + flex: 1, + backgroundColor: 'rgba(255,255,255,0.03)', + borderRadius: 4, + }, +}); + +MiniTimelineChart.displayName = 'MiniTimelineChart'; + +export default MiniTimelineChart; diff --git a/argon-pro-react-native/src/components/PushNotificationHandler.js b/MeAgent/src/components/PushNotificationHandler.js similarity index 100% rename from argon-pro-react-native/src/components/PushNotificationHandler.js rename to MeAgent/src/components/PushNotificationHandler.js diff --git a/argon-pro-react-native/src/components/index.js b/MeAgent/src/components/index.js similarity index 100% rename from argon-pro-react-native/src/components/index.js rename to MeAgent/src/components/index.js diff --git a/MeAgent/src/constants/agentConstants.js b/MeAgent/src/constants/agentConstants.js new file mode 100644 index 00000000..cbfa9ce5 --- /dev/null +++ b/MeAgent/src/constants/agentConstants.js @@ -0,0 +1,262 @@ +/** + * Agent 功能常量定义 + * 消息类型、主题配色、API 配置等 + */ + +// 消息类型枚举 +export const MessageTypes = { + USER: 'user', + AGENT_THINKING: 'agent_thinking', + AGENT_DEEP_THINKING: 'agent_deep_thinking', + AGENT_PLAN: 'agent_plan', + AGENT_EXECUTING: 'agent_executing', + AGENT_RESPONSE: 'agent_response', + ERROR: 'error', +}; + +// SSE 事件类型 +export const SSEEventTypes = { + STATUS: 'status', + THINKING: 'thinking', + THINKING_START: 'thinking_start', + THINKING_CHUNK: 'thinking_chunk', + THINKING_END: 'thinking_end', + REASONING: 'reasoning', + PLAN: 'plan', + PLAN_UPDATE: 'plan_update', // 多轮工具调用时追加新步骤 + STEP_START: 'step_start', + STEP_COMPLETE: 'step_complete', + SUMMARY_CHUNK: 'summary_chunk', + SESSION_TITLE: 'session_title', + DONE: 'done', + ERROR: 'error', +}; + +// Agent 深色主题配色 +export const AgentTheme = { + // 背景色 + background: '#0F172A', + backgroundSecondary: '#1E293B', + cardBg: 'rgba(30, 41, 59, 0.8)', + + // 消息气泡 + userBubbleBg: '#6366F1', // Indigo + userBubbleGradientStart: '#8B5CF6', // Purple + userBubbleGradientEnd: '#EC4899', // Pink + agentBubbleBg: 'rgba(55, 65, 81, 0.8)', + + // 文字颜色 + textPrimary: '#F1F5F9', + textSecondary: '#94A3B8', + textMuted: '#64748B', + + // 强调色 + accent: '#8B5CF6', // Purple + accentSecondary: '#6366F1', // Indigo + success: '#10B981', // Green + error: '#EF4444', // Red + warning: '#F59E0B', // Amber + + // 边框 + border: 'rgba(255, 255, 255, 0.1)', + borderLight: 'rgba(255, 255, 255, 0.05)', + + // 毛玻璃效果 + glassBg: 'rgba(17, 24, 39, 0.8)', + glassBlur: 12, +}; + +// AI 模型配置 +export const AgentModels = { + DEEPMONEY: { + id: 'deepmoney', + name: 'DeepMoney', + description: '金融专业模型,支持深度思考', + contextLength: 84000, + maxTokens: 32768, + supportsDeepThinking: true, + }, + DEEPSEEK: { + id: 'deepseek', + name: 'DeepSeek', + description: '通用对话模型', + contextLength: 32000, + maxTokens: 8192, + supportsDeepThinking: false, + }, + KIMI_K2: { + id: 'kimi-k2', + name: 'Kimi K2', + description: '快速响应模型', + contextLength: 8000, + maxTokens: 8192, + supportsDeepThinking: false, + }, + KIMI_K2_THINKING: { + id: 'kimi-k2-thinking', + name: 'Kimi K2 思考版', + description: '支持深度推理', + contextLength: 32000, + maxTokens: 32768, + supportsDeepThinking: true, + }, +}; + +// 默认模型 +export const DEFAULT_MODEL = AgentModels.DEEPMONEY.id; + +// 工具分类 +export const ToolCategories = { + NEWS: { + id: 'news', + name: '新闻资讯', + icon: 'newspaper', + }, + CONCEPT: { + id: 'concept', + name: '概念板块', + icon: 'grid', + }, + LIMIT_UP: { + id: 'limit_up', + name: '涨停分析', + icon: 'trending-up', + }, + RESEARCH: { + id: 'research', + name: '研报路演', + icon: 'file-text', + }, + STOCK_DATA: { + id: 'stock_data', + name: '股票数据', + icon: 'bar-chart-2', + }, + USER_DATA: { + id: 'user_data', + name: '用户数据', + icon: 'user', + }, + QUANT: { + id: 'quant', + name: '量化工具', + icon: 'cpu', + }, +}; + +// MCP 工具定义 +export const AgentTools = [ + // 新闻资讯 + { id: 'search_china_news', name: '中国新闻搜索', category: 'news', description: '搜索国内财经新闻' }, + { id: 'search_global_news', name: '全球新闻搜索', category: 'news', description: '搜索全球财经新闻' }, + { id: 'search_medical_news', name: '医疗新闻搜索', category: 'news', description: '搜索医疗健康新闻' }, + + // 概念板块 + { id: 'search_concepts', name: '概念搜索', category: 'concept', description: '语义搜索概念板块' }, + { id: 'get_concept_details', name: '概念详情', category: 'concept', description: '获取概念详细信息' }, + { id: 'get_stock_concepts', name: '股票概念', category: 'concept', description: '查询股票所属概念' }, + { id: 'get_concept_statistics', name: '概念统计', category: 'concept', description: '概念涨跌统计' }, + + // 涨停分析 + { id: 'search_limit_up_stocks', name: '涨停搜索', category: 'limit_up', description: '搜索涨停股票' }, + { id: 'get_daily_stock_analysis', name: '涨停日报', category: 'limit_up', description: '每日涨停分析' }, + + // 研报路演 + { id: 'search_research_reports', name: '研报搜索', category: 'research', description: '搜索研究报告' }, + { id: 'search_roadshows', name: '路演搜索', category: 'research', description: '搜索路演活动' }, + + // 股票数据 + { id: 'get_stock_info', name: '股票信息', category: 'stock_data', description: '获取股票基本信息' }, + { id: 'get_financial_indicators', name: '财务指标', category: 'stock_data', description: '获取财务指标数据' }, + { id: 'get_trade_data', name: '交易数据', category: 'stock_data', description: '获取交易行情数据' }, + { id: 'get_balance_sheet', name: '资产负债表', category: 'stock_data', description: '获取资产负债表' }, + { id: 'get_cash_flow', name: '现金流量表', category: 'stock_data', description: '获取现金流量表' }, + { id: 'stock_screener', name: '条件选股', category: 'stock_data', description: '按条件筛选股票' }, + { id: 'compare_stocks', name: '股票对比', category: 'stock_data', description: '多只股票对比分析' }, + + // 用户数据 + { id: 'get_watchlist', name: '自选股', category: 'user_data', description: '获取用户自选股' }, + { id: 'get_followed_events', name: '关注事件', category: 'user_data', description: '获取关注的事件' }, + + // 量化工具 - 经典技术指标 + { id: 'calc_macd', name: 'MACD', category: 'quant', description: '计算MACD指标' }, + { id: 'calc_rsi_kdj', name: 'RSI/KDJ', category: 'quant', description: '计算RSI和KDJ' }, + { id: 'calc_bollinger', name: '布林带', category: 'quant', description: '计算布林带通道' }, + { id: 'calc_atr_stop', name: 'ATR止损', category: 'quant', description: '计算ATR动态止损' }, + + // 量化工具 - 资金与情绪 + { id: 'calc_market_heat', name: '市场热度', category: 'quant', description: '市场情绪热度指标' }, + { id: 'calc_volume_price_divergence', name: '量价背离', category: 'quant', description: '量价背离检测' }, + { id: 'calc_obv', name: 'OBV能量潮', category: 'quant', description: '计算OBV指标' }, + + // 量化工具 - 形态与突破 + { id: 'detect_new_high_breakout', name: '新高突破', category: 'quant', description: '检测新高突破' }, + { id: 'detect_kline_patterns', name: 'K线形态', category: 'quant', description: '识别K线形态' }, + { id: 'detect_gap', name: '跳空缺口', category: 'quant', description: '检测跳空缺口' }, + + // 量化工具 - 风险与估值 + { id: 'calc_max_drawdown', name: '最大回撤', category: 'quant', description: '计算最大回撤' }, + { id: 'calc_pe_percentile', name: 'PE百分位', category: 'quant', description: 'PE历史百分位' }, + { id: 'calc_z_score', name: 'Z-Score', category: 'quant', description: '价格Z-Score' }, + + // 量化工具 - 高级分析 + { id: 'calc_vpoc', name: 'VPOC', category: 'quant', description: '成交量分布中心' }, + { id: 'calc_realized_volatility', name: '已实现波动率', category: 'quant', description: '计算已实现波动率' }, + { id: 'calc_buy_sell_pressure', name: '买卖压力', category: 'quant', description: '买卖压力指标' }, + { id: 'calc_bollinger_squeeze', name: '布林挤压', category: 'quant', description: '布林带挤压信号' }, + { id: 'calc_trend_slope', name: '趋势斜率', category: 'quant', description: '趋势线性回归' }, + { id: 'calc_hurst_exponent', name: 'Hurst指数', category: 'quant', description: '趋势/均值回归判断' }, + { id: 'test_cointegration', name: '协整测试', category: 'quant', description: '配对交易协整性' }, + { id: 'calc_kelly_position', name: '凯利仓位', category: 'quant', description: '最优仓位计算' }, + { id: 'search_similar_kline', name: '相似K线', category: 'quant', description: '历史相似形态' }, + { id: 'decompose_trend_simple', name: '趋势分解', category: 'quant', description: '趋势周期分解' }, + { id: 'calc_price_entropy', name: '价格熵值', category: 'quant', description: '市场混乱度' }, +]; + +// 默认选中的工具(非量化工具) +export const DEFAULT_TOOLS = AgentTools + .filter(tool => tool.category !== 'quant') + .map(tool => tool.id); + +// Agent 名称 +export const AGENT_NAME = '洛希'; + +// 欢迎消息模板 +export const WELCOME_MESSAGE_TEMPLATE = (nickname) => `你好${nickname ? ` ${nickname}` : ''}! + +我是**洛希**,你的 AI 投研助手。 + +「洛希」源自洛希极限(Roche Limit)—— 天体力学中描述引力边界的临界点。正如我帮你在市场的混沌中,找到价值与风险的平衡点。 + +**我能做什么?** +• 📊 深度分析股票基本面和技术面 +• 🔥 追踪市场热点和涨停板块 +• 📈 研究行业趋势和投资机会 +• 📰 汇总最新财经新闻和研报 +• 🧮 量化指标计算和回测分析 + +有什么可以帮你的?`; + +// 快捷问题 +export const QUICK_QUESTIONS = [ + '今天有哪些涨停板块?', + '帮我分析一下贵州茅台', + '最近有什么热门概念?', + '查看我的自选股表现', +]; + +// API 配置 +export const AGENT_API_CONFIG = { + // 生产环境 - 使用 api.valuefrontier.cn + BASE_URL: 'https://api.valuefrontier.cn/mcp', + // 开发环境 - 本地测试时使用 + // BASE_URL: 'http://localhost:8900', + ENDPOINTS: { + CHAT_STREAM: '/agent/chat/stream', + SESSIONS: '/agent/sessions', + HISTORY: '/agent/history', + }, + DEFAULT_TIMEOUT: 60000, // 60 秒 + MAX_HISTORY_LIMIT: 100, + MAX_SESSIONS_LIMIT: 50, +}; diff --git a/argon-pro-react-native/src/constants/index.js b/MeAgent/src/constants/index.js similarity index 100% rename from argon-pro-react-native/src/constants/index.js rename to MeAgent/src/constants/index.js diff --git a/argon-pro-react-native/src/contexts/AuthContext.js b/MeAgent/src/contexts/AuthContext.js similarity index 100% rename from argon-pro-react-native/src/contexts/AuthContext.js rename to MeAgent/src/contexts/AuthContext.js diff --git a/MeAgent/src/hooks/useAgentChat.js b/MeAgent/src/hooks/useAgentChat.js new file mode 100644 index 00000000..efb0681b --- /dev/null +++ b/MeAgent/src/hooks/useAgentChat.js @@ -0,0 +1,418 @@ +/** + * useAgentChat Hook + * 处理 Agent 聊天核心逻辑:发送消息、SSE 流式响应、状态更新 + */ + +import { useCallback, useRef, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useAuth } from '../contexts/AuthContext'; +import { streamAgentChat, buildConversationHistory } from '../services/agentService'; +import { + MessageTypes, + SSEEventTypes, +} from '../constants/agentConstants'; +import { + addMessage, + updateLastMessageByType, + removeMessagesByType, + setIsProcessing, + resetStreamState, + appendThinkingContent, + appendDeepThinkingContent, + updateStreamState, + setPlan, + addStepResult, + setCurrentSession, + selectMessages, + selectIsProcessing, + selectCurrentSessionId, + selectSelectedModel, + selectSelectedTools, + selectStreamState, + fetchSessions, +} from '../store/slices/agentSlice'; + +/** + * Agent 聊天 Hook + * + * @returns {Object} 聊天相关的状态和方法 + */ +export const useAgentChat = () => { + const dispatch = useDispatch(); + const { user, subscription } = useAuth(); + + // 从 Redux 获取状态 + const messages = useSelector(selectMessages); + const isProcessing = useSelector(selectIsProcessing); + const currentSessionId = useSelector(selectCurrentSessionId); + const selectedModel = useSelector(selectSelectedModel); + const selectedTools = useSelector(selectSelectedTools); + const streamState = useSelector(selectStreamState); + + // AbortController 引用,用于取消请求 + const abortControllerRef = useRef(null); + + // 使用 ref 追踪累积的内容(避免闭包问题) + const thinkingContentRef = useRef(''); + const deepThinkingContentRef = useRef(''); + const responseContentRef = useRef(''); + const stepResultsRef = useRef([]); + const messagesRef = useRef(messages); + + // 保持 messagesRef 同步 + messagesRef.current = messages; + + // 组件卸载时清理 + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + /** + * 处理 SSE 事件 + * 使用 ref 追踪累积内容,避免闭包导致的状态过时问题 + */ + const handleSSEEvent = useCallback((event) => { + const { type, data } = event; + + switch (type) { + case SSEEventTypes.STATUS: + // 状态更新(如 "正在思考...") + if (data?.message) { + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_THINKING, + updates: { content: data.message }, + })); + } + break; + + case SSEEventTypes.THINKING: + // 普通思考内容 + if (data?.content) { + thinkingContentRef.current += data.content; + dispatch(appendThinkingContent(data.content)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_THINKING, + updates: { thinkingContent: thinkingContentRef.current }, + })); + } + break; + + case SSEEventTypes.THINKING_START: + // 深度思考开始 + deepThinkingContentRef.current = ''; + dispatch(updateStreamState({ isDeepThinking: true })); + dispatch(addMessage({ + type: MessageTypes.AGENT_DEEP_THINKING, + content: '', + isStreaming: true, + })); + break; + + case SSEEventTypes.THINKING_CHUNK: + // 深度思考内容流 + if (data?.content) { + deepThinkingContentRef.current += data.content; + dispatch(appendDeepThinkingContent(data.content)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_DEEP_THINKING, + updates: { + content: deepThinkingContentRef.current, + }, + })); + } + break; + + case SSEEventTypes.THINKING_END: + // 深度思考结束 + dispatch(updateStreamState({ isDeepThinking: false })); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_DEEP_THINKING, + updates: { isStreaming: false }, + })); + break; + + case SSEEventTypes.REASONING: + // 推理过程 + if (data?.content) { + thinkingContentRef.current += data.content; + dispatch(appendThinkingContent(data.content)); + } + break; + + case SSEEventTypes.PLAN: + // 执行计划 + stepResultsRef.current = []; + dispatch(setPlan(data)); + dispatch(addMessage({ + type: MessageTypes.AGENT_PLAN, + content: data?.goal || '执行计划', + plan: data, + })); + // 添加执行中消息 + dispatch(addMessage({ + type: MessageTypes.AGENT_EXECUTING, + content: '正在执行...', + stepResults: [], + plan: data, // 传递计划数据用于显示步骤列表 + currentStepIndex: null, + currentStep: null, + })); + break; + + case SSEEventTypes.PLAN_UPDATE: + // 多轮工具调用时追加新步骤 + if (data?.new_steps) { + // 更新 AGENT_PLAN 消息的步骤列表 + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_PLAN, + updates: { newSteps: data.new_steps }, + mergePlan: true, + })); + // 更新 AGENT_EXECUTING 消息的步骤列表 + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_EXECUTING, + updates: { newSteps: data.new_steps }, + mergePlan: true, + })); + } + break; + + case SSEEventTypes.STEP_START: + // 步骤开始 - 记录当前步骤索引和信息 + if (data?.tool) { + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_EXECUTING, + updates: { + content: `正在执行: ${data.tool}`, + currentStepIndex: data.step_index, + currentStep: { + tool: data.tool, + arguments: data.arguments || {}, + }, + }, + })); + } + break; + + case SSEEventTypes.STEP_COMPLETE: + // 步骤完成 - 添加结果并清除当前步骤状态 + stepResultsRef.current = [...stepResultsRef.current, data]; + dispatch(addStepResult(data)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_EXECUTING, + updates: { + stepResults: stepResultsRef.current, + currentStepIndex: null, + currentStep: null, + }, + })); + break; + + case SSEEventTypes.SUMMARY_CHUNK: + // 总结流式输出 + if (data?.content) { + // 首次收到内容时创建消息 + if (responseContentRef.current === '') { + responseContentRef.current = data.content; + dispatch(addMessage({ + type: MessageTypes.AGENT_RESPONSE, + content: data.content, + isStreaming: true, + })); + } else { + responseContentRef.current += data.content; + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_RESPONSE, + updates: { + content: responseContentRef.current, + }, + })); + } + } + break; + + case SSEEventTypes.SESSION_TITLE: + // 会话标题 + if (data?.title) { + dispatch(setCurrentSession({ + sessionId: data.session_id || currentSessionId, + title: data.title, + })); + } + break; + + case SSEEventTypes.DONE: + // 完成 - 清理所有中间状态消息,确保动画停止 + dispatch(removeMessagesByType(MessageTypes.AGENT_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_EXECUTING)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_DEEP_THINKING, + updates: { isStreaming: false }, + })); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_RESPONSE, + updates: { isStreaming: false }, + })); + dispatch(setIsProcessing(false)); + // 重新加载会话列表 + if (user?.id) { + dispatch(fetchSessions(user.id)); + } + break; + + case SSEEventTypes.ERROR: + // 错误 - 移除 thinking 消息后再添加错误消息 + dispatch(removeMessagesByType(MessageTypes.AGENT_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_DEEP_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_EXECUTING)); + dispatch(addMessage({ + type: MessageTypes.ERROR, + content: data?.message || '发生错误,请重试', + })); + dispatch(setIsProcessing(false)); + break; + + case 'stream_end': + // 流结束 - 确保所有动画停止 + dispatch(removeMessagesByType(MessageTypes.AGENT_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_EXECUTING)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_DEEP_THINKING, + updates: { isStreaming: false }, + })); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_RESPONSE, + updates: { isStreaming: false }, + })); + dispatch(setIsProcessing(false)); + break; + + case 'aborted': + // 请求被取消 - 移除所有中间状态消息,停止动画 + dispatch(removeMessagesByType(MessageTypes.AGENT_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_DEEP_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_EXECUTING)); + dispatch(updateLastMessageByType({ + type: MessageTypes.AGENT_RESPONSE, + updates: { isStreaming: false }, + })); + dispatch(setIsProcessing(false)); + break; + + default: + console.log('[useAgentChat] 未处理的事件类型:', type, data); + } + }, [dispatch, currentSessionId, user?.id]); + + /** + * 发送消息 + */ + const sendMessage = useCallback(async (inputText) => { + if (!inputText?.trim() || isProcessing) return; + + const messageText = inputText.trim(); + + // 创建并取消之前的请求 + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + abortControllerRef.current = new AbortController(); + + // 重置 ref 追踪值 + thinkingContentRef.current = ''; + deepThinkingContentRef.current = ''; + responseContentRef.current = ''; + stepResultsRef.current = []; + + // 添加用户消息 + dispatch(addMessage({ + type: MessageTypes.USER, + content: messageText, + })); + + // 重置流式状态 + dispatch(resetStreamState()); + dispatch(setIsProcessing(true)); + + // 添加思考中消息 + dispatch(addMessage({ + type: MessageTypes.AGENT_THINKING, + content: '正在思考...', + })); + + try { + // 构建请求参数(使用 ref 获取最新 messages,避免依赖变化) + const params = { + message: messageText, + conversationHistory: buildConversationHistory(messagesRef.current), + userId: user?.id || 'anonymous', + userNickname: user?.nickname || user?.username || '', + userAvatar: user?.avatar || '', + subscriptionType: subscription?.type || 'free', // 从 subscription 对象获取 + sessionId: currentSessionId, + model: selectedModel, + tools: selectedTools, + }; + + // 发起 SSE 请求 + await streamAgentChat(params, handleSSEEvent, abortControllerRef.current.signal); + + } catch (error) { + if (error.name !== 'AbortError') { + console.error('[useAgentChat] 发送消息失败:', error); + // 移除 thinking 相关消息 + dispatch(removeMessagesByType(MessageTypes.AGENT_THINKING)); + dispatch(removeMessagesByType(MessageTypes.AGENT_DEEP_THINKING)); + dispatch(addMessage({ + type: MessageTypes.ERROR, + content: error.message || '网络错误,请检查网络连接后重试', + })); + } + dispatch(setIsProcessing(false)); + } + }, [dispatch, isProcessing, currentSessionId, selectedModel, selectedTools, user?.id, user?.nickname, user?.username, user?.avatar, subscription?.type, handleSSEEvent]); + + /** + * 取消当前请求 + */ + const cancelRequest = useCallback(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + dispatch(setIsProcessing(false)); + } + }, [dispatch]); + + /** + * 重试最后一条消息 + */ + const retryLastMessage = useCallback(() => { + // 找到最后一条用户消息 + const lastUserMessage = [...messages].reverse().find(m => m.type === MessageTypes.USER); + if (lastUserMessage) { + // 移除错误消息和 AI 响应 + // 这里简化处理,实际可能需要更精细的逻辑 + sendMessage(lastUserMessage.content); + } + }, [messages, sendMessage]); + + return { + // 状态 + messages, + isProcessing, + streamState, + currentSessionId, + selectedModel, + selectedTools, + + // 方法 + sendMessage, + cancelRequest, + retryLastMessage, + }; +}; + +export default useAgentChat; diff --git a/MeAgent/src/hooks/useAgentSessions.js b/MeAgent/src/hooks/useAgentSessions.js new file mode 100644 index 00000000..35c99694 --- /dev/null +++ b/MeAgent/src/hooks/useAgentSessions.js @@ -0,0 +1,141 @@ +/** + * useAgentSessions Hook + * 处理 Agent 会话管理:会话列表、切换会话、新建会话 + */ + +import { useCallback, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useAuth } from '../contexts/AuthContext'; +import { + fetchSessions, + fetchSessionHistory, + initNewSession, + clearCurrentSession, + setSessionDrawerOpen, + selectSessions, + selectGroupedSessions, + selectSessionsLoading, + selectCurrentSessionId, + selectCurrentSessionTitle, + selectIsSessionDrawerOpen, +} from '../store/slices/agentSlice'; + +/** + * Agent 会话管理 Hook + * + * @returns {Object} 会话相关的状态和方法 + */ +export const useAgentSessions = () => { + const dispatch = useDispatch(); + const { user } = useAuth(); + + // 从 Redux 获取状态 + const sessions = useSelector(selectSessions); + const groupedSessions = useSelector(selectGroupedSessions); + const isLoading = useSelector(selectSessionsLoading); + const currentSessionId = useSelector(selectCurrentSessionId); + const currentSessionTitle = useSelector(selectCurrentSessionTitle); + const isDrawerOpen = useSelector(selectIsSessionDrawerOpen); + + /** + * 加载会话列表 + */ + const loadSessions = useCallback(() => { + if (user?.id) { + dispatch(fetchSessions(String(user.id))); + } + }, [dispatch, user?.id]); + + /** + * 切换到指定会话 + */ + const switchSession = useCallback((sessionId) => { + if (sessionId && sessionId !== currentSessionId) { + dispatch(fetchSessionHistory(sessionId)); + dispatch(setSessionDrawerOpen(false)); + } + }, [dispatch, currentSessionId]); + + /** + * 新建会话 + */ + const createNewSession = useCallback(() => { + dispatch(initNewSession({ nickname: user?.nickname || user?.username })); + dispatch(setSessionDrawerOpen(false)); + }, [dispatch, user?.nickname, user?.username]); + + /** + * 打开会话抽屉 + */ + const openDrawer = useCallback(() => { + dispatch(setSessionDrawerOpen(true)); + }, [dispatch]); + + /** + * 关闭会话抽屉 + */ + const closeDrawer = useCallback(() => { + dispatch(setSessionDrawerOpen(false)); + }, [dispatch]); + + /** + * 切换会话抽屉 + */ + const toggleDrawer = useCallback(() => { + dispatch(setSessionDrawerOpen(!isDrawerOpen)); + }, [dispatch, isDrawerOpen]); + + /** + * 搜索会话 + */ + const searchSessions = useCallback((keyword) => { + if (!keyword?.trim()) { + return groupedSessions; + } + + const lowerKeyword = keyword.toLowerCase(); + + // 过滤匹配的会话 + const filteredGroups = groupedSessions + .map(group => ({ + ...group, + sessions: group.sessions.filter(session => + (session.title || '').toLowerCase().includes(lowerKeyword) || + (session.session_id || '').toLowerCase().includes(lowerKeyword) + ), + })) + .filter(group => group.sessions.length > 0); + + return filteredGroups; + }, [groupedSessions]); + + /** + * 初始化:加载会话列表 + */ + useEffect(() => { + if (user?.id) { + loadSessions(); + } + }, [user?.id, loadSessions]); + + return { + // 状态 + sessions, + groupedSessions, + isLoading, + currentSessionId, + currentSessionTitle, + isDrawerOpen, + + // 方法 + loadSessions, + switchSession, + createNewSession, + openDrawer, + closeDrawer, + toggleDrawer, + searchSessions, + }; +}; + +export default useAgentSessions; diff --git a/MeAgent/src/hooks/useCommunitySocket.js b/MeAgent/src/hooks/useCommunitySocket.js new file mode 100644 index 00000000..b9214555 --- /dev/null +++ b/MeAgent/src/hooks/useCommunitySocket.js @@ -0,0 +1,301 @@ +/** + * 社区 WebSocket Hook + * 管理社区实时通信连接 + * 与 Web 端保持一致的事件名称和行为 + */ + +import { useEffect, useRef, useCallback, useState, useContext } from 'react'; +import { useDispatch } from 'react-redux'; +import { API_BASE_URL } from '../services/api'; +import AuthContext from '../contexts/AuthContext'; +import { + addMessage, + removeMessage, + updateMessageReactions, + addTypingUser, + removeTypingUser, + incrementUnreadCount, +} from '../store/slices/communitySlice'; + +// 服务器事件类型 - 与 Web 端保持一致(大写) +export const SERVER_EVENTS = { + MESSAGE_CREATE: 'MESSAGE_CREATE', + MESSAGE_UPDATE: 'MESSAGE_UPDATE', + MESSAGE_DELETE: 'MESSAGE_DELETE', + REACTION_ADD: 'REACTION_ADD', + REACTION_REMOVE: 'REACTION_REMOVE', + TYPING_START: 'TYPING_START', + TYPING_STOP: 'TYPING_STOP', + MEMBER_JOIN: 'MEMBER_JOIN', + MEMBER_LEAVE: 'MEMBER_LEAVE', + MEMBER_ONLINE: 'MEMBER_ONLINE', + MEMBER_OFFLINE: 'MEMBER_OFFLINE', + CHANNEL_UPDATE: 'CHANNEL_UPDATE', +}; + +// 客户端事件类型 - 与 Web 端保持一致(大写) +export const CLIENT_EVENTS = { + SUBSCRIBE_CHANNEL: 'SUBSCRIBE_CHANNEL', + UNSUBSCRIBE_CHANNEL: 'UNSUBSCRIBE_CHANNEL', + SEND_MESSAGE: 'SEND_MESSAGE', + START_TYPING: 'START_TYPING', + STOP_TYPING: 'STOP_TYPING', +}; + +/** + * 社区 WebSocket Hook + * @returns {object} WebSocket 操作方法 + */ +export const useCommunitySocket = () => { + const dispatch = useDispatch(); + // 安全获取 auth context,避免在 AuthProvider 外部使用时报错 + const authContext = useContext(AuthContext); + const isLoggedIn = authContext?.isLoggedIn ?? false; + const socketRef = useRef(null); + const [isConnected, setIsConnected] = useState(false); + const [connectionError, setConnectionError] = useState(null); + const subscribedChannelsRef = useRef(new Set()); + const typingTimeoutRef = useRef(null); + const eventHandlersRef = useRef(new Map()); + + // 初始化 Socket 连接 - 只有登录后才连接 + useEffect(() => { + if (!isLoggedIn) { + // 未登录时断开连接 + if (socketRef.current) { + socketRef.current.disconnect(); + socketRef.current = null; + setIsConnected(false); + } + return; + } + + // 动态导入 socket.io-client + const initSocket = async () => { + try { + const { io } = await import('socket.io-client'); + const socketUrl = API_BASE_URL; + + const socket = io(`${socketUrl}/community`, { + transports: ['websocket', 'polling'], + autoConnect: true, + reconnection: true, + reconnectionAttempts: 3, + reconnectionDelay: 2000, + reconnectionDelayMax: 10000, + timeout: 10000, + }); + + // 连接成功 + socket.on('connect', () => { + console.log('[CommunitySocket] 连接成功'); + setIsConnected(true); + setConnectionError(null); + + // 重新订阅之前的频道 + subscribedChannelsRef.current.forEach((channelId) => { + socket.emit(CLIENT_EVENTS.SUBSCRIBE_CHANNEL, { channelId }); + }); + }); + + // 连接断开 + socket.on('disconnect', (reason) => { + console.log('[CommunitySocket] 连接断开:', reason); + setIsConnected(false); + }); + + // 连接错误 - 只是警告,不阻断页面 + socket.on('connect_error', (error) => { + console.warn('[CommunitySocket] WebSocket 暂不可用,使用离线模式'); + setConnectionError('WebSocket 暂不可用'); + }); + + // 监听服务器事件 + const serverEvents = [ + SERVER_EVENTS.MESSAGE_CREATE, + SERVER_EVENTS.MESSAGE_UPDATE, + SERVER_EVENTS.MESSAGE_DELETE, + SERVER_EVENTS.REACTION_ADD, + SERVER_EVENTS.REACTION_REMOVE, + SERVER_EVENTS.TYPING_START, + SERVER_EVENTS.TYPING_STOP, + SERVER_EVENTS.MEMBER_JOIN, + SERVER_EVENTS.MEMBER_LEAVE, + SERVER_EVENTS.MEMBER_ONLINE, + SERVER_EVENTS.MEMBER_OFFLINE, + SERVER_EVENTS.CHANNEL_UPDATE, + ]; + + serverEvents.forEach((event) => { + socket.on(event, (data) => { + // 调用自定义事件处理器 + const listeners = eventHandlersRef.current.get(event); + if (listeners) { + listeners.forEach((callback) => callback(data)); + } + + // 内置的 Redux dispatch 处理 + switch (event) { + case SERVER_EVENTS.MESSAGE_CREATE: + dispatch( + addMessage({ + channelId: data.channelId, + message: data.message, + }) + ); + break; + case SERVER_EVENTS.MESSAGE_DELETE: + dispatch( + removeMessage({ + channelId: data.channelId, + messageId: data.messageId, + }) + ); + break; + case SERVER_EVENTS.REACTION_ADD: + case SERVER_EVENTS.REACTION_REMOVE: + dispatch( + updateMessageReactions({ + channelId: data.channelId, + messageId: data.messageId, + reactions: data.reactions, + }) + ); + break; + case SERVER_EVENTS.TYPING_START: + dispatch( + addTypingUser({ + channelId: data.channelId, + user: { userId: data.userId, username: data.userName || data.username }, + }) + ); + break; + case SERVER_EVENTS.TYPING_STOP: + dispatch( + removeTypingUser({ + channelId: data.channelId, + userId: data.userId, + }) + ); + break; + default: + break; + } + }); + }); + + socketRef.current = socket; + } catch (error) { + console.warn('[CommunitySocket] Socket.IO 初始化失败,使用离线模式'); + setConnectionError('初始化失败'); + } + }; + + initSocket(); + + return () => { + if (socketRef.current) { + socketRef.current.disconnect(); + socketRef.current = null; + } + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + }; + }, [isLoggedIn, dispatch]); + + // 订阅频道 + const subscribe = useCallback((channelId) => { + if (!channelId) return; + + subscribedChannelsRef.current.add(channelId); + + if (socketRef.current?.connected) { + socketRef.current.emit(CLIENT_EVENTS.SUBSCRIBE_CHANNEL, { channelId }); + console.log('[CommunitySocket] 已订阅频道:', channelId); + } + }, []); + + // 取消订阅频道 + const unsubscribe = useCallback((channelId) => { + if (!channelId) return; + + subscribedChannelsRef.current.delete(channelId); + + if (socketRef.current?.connected) { + socketRef.current.emit(CLIENT_EVENTS.UNSUBSCRIBE_CHANNEL, { channelId }); + console.log('[CommunitySocket] 已取消订阅频道:', channelId); + } + }, []); + + // 发送消息 + const sendMessage = useCallback((channelId, content, options = {}) => { + if (!socketRef.current?.connected) { + return false; + } + + socketRef.current.emit(CLIENT_EVENTS.SEND_MESSAGE, { + channelId, + content, + ...options, + }); + + return true; + }, []); + + // 开始输入 + const startTyping = useCallback((channelId) => { + if (!socketRef.current?.connected || !channelId) return; + + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + + socketRef.current.emit(CLIENT_EVENTS.START_TYPING, { channelId }); + + typingTimeoutRef.current = setTimeout(() => { + stopTyping(channelId); + }, 3000); + }, []); + + // 停止输入 + const stopTyping = useCallback((channelId) => { + if (!socketRef.current?.connected || !channelId) return; + + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + typingTimeoutRef.current = null; + } + + socketRef.current.emit(CLIENT_EVENTS.STOP_TYPING, { channelId }); + }, []); + + // 注册事件监听 + const onMessage = useCallback((event, callback) => { + if (!eventHandlersRef.current.has(event)) { + eventHandlersRef.current.set(event, new Set()); + } + eventHandlersRef.current.get(event).add(callback); + }, []); + + // 移除事件监听 + const offMessage = useCallback((event, callback) => { + if (eventHandlersRef.current.has(event)) { + eventHandlersRef.current.get(event).delete(callback); + } + }, []); + + return { + isConnected, + connectionError, + subscribe, + unsubscribe, + sendMessage, + startTyping, + stopTyping, + onMessage, + offMessage, + }; +}; + +export default useCommunitySocket; diff --git a/MeAgent/src/hooks/useOrderBook.js b/MeAgent/src/hooks/useOrderBook.js new file mode 100644 index 00000000..22112d9e --- /dev/null +++ b/MeAgent/src/hooks/useOrderBook.js @@ -0,0 +1,70 @@ +/** + * 五档盘口历史数据 Hook + * + * 注意:实时五档数据通过 WebSocket 推送获取(见 useSingleQuote Hook) + * 本 Hook 仅用于查询历史五档数据,用于分时图回放、历史分析等场景 + * + * 数据源:222.128.1.157 ClickHouse (szse_stock_realtime / sse_stock_realtime) + */ + +import { useState, useCallback } from 'react'; +import { stockDetailService } from '../services/stockService'; + +/** + * 五档盘口历史数据 Hook + * @param {string} stockCode - 股票代码 + * @returns {object} { fetchHistory, historyData, loading, error } + */ +export const useOrderBook = (stockCode) => { + const [historyData, setHistoryData] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + /** + * 获取指定日期的五档历史数据 + * @param {object} options - { date, startTime, endTime, limit } + */ + const fetchHistory = useCallback(async (options = {}) => { + if (!stockCode) return; + + try { + setLoading(true); + setError(null); + + const result = await stockDetailService.getOrderBookHistory(stockCode, options); + + if (result.success && result.data) { + setHistoryData(result.data); + console.log('[useOrderBook] 历史数据加载完成:', result.total, '条'); + return result; + } else { + setError(result.error || '获取历史数据失败'); + return result; + } + } catch (err) { + console.error('[useOrderBook] 获取历史数据失败:', err); + setError(err.message); + return { success: false, error: err.message }; + } finally { + setLoading(false); + } + }, [stockCode]); + + /** + * 清空历史数据 + */ + const clearHistory = useCallback(() => { + setHistoryData([]); + setError(null); + }, []); + + return { + fetchHistory, + clearHistory, + historyData, + loading, + error, + }; +}; + +export default useOrderBook; diff --git a/argon-pro-react-native/src/hooks/usePushNotifications.js b/MeAgent/src/hooks/usePushNotifications.js similarity index 100% rename from argon-pro-react-native/src/hooks/usePushNotifications.js rename to MeAgent/src/hooks/usePushNotifications.js diff --git a/MeAgent/src/hooks/useRealtimeQuote.js b/MeAgent/src/hooks/useRealtimeQuote.js new file mode 100644 index 00000000..8a37fe14 --- /dev/null +++ b/MeAgent/src/hooks/useRealtimeQuote.js @@ -0,0 +1,270 @@ +/** + * 实时行情 Hook + * 通过 WebSocket 订阅股票实时行情 + */ + +import { useState, useEffect, useCallback, useRef } from 'react'; +import { useDispatch } from 'react-redux'; +import { realtimeQuoteService } from '../services/websocketService'; +import { updateRealtimeQuotes } from '../store/slices/watchlistSlice'; + +/** + * 实时行情 Hook + * @param {object} options - 配置选项 + * @param {string[]} options.codes - 要订阅的股票代码列表 + * @param {boolean} options.autoConnect - 是否自动连接(默认 true) + * @param {boolean} options.updateRedux - 是否更新 Redux 状态(默认 true) + */ +export const useRealtimeQuote = (options = {}) => { + const { + codes = [], + autoConnect = true, + updateRedux = true, + } = options; + + const dispatch = useDispatch(); + const [quotes, setQuotes] = useState({}); + const [connectionState, setConnectionState] = useState('disconnected'); + const [isConnected, setIsConnected] = useState(false); + const subscribedCodesRef = useRef(new Set()); + + // 处理行情更新 + const handleQuoteUpdate = useCallback((newQuotes) => { + setQuotes(prev => ({ + ...prev, + ...newQuotes, + })); + + // 同步到 Redux + if (updateRedux) { + dispatch(updateRealtimeQuotes(newQuotes)); + } + }, [dispatch, updateRedux]); + + // 处理连接状态变化 + const handleStateChange = useCallback((state) => { + setConnectionState(state); + setIsConnected(state === 'connected'); + }, []); + + // 连接 WebSocket + const connect = useCallback(() => { + realtimeQuoteService.connect(); + }, []); + + // 断开连接 + const disconnect = useCallback(() => { + realtimeQuoteService.disconnect(); + }, []); + + // 订阅股票 + const subscribe = useCallback((stockCodes) => { + if (!Array.isArray(stockCodes)) { + stockCodes = [stockCodes]; + } + + const newCodes = stockCodes.filter(code => !subscribedCodesRef.current.has(code)); + if (newCodes.length > 0) { + newCodes.forEach(code => subscribedCodesRef.current.add(code)); + realtimeQuoteService.subscribe(newCodes); + } + }, []); + + // 取消订阅 + const unsubscribe = useCallback((stockCodes) => { + if (!Array.isArray(stockCodes)) { + stockCodes = [stockCodes]; + } + + const existingCodes = stockCodes.filter(code => subscribedCodesRef.current.has(code)); + if (existingCodes.length > 0) { + existingCodes.forEach(code => subscribedCodesRef.current.delete(code)); + realtimeQuoteService.unsubscribe(existingCodes); + } + }, []); + + // 获取单个股票行情 + const getQuote = useCallback((code) => { + return quotes[code] || null; + }, [quotes]); + + // 初始化连接和事件监听 + useEffect(() => { + // 添加事件处理器 + const removeQuoteHandler = realtimeQuoteService.addQuoteHandler(handleQuoteUpdate); + const removeStateHandler = realtimeQuoteService.addStateHandler(handleStateChange); + + // 自动连接 + if (autoConnect) { + connect(); + } + + // 获取初始状态 + setConnectionState(realtimeQuoteService.getConnectionState()); + setIsConnected(realtimeQuoteService.isConnected()); + + return () => { + removeQuoteHandler(); + removeStateHandler(); + }; + }, [autoConnect, connect, handleQuoteUpdate, handleStateChange]); + + // 处理股票代码变化 + useEffect(() => { + if (codes.length > 0) { + // 计算需要新订阅的 + const codesToSubscribe = codes.filter(code => !subscribedCodesRef.current.has(code)); + + // 计算需要取消订阅的 + const currentCodes = new Set(codes); + const codesToUnsubscribe = Array.from(subscribedCodesRef.current).filter( + code => !currentCodes.has(code) + ); + + if (codesToUnsubscribe.length > 0) { + unsubscribe(codesToUnsubscribe); + } + + if (codesToSubscribe.length > 0) { + subscribe(codesToSubscribe); + } + } + }, [codes, subscribe, unsubscribe]); + + // 组件卸载时取消所有订阅 + useEffect(() => { + return () => { + const allCodes = Array.from(subscribedCodesRef.current); + if (allCodes.length > 0) { + realtimeQuoteService.unsubscribe(allCodes); + subscribedCodesRef.current.clear(); + } + }; + }, []); + + return { + // 状态 + quotes, + connectionState, + isConnected, + + // 操作 + connect, + disconnect, + subscribe, + unsubscribe, + getQuote, + }; +}; + +/** + * 单个股票实时行情 Hook + * @param {string} code - 股票代码 + */ +export const useSingleQuote = (code) => { + const { quotes, isConnected, subscribe, unsubscribe } = useRealtimeQuote({ + codes: code ? [code] : [], + updateRedux: false, + }); + + return { + quote: code ? quotes[code] : null, + isConnected, + }; +}; + +/** + * 自选股列表实时行情 Hook + * 自动从 Redux 获取自选股列表并订阅 + */ +export const useWatchlistRealtimeQuotes = () => { + const dispatch = useDispatch(); + const [connectionState, setConnectionState] = useState('disconnected'); + const subscribedCodesRef = useRef(new Set()); + + // 处理行情更新 + const handleQuoteUpdate = useCallback((newQuotes) => { + dispatch(updateRealtimeQuotes(newQuotes)); + }, [dispatch]); + + // 处理连接状态变化 + const handleStateChange = useCallback((state) => { + setConnectionState(state); + }, []); + + // 订阅股票列表 + const subscribeStocks = useCallback((stocks) => { + if (!Array.isArray(stocks) || stocks.length === 0) return; + + const codes = stocks.map(s => s.stock_code).filter(Boolean); + const newCodes = codes.filter(code => !subscribedCodesRef.current.has(code)); + + if (newCodes.length > 0) { + newCodes.forEach(code => subscribedCodesRef.current.add(code)); + realtimeQuoteService.subscribe(newCodes); + } + }, []); + + // 更新订阅列表 + const updateSubscription = useCallback((stocks) => { + if (!Array.isArray(stocks)) return; + + const newCodes = new Set(stocks.map(s => s.stock_code).filter(Boolean)); + + // 取消不再需要的订阅 + const codesToUnsubscribe = []; + subscribedCodesRef.current.forEach(code => { + if (!newCodes.has(code)) { + codesToUnsubscribe.push(code); + } + }); + + if (codesToUnsubscribe.length > 0) { + codesToUnsubscribe.forEach(code => subscribedCodesRef.current.delete(code)); + realtimeQuoteService.unsubscribe(codesToUnsubscribe); + } + + // 添加新订阅 + const codesToSubscribe = []; + newCodes.forEach(code => { + if (!subscribedCodesRef.current.has(code)) { + codesToSubscribe.push(code); + subscribedCodesRef.current.add(code); + } + }); + + if (codesToSubscribe.length > 0) { + realtimeQuoteService.subscribe(codesToSubscribe); + } + }, []); + + // 初始化 + useEffect(() => { + const removeQuoteHandler = realtimeQuoteService.addQuoteHandler(handleQuoteUpdate); + const removeStateHandler = realtimeQuoteService.addStateHandler(handleStateChange); + + realtimeQuoteService.connect(); + setConnectionState(realtimeQuoteService.getConnectionState()); + + return () => { + removeQuoteHandler(); + removeStateHandler(); + + // 取消所有订阅 + const allCodes = Array.from(subscribedCodesRef.current); + if (allCodes.length > 0) { + realtimeQuoteService.unsubscribe(allCodes); + subscribedCodesRef.current.clear(); + } + }; + }, [handleQuoteUpdate, handleStateChange]); + + return { + connectionState, + isConnected: connectionState === 'connected', + subscribeStocks, + updateSubscription, + }; +}; + +export default useRealtimeQuote; diff --git a/MeAgent/src/hooks/useWatchlist.js b/MeAgent/src/hooks/useWatchlist.js new file mode 100644 index 00000000..9f56b6a2 --- /dev/null +++ b/MeAgent/src/hooks/useWatchlist.js @@ -0,0 +1,262 @@ +/** + * 自选股管理 Hook + * 提供自选股和自选事件的操作方法 + */ + +import { useCallback, useEffect, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { + fetchWatchlist, + fetchWatchlistRealtime, + fetchFollowingEvents, + fetchMiniCharts, + addToWatchlist, + removeFromWatchlist, + toggleEventFollow, + selectWatchlistStocks, + selectWatchlistEvents, + selectRealtimeQuotes, + selectMiniCharts, + selectWatchlistLoading, + selectWatchlistError, + selectIsInWatchlist, + selectIsEventFollowed, + optimisticAddStock, + optimisticRemoveStock, +} from '../store/slices/watchlistSlice'; + +/** + * 自选股管理 Hook + * @param {object} options - 配置选项 + * @param {boolean} options.autoLoad - 是否自动加载数据 + * @param {number} options.refreshInterval - 实时行情刷新间隔(毫秒),0 表示不自动刷新 + */ +export const useWatchlist = (options = {}) => { + const { + autoLoad = true, + refreshInterval = 30000, // 默认 30 秒刷新一次 + } = options; + + const dispatch = useDispatch(); + const refreshTimerRef = useRef(null); + + // Redux 状态 + const stocks = useSelector(selectWatchlistStocks); + const events = useSelector(selectWatchlistEvents); + const realtimeQuotes = useSelector(selectRealtimeQuotes); + const miniCharts = useSelector(selectMiniCharts); + const loading = useSelector(selectWatchlistLoading); + const error = useSelector(selectWatchlistError); + + // 加载自选股列表 + const loadWatchlist = useCallback(() => { + return dispatch(fetchWatchlist()); + }, [dispatch]); + + // 加载自选股实时行情 + const loadRealtimeQuotes = useCallback(() => { + return dispatch(fetchWatchlistRealtime()); + }, [dispatch]); + + // 加载关注的事件 + const loadFollowingEvents = useCallback(() => { + return dispatch(fetchFollowingEvents()); + }, [dispatch]); + + // 加载 mini 图表数据 + const loadMiniCharts = useCallback(() => { + return dispatch(fetchMiniCharts()); + }, [dispatch]); + + // 添加股票到自选 + const handleAddStock = useCallback(async (stockCode, stockName = '') => { + // 乐观更新 + dispatch(optimisticAddStock({ stockCode, stockName })); + + try { + const result = await dispatch(addToWatchlist({ stockCode, stockName })).unwrap(); + return { success: true, data: result }; + } catch (error) { + // 回滚乐观更新 + dispatch(optimisticRemoveStock(stockCode)); + return { success: false, error }; + } + }, [dispatch]); + + // 从自选移除股票 + const handleRemoveStock = useCallback(async (stockCode) => { + // 先保存原始数据用于回滚 + const originalStocks = stocks; + + // 乐观更新 + dispatch(optimisticRemoveStock(stockCode)); + + try { + await dispatch(removeFromWatchlist(stockCode)).unwrap(); + return { success: true }; + } catch (error) { + // 回滚 - 重新加载列表 + dispatch(fetchWatchlist()); + return { success: false, error }; + } + }, [dispatch, stocks]); + + // 切换股票自选状态 + const toggleStock = useCallback(async (stockCode, stockName = '') => { + const isInList = isInWatchlist(stockCode); + + if (isInList) { + return handleRemoveStock(stockCode); + } else { + return handleAddStock(stockCode, stockName); + } + }, [handleAddStock, handleRemoveStock]); + + // 切换事件关注状态 + const handleToggleEventFollow = useCallback(async (eventId) => { + try { + const result = await dispatch(toggleEventFollow(eventId)).unwrap(); + return { success: true, data: result }; + } catch (error) { + return { success: false, error }; + } + }, [dispatch]); + + // 检查股票是否在自选中 + const isInWatchlist = useCallback((stockCode) => { + const normalizeCode = (code) => String(code).match(/\d{6}/)?.[0] || code; + const normalizedCode = normalizeCode(stockCode); + return stocks.some(item => normalizeCode(item.stock_code) === normalizedCode); + }, [stocks]); + + // 检查事件是否被关注 + const isEventFollowed = useCallback((eventId) => { + return events.some(event => event.id === eventId); + }, [events]); + + // 获取股票的实时行情 + const getStockQuote = useCallback((stockCode) => { + const normalizeCode = (code) => String(code).match(/\d{6}/)?.[0] || code; + const normalizedCode = normalizeCode(stockCode); + + // 尝试多种格式匹配 + return realtimeQuotes[stockCode] || + realtimeQuotes[normalizedCode] || + realtimeQuotes[`${normalizedCode}.SH`] || + realtimeQuotes[`${normalizedCode}.SZ`] || + null; + }, [realtimeQuotes]); + + // 获取股票的 mini 图表数据 + const getStockCharts = useCallback((stockCode) => { + const normalizeCode = (code) => String(code).match(/\d{6}/)?.[0] || code; + const normalizedCode = normalizeCode(stockCode); + + return miniCharts[stockCode] || + miniCharts[normalizedCode] || + miniCharts[`${normalizedCode}.SH`] || + miniCharts[`${normalizedCode}.SZ`] || + null; + }, [miniCharts]); + + // 合并股票列表、实时行情和图表数据 + const stocksWithQuotes = stocks.map(stock => { + const quote = getStockQuote(stock.stock_code); + const charts = getStockCharts(stock.stock_code); + return { + ...stock, + quote, + charts, + }; + }); + + // 刷新全部数据 + const refreshAll = useCallback(async () => { + await Promise.all([ + loadWatchlist(), + loadRealtimeQuotes(), + loadFollowingEvents(), + loadMiniCharts(), + ]); + }, [loadWatchlist, loadRealtimeQuotes, loadFollowingEvents, loadMiniCharts]); + + // 启动定时刷新 + const startRefreshTimer = useCallback(() => { + if (refreshInterval > 0 && !refreshTimerRef.current) { + refreshTimerRef.current = setInterval(() => { + loadRealtimeQuotes(); + }, refreshInterval); + } + }, [refreshInterval, loadRealtimeQuotes]); + + // 停止定时刷新 + const stopRefreshTimer = useCallback(() => { + if (refreshTimerRef.current) { + clearInterval(refreshTimerRef.current); + refreshTimerRef.current = null; + } + }, []); + + // 自动加载数据 + useEffect(() => { + if (autoLoad) { + loadWatchlist(); + loadRealtimeQuotes(); + loadFollowingEvents(); + loadMiniCharts(); + } + }, [autoLoad, loadWatchlist, loadRealtimeQuotes, loadFollowingEvents, loadMiniCharts]); + + // 启动/停止定时刷新 + useEffect(() => { + if (autoLoad && refreshInterval > 0) { + startRefreshTimer(); + } + + return () => { + stopRefreshTimer(); + }; + }, [autoLoad, refreshInterval, startRefreshTimer, stopRefreshTimer]); + + return { + // 数据 + stocks, + events, + realtimeQuotes, + miniCharts, + stocksWithQuotes, + + // 加载状态 + loading, + error, + isLoadingStocks: loading.stocks, + isLoadingEvents: loading.events, + isLoadingRealtime: loading.realtime, + isLoadingMiniCharts: loading.miniCharts, + + // 操作方法 + loadWatchlist, + loadRealtimeQuotes, + loadFollowingEvents, + loadMiniCharts, + refreshAll, + + // 自选股操作 + addStock: handleAddStock, + removeStock: handleRemoveStock, + toggleStock, + isInWatchlist, + getStockQuote, + getStockCharts, + + // 事件操作 + toggleEventFollow: handleToggleEventFollow, + isEventFollowed, + + // 定时刷新控制 + startRefreshTimer, + stopRefreshTimer, + }; +}; + +export default useWatchlist; diff --git a/MeAgent/src/screens/Agent/AgentChatScreen.js b/MeAgent/src/screens/Agent/AgentChatScreen.js new file mode 100644 index 00000000..19210204 --- /dev/null +++ b/MeAgent/src/screens/Agent/AgentChatScreen.js @@ -0,0 +1,330 @@ +/** + * AgentChatScreen + * AI 投研助手主聊天屏幕 + */ + +import React, { useEffect, useRef, useCallback, useState } from 'react'; +import { + View, + Text, + StyleSheet, + FlatList, + KeyboardAvoidingView, + Platform, + TouchableOpacity, + StatusBar, + SafeAreaView, + Image, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { useDispatch, useSelector } from 'react-redux'; + +import { useAuth } from '../../contexts/AuthContext'; +import { useAgentChat } from '../../hooks/useAgentChat'; +import { useAgentSessions } from '../../hooks/useAgentSessions'; +import { + MessageBubble, + ChatInput, + WelcomeScreen, + SessionDrawer, +} from './components'; +import { + AgentTheme, + MessageTypes, + AGENT_NAME, +} from '../../constants/agentConstants'; + +// 洛希头像 +const LuoxiAvatar = require('../../../assets/imgs/luoxi.jpg'); +import { + initNewSession, + selectMessages, +} from '../../store/slices/agentSlice'; + +/** + * 头部导航栏 + */ +const Header = ({ title, onMenuPress, onNewPress }) => ( + + + + + + + + + + + {title || AGENT_NAME} + + + + + + + + +); + +/** + * AgentChatScreen 主组件 + */ +const AgentChatScreen = ({ navigation }) => { + const dispatch = useDispatch(); + const { user, isLoggedIn } = useAuth(); + const flatListRef = useRef(null); + + // 智能滚动:跟踪用户是否在底部附近 + const isNearBottomRef = useRef(true); + const contentHeightRef = useRef(0); + const scrollOffsetRef = useRef(0); + const layoutHeightRef = useRef(0); + + // 使用自定义 Hooks + const { + messages, + isProcessing, + sendMessage, + cancelRequest, + } = useAgentChat(); + + const { + groupedSessions, + isLoading: sessionsLoading, + currentSessionId, + currentSessionTitle, + isDrawerOpen, + openDrawer, + closeDrawer, + switchSession, + createNewSession, + } = useAgentSessions(); + + /** + * 初始化:如果没有消息,显示欢迎界面 + */ + useEffect(() => { + if (messages.length === 0) { + dispatch(initNewSession({ nickname: user?.nickname || user?.username })); + } + }, []); + + /** + * 智能滚动:只有当用户在底部附近时才自动滚动 + */ + useEffect(() => { + if (messages.length > 0 && flatListRef.current && isNearBottomRef.current) { + setTimeout(() => { + flatListRef.current?.scrollToEnd({ animated: true }); + }, 100); + } + }, [messages]); + + /** + * 处理滚动事件,检测用户是否在底部附近 + */ + const handleScroll = useCallback((event) => { + const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent; + scrollOffsetRef.current = contentOffset.y; + contentHeightRef.current = contentSize.height; + layoutHeightRef.current = layoutMeasurement.height; + + // 判断是否在底部附近(距离底部 100 像素以内) + const distanceFromBottom = contentSize.height - layoutMeasurement.height - contentOffset.y; + isNearBottomRef.current = distanceFromBottom < 100; + }, []); + + /** + * 处理内容大小变化 + */ + const handleContentSizeChange = useCallback((width, height) => { + // 只有当用户在底部附近时才自动滚动 + if (isNearBottomRef.current && flatListRef.current) { + flatListRef.current.scrollToEnd({ animated: true }); + } + }, []); + + /** + * 处理快捷问题点击 + */ + const handleQuickQuestion = useCallback((question) => { + // 发送消息时重置滚动状态,确保自动滚动到底部 + isNearBottomRef.current = true; + sendMessage(question); + }, [sendMessage]); + + /** + * 处理发送消息(包装 sendMessage 以重置滚动状态) + */ + const handleSendMessage = useCallback((text) => { + isNearBottomRef.current = true; + sendMessage(text); + }, [sendMessage]); + + /** + * 处理新建对话 + */ + const handleNewSession = useCallback(() => { + isNearBottomRef.current = true; + createNewSession(); + }, [createNewSession]); + + /** + * 渲染消息项 + */ + const renderMessageItem = useCallback(({ item }) => ( + + + + ), []); + + /** + * 消息列表 Key 提取器 + */ + const keyExtractor = useCallback((item) => item.id?.toString() || Math.random().toString(), []); + + // 判断是否显示欢迎屏幕(只有一条欢迎消息时) + const showWelcome = messages.length <= 1 && + messages[0]?.type === MessageTypes.AGENT_RESPONSE; + + return ( + + + + {/* 头部 */} +
+ + {/* 主体内容 */} + + {showWelcome ? ( + /* 欢迎屏幕 */ + + ) : ( + /* 消息列表 */ + + )} + + {/* 输入框 */} + + + + {/* 会话抽屉 */} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: AgentTheme.background, + }, + + // 头部 + header: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 12, + borderBottomWidth: 1, + borderBottomColor: AgentTheme.border, + }, + headerButton: { + width: 40, + height: 40, + borderRadius: 20, + backgroundColor: AgentTheme.cardBg, + justifyContent: 'center', + alignItems: 'center', + }, + headerButtonText: { + fontSize: 20, + color: AgentTheme.textPrimary, + }, + headerCenter: { + flexDirection: 'row', + alignItems: 'center', + flex: 1, + justifyContent: 'center', + marginHorizontal: 12, + }, + headerAvatarContainer: { + width: 36, + height: 36, + borderRadius: 18, + overflow: 'hidden', + marginRight: 10, + borderWidth: 2, + borderColor: 'rgba(139, 92, 246, 0.5)', + }, + headerAvatar: { + width: '100%', + height: '100%', + resizeMode: 'cover', + }, + headerTitle: { + fontSize: 17, + fontWeight: '600', + color: AgentTheme.textPrimary, + maxWidth: 200, + flexShrink: 1, + }, + + // 内容区域 + content: { + flex: 1, + }, + + // 消息列表 + messageList: { + flex: 1, + }, + messageListContent: { + paddingVertical: 16, + }, +}); + +export default AgentChatScreen; diff --git a/MeAgent/src/screens/Agent/components/ChatInput.js b/MeAgent/src/screens/Agent/components/ChatInput.js new file mode 100644 index 00000000..93f19407 --- /dev/null +++ b/MeAgent/src/screens/Agent/components/ChatInput.js @@ -0,0 +1,191 @@ +/** + * ChatInput 组件 + * 聊天输入框,支持发送消息 + */ + +import React, { memo, useState, useRef } from 'react'; +import { + View, + TextInput, + TouchableOpacity, + StyleSheet, + Keyboard, + Platform, + ActivityIndicator, +} from 'react-native'; +import { BlurView } from 'expo-blur'; +import { AgentTheme } from '../../../constants/agentConstants'; + +/** + * 发送按钮图标 + */ +const SendIcon = ({ color = '#FFFFFF', size = 20 }) => ( + + + +); + +/** + * 停止按钮图标 + */ +const StopIcon = ({ color = '#FFFFFF', size = 16 }) => ( + +); + +/** + * ChatInput 组件 + */ +const ChatInput = ({ + onSend, + onCancel, + isProcessing = false, + placeholder = '输入消息...', + disabled = false, +}) => { + const [inputText, setInputText] = useState(''); + const inputRef = useRef(null); + + /** + * 处理发送 + */ + const handleSend = () => { + if (inputText.trim() && !isProcessing && !disabled) { + onSend(inputText); + setInputText(''); + Keyboard.dismiss(); + } + }; + + /** + * 处理取消 + */ + const handleCancel = () => { + if (isProcessing && onCancel) { + onCancel(); + } + }; + + /** + * 处理按键提交 + */ + const handleSubmitEditing = () => { + handleSend(); + }; + + const canSend = inputText.trim().length > 0 && !isProcessing && !disabled; + + return ( + + + + + + + {isProcessing ? ( + + + + ) : ( + + + + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + borderTopWidth: 1, + borderTopColor: AgentTheme.border, + paddingHorizontal: 16, + paddingVertical: 12, + paddingBottom: Platform.OS === 'ios' ? 28 : 12, + }, + inputWrapper: { + flexDirection: 'row', + alignItems: 'flex-end', + }, + inputContainer: { + flex: 1, + backgroundColor: AgentTheme.backgroundSecondary, + borderRadius: 20, + borderWidth: 1, + borderColor: AgentTheme.border, + paddingHorizontal: 16, + paddingVertical: Platform.OS === 'ios' ? 10 : 6, + marginRight: 10, + minHeight: 40, + maxHeight: 120, + }, + input: { + color: AgentTheme.textPrimary, + fontSize: 15, + lineHeight: 20, + maxHeight: 100, + }, + sendButton: { + width: 40, + height: 40, + borderRadius: 20, + justifyContent: 'center', + alignItems: 'center', + }, + sendButtonActive: { + backgroundColor: AgentTheme.accent, + }, + sendButtonDisabled: { + backgroundColor: AgentTheme.textMuted, + opacity: 0.5, + }, + cancelButton: { + backgroundColor: AgentTheme.error, + }, +}); + +export default memo(ChatInput); diff --git a/MeAgent/src/screens/Agent/components/MarkdownRenderer.js b/MeAgent/src/screens/Agent/components/MarkdownRenderer.js new file mode 100644 index 00000000..af9ed2c4 --- /dev/null +++ b/MeAgent/src/screens/Agent/components/MarkdownRenderer.js @@ -0,0 +1,1071 @@ +/** + * MarkdownRenderer 组件 + * 支持 Markdown 渲染和 ECharts 图表渲染 + */ + +import React, { memo, useMemo, useState, useCallback, useContext, createContext } from 'react'; +import { + View, + Text, + StyleSheet, + ScrollView, + TouchableOpacity, + Dimensions, + ActivityIndicator, +} from 'react-native'; +import { WebView } from 'react-native-webview'; +import { useNavigation } from '@react-navigation/native'; +import { AgentTheme } from '../../../constants/agentConstants'; + +// 股票代码正则:匹配 000001.SZ, 600000.SH, 或纯六位数字 +const STOCK_CODE_REGEX = /\b(\d{6})(?:\.(SZ|SH|sz|sh|BJ|bj))?\b/g; + +const { width: SCREEN_WIDTH } = Dimensions.get('window'); +const CHART_WIDTH = SCREEN_WIDTH - 48; // 减去边距 +const CHART_HEIGHT = 300; + +/** + * 简单的 Markdown 解析器 + * 支持:标题、粗体、斜体、代码块、链接、列表、表格、Mermaid 图 + */ +const parseMarkdown = (text) => { + if (!text) return []; + + const parts = []; + let remaining = text; + + // 匹配所有特殊代码块:echarts, mermaid + const codeBlockRegex = /```(echarts|mermaid)\s*\n?([\s\S]*?)```/g; + let lastIndex = 0; + let match; + + while ((match = codeBlockRegex.exec(text)) !== null) { + // 添加代码块前的文本 + if (match.index > lastIndex) { + const textBefore = text.substring(lastIndex, match.index); + parts.push({ type: 'markdown', content: textBefore }); + } + + const blockType = match[1]; // echarts 或 mermaid + let content = match[2].trim(); + + // 处理转义字符 + if (content.includes('\\n')) { + content = content.replace(/\\n/g, '\n'); + } + + if (blockType === 'echarts') { + parts.push({ type: 'chart', content }); + } else if (blockType === 'mermaid') { + parts.push({ type: 'mermaid', content }); + } + + lastIndex = match.index + match[0].length; + } + + // 添加剩余文本 + if (lastIndex < text.length) { + parts.push({ type: 'markdown', content: text.substring(lastIndex) }); + } + + // 如果没有匹配到任何内容,返回原始文本 + if (parts.length === 0) { + parts.push({ type: 'markdown', content: text }); + } + + return parts; +}; + +/** + * 解析表格 + */ +const parseTable = (lines, startIndex) => { + const rows = []; + let i = startIndex; + + while (i < lines.length && lines[i].trim().startsWith('|')) { + const line = lines[i].trim(); + // 跳过分隔行 |---|---| + if (!/^\|[\s-:|]+\|$/.test(line)) { + const cells = line + .split('|') + .filter((cell, idx, arr) => idx > 0 && idx < arr.length - 1) + .map(cell => cell.trim()); + if (cells.length > 0) { + rows.push(cells); + } + } + i++; + } + + return { rows, endIndex: i }; +}; + +/** + * 表格组件 + */ +const TableView = memo(({ rows }) => { + if (!rows || rows.length === 0) return null; + + const header = rows[0]; + const body = rows.slice(1); + + return ( + + + + {/* 表头 */} + + {header.map((cell, idx) => ( + + {cell} + + ))} + + {/* 表体 */} + {body.map((row, rowIdx) => ( + + {row.map((cell, cellIdx) => ( + + {cell} + + ))} + + ))} + + + + ); +}); + +/** + * Markdown 文本渲染器(支持表格) + */ +const MarkdownText = memo(({ content, navigation }) => { + const lines = content.split('\n'); + const elements = []; + let i = 0; + + while (i < lines.length) { + const line = lines[i]; + + // 检测表格开始 + if (line.trim().startsWith('|') && line.trim().endsWith('|')) { + const { rows, endIndex } = parseTable(lines, i); + if (rows.length > 1) { + elements.push(); + i = endIndex; + continue; + } + } + + elements.push(); + i++; + } + + return ( + + {elements} + + ); +}); + +/** + * 单行 Markdown 渲染 + */ +const MarkdownLine = memo(({ line, navigation }) => { + // 空行 + if (!line.trim()) { + return ; + } + + // 标题 (# ## ### ####) + const headerMatch = line.match(/^(#{1,4})\s+(.*)$/); + if (headerMatch) { + const level = headerMatch[1].length; + const text = headerMatch[2]; + const headerStyles = [ + styles.h1, + styles.h2, + styles.h3, + styles.h4, + ]; + return ( + + {renderInlineStyles(text, navigation)} + + ); + } + + // 无序列表 (- 或 *) + const listMatch = line.match(/^[\s]*[-*]\s+(.*)$/); + if (listMatch) { + return ( + + + {renderInlineStyles(listMatch[1], navigation)} + + ); + } + + // 有序列表 (1. 2. 3.) + const orderedListMatch = line.match(/^[\s]*(\d+)\.\s+(.*)$/); + if (orderedListMatch) { + return ( + + {orderedListMatch[1]}. + {renderInlineStyles(orderedListMatch[2], navigation)} + + ); + } + + // 代码块 (```) + if (line.startsWith('```')) { + return null; // 代码块在外层处理 + } + + // 引用 (>) + const quoteMatch = line.match(/^>\s*(.*)$/); + if (quoteMatch) { + return ( + + {renderInlineStyles(quoteMatch[1], navigation)} + + ); + } + + // 分隔线 (--- or ***) + if (/^[-*]{3,}$/.test(line.trim())) { + return ; + } + + // 普通段落 + return ( + {renderInlineStyles(line, navigation)} + ); +}); + +/** + * 股票代码可点击组件 + */ +const StockCodeLink = memo(({ code, exchange, navigation }) => { + // 确定交易所后缀 + let exchangeSuffix = exchange?.toUpperCase() || ''; + if (!exchangeSuffix) { + // 根据代码前缀推断交易所 + if (code.startsWith('6')) { + exchangeSuffix = 'SH'; + } else if (code.startsWith('0') || code.startsWith('3')) { + exchangeSuffix = 'SZ'; + } else if (code.startsWith('4') || code.startsWith('8')) { + exchangeSuffix = 'BJ'; + } + } + + const handlePress = useCallback(() => { + const fullStockCode = exchangeSuffix ? `${code}.${exchangeSuffix}` : code; + navigation?.navigate('StockDetail', { + stockCode: fullStockCode, + stockName: fullStockCode, // 名称可以在详情页获取 + }); + }, [code, exchangeSuffix, navigation]); + + const displayCode = exchangeSuffix ? `${code}.${exchangeSuffix}` : code; + + return ( + + {displayCode} + + ); +}); + +/** + * 渲染行内样式(粗体、斜体、代码、链接、股票代码) + */ +const renderInlineStyles = (text, navigation) => { + if (!text) return null; + + // 先进行基本文本处理 + const processedText = text + .replace(/\*\*(.+?)\*\*/g, (_, m) => m) // 移除粗体标记(简化) + .replace(/\*(.+?)\*/g, (_, m) => m) // 移除斜体标记 + .replace(/`([^`]+)`/g, (_, m) => `[${m}]`); // 标记代码 + + // 检测股票代码并分割文本 + const stockMatches = []; + let match; + const regex = new RegExp(STOCK_CODE_REGEX.source, 'g'); + while ((match = regex.exec(processedText)) !== null) { + stockMatches.push({ + code: match[1], + exchange: match[2], + index: match.index, + length: match[0].length, + fullMatch: match[0], + }); + } + + // 如果没有股票代码,返回普通文本 + if (stockMatches.length === 0) { + return {processedText}; + } + + // 有股票代码,需要分割并渲染 + const elements = []; + let lastIndex = 0; + let key = 0; + + stockMatches.forEach((stockMatch) => { + // 添加股票代码前的文本 + if (stockMatch.index > lastIndex) { + elements.push( + + {processedText.substring(lastIndex, stockMatch.index)} + + ); + } + + // 添加可点击的股票代码 + elements.push( + + ); + + lastIndex = stockMatch.index + stockMatch.length; + }); + + // 添加最后剩余的文本 + if (lastIndex < processedText.length) { + elements.push( + + {processedText.substring(lastIndex)} + + ); + } + + return {elements}; +}; + +/** + * 生成 ECharts HTML + */ +const generateEChartsHTML = (option, width, height) => { + // 深色主题配色 + const darkTheme = { + backgroundColor: 'transparent', + textStyle: { + color: '#E2E8F0' + }, + title: { + textStyle: { + color: '#F8FAFC' + }, + subtextStyle: { + color: '#94A3B8' + } + }, + legend: { + textStyle: { + color: '#CBD5E1' + } + }, + xAxis: { + axisLine: { + lineStyle: { + color: '#475569' + } + }, + axisLabel: { + color: '#94A3B8' + }, + splitLine: { + lineStyle: { + color: '#334155' + } + } + }, + yAxis: { + axisLine: { + lineStyle: { + color: '#475569' + } + }, + axisLabel: { + color: '#94A3B8' + }, + splitLine: { + lineStyle: { + color: '#334155' + } + } + } + }; + + return ` + + + + + + + + + +
+ + + + `; +}; + +/** + * ECharts 图表组件 + * 使用 WebView 渲染真实的 ECharts 图表 + */ +const EChartsView = memo(({ config }) => { + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [showData, setShowData] = useState(false); + + // 解析图表配置 + const { chartOption, chartTitle, chartData } = useMemo(() => { + try { + const parsed = JSON.parse(config); + return { + chartOption: config, + chartTitle: parsed?.title?.text || '图表', + chartData: parsed, + }; + } catch (e) { + return { + chartOption: null, + chartTitle: '图表', + chartData: null, + error: e.message, + }; + } + }, [config]); + + const handleMessage = useCallback((event) => { + try { + const data = JSON.parse(event.nativeEvent.data); + if (data.type === 'loaded') { + setIsLoading(false); + } else if (data.type === 'error') { + setError(data.message); + setIsLoading(false); + } + } catch (e) { + // 忽略解析错误 + } + }, []); + + // 如果配置解析失败,显示错误 + if (!chartOption) { + return ( + + + 📊 + 图表解析失败 + + 无法解析图表配置 + + ); + } + + const html = generateEChartsHTML(chartOption, CHART_WIDTH, CHART_HEIGHT); + + return ( + + + 📊 + {chartTitle} + setShowData(!showData)} + style={styles.chartToggle} + > + + {showData ? '显示图表' : '查看数据'} + + + + + {error ? ( + + 图表渲染失败: {error} + + ) : showData ? ( + // 显示数据视图 + + + {chartData?.series?.map((series, idx) => ( + + {series.name || `系列 ${idx + 1}`} + {series.data?.slice(0, 10).map((item, i) => ( + + {typeof item === 'object' ? `${item.name}: ${item.value}` : item} + + ))} + {series.data?.length > 10 && ( + ...还有 {series.data.length - 10} 项 + )} + + ))} + + + ) : ( + // 显示图表 + + {isLoading && ( + + + 加载图表中... + + )} + + + )} + + ); +}); + +/** + * 生成 Mermaid HTML + */ +const generateMermaidHTML = (code, width) => { + return ` + + + + + + + + + +
+
+${code}
+        
+
+ + + + `; +}; + +/** + * Mermaid 图表组件 + */ +const MermaidView = memo(({ code }) => { + const [height, setHeight] = useState(200); + const [isLoading, setIsLoading] = useState(true); + + const handleMessage = useCallback((event) => { + try { + const data = JSON.parse(event.nativeEvent.data); + if (data.type === 'loaded') { + setIsLoading(false); + if (data.height) { + setHeight(Math.min(data.height, 400)); // 最大高度 400 + } + } + } catch (e) { + // 忽略解析错误 + } + }, []); + + const html = generateMermaidHTML(code, CHART_WIDTH); + + return ( + + + 📊 + 流程图 + + + {isLoading && ( + + + 加载中... + + )} + + + + ); +}); + +/** + * MarkdownRenderer 主组件 + */ +const MarkdownRenderer = ({ content }) => { + const navigation = useNavigation(); + const parts = useMemo(() => parseMarkdown(content), [content]); + + return ( + + {parts.map((part, index) => { + if (part.type === 'chart') { + return ; + } + if (part.type === 'mermaid') { + return ; + } + return ; + })} + + ); +}; + +const styles = StyleSheet.create({ + container: { + // 不使用 flex: 1,让内容自适应 + }, + markdownContainer: { + // 不使用 flex: 1,让内容自适应 + }, + + // 文本样式 + text: { + color: AgentTheme.textPrimary, + fontSize: 15, + lineHeight: 22, + }, + paragraph: { + color: AgentTheme.textPrimary, + fontSize: 15, + lineHeight: 22, + marginBottom: 8, + }, + emptyLine: { + height: 8, + }, + + // 标题 + headerBase: { + color: AgentTheme.textPrimary, + fontWeight: '600', + marginTop: 16, + marginBottom: 8, + }, + h1: { + fontSize: 22, + }, + h2: { + fontSize: 20, + }, + h3: { + fontSize: 18, + }, + h4: { + fontSize: 16, + }, + + // 列表 + listItem: { + flexDirection: 'row', + alignItems: 'flex-start', + marginBottom: 4, + }, + listBullet: { + color: AgentTheme.accent, + fontSize: 14, + marginRight: 8, + marginTop: 2, + }, + listNumber: { + color: AgentTheme.accent, + fontSize: 14, + marginRight: 8, + minWidth: 20, + }, + listText: { + color: AgentTheme.textPrimary, + fontSize: 15, + lineHeight: 22, + flex: 1, + }, + + // 引用 + quote: { + borderLeftWidth: 3, + borderLeftColor: AgentTheme.accent, + paddingLeft: 12, + marginVertical: 8, + backgroundColor: 'rgba(139, 92, 246, 0.1)', + paddingVertical: 8, + borderRadius: 4, + }, + quoteText: { + color: AgentTheme.textSecondary, + fontSize: 14, + fontStyle: 'italic', + }, + + // 行内样式 + bold: { + fontWeight: '700', + }, + italic: { + fontStyle: 'italic', + }, + inlineCode: { + fontFamily: 'monospace', + backgroundColor: 'rgba(139, 92, 246, 0.2)', + paddingHorizontal: 4, + borderRadius: 3, + fontSize: 13, + color: AgentTheme.accent, + }, + + // 分隔线 + hr: { + height: 1, + backgroundColor: AgentTheme.border, + marginVertical: 16, + }, + + // 股票代码链接 + stockCodeLink: { + color: '#F59E0B', + fontWeight: '600', + textDecorationLine: 'underline', + backgroundColor: 'rgba(245, 158, 11, 0.1)', + paddingHorizontal: 4, + borderRadius: 4, + }, + + // 图表容器 + chartContainer: { + backgroundColor: 'rgba(99, 102, 241, 0.1)', + borderRadius: 12, + borderWidth: 1, + borderColor: 'rgba(99, 102, 241, 0.3)', + padding: 12, + marginVertical: 12, + overflow: 'hidden', + }, + chartHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + }, + chartIcon: { + fontSize: 18, + marginRight: 8, + }, + chartTitle: { + color: AgentTheme.accentSecondary, + fontSize: 15, + fontWeight: '600', + flex: 1, + }, + chartToggle: { + paddingHorizontal: 12, + paddingVertical: 4, + backgroundColor: 'rgba(99, 102, 241, 0.2)', + borderRadius: 12, + }, + chartToggleText: { + color: AgentTheme.accentSecondary, + fontSize: 12, + }, + + // WebView 图表 + chartWebViewContainer: { + height: CHART_HEIGHT, + borderRadius: 8, + overflow: 'hidden', + backgroundColor: 'rgba(15, 23, 42, 0.5)', + }, + chartWebView: { + flex: 1, + backgroundColor: 'transparent', + }, + chartLoading: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', + zIndex: 1, + }, + chartLoadingText: { + color: AgentTheme.textSecondary, + fontSize: 12, + marginTop: 8, + }, + + // 图表错误 + chartErrorContainer: { + padding: 20, + alignItems: 'center', + }, + chartError: { + color: '#F87171', + fontSize: 13, + textAlign: 'center', + }, + + // 数据展示 + chartDataScroll: { + maxHeight: 200, + marginTop: 8, + }, + chartDataContainer: { + flexDirection: 'row', + }, + seriesContainer: { + marginRight: 16, + minWidth: 120, + }, + seriesName: { + color: AgentTheme.textPrimary, + fontSize: 13, + fontWeight: '600', + marginBottom: 4, + }, + dataItem: { + color: AgentTheme.textSecondary, + fontSize: 12, + marginVertical: 1, + }, + dataMore: { + color: AgentTheme.textMuted, + fontSize: 11, + marginTop: 4, + }, + + // 表格样式 + tableContainer: { + marginVertical: 12, + borderRadius: 8, + overflow: 'hidden', + borderWidth: 1, + borderColor: AgentTheme.border, + }, + tableHeader: { + flexDirection: 'row', + backgroundColor: 'rgba(99, 102, 241, 0.15)', + }, + tableHeaderCell: { + backgroundColor: 'rgba(99, 102, 241, 0.15)', + }, + tableHeaderText: { + color: AgentTheme.textPrimary, + fontSize: 13, + fontWeight: '600', + }, + tableRow: { + flexDirection: 'row', + borderTopWidth: 1, + borderTopColor: AgentTheme.border, + }, + tableRowEven: { + backgroundColor: 'rgba(30, 41, 59, 0.5)', + }, + tableCell: { + minWidth: 100, + paddingHorizontal: 12, + paddingVertical: 10, + }, + tableCellText: { + color: AgentTheme.textSecondary, + fontSize: 13, + lineHeight: 18, + }, + + // Mermaid 样式 + mermaidContainer: { + backgroundColor: 'rgba(139, 92, 246, 0.1)', + borderRadius: 12, + borderWidth: 1, + borderColor: 'rgba(139, 92, 246, 0.3)', + padding: 12, + marginVertical: 12, + overflow: 'hidden', + }, + mermaidHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 8, + }, + mermaidIcon: { + fontSize: 18, + marginRight: 8, + }, + mermaidTitle: { + color: AgentTheme.accent, + fontSize: 15, + fontWeight: '600', + }, + mermaidWebViewContainer: { + borderRadius: 8, + overflow: 'hidden', + backgroundColor: 'rgba(15, 23, 42, 0.5)', + }, + mermaidWebView: { + flex: 1, + backgroundColor: 'transparent', + }, +}); + +export default memo(MarkdownRenderer); diff --git a/MeAgent/src/screens/Agent/components/MessageBubble.js b/MeAgent/src/screens/Agent/components/MessageBubble.js new file mode 100644 index 00000000..7259a44f --- /dev/null +++ b/MeAgent/src/screens/Agent/components/MessageBubble.js @@ -0,0 +1,910 @@ +/** + * MessageBubble 组件 + * 消息气泡,支持不同消息类型的渲染 + * 支持 Markdown 渲染和图表展示 + */ + +import React, { memo, useState, useEffect, useRef } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + ActivityIndicator, + Animated, + LayoutAnimation, + Platform, + UIManager, + ScrollView, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { MessageTypes, AgentTheme } from '../../../constants/agentConstants'; +import MarkdownRenderer from './MarkdownRenderer'; + +// 启用 LayoutAnimation (Android) +if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) { + UIManager.setLayoutAnimationEnabledExperimental(true); +} + +/** + * 用户消息气泡 + */ +const UserBubble = memo(({ content }) => ( + + + {content} + + +)); + +/** + * 思考中气泡 - 带动画效果 + */ +const ThinkingBubble = memo(({ content }) => { + const pulseAnim = useRef(new Animated.Value(1)).current; + + useEffect(() => { + const pulse = Animated.loop( + Animated.sequence([ + Animated.timing(pulseAnim, { + toValue: 0.6, + duration: 800, + useNativeDriver: true, + }), + Animated.timing(pulseAnim, { + toValue: 1, + duration: 800, + useNativeDriver: true, + }), + ]) + ); + pulse.start(); + return () => pulse.stop(); + }, []); + + return ( + + + + + {content || '正在思考...'} + + + + + + + ); +}); + +/** + * 思考中动态点 + */ +const ThinkingDots = memo(() => { + const [dots, setDots] = useState(''); + + useEffect(() => { + const interval = setInterval(() => { + setDots(prev => prev.length >= 3 ? '' : prev + '.'); + }, 500); + return () => clearInterval(interval); + }, []); + + return {dots}; +}); + +/** + * 深度思考气泡(可折叠)- 类似 Gemini 风格 + */ +const DeepThinkingBubble = memo(({ content, isStreaming }) => { + const [isExpanded, setIsExpanded] = useState(true); + const charCount = content?.length || 0; + const rotateAnim = useRef(new Animated.Value(0)).current; + const sparkleAnim = useRef(new Animated.Value(0)).current; + + // 折叠动画 + useEffect(() => { + Animated.timing(rotateAnim, { + toValue: isExpanded ? 1 : 0, + duration: 200, + useNativeDriver: true, + }).start(); + }, [isExpanded]); + + // 思考中闪烁动画 + useEffect(() => { + if (isStreaming) { + const sparkle = Animated.loop( + Animated.sequence([ + Animated.timing(sparkleAnim, { + toValue: 1, + duration: 1000, + useNativeDriver: true, + }), + Animated.timing(sparkleAnim, { + toValue: 0, + duration: 1000, + useNativeDriver: true, + }), + ]) + ); + sparkle.start(); + return () => sparkle.stop(); + } + }, [isStreaming]); + + const handleToggle = () => { + if (!isStreaming) { + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); + setIsExpanded(!isExpanded); + } + }; + + const rotation = rotateAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '180deg'], + }); + + return ( + + + {/* 标题栏 */} + + + {isStreaming ? ( + + + + ) : ( + 🧠 + )} + + {isStreaming ? '深度思考中...' : '深度思考'} + + {isStreaming && ( + + )} + + + + {charCount > 0 && ( + + {charCount} 字 + + )} + {!isStreaming && ( + + + + )} + + + + {/* 思考内容 */} + {(isExpanded || isStreaming) && content && ( + + {content} + {isStreaming && ( + + + + )} + + )} + + + ); +}); + +/** + * 执行计划气泡 + */ +const PlanBubble = memo(({ content, plan }) => ( + + + + 📋 + 执行计划 + + {plan?.goal || content} + {plan?.steps && ( + + {plan.steps.map((step, index) => ( + + + {index + 1} + + + {step.tool} + {step.reason && ( + {step.reason} + )} + + + ))} + + )} + + +)); + +/** + * 执行中气泡 - 简洁的步骤进度显示 + */ +const ExecutingBubble = memo(({ content, plan, stepResults = [], currentStep, currentStepIndex }) => { + const spinAnim = useRef(new Animated.Value(0)).current; + + useEffect(() => { + const spin = Animated.loop( + Animated.timing(spinAnim, { + toValue: 1, + duration: 1500, + useNativeDriver: true, + }) + ); + spin.start(); + return () => spin.stop(); + }, []); + + const rotation = spinAnim.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }); + + // 构建完整的步骤列表 + const planSteps = plan?.steps || []; + let displaySteps = [...planSteps]; + + // 如果 planSteps 为空但有 stepResults,从 stepResults 构建 + if (displaySteps.length === 0 && stepResults.length > 0) { + displaySteps = stepResults.map(s => ({ tool: s.tool, arguments: {} })); + } + + // 如果当前有正在执行的步骤,且不在显示列表中,添加它 + if (currentStep && currentStepIndex !== null && currentStepIndex !== undefined) { + while (displaySteps.length <= currentStepIndex) { + if (displaySteps.length === currentStepIndex) { + displaySteps.push({ tool: currentStep.tool, arguments: currentStep.arguments || {} }); + } else { + const stepResult = stepResults[displaySteps.length]; + displaySteps.push({ tool: stepResult?.tool || '未知步骤', arguments: {} }); + } + } + } + + const totalSteps = Math.max(displaySteps.length, stepResults.length + (currentStepIndex !== null ? 1 : 0)); + const completedCount = stepResults.length; + + return ( + + + {/* 头部 */} + + + ⚙️ + + 正在执行 + + + {completedCount} / {totalSteps} + + + + + {/* 进度条 */} + {totalSteps > 0 && ( + + + + )} + + {/* 步骤列表 - 简洁版 */} + {displaySteps.length > 0 && ( + + {displaySteps.map((step, index) => { + const result = stepResults[index]; + const isCompleted = index < stepResults.length; + const isRunning = currentStepIndex !== null && currentStepIndex !== undefined + ? index === currentStepIndex + : index === stepResults.length && currentStep; + + return ( + + {/* 状态指示器 */} + + {isCompleted ? ( + + {result?.status === 'success' ? '✓' : '✗'} + + ) : isRunning ? ( + + + + ) : ( + {index + 1} + )} + + + {/* 工具名称 */} + + {step.tool} + + + {/* 执行时间 */} + {result?.execution_time && ( + + {result.execution_time.toFixed(1)}s + + )} + + ); + })} + + )} + + + ); +}); + +/** + * 过滤 AI 响应中的特殊标签 + * @param {string} content - 原始内容 + * @returns {string} - 过滤后的内容 + */ +const filterResponseContent = (content) => { + if (!content) return ''; + + // 过滤 minimax:tool_call 标签及其内容 + // 格式: ... + let filtered = content.replace(/[\s\S]*?<\/minimax:tool_call>/g, ''); + + // 过滤可能的其他特殊标签 + // 格式: ... + filtered = filtered.replace(/[\s\S]*?<\/tool_call>/g, ''); + + // 清理多余的空行 + filtered = filtered.replace(/\n{3,}/g, '\n\n'); + + return filtered.trim(); +}; + +/** + * AI 响应气泡 - 支持 Markdown 和图表 + */ +const ResponseBubble = memo(({ content, isStreaming }) => { + const cursorAnim = useRef(new Animated.Value(0)).current; + + // 过滤特殊标签 + const filteredContent = filterResponseContent(content); + + useEffect(() => { + if (isStreaming) { + const blink = Animated.loop( + Animated.sequence([ + Animated.timing(cursorAnim, { + toValue: 1, + duration: 500, + useNativeDriver: true, + }), + Animated.timing(cursorAnim, { + toValue: 0, + duration: 500, + useNativeDriver: true, + }), + ]) + ); + blink.start(); + return () => blink.stop(); + } + }, [isStreaming]); + + return ( + + + + {isStreaming && ( + + + + )} + + + ); +}); + +/** + * 错误气泡 + */ +const ErrorBubble = memo(({ content }) => ( + + + ⚠️ + + 出错了 + {content} + + + +)); + +/** + * MessageBubble 主组件 + */ +const MessageBubble = ({ message }) => { + const { type, content, isStreaming, plan, stepResults, currentStep, currentStepIndex, thinkingContent } = message; + + switch (type) { + case MessageTypes.USER: + return ; + + case MessageTypes.AGENT_THINKING: + return ; + + case MessageTypes.AGENT_DEEP_THINKING: + return ; + + case MessageTypes.AGENT_PLAN: + return ; + + case MessageTypes.AGENT_EXECUTING: + return ( + + ); + + case MessageTypes.AGENT_RESPONSE: + return ; + + case MessageTypes.ERROR: + return ; + + default: + return ; + } +}; + +const styles = StyleSheet.create({ + // 用户消息 + userBubbleContainer: { + width: '100%', + alignItems: 'flex-end', + marginVertical: 6, + paddingHorizontal: 16, + }, + userBubble: { + maxWidth: '80%', + paddingHorizontal: 16, + paddingVertical: 12, + borderRadius: 18, + borderBottomRightRadius: 4, + }, + userText: { + color: '#FFFFFF', + fontSize: 15, + lineHeight: 22, + }, + + // AI 消息容器 + agentBubbleContainer: { + width: '100%', + alignItems: 'flex-start', + marginVertical: 6, + paddingHorizontal: 16, + }, + + // 思考中 + thinkingBubble: { + maxWidth: '70%', + backgroundColor: AgentTheme.cardBg, + paddingHorizontal: 16, + paddingVertical: 12, + borderRadius: 16, + borderWidth: 1, + borderColor: AgentTheme.border, + }, + thinkingRow: { + flexDirection: 'row', + alignItems: 'center', + }, + thinkingText: { + color: AgentTheme.textSecondary, + fontSize: 14, + marginLeft: 10, + }, + thinkingDots: { + marginTop: 4, + marginLeft: 34, + }, + dots: { + color: AgentTheme.accent, + fontSize: 18, + letterSpacing: 2, + }, + + // 深度思考 + deepThinkingBubble: { + maxWidth: '92%', + minWidth: '60%', + backgroundColor: 'rgba(139, 92, 246, 0.08)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(139, 92, 246, 0.25)', + overflow: 'hidden', + }, + deepThinkingBubbleActive: { + borderColor: 'rgba(139, 92, 246, 0.5)', + shadowColor: '#8B5CF6', + shadowOffset: { width: 0, height: 0 }, + shadowOpacity: 0.3, + shadowRadius: 12, + elevation: 8, + }, + deepThinkingHeader: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 14, + paddingVertical: 12, + backgroundColor: 'rgba(139, 92, 246, 0.12)', + borderBottomWidth: 1, + borderBottomColor: 'rgba(139, 92, 246, 0.15)', + }, + deepThinkingTitleRow: { + flexDirection: 'row', + alignItems: 'center', + }, + sparkleIcon: { + fontSize: 16, + marginRight: 8, + }, + brainIcon: { + fontSize: 16, + marginRight: 8, + }, + deepThinkingTitle: { + color: AgentTheme.accent, + fontSize: 14, + fontWeight: '600', + }, + deepThinkingMeta: { + flexDirection: 'row', + alignItems: 'center', + }, + charCountBadge: { + backgroundColor: 'rgba(139, 92, 246, 0.2)', + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 10, + marginRight: 8, + }, + charCount: { + color: AgentTheme.accent, + fontSize: 11, + fontWeight: '500', + }, + expandIcon: { + color: AgentTheme.textMuted, + fontSize: 10, + }, + deepThinkingContent: { + paddingHorizontal: 14, + paddingVertical: 12, + maxHeight: 250, + }, + deepThinkingContentStreaming: { + maxHeight: 300, + }, + deepThinkingText: { + color: AgentTheme.textSecondary, + fontSize: 13, + lineHeight: 20, + fontStyle: 'italic', + }, + cursorContainer: { + marginTop: 4, + }, + cursor: { + width: 2, + height: 16, + backgroundColor: AgentTheme.accent, + borderRadius: 1, + }, + + // 执行计划 + planBubble: { + maxWidth: '92%', + backgroundColor: 'rgba(16, 185, 129, 0.08)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(16, 185, 129, 0.25)', + padding: 14, + }, + planHeader: { + flexDirection: 'row', + alignItems: 'center', + marginBottom: 10, + }, + planIcon: { + fontSize: 18, + marginRight: 8, + }, + planTitle: { + color: AgentTheme.success, + fontSize: 15, + fontWeight: '600', + }, + planGoal: { + color: AgentTheme.textPrimary, + fontSize: 14, + marginBottom: 12, + lineHeight: 20, + }, + planSteps: { + marginTop: 4, + }, + planStep: { + flexDirection: 'row', + alignItems: 'flex-start', + marginVertical: 6, + }, + stepNumber: { + width: 22, + height: 22, + borderRadius: 11, + backgroundColor: 'rgba(16, 185, 129, 0.2)', + justifyContent: 'center', + alignItems: 'center', + marginRight: 10, + }, + stepNumberText: { + color: AgentTheme.success, + fontSize: 12, + fontWeight: '600', + }, + stepContent: { + flex: 1, + }, + stepTool: { + color: AgentTheme.textPrimary, + fontSize: 13, + fontWeight: '500', + }, + stepReason: { + color: AgentTheme.textMuted, + fontSize: 12, + marginTop: 2, + }, + + // 执行中 + executingBubble: { + maxWidth: '92%', + backgroundColor: 'rgba(99, 102, 241, 0.08)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(99, 102, 241, 0.25)', + padding: 14, + }, + executingHeader: { + flexDirection: 'row', + alignItems: 'center', + flexWrap: 'wrap', + }, + gearIcon: { + fontSize: 18, + marginRight: 10, + }, + executingTitle: { + color: AgentTheme.accentSecondary, + fontSize: 14, + fontWeight: '500', + }, + executingBadge: { + backgroundColor: 'rgba(99, 102, 241, 0.2)', + paddingHorizontal: 8, + paddingVertical: 2, + borderRadius: 10, + marginLeft: 8, + }, + executingBadgeText: { + color: AgentTheme.accentSecondary, + fontSize: 11, + fontWeight: '600', + }, + currentStepText: { + color: 'rgba(99, 102, 241, 0.8)', + fontSize: 12, + marginLeft: 8, + }, + progressBarContainer: { + marginTop: 10, + height: 3, + backgroundColor: 'rgba(99, 102, 241, 0.15)', + borderRadius: 2, + overflow: 'hidden', + }, + progressBar: { + height: '100%', + backgroundColor: AgentTheme.accentSecondary, + borderRadius: 2, + }, + stepResults: { + marginTop: 10, + }, + // 简洁版步骤样式 + stepResultSimple: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: 6, + paddingHorizontal: 8, + marginVertical: 2, + borderRadius: 8, + backgroundColor: 'rgba(255, 255, 255, 0.03)', + }, + stepIndicator: { + width: 22, + height: 22, + borderRadius: 11, + backgroundColor: 'rgba(107, 114, 128, 0.3)', + justifyContent: 'center', + alignItems: 'center', + marginRight: 10, + }, + stepIndicatorSuccess: { + backgroundColor: 'rgba(16, 185, 129, 0.25)', + }, + stepIndicatorFailed: { + backgroundColor: 'rgba(239, 68, 68, 0.25)', + }, + stepIndicatorRunning: { + backgroundColor: 'rgba(99, 102, 241, 0.3)', + }, + stepIndicatorText: { + fontSize: 12, + color: '#FFFFFF', + fontWeight: '600', + }, + stepIndicatorTextPending: { + fontSize: 11, + color: AgentTheme.textMuted, + fontWeight: '500', + }, + stepToolName: { + flex: 1, + fontSize: 13, + color: AgentTheme.textSecondary, + }, + stepToolNameCompleted: { + color: AgentTheme.textPrimary, + }, + stepToolNameRunning: { + color: AgentTheme.accentSecondary, + fontWeight: '500', + }, + stepTimeSimple: { + fontSize: 11, + color: AgentTheme.textMuted, + marginLeft: 8, + }, + + // AI 响应 + responseBubble: { + maxWidth: '92%', + minWidth: '50%', + backgroundColor: AgentTheme.cardBg, + paddingHorizontal: 16, + paddingVertical: 14, + borderRadius: 18, + borderBottomLeftRadius: 4, + borderWidth: 1, + borderColor: AgentTheme.border, + }, + streamingIndicator: { + marginTop: 8, + }, + streamingCursor: { + width: 2, + height: 16, + backgroundColor: AgentTheme.accent, + borderRadius: 1, + }, + + // 错误 + errorBubble: { + maxWidth: '85%', + backgroundColor: 'rgba(239, 68, 68, 0.08)', + borderRadius: 16, + borderWidth: 1, + borderColor: 'rgba(239, 68, 68, 0.25)', + paddingHorizontal: 14, + paddingVertical: 12, + flexDirection: 'row', + alignItems: 'flex-start', + }, + errorIcon: { + fontSize: 18, + marginRight: 10, + marginTop: 2, + }, + errorContent: { + flex: 1, + }, + errorTitle: { + color: AgentTheme.error, + fontSize: 14, + fontWeight: '600', + marginBottom: 4, + }, + errorText: { + color: AgentTheme.textSecondary, + fontSize: 13, + lineHeight: 18, + }, +}); + +export default memo(MessageBubble); diff --git a/MeAgent/src/screens/Agent/components/SessionDrawer.js b/MeAgent/src/screens/Agent/components/SessionDrawer.js new file mode 100644 index 00000000..c3050a7c --- /dev/null +++ b/MeAgent/src/screens/Agent/components/SessionDrawer.js @@ -0,0 +1,402 @@ +/** + * SessionDrawer 组件 + * 会话历史抽屉,支持会话列表、搜索、新建 + */ + +import React, { memo, useState, useCallback } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + TextInput, + FlatList, + Modal, + Dimensions, + Animated, +} from 'react-native'; +import { BlurView } from 'expo-blur'; +import { AgentTheme } from '../../../constants/agentConstants'; + +const { width: SCREEN_WIDTH } = Dimensions.get('window'); +const DRAWER_WIDTH = SCREEN_WIDTH * 0.85; + +/** + * 会话卡片 + */ +const SessionCard = memo(({ session, isActive, onPress }) => { + const title = session.title || '新对话'; + const messageCount = session.message_count || 0; + + return ( + onPress(session.session_id)} + activeOpacity={0.7} + > + + 💬 + + + + {title} + + + {messageCount} 条消息 + + + + ); +}); + +/** + * 日期分组标题 + */ +const DateGroupHeader = memo(({ title }) => ( + + {title} + +)); + +/** + * SessionDrawer 组件 + */ +const SessionDrawer = ({ + visible, + onClose, + groupedSessions = [], + currentSessionId, + onSelectSession, + onNewSession, + isLoading, +}) => { + const [searchText, setSearchText] = useState(''); + + /** + * 过滤会话 + */ + const filteredGroups = useCallback(() => { + if (!searchText.trim()) { + return groupedSessions; + } + + const lowerKeyword = searchText.toLowerCase(); + + return groupedSessions + .map(group => ({ + ...group, + sessions: group.sessions.filter(session => + (session.title || '').toLowerCase().includes(lowerKeyword) || + (session.session_id || '').toLowerCase().includes(lowerKeyword) + ), + })) + .filter(group => group.sessions.length > 0); + }, [groupedSessions, searchText]); + + /** + * 渲染会话项 + */ + const renderSessionItem = ({ item: session }) => ( + + ); + + /** + * 渲染分组 + */ + const renderGroup = ({ item: group }) => ( + + + {group.sessions.map(session => ( + + ))} + + ); + + const groups = filteredGroups(); + + return ( + + + {/* 点击遮罩关闭 */} + + + {/* 抽屉内容 */} + + + {/* 头部 */} + + 对话历史 + + + + + + {/* 新建按钮 */} + + + + 新建对话 + + + {/* 搜索框 */} + + 🔍 + + {searchText.length > 0 && ( + setSearchText('')} + style={styles.clearButton} + > + + + )} + + + {/* 会话列表 */} + {isLoading ? ( + + 加载中... + + ) : groups.length === 0 ? ( + + 📭 + + {searchText ? '没有找到匹配的对话' : '暂无对话历史'} + + + ) : ( + item.title} + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.listContent} + /> + )} + + + + + ); +}; + +const styles = StyleSheet.create({ + overlay: { + flex: 1, + flexDirection: 'row', + }, + backdrop: { + flex: 1, + backgroundColor: 'rgba(0, 0, 0, 0.5)', + }, + drawer: { + width: DRAWER_WIDTH, + backgroundColor: AgentTheme.background, + }, + drawerContent: { + flex: 1, + }, + + // 头部 + header: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 20, + paddingTop: 60, + paddingBottom: 16, + borderBottomWidth: 1, + borderBottomColor: AgentTheme.border, + }, + headerTitle: { + fontSize: 20, + fontWeight: '700', + color: AgentTheme.textPrimary, + }, + closeButton: { + width: 32, + height: 32, + borderRadius: 16, + backgroundColor: AgentTheme.cardBg, + justifyContent: 'center', + alignItems: 'center', + }, + closeButtonText: { + fontSize: 16, + color: AgentTheme.textSecondary, + }, + + // 新建按钮 + newButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginHorizontal: 20, + marginTop: 16, + paddingVertical: 12, + backgroundColor: AgentTheme.accent, + borderRadius: 12, + }, + newButtonIcon: { + fontSize: 20, + color: '#FFFFFF', + marginRight: 8, + }, + newButtonText: { + fontSize: 15, + fontWeight: '600', + color: '#FFFFFF', + }, + + // 搜索框 + searchContainer: { + flexDirection: 'row', + alignItems: 'center', + marginHorizontal: 20, + marginTop: 16, + marginBottom: 8, + paddingHorizontal: 12, + paddingVertical: 10, + backgroundColor: AgentTheme.cardBg, + borderRadius: 10, + borderWidth: 1, + borderColor: AgentTheme.border, + }, + searchIcon: { + fontSize: 14, + marginRight: 8, + }, + searchInput: { + flex: 1, + fontSize: 14, + color: AgentTheme.textPrimary, + }, + clearButton: { + padding: 4, + }, + clearButtonText: { + fontSize: 12, + color: AgentTheme.textMuted, + }, + + // 列表 + listContent: { + paddingHorizontal: 20, + paddingBottom: 40, + }, + groupContainer: { + marginTop: 16, + }, + + // 日期分组 + dateGroup: { + paddingVertical: 8, + }, + dateGroupText: { + fontSize: 12, + fontWeight: '600', + color: AgentTheme.textMuted, + textTransform: 'uppercase', + }, + + // 会话卡片 + sessionCard: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: 12, + paddingVertical: 12, + backgroundColor: AgentTheme.cardBg, + borderRadius: 10, + marginVertical: 4, + borderWidth: 1, + borderColor: 'transparent', + }, + sessionCardActive: { + borderColor: AgentTheme.accent, + backgroundColor: 'rgba(139, 92, 246, 0.1)', + }, + sessionIcon: { + width: 36, + height: 36, + borderRadius: 18, + backgroundColor: AgentTheme.backgroundSecondary, + justifyContent: 'center', + alignItems: 'center', + marginRight: 12, + }, + sessionIconText: { + fontSize: 18, + }, + sessionInfo: { + flex: 1, + }, + sessionTitle: { + fontSize: 14, + fontWeight: '500', + color: AgentTheme.textPrimary, + marginBottom: 2, + }, + sessionMeta: { + fontSize: 12, + color: AgentTheme.textMuted, + }, + + // 加载和空状态 + loadingContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + loadingText: { + fontSize: 14, + color: AgentTheme.textMuted, + }, + emptyContainer: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 40, + }, + emptyIcon: { + fontSize: 48, + marginBottom: 16, + }, + emptyText: { + fontSize: 14, + color: AgentTheme.textMuted, + textAlign: 'center', + }, +}); + +export default memo(SessionDrawer); diff --git a/MeAgent/src/screens/Agent/components/WelcomeScreen.js b/MeAgent/src/screens/Agent/components/WelcomeScreen.js new file mode 100644 index 00000000..b045e5f8 --- /dev/null +++ b/MeAgent/src/screens/Agent/components/WelcomeScreen.js @@ -0,0 +1,303 @@ +/** + * WelcomeScreen 组件 + * Agent 聊天欢迎界面 - Bento Card 风格 + */ + +import React, { memo } from 'react'; +import { + View, + Text, + StyleSheet, + TouchableOpacity, + ScrollView, + Image, + Dimensions, +} from 'react-native'; +import { LinearGradient } from 'expo-linear-gradient'; +import { AgentTheme, QUICK_QUESTIONS, AGENT_NAME } from '../../../constants/agentConstants'; + +const { width: SCREEN_WIDTH } = Dimensions.get('window'); +const CARD_WIDTH = (SCREEN_WIDTH - 48 - 12) / 2; // 两列布局 + +// 洛希头像 +const LuoxiAvatar = require('../../../../assets/imgs/luoxi.jpg'); + +/** + * AI 头像组件 + */ +const AIAvatar = () => ( + + + + +); + +/** + * Bento 功能卡片 + */ +const BentoCard = memo(({ icon, title, description, color, size = 'small' }) => ( + + {icon} + {title} + {description} + +)); + +/** + * 快捷问题卡片 + */ +const QuickQuestionCard = memo(({ question, onPress, index }) => ( + onPress(question)} + activeOpacity={0.7} + > + + + {question} + + + + + + +)); + +/** + * WelcomeScreen 组件 + */ +const WelcomeScreen = ({ onQuickQuestion }) => { + return ( + + {/* 头部 - 洛希介绍 */} + + + {AGENT_NAME} + AI 投研助手 · 洛希极限 + 在市场的混沌中,找到价值与风险的平衡点 + + + {/* 快捷问题 */} + + + 快速开始 + 试试这些问题 + + + {QUICK_QUESTIONS.map((question, index) => ( + + ))} + + + + {/* 底部提示 */} + + + 💡 + + 直接输入股票代码或名称,我会为你进行全面分析 + + + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + contentContainer: { + paddingHorizontal: 20, + paddingTop: 20, + paddingBottom: 30, + }, + + // 头部 + header: { + alignItems: 'center', + marginBottom: 28, + }, + avatarContainer: { + width: 88, + height: 88, + marginBottom: 16, + position: 'relative', + }, + avatarGlow: { + position: 'absolute', + width: 96, + height: 96, + borderRadius: 48, + top: -4, + left: -4, + }, + avatar: { + width: 88, + height: 88, + borderRadius: 44, + borderWidth: 3, + borderColor: 'rgba(139, 92, 246, 0.5)', + }, + title: { + fontSize: 32, + fontWeight: '800', + color: AgentTheme.textPrimary, + marginBottom: 4, + letterSpacing: 1, + }, + subtitle: { + fontSize: 15, + color: AgentTheme.accent, + fontWeight: '500', + marginBottom: 8, + }, + tagline: { + fontSize: 13, + color: AgentTheme.textMuted, + textAlign: 'center', + paddingHorizontal: 20, + }, + + // Bento Grid + bentoGrid: { + marginBottom: 28, + }, + bentoRow: { + flexDirection: 'row', + justifyContent: 'space-between', + marginBottom: 12, + }, + bentoCard: { + width: CARD_WIDTH, + backgroundColor: AgentTheme.cardBg, + borderRadius: 16, + padding: 16, + borderWidth: 1, + borderColor: AgentTheme.border, + borderLeftWidth: 3, + }, + bentoCardLarge: { + width: '100%', + }, + bentoIcon: { + fontSize: 24, + marginBottom: 8, + }, + bentoTitle: { + fontSize: 15, + fontWeight: '600', + color: AgentTheme.textPrimary, + marginBottom: 4, + }, + bentoDesc: { + fontSize: 12, + color: AgentTheme.textMuted, + }, + + // 快捷问题区域 + quickSection: { + marginBottom: 20, + }, + sectionHeader: { + marginBottom: 14, + }, + sectionTitle: { + fontSize: 18, + fontWeight: '700', + color: AgentTheme.textPrimary, + marginBottom: 2, + }, + sectionSubtitle: { + fontSize: 13, + color: AgentTheme.textMuted, + }, + questionList: { + gap: 10, + }, + questionCard: { + borderRadius: 14, + overflow: 'hidden', + marginBottom: 8, + }, + questionGradient: { + borderRadius: 14, + borderWidth: 1, + borderColor: 'rgba(139, 92, 246, 0.2)', + }, + questionContent: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 16, + paddingVertical: 14, + }, + questionText: { + fontSize: 14, + color: AgentTheme.textPrimary, + flex: 1, + }, + questionArrowContainer: { + width: 28, + height: 28, + borderRadius: 14, + backgroundColor: 'rgba(139, 92, 246, 0.2)', + justifyContent: 'center', + alignItems: 'center', + marginLeft: 12, + }, + questionArrow: { + fontSize: 14, + color: AgentTheme.accent, + fontWeight: '600', + }, + + // 底部提示 + footer: { + marginTop: 8, + }, + tipCard: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: 'rgba(245, 158, 11, 0.1)', + borderRadius: 12, + padding: 14, + borderWidth: 1, + borderColor: 'rgba(245, 158, 11, 0.2)', + }, + tipIcon: { + fontSize: 18, + marginRight: 10, + }, + tipText: { + fontSize: 13, + color: AgentTheme.textSecondary, + flex: 1, + lineHeight: 18, + }, +}); + +export default memo(WelcomeScreen); diff --git a/MeAgent/src/screens/Agent/components/index.js b/MeAgent/src/screens/Agent/components/index.js new file mode 100644 index 00000000..d31f7dc8 --- /dev/null +++ b/MeAgent/src/screens/Agent/components/index.js @@ -0,0 +1,9 @@ +/** + * Agent 组件导出 + */ + +export { default as MessageBubble } from './MessageBubble'; +export { default as ChatInput } from './ChatInput'; +export { default as WelcomeScreen } from './WelcomeScreen'; +export { default as SessionDrawer } from './SessionDrawer'; +export { default as MarkdownRenderer } from './MarkdownRenderer'; diff --git a/MeAgent/src/screens/Agent/index.js b/MeAgent/src/screens/Agent/index.js new file mode 100644 index 00000000..14f635c3 --- /dev/null +++ b/MeAgent/src/screens/Agent/index.js @@ -0,0 +1,5 @@ +/** + * Agent 模块导出 + */ + +export { default as AgentChatScreen } from './AgentChatScreen'; diff --git a/argon-pro-react-native/src/screens/Auth/LoginScreen.js b/MeAgent/src/screens/Auth/LoginScreen.js similarity index 100% rename from argon-pro-react-native/src/screens/Auth/LoginScreen.js rename to MeAgent/src/screens/Auth/LoginScreen.js diff --git a/argon-pro-react-native/src/screens/Auth/index.js b/MeAgent/src/screens/Auth/index.js similarity index 100% rename from argon-pro-react-native/src/screens/Auth/index.js rename to MeAgent/src/screens/Auth/index.js diff --git a/MeAgent/src/screens/Community/ChannelDetail.js b/MeAgent/src/screens/Community/ChannelDetail.js new file mode 100644 index 00000000..110e7c83 --- /dev/null +++ b/MeAgent/src/screens/Community/ChannelDetail.js @@ -0,0 +1,585 @@ +/** + * 频道详情页面 + * 显示消息列表和输入框 + */ + +import React, { useEffect, useState, useCallback, useRef } from 'react'; +import { + FlatList, + KeyboardAvoidingView, + Platform, + StyleSheet, + Keyboard, + TextInput, + Image, + Dimensions, +} from 'react-native'; +import { + Box, + VStack, + HStack, + Text, + Icon, + Pressable, + Spinner, + Center, + Avatar, +} from 'native-base'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useDispatch, useSelector } from 'react-redux'; + +import { + fetchMessages, + sendMessage, + addMessage, +} from '../../store/slices/communitySlice'; +import { useCommunitySocket } from '../../hooks/useCommunitySocket'; + +// 消息分组:按日期 +const groupMessagesByDate = (messages) => { + const groups = []; + let currentDate = null; + let currentGroup = null; + + messages.forEach((message) => { + const messageDate = new Date(message.createdAt).toDateString(); + + if (messageDate !== currentDate) { + currentDate = messageDate; + currentGroup = { + date: message.createdAt, + messages: [message], + }; + groups.push(currentGroup); + } else { + currentGroup.messages.push(message); + } + }); + + return groups; +}; + +// 格式化日期分隔符 +const formatDateDivider = (dateStr) => { + const date = new Date(dateStr); + const today = new Date(); + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + + if (date.toDateString() === today.toDateString()) { + return '今天'; + } + if (date.toDateString() === yesterday.toDateString()) { + return '昨天'; + } + + const month = date.getMonth() + 1; + const day = date.getDate(); + const year = date.getFullYear(); + + if (year === today.getFullYear()) { + return `${month}月${day}日`; + } + return `${year}年${month}月${day}日`; +}; + +// 格式化时间 +const formatTime = (dateStr) => { + const date = new Date(dateStr); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${hours}:${minutes}`; +}; + +// 解析消息内容,提取文本和图片 +const parseMessageContent = (content) => { + if (!content) return { text: '', images: [] }; + + const images = []; + let text = content; + + // 1. 匹配 Markdown 图片格式: ![alt](data:image/xxx;base64,xxx) 或 ![alt](url) + const markdownImgRegex = /!\[([^\]]*)\]\((data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+|https?:\/\/[^\s)]+)\)/g; + let mdMatch; + while ((mdMatch = markdownImgRegex.exec(content)) !== null) { + if (!images.includes(mdMatch[2])) { + images.push(mdMatch[2]); + } + text = text.replace(mdMatch[0], '').trim(); + } + + // 2. 匹配直接的 base64 图片格式: data:image/xxx;base64,xxxxx + const base64Regex = /(data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+)/g; + const matches = text.match(base64Regex); + + if (matches) { + matches.forEach((match) => { + if (!images.includes(match)) { + images.push(match); + } + text = text.replace(match, '').trim(); + }); + } + + // 3. 匹配 [图片] 标记后跟 base64 的情况 + const imgTagRegex = /\[图片\]\s*(data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+)/g; + let imgMatch; + while ((imgMatch = imgTagRegex.exec(content)) !== null) { + if (!images.includes(imgMatch[1])) { + images.push(imgMatch[1]); + } + text = text.replace(imgMatch[0], '').trim(); + } + + // 移除多余的 [图片] 标记 + text = text.replace(/\[图片\]/g, '').trim(); + + return { text, images }; +}; + +// 获取屏幕宽度用于图片尺寸计算 +const screenWidth = Dimensions.get('window').width; + +// 判断是否显示头像(同用户5分钟内的消息合并) +const shouldShowAvatar = (message, index, messages) => { + if (index === 0) return true; + + const prevMessage = messages[index - 1]; + if (prevMessage.authorId !== message.authorId) return true; + + const timeDiff = new Date(message.createdAt) - new Date(prevMessage.createdAt); + return timeDiff > 5 * 60 * 1000; // 5分钟 +}; + +const ChannelDetail = ({ route, navigation }) => { + const { channel } = route.params; + const dispatch = useDispatch(); + const insets = useSafeAreaInsets(); + const flatListRef = useRef(null); + + const communityState = useSelector((state) => state.community); + const messages = communityState?.messages || {}; + const messagesHasMore = communityState?.messagesHasMore || {}; + const loading = communityState?.loading || {}; + const typingUsers = communityState?.typingUsers || {}; + + const channelMessages = messages[channel?.id] || []; + const hasMore = messagesHasMore[channel?.id] ?? true; + const channelTypingUsers = typingUsers[channel?.id] || []; + + const [inputText, setInputText] = useState(''); + const [isSending, setIsSending] = useState(false); + + // WebSocket 连接 + const { + isConnected, + subscribe, + unsubscribe, + startTyping, + stopTyping, + } = useCommunitySocket(); + + // 订阅频道 WebSocket + useEffect(() => { + if (isConnected && channel?.id) { + subscribe(channel.id); + } + + return () => { + if (channel?.id) { + unsubscribe(channel.id); + stopTyping(channel.id); + } + }; + }, [isConnected, channel?.id, subscribe, unsubscribe, stopTyping]); + + // 监听新消息滚动到底部 + useEffect(() => { + if (channelMessages.length > 0) { + setTimeout(() => { + flatListRef.current?.scrollToEnd({ animated: true }); + }, 100); + } + }, [channelMessages.length]); + + // 输入时发送输入状态 + const handleTextChange = useCallback((text) => { + setInputText(text); + if (text.trim() && isConnected && channel?.id) { + startTyping(channel.id); + } + }, [isConnected, channel?.id, startTyping]); + + // 设置导航标题 + useEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: channel?.name || '频道', + headerStyle: { + backgroundColor: '#0F172A', + elevation: 0, + shadowOpacity: 0, + borderBottomWidth: 1, + borderBottomColor: 'rgba(255, 255, 255, 0.1)', + }, + headerTintColor: '#FFFFFF', + headerTitleStyle: { + fontWeight: '600', + fontSize: 16, + }, + headerRight: () => ( + + navigation.navigate('MemberList', { channel })}> + + + {}}> + + + + ), + }); + }, [navigation, channel]); + + // 加载消息 + useEffect(() => { + if (channel?.id) { + dispatch(fetchMessages({ channelId: channel.id })); + } + }, [dispatch, channel?.id]); + + // 加载更多消息 + const handleLoadMore = useCallback(() => { + if (loading.messages || !hasMore || channelMessages.length === 0 || !channel?.id) return; + + const oldestMessage = channelMessages[0]; + dispatch( + fetchMessages({ + channelId: channel.id, + options: { before: oldestMessage.createdAt }, + }) + ); + }, [dispatch, channel?.id, channelMessages, loading.messages, hasMore]); + + // 发送消息 + const handleSend = useCallback(async () => { + const content = inputText.trim(); + if (!content || isSending || !channel?.id) return; + + setIsSending(true); + Keyboard.dismiss(); + + // 停止输入状态 + stopTyping(channel.id); + + try { + await dispatch( + sendMessage({ + channelId: channel.id, + data: { content }, + }) + ).unwrap(); + + setInputText(''); + // 滚动到底部 + setTimeout(() => { + flatListRef.current?.scrollToEnd({ animated: true }); + }, 100); + } catch (error) { + console.error('发送消息失败:', error); + } finally { + setIsSending(false); + } + }, [dispatch, channel?.id, inputText, isSending, stopTyping]); + + // 渲染日期分隔符 + const renderDateDivider = (date) => ( + + + + {formatDateDivider(date)} + + + + ); + + // 渲染单条消息 + const renderMessage = ({ item: message, index }) => { + const showAvatar = shouldShowAvatar(message, index, channelMessages); + const { text, images } = parseMessageContent(message.content); + + return ( + + {/* 头像区域 */} + + {showAvatar ? ( + + {message.authorName?.[0]?.toUpperCase() || '?'} + + ) : ( + + {formatTime(message.createdAt)} + + )} + + + {/* 消息内容 */} + + {showAvatar ? ( + + + {message.authorName || '匿名用户'} + + + {formatTime(message.createdAt)} + + + ) : null} + + {/* 文本内容 */} + {text ? ( + + {text} + + ) : null} + + {/* Base64 图片 */} + {images.length > 0 ? ( + + {images.map((imgUri, imgIndex) => ( + { + // TODO: 可以添加图片预览功能 + }} + > + + + ))} + + ) : null} + + {/* 表情反应 */} + {message.reactions && Object.keys(message.reactions).length > 0 ? ( + + {Object.entries(message.reactions).map(([emoji, users]) => ( + + + {emoji} + + {users.length} + + + + ))} + + ) : null} + + + ); + }; + + // 渲染消息列表 + const renderMessageList = () => { + if (loading.messages && channelMessages.length === 0) { + return ( +
+ + + 加载消息... + +
+ ); + } + + if (channelMessages.length === 0) { + return ( +
+ + + 还没有消息 + + + 成为第一个发言的人吧! + +
+ ); + } + + return ( + item.id} + inverted={false} + onEndReached={handleLoadMore} + onEndReachedThreshold={0.1} + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.messageList} + ListHeaderComponent={ + hasMore && loading.messages ? ( +
+ +
+ ) : null + } + /> + ); + }; + + // 渲染输入中提示 + const renderTypingIndicator = () => { + if (channelTypingUsers.length === 0) return null; + + const names = channelTypingUsers.map((u) => u.username).join('、'); + return ( + + + + + + + + {names} 正在输入... + + + ); + }; + + // 渲染输入框 + const renderInput = () => ( + + {renderTypingIndicator()} + + {/* 附件按钮 */} + + + + + {/* 输入框 */} + + + + + {/* 发送按钮 */} + + {isSending ? ( + + ) : ( + + )} + + + + ); + + return ( + + + {/* 频道话题 */} + {channel?.topic ? ( + + + + + {channel.topic} + + + + ) : null} + + {/* 消息列表 */} + {renderMessageList()} + + {/* 输入框 */} + {renderInput()} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0F172A', + }, + messageList: { + paddingVertical: 8, + }, + textInput: { + flex: 1, + color: '#FFFFFF', + fontSize: 14, + maxHeight: 100, + minHeight: 36, + paddingVertical: Platform.OS === 'ios' ? 8 : 10, + paddingHorizontal: 0, + }, +}); + +export default ChannelDetail; diff --git a/MeAgent/src/screens/Community/ChannelList.js b/MeAgent/src/screens/Community/ChannelList.js new file mode 100644 index 00000000..62a4c389 --- /dev/null +++ b/MeAgent/src/screens/Community/ChannelList.js @@ -0,0 +1,371 @@ +/** + * 频道列表页面 + * Discord 风格的频道分类展示 + */ + +import React, { useEffect, useState, useCallback } from 'react'; +import { + SectionList, + RefreshControl, + StyleSheet, + Pressable as RNPressable, +} from 'react-native'; +import { + Box, + VStack, + HStack, + Text, + Icon, + Input, + Pressable, + Spinner, + Center, +} from 'native-base'; +import { Ionicons } from '@expo/vector-icons'; +import { useDispatch, useSelector } from 'react-redux'; + +import { fetchChannels, setCurrentChannel } from '../../store/slices/communitySlice'; +import { CHANNEL_TYPES } from '../../services/communityService'; + +// 频道图标映射 +const CHANNEL_ICONS = { + [CHANNEL_TYPES.TEXT]: 'chatbubble', + [CHANNEL_TYPES.FORUM]: 'document-text', + [CHANNEL_TYPES.ANNOUNCEMENT]: 'megaphone', +}; + +// 分类图标映射 +const CATEGORY_ICONS = { + general: 'chatbubbles', + hot: 'flame', + announcement: 'megaphone', +}; + +const ChannelList = ({ navigation }) => { + const dispatch = useDispatch(); + const communityState = useSelector((state) => state.community); + const categories = communityState?.categories || []; + const loading = communityState?.loading || {}; + + const [searchText, setSearchText] = useState(''); + const [collapsedCategories, setCollapsedCategories] = useState(new Set()); + const [refreshing, setRefreshing] = useState(false); + + // 加载频道列表 + useEffect(() => { + dispatch(fetchChannels()); + }, [dispatch]); + + // 下拉刷新 + const handleRefresh = useCallback(async () => { + setRefreshing(true); + await dispatch(fetchChannels()); + setRefreshing(false); + }, [dispatch]); + + // 切换分类折叠状态 + const toggleCategory = useCallback((categoryId) => { + setCollapsedCategories((prev) => { + const newSet = new Set(prev); + if (newSet.has(categoryId)) { + newSet.delete(categoryId); + } else { + newSet.add(categoryId); + } + return newSet; + }); + }, []); + + // 点击频道 + const handleChannelPress = useCallback((channel) => { + dispatch(setCurrentChannel(channel)); + + // 根据频道类型导航到不同页面 + if (channel.type === CHANNEL_TYPES.FORUM) { + navigation.navigate('ForumChannel', { channel }); + } else { + navigation.navigate('ChannelDetail', { channel }); + } + }, [dispatch, navigation]); + + // 过滤频道(安全处理 undefined) + const filteredCategories = (categories || []).map((category) => ({ + ...category, + data: collapsedCategories.has(category.id) + ? [] + : (category.channels || []).filter((channel) => + channel.name?.toLowerCase().includes(searchText.toLowerCase()) + ), + })); + + // 渲染搜索栏 + const renderHeader = () => ( + + + } + _focus={{ + borderColor: 'primary.500', + bg: 'rgba(255, 255, 255, 0.08)', + }} + /> + + ); + + // 渲染分类标题 + const renderSectionHeader = ({ section }) => { + if (!section) return null; + + return ( + section.id && toggleCategory(section.id)}> + + + + + {section.name || '未命名分类'} + + + + + + ); + }; + + // 渲染频道项 + const renderChannelItem = ({ item: channel }) => { + if (!channel) return null; + + const unreadCount = channel.unreadCount || 0; + const channelName = channel.name || '未命名频道'; + const channelType = channel.type || CHANNEL_TYPES.TEXT; + + return ( + handleChannelPress(channel)}> + {({ isPressed }) => ( + + {/* 频道图标 */} + 0 ? 'rgba(124, 58, 237, 0.2)' : 'rgba(255, 255, 255, 0.05)'} + > + 0 ? '#A78BFA' : 'gray.500'} + /> + + + {/* 频道信息 */} + + + 0 ? 'bold' : 'medium'} + color={unreadCount > 0 ? 'white' : 'gray.300'} + numberOfLines={1} + > + {channelName} + + {channelType === CHANNEL_TYPES.ANNOUNCEMENT && ( + + )} + + {channel.topic ? ( + + {channel.topic} + + ) : null} + + + {/* 未读标记 */} + {unreadCount > 0 ? ( + + + {unreadCount > 99 ? '99+' : String(unreadCount)} + + + ) : null} + + {/* 成员数 */} + {!unreadCount && channel.subscriberCount ? ( + + + + {formatNumber(channel.subscriberCount)} + + + ) : null} + + )} + + ); + }; + + // 渲染空状态 + const renderEmpty = () => { + if (loading.channels) { + return ( +
+ + + 加载频道列表... + +
+ ); + } + + if (searchText) { + return ( +
+ + + 未找到匹配的频道 + + + 尝试其他搜索词 + +
+ ); + } + + return ( +
+ + + 暂无频道 + +
+ ); + }; + + // 渲染底部 + const renderFooter = () => ( + + { + navigation.navigate('CreateChannel'); + }} + > + + + + 创建频道 + + + + + ); + + return ( + + item?.id || `item-${index}`} + renderItem={renderChannelItem} + renderSectionHeader={renderSectionHeader} + ListHeaderComponent={renderHeader} + ListEmptyComponent={renderEmpty} + ListFooterComponent={renderFooter} + stickySectionHeadersEnabled={false} + refreshControl={ + + } + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.listContent} + /> + + ); +}; + +// 格式化数字 +const formatNumber = (num) => { + if (num == null || isNaN(num)) return '0'; + if (num >= 10000) { + return (num / 10000).toFixed(1) + 'w'; + } + if (num >= 1000) { + return (num / 1000).toFixed(1) + 'k'; + } + return String(num); +}; + +const styles = StyleSheet.create({ + listContent: { + flexGrow: 1, + }, +}); + +export default ChannelList; diff --git a/MeAgent/src/screens/Community/CreateChannel.js b/MeAgent/src/screens/Community/CreateChannel.js new file mode 100644 index 00000000..44e25adc --- /dev/null +++ b/MeAgent/src/screens/Community/CreateChannel.js @@ -0,0 +1,378 @@ +/** + * 创建频道页面 + */ + +import React, { useState, useCallback, useEffect } from 'react'; +import { + ScrollView, + KeyboardAvoidingView, + Platform, + StyleSheet, + Keyboard, + Alert, + TextInput, +} from 'react-native'; +import { + Box, + VStack, + HStack, + Text, + Icon, + Pressable, + Spinner, + Center, +} from 'native-base'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useDispatch, useSelector } from 'react-redux'; + +import { channelService, CHANNEL_TYPES } from '../../services/communityService'; +import { fetchChannels } from '../../store/slices/communitySlice'; + +// 频道类型选项 +const CHANNEL_TYPE_OPTIONS = [ + { + value: CHANNEL_TYPES.TEXT, + label: '文字频道', + icon: 'chatbubble', + description: '实时消息聊天', + }, + { + value: CHANNEL_TYPES.FORUM, + label: '论坛频道', + icon: 'document-text', + description: '发帖讨论,支持长文', + }, +]; + +const CreateChannel = ({ navigation }) => { + const dispatch = useDispatch(); + const insets = useSafeAreaInsets(); + + // 从 Redux 获取真实的 categories 列表 + const communityState = useSelector((state) => state.community); + const categories = communityState?.categories || []; + const loadingCategories = communityState?.loading?.channels || false; + + const [name, setName] = useState(''); + const [topic, setTopic] = useState(''); + const [channelType, setChannelType] = useState(CHANNEL_TYPES.TEXT); + const [category, setCategory] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + // 加载 categories(如果还没有加载) + useEffect(() => { + if (categories.length === 0) { + dispatch(fetchChannels()); + } else if (!category && categories.length > 0) { + // 设置默认选中第一个分类 + setCategory(categories[0].id); + } + }, [dispatch, categories, category]); + + const NAME_MAX = 50; + const TOPIC_MAX = 200; + + // 验证表单 + const validateForm = useCallback(() => { + if (!name.trim()) { + Alert.alert('提示', '请输入频道名称'); + return false; + } + if (name.length > NAME_MAX) { + Alert.alert('提示', `频道名称不能超过${NAME_MAX}个字符`); + return false; + } + if (!category) { + Alert.alert('提示', '请选择所属分类'); + return false; + } + if (topic.length > TOPIC_MAX) { + Alert.alert('提示', `频道话题不能超过${TOPIC_MAX}个字符`); + return false; + } + return true; + }, [name, topic, category]); + + // 创建频道 + const handleSubmit = useCallback(async () => { + if (!validateForm() || isSubmitting) return; + + setIsSubmitting(true); + Keyboard.dismiss(); + + try { + const newChannel = await channelService.createChannel({ + name: name.trim(), + type: channelType, + topic: topic.trim() || undefined, + categoryId: category, + }); + + // 刷新频道列表 + dispatch(fetchChannels()); + + Alert.alert('创建成功', `已创建频道 #${newChannel.name || name}`, [ + { + text: '进入频道', + onPress: () => { + navigation.goBack(); + // 如果需要直接进入新频道,可以导航到对应页面 + if (newChannel) { + if (channelType === CHANNEL_TYPES.FORUM) { + navigation.navigate('ForumChannel', { channel: newChannel }); + } else { + navigation.navigate('ChannelDetail', { channel: newChannel }); + } + } + }, + }, + { + text: '返回列表', + onPress: () => navigation.goBack(), + }, + ]); + } catch (error) { + Alert.alert('创建失败', error.message || '请稍后重试'); + } finally { + setIsSubmitting(false); + } + }, [dispatch, name, topic, channelType, category, validateForm, isSubmitting, navigation]); + + // 设置导航 + React.useEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: '创建频道', + headerStyle: { + backgroundColor: '#0F172A', + elevation: 0, + shadowOpacity: 0, + borderBottomWidth: 1, + borderBottomColor: 'rgba(255, 255, 255, 0.1)', + }, + headerTintColor: '#FFFFFF', + headerTitleStyle: { + fontWeight: '600', + fontSize: 16, + }, + headerRight: () => ( + + {isSubmitting ? ( + + ) : ( + + 创建 + + )} + + ), + }); + }, [navigation, handleSubmit, isSubmitting, name]); + + return ( + + + {/* 频道名称 */} + + + + 频道名称 * + + NAME_MAX ? 'red.500' : 'gray.500'}> + {name.length}/{NAME_MAX} + + + + + + + + {/* 频道类型 */} + + + 频道类型 + + + {CHANNEL_TYPE_OPTIONS.map((option) => ( + setChannelType(option.value)} + > + + + + + {option.label} + + + {option.description} + + + + + ))} + + + + {/* 所属分类 */} + + + 所属分类 + + {loadingCategories ? ( +
+ + 加载分类... +
+ ) : categories.length === 0 ? ( + 暂无可用分类 + ) : ( + + {categories.map((cat) => ( + setCategory(cat.id)} + mb={2} + > + + + {cat.name || cat.id} + + + + ))} + + )} +
+ + {/* 频道话题 */} + + + + 频道话题(可选) + + TOPIC_MAX ? 'red.500' : 'gray.500'}> + {topic.length}/{TOPIC_MAX} + + + + + + + + {/* 提示 */} + + + + + + 创建须知 + + + {`• 频道名称应与讨论主题相关\n• 创建后您将成为频道管理员\n• 请遵守社区规范,文明交流`} + + + + +
+
+ ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: '#0F172A', + }, + content: { + flexGrow: 1, + }, + textInput: { + color: '#FFFFFF', + fontSize: 14, + minHeight: 40, + paddingVertical: Platform.OS === 'ios' ? 0 : 12, + }, + textArea: { + minHeight: 80, + paddingTop: Platform.OS === 'ios' ? 0 : 12, + }, +}); + +export default CreateChannel; diff --git a/MeAgent/src/screens/Community/CreatePost.js b/MeAgent/src/screens/Community/CreatePost.js new file mode 100644 index 00000000..e4f4db30 --- /dev/null +++ b/MeAgent/src/screens/Community/CreatePost.js @@ -0,0 +1,396 @@ +/** + * 发帖页面 + * 创建新的论坛帖子 + */ + +import React, { useState, useCallback } from 'react'; +import { + ScrollView, + KeyboardAvoidingView, + Platform, + StyleSheet, + Keyboard, + Alert, +} from 'react-native'; +import { + Box, + VStack, + HStack, + Text, + Icon, + Pressable, + Input, + TextArea, + Spinner, +} from 'native-base'; +import { LinearGradient } from 'expo-linear-gradient'; +import { Ionicons } from '@expo/vector-icons'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { useDispatch } from 'react-redux'; + +import { createPost } from '../../store/slices/communitySlice'; +import { gradients } from '../../theme'; + +// 预设标签 +const PRESET_TAGS = [ + '技术分析', '基本面', '短线', '中长线', '新手', + '价值投资', '趋势', '消息面', '情绪', '复盘', +]; + +const CreatePost = ({ route, navigation }) => { + const { channel } = route.params; + const dispatch = useDispatch(); + const insets = useSafeAreaInsets(); + + const [title, setTitle] = useState(''); + const [content, setContent] = useState(''); + const [tags, setTags] = useState([]); + const [customTag, setCustomTag] = useState(''); + const [isSubmitting, setIsSubmitting] = useState(false); + + const TITLE_MAX = 100; + const CONTENT_MAX = 5000; + const TAGS_MAX = 5; + + // 添加标签 + const handleAddTag = useCallback((tag) => { + if (tags.length >= TAGS_MAX) { + Alert.alert('提示', `最多添加${TAGS_MAX}个标签`); + return; + } + if (tags.includes(tag)) { + return; + } + setTags([...tags, tag]); + }, [tags]); + + // 移除标签 + const handleRemoveTag = useCallback((tag) => { + setTags(tags.filter((t) => t !== tag)); + }, [tags]); + + // 添加自定义标签 + const handleAddCustomTag = useCallback(() => { + const tag = customTag.trim(); + if (!tag) return; + if (tag.length > 20) { + Alert.alert('提示', '标签长度不能超过20个字符'); + return; + } + handleAddTag(tag); + setCustomTag(''); + }, [customTag, handleAddTag]); + + // 验证表单 + const validateForm = useCallback(() => { + if (!title.trim()) { + Alert.alert('提示', '请输入标题'); + return false; + } + if (title.length > TITLE_MAX) { + Alert.alert('提示', `标题不能超过${TITLE_MAX}个字符`); + return false; + } + if (!content.trim()) { + Alert.alert('提示', '请输入内容'); + return false; + } + if (content.length > CONTENT_MAX) { + Alert.alert('提示', `内容不能超过${CONTENT_MAX}个字符`); + return false; + } + return true; + }, [title, content]); + + // 提交帖子 + const handleSubmit = useCallback(async () => { + if (!validateForm() || isSubmitting) return; + + setIsSubmitting(true); + Keyboard.dismiss(); + + try { + await dispatch( + createPost({ + channelId: channel.id, + data: { + title: title.trim(), + content: content.trim(), + tags, + }, + }) + ).unwrap(); + + Alert.alert('成功', '帖子发布成功', [ + { + text: '确定', + onPress: () => navigation.goBack(), + }, + ]); + } catch (error) { + Alert.alert('发布失败', error.message || '请稍后重试'); + } finally { + setIsSubmitting(false); + } + }, [dispatch, channel.id, title, content, tags, validateForm, isSubmitting, navigation]); + + // 设置导航 + React.useEffect(() => { + navigation.setOptions({ + headerShown: true, + headerTitle: '发布帖子', + headerStyle: { + backgroundColor: '#0F172A', + elevation: 0, + shadowOpacity: 0, + borderBottomWidth: 1, + borderBottomColor: 'rgba(255, 255, 255, 0.1)', + }, + headerTintColor: '#FFFFFF', + headerTitleStyle: { + fontWeight: '600', + fontSize: 16, + }, + headerRight: () => ( + + {isSubmitting ? ( + + ) : ( + + 发布 + + )} + + ), + }); + }, [navigation, handleSubmit, isSubmitting, title, content]); + + return ( + + + {/* 频道信息 */} + + + + + 发布到: {channel.name} + + + + + {/* 标题输入 */} + + + + 标题 + + TITLE_MAX ? 'red.500' : 'gray.500'}> + {title.length}/{TITLE_MAX} + + + + + + {/* 内容输入 */} + + + + 内容 + + CONTENT_MAX ? 'red.500' : 'gray.500'}> + {content.length}/{CONTENT_MAX} + + +